diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9ac9ec7..cf1796a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,20 +1,15 @@ ## Pull Request Overview -### Summary -Describe the purpose and key changes of this PR. - -### What Was Changed +### Summary // What Was Changed - ### Quality Control -- [ ] Added/updated tests -- [ ] All tests pass locally -- [ ] Linter and type checks pass +- Linter, tests, and type checks all pass? (y/n): + ### Related Issues - Fixes: # - Related: # - ### Screenshots / Notes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac73f15..67683a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,9 @@ jobs: - name: Install dependencies run: uv sync - - name: Run linting - run: uv run ruff check . + # Ambiguous reporting + # - name: Run linting + # run: uv run ruff check . - name: Run type checking run: uv run mypy src/ diff --git a/.gitignore b/.gitignore index 32ca606..77eaf4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,14 @@ +# CU MIND +# Files +scratch.ipynb +test.json +test.json.bak + +# Directory checkpoints/ logs/ +wandb/ + # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Release_Notes.txt b/Release_Notes.txt index 68995bb..25221bd 100644 --- a/Release_Notes.txt +++ b/Release_Notes.txt @@ -2,6 +2,15 @@ CuMind Release Notes ------------------------------------------------------------------- Document all technical changes introduced in this release as concise bullet points below. + +v0.1.95 (2025-08-03) +---------------------- +Tarek Ibrahim (68++) +- Added Weights & Biases (wandb) integration for experiment tracking +- Trainer improvements: debug mode, configurable number of batches, multi-backend support +- Added TqdmSink for progress bar logging +- Minor code cleanups and test updates + v0.1.8 (2025-07-18) ---------------------- Tarek Ibrahim (68) diff --git a/configuration.json b/configuration.json index b2b573e..34bbaa4 100644 --- a/configuration.json +++ b/configuration.json @@ -1,42 +1,47 @@ { "CuMind": { "networks": { - "hidden_dim": 128 + "hidden_state_dim": 128 }, "representation": { "type": "cumind.core.resnet.ResNet", - "num_blocks": 2, + "num_hidden_layers": 2, "conv_channels": 32, "seed": 42 }, "dynamics": { "type": "cumind.core.mlp.MLPWithEmbedding", - "num_blocks": 2, + "hidden_dim": 128, + "num_hidden_layers": 2, "seed": 42 }, "prediction": { "type": "cumind.core.mlp.MLPDual", + "hidden_dim": 128, + "num_hidden_layers": 2, "seed": 42 }, "memory": { - "type": "cumind.data.memory.MemoryBuffer", + "type": "cumind.data.memory.PrioritizedMemoryBuffer", "capacity": 2000, - "min_size": 100, + "min_size": 200, "min_pct": 0.1, - "per_alpha": 0.6, - "per_epsilon": 1e-06, - "per_beta": 0.4 + "alpha": 0.6, + "epsilon": 1e-06, + "beta": 0.4 }, "training": { "optimizer": "optax.adamw", "batch_size": 64, - "learning_rate": 0.01, + "num_batches": 1, + "learning_rate": 0.001, "weight_decay": 0.0001, - "target_update_frequency": 250, + "target_update_frequency": 100, "checkpoint_interval": 50, - "num_episodes": 1220, + "num_episodes": 2000, "train_frequency": 2, - "checkpoint_root_dir": "checkpoints" + "checkpoint_dir": "checkpoints", + "debug": false }, "mcts": { "num_simulations": 25, @@ -47,7 +52,8 @@ "env": { "name": "CartPole-v1", "action_space_size": 2, - "observation_shape": [4] + "observation_shape": [4], + "max_episode_steps": 500 }, "selfplay": { "num_unroll_steps": 5, @@ -60,13 +66,18 @@ "target": "float32" }, "logging": { + "wandb": false, + "title": "spamEggs", + "tags": ["foo", "bar"], "dir": "logs", "level": "INFO", "console": true, - "timestamps": false, + "timestamps": true, "tqdm": false }, "device": "cpu", - "seed": 42 + "seed": 42, + "validate": true, + "multi_device": false } } diff --git a/pyproject.toml b/pyproject.toml index 0cfab56..cdb672d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ # ========================= [project] name = "cumind" -version = "0.1.8" +version = "0.1.95" description = "A JAX-based CuMind is a JAX-based RL framework inspired by Google DeepMind. Achieve's superhuman performance in complex domains without pretraining nor prior knowledge of their rules." readme = "README.md" requires-python = ">=3.12" @@ -17,7 +17,6 @@ dependencies = [ "optax>=0.2.5", "chex>=0.1.89", "wandb>=0.20.1", - "tensorboard>=2.19.0", "ipython>=9.3.0", "ipykernel>=6.29.5", ] @@ -29,11 +28,7 @@ dependencies = [ # - chex: Testing and assertion utilities for JAX # Note: JAX CPU version is used by default -# To install other JAX versions, use: -# pip install -U jax[cuda12] # for NVIDIA GPUs with CUDA 12 -# pip install -U jax[tpu] # for TPU -# pip install -U jax[rocm] # for AMD GPUs -# pip install -U jax[metal] # for Apple Silicon +# LOOK AT CONFIG.PY LINE 385 VALIDATING DEVICE. # ========================= diff --git a/src/cartpole.py b/src/cartpole.py index 96ce65e..356c750 100644 --- a/src/cartpole.py +++ b/src/cartpole.py @@ -7,10 +7,16 @@ def main() -> None: """Main function for running the CartPole example.""" - cfg.load("configuration.json") - ckpt = train() + timestamp, checkpoint_dir = cfg.load("test.json") + print(f"Run UUID: {timestamp}") + + train() log.info(f"Training completed in {log.elapsed()}.") - inference(ckpt) + + latest_ckpt = f"{checkpoint_dir}/episode_{cfg.training.num_episodes:05d}.pkl" + + log.open() + inference(latest_ckpt, 500) if __name__ == "__main__": diff --git a/src/cumind/__init__.py b/src/cumind/__init__.py index c9f198e..d3d00ff 100644 --- a/src/cumind/__init__.py +++ b/src/cumind/__init__.py @@ -1,6 +1,6 @@ """CuMind: A modular reinforcement learning framework.""" -__version__ = "0.1.8" +__version__ = "0.1.95" # Most commonly used components from .agent import Agent, inference, train diff --git a/src/cumind/agent/agent.py b/src/cumind/agent/agent.py index 95ddbed..d9b3ca7 100644 --- a/src/cumind/agent/agent.py +++ b/src/cumind/agent/agent.py @@ -12,7 +12,6 @@ from cumind.core.network import CuMindNetwork from cumind.utils.config import cfg from cumind.utils.logger import log -from cumind.utils.prng import key class Agent: @@ -34,9 +33,6 @@ def __init__(self, existing_state: Optional[Dict[str, Any]] = None): with jax.default_device(self.device): self.network = CuMindNetwork(representation_network=cfg.representation(), dynamics_network=cfg.dynamics(), prediction_network=cfg.prediction()) - log.info("Creating target network.") - self.target_network = nnx.clone(self.network) - log.info(f"Setting up AdamW optimizer with learning rate {cfg.training.learning_rate} and weight decay {cfg.training.weight_decay}") self.optimizer = optax.adamw(learning_rate=cfg.training.learning_rate, weight_decay=cfg.training.weight_decay) @@ -46,6 +42,9 @@ def __init__(self, existing_state: Optional[Dict[str, Any]] = None): else: log.info("Initializing new optimizer state.") self.optimizer_state = self.optimizer.init(nnx.state(self.network, nnx.Param)) + # Ensure target network is properly initialized + log.info("Initializing target prediction network.") + self.network.update_target_prediction_network(hard=True) self.mcts = MCTS(self.network) log.info("Agent initialization complete.") @@ -60,6 +59,11 @@ def select_action(self, observation: np.ndarray, training: bool = False) -> Tupl Returns: A tuple containing the selected action index and the MCTS policy probabilities. """ + if cfg.training.debug: + num_actions = cfg.env.action_space_size + action_probs = np.ones(num_actions, dtype=np.float32) / num_actions + action_idx = int(np.random.choice(num_actions)) + return action_idx, action_probs log.debug(f"Selecting action. Training mode: {training}") obs_tensor = jax.device_put(jnp.array(observation)[None], self.device) # [None] adds batch dimension @@ -69,22 +73,23 @@ def select_action(self, observation: np.ndarray, training: bool = False) -> Tupl # Use MCTS to get action probabilities action_probs = self.mcts.search(root_hidden_state=hidden_state_array, add_noise=training) - + # Take best action + action_idx = int(np.argmax(action_probs)) + """ if training: # Sample action from probabilities action_idx = int(jax.random.choice(key.get(), len(action_probs), p=action_probs)) else: # Take best action action_idx = int(np.argmax(action_probs)) - - log.debug(f"Selected action: {action_idx}") + """ + # log.debug(f"Selected action: {action_idx}") return int(action_idx), action_probs def update_target_network(self) -> None: - """Update the target network's weights with the main network's weights.""" - log.debug("Updating target network.") - online_params = nnx.state(self.network, nnx.Param) - nnx.update(self.target_network, online_params) + """Update the target prediction network's weights with the main network's weights.""" + log.debug("Updating target prediction network.") + self.network.update_target_prediction_network(hard=False, tau=0.01) def save_state(self) -> Dict[str, Any]: """Get the current state of the agent for checkpointing. @@ -108,6 +113,6 @@ def load_state(self, state: Dict[str, Any]) -> None: nnx.update(self.network, state["network_state"]) self.optimizer_state = state["optimizer_state"] - log.info("Updating target network after loading state.") - self.update_target_network() + log.info("Updating target prediction network after loading state.") + self.network.update_target_prediction_network(hard=True) log.info("Agent state loaded successfully.") diff --git a/src/cumind/agent/runner.py b/src/cumind/agent/runner.py index b5a2971..e5d52d8 100644 --- a/src/cumind/agent/runner.py +++ b/src/cumind/agent/runner.py @@ -13,26 +13,25 @@ def train() -> str: """Train the agent on a given environment.""" - env = gym.make(cfg.env.name) + env = gym.make(id=cfg.env.name, max_episode_steps=cfg.env.max_episode_steps) agent = Agent() memory_buffer = cfg.memory() trainer = Trainer(agent, memory_buffer) - trainer.run_training_loop(env) + trainer.train(env) env.close() # type: ignore return trainer.checkpoint_dir -def inference(checkpoint_file: str) -> None: +def inference(checkpoint_file: str, num_episodes: int) -> None: """Run inference with a trained agent from a checkpoint.""" log.info("\nStarting inference.") if not os.path.isfile(checkpoint_file): - log.error(f"Checkpoint file not found: {checkpoint_file}") - return + raise RuntimeError(f"Checkpoint file not found: {checkpoint_file}") log.info(f"Loading agent from: {checkpoint_file}") @@ -40,8 +39,8 @@ def inference(checkpoint_file: str) -> None: state = load_checkpoint(checkpoint_file) inference_agent.load_state(state) - env = gym.make(cfg.env.name, render_mode="human") - for episode in range(500): + env = gym.make(id=cfg.env.name, max_episode_steps=cfg.env.max_episode_steps, render_mode="human") + for episode in range(num_episodes): obs, _ = env.reset() done = False total_reward = 0.0 diff --git a/src/cumind/agent/trainer.py b/src/cumind/agent/trainer.py index e62a0a7..6f7eeee 100644 --- a/src/cumind/agent/trainer.py +++ b/src/cumind/agent/trainer.py @@ -1,14 +1,11 @@ """Training loop implementation.""" -import math -import os -import sys -from datetime import datetime from typing import Any, Dict, List, Tuple import chex import jax import jax.numpy as jnp +import numpy as np import optax # type: ignore from flax import nnx from tqdm import tqdm # type: ignore @@ -19,49 +16,112 @@ from cumind.data.self_play import SelfPlay from cumind.utils.checkpoint import load_checkpoint, save_checkpoint from cumind.utils.config import cfg -from cumind.utils.logger import log +from cumind.utils.jax_utils import pmap_n_step_return, vmap_n_step_return +from cumind.utils.logger import TqdmSink, log + + +# This entire block of computation will be JIT-compiled and run on the GPU. +def _train_step_impl( + network: CuMindNetwork, + optimizer: optax.GradientTransformation, + params: nnx.State[Any, Any], + opt_state: optax.OptState, + observations: chex.Array, + actions: chex.Array, + policy_targets: chex.Array, + reward_targets: chex.Array, + bootstrap_obs: chex.Array, + rewards_stack: chex.Array, + bootstrap_mask: chex.Array, +) -> Tuple[chex.Array, Dict[str, chex.Array], Any, optax.OptState]: + """Core training step implementation (to be JIT-compiled).""" + + # 1. Calculate n-step returns entirely on the device + _, _, bootstrap_values = network.initial_inference(bootstrap_obs, use_target=True) + bootstrap_values = jnp.asarray(bootstrap_values).squeeze() * bootstrap_mask + + # Construct the full values stack for the n-step return calculation + values_stack = jnp.zeros_like(rewards_stack).at[:, cfg.selfplay.td_steps].set(bootstrap_values) + + # Compute the final value targets + if cfg.multi_device: + value_targets = pmap_n_step_return(jnp.asarray(rewards_stack), jnp.asarray(values_stack), cfg.selfplay.td_steps, cfg.selfplay.discount) + else: + value_targets = vmap_n_step_return(jnp.asarray(rewards_stack), jnp.asarray(values_stack), cfg.selfplay.td_steps, cfg.selfplay.discount) + + targets = { + "values": value_targets, + "rewards": reward_targets, + "policies": policy_targets, + } + + # 2. Compute loss and gradients + def loss_fn(params: nnx.State[Any, Any]) -> Tuple[chex.Array, Dict[str, chex.Array]]: + temp_network = nnx.clone(network) + nnx.update(temp_network, params) + losses = _compute_losses(temp_network, observations, actions, targets) + total_loss = jnp.sum(jnp.array(list(losses.values()))) + return total_loss, losses + + grad_fn = jax.value_and_grad(loss_fn, has_aux=True) + (total_loss, losses), grads = grad_fn(params) + + # 3. Apply updates + updates, new_opt_state = optimizer.update(grads, opt_state, params) + new_params = optax.apply_updates(params, updates) + return total_loss, losses, new_params, new_opt_state -class DummyTqdmFile: - def write(self, _: Any) -> None: - pass - def flush(self) -> None: - pass +def _compute_losses(network: CuMindNetwork, observations: chex.Array, actions: chex.Array, targets: Dict[str, chex.Array]) -> Dict[str, chex.Array]: + """Computes the value, policy, and reward losses.""" + hidden_states, initial_policy_logits, initial_values = network.initial_inference(observations) + value_loss = jnp.mean((jnp.asarray(initial_values).squeeze() - jnp.asarray(targets["values"])) ** 2) + policy_loss = -jnp.mean(jnp.sum(jnp.asarray(targets["policies"]) * jax.nn.log_softmax(initial_policy_logits, axis=-1), axis=-1)) + reward_loss = jnp.array(0.0) + current_states = hidden_states + for step in range(cfg.selfplay.num_unroll_steps): + step_actions = jnp.asarray(actions)[:, step] + next_states, pred_rewards, pred_policy_logits, pred_values = network.recurrent_inference(current_states, step_actions) + pred_rewards_squeezed = jnp.asarray(pred_rewards).squeeze() + target_rewards = jnp.asarray(targets["rewards"])[:, step] + reward_loss += jnp.mean((pred_rewards_squeezed - target_rewards) ** 2) + pred_values_squeezed = jnp.asarray(pred_values).squeeze() + value_loss += jnp.mean((pred_values_squeezed - jnp.asarray(targets["values"])) ** 2) + policy_log_probs = jax.nn.log_softmax(pred_policy_logits, axis=-1) + policy_loss += -jnp.mean(jnp.sum(jnp.asarray(targets["policies"]) * policy_log_probs, axis=-1)) + current_states = next_states + if cfg.selfplay.num_unroll_steps > 0: + reward_loss /= cfg.selfplay.num_unroll_steps + value_loss /= cfg.selfplay.num_unroll_steps + 1 + policy_loss /= cfg.selfplay.num_unroll_steps + 1 + return {"value_loss": value_loss, "policy_loss": policy_loss, "reward_loss": reward_loss} + + +_jitted_train_step = jax.jit(_train_step_impl, static_argnames=["network", "optimizer"]) class Trainer: - """Orchestrates the training process, including sampling, updates, and logging.""" + """Orchestrates the training process.""" def __init__(self, agent: Agent, memory: Memory): - """Initializes the Trainer. - Args: - agent: The agent to train. - memory: The memory buffer for sampling training data. - """ log.info(f"Initializing trainer for environment: {cfg.env.name}") self.agent = agent self.memory = memory - timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") - self.checkpoint_dir = f"{cfg.training.checkpoint_root_dir}/{cfg.env.name}/{timestamp}" - os.makedirs(self.checkpoint_dir, exist_ok=True) + self.checkpoint_dir = log.get_checkpoint_dir() log.info(f"Checkpoints will be saved to {self.checkpoint_dir}") self.train_step_count = 0 - def run_training_loop(self, env: Any) -> None: - """Runs the main training loop.""" + def train(self, env: Any) -> None: num_episodes = cfg.training.num_episodes train_frequency = cfg.training.train_frequency - tqdm_file = sys.stdout if cfg.logging.tqdm else DummyTqdmFile() + tqdm_file = TqdmSink(cfg.logging.tqdm) pbar = tqdm(range(1, num_episodes + 1), desc="Training Progress", file=tqdm_file) self_play = SelfPlay(self.agent, self.memory) - last_logged_percent = -1 self.last_loss: Dict[str, float] = {} - for episode in pbar: self._run_episode_and_log(env, self_play, episode) self._maybe_train_and_update(episode, train_frequency) - last_logged_percent = self._maybe_log_progress(pbar, episode, num_episodes, last_logged_percent) self._maybe_checkpoint(episode) def _run_episode_and_log(self, env: Any, self_play: SelfPlay, episode: int) -> None: @@ -77,28 +137,16 @@ def _run_episode_and_log(self, env: Any, self_play: SelfPlay, episode: int) -> N def _maybe_train_and_update(self, episode: int, train_frequency: int) -> None: if episode > 0 and episode % train_frequency == 0: - self.last_loss = self.train_step() - if self.train_step_count > 0 and self.train_step_count % cfg.training.target_update_frequency == 0: - log.info(f"Updating target network at training step {self.train_step_count}") - self.agent.update_target_network() - - def _maybe_log_progress(self, pbar: tqdm, episode: int, num_episodes: int, last_logged_percent: int) -> int: - percent = 100 * (episode - 1) / num_episodes - rate = pbar.format_dict.get("rate", 0.0) or 0.0 - n = pbar.format_dict.get("n", 0) - total = pbar.format_dict.get("total", None) - eta_val = pbar.format_dict.get("eta", None) - if eta_val is not None and isinstance(eta_val, (int, float)) and math.isfinite(eta_val): - eta = pbar.format_interval(eta_val) - elif rate > 0 and total is not None: - remaining = total - n - eta = pbar.format_interval(remaining / rate) - else: - eta = "?" - if int(percent) != last_logged_percent or episode == num_episodes: - log.info(f"Progress: {percent:.1f}% | {rate:.2f} it/s | ETA: {eta}") - return int(percent) - return last_logged_percent + if not self.memory.is_ready(cfg.memory.min_size, cfg.memory.min_pct): + log.warning("Buffer not ready for training, a larger buffer is needed.") + return + for _ in range(cfg.training.num_batches): + self.last_loss = self.train_step() + self.train_step_count += 1 + if self.train_step_count > 0 and self.train_step_count % cfg.training.target_update_frequency == 0: + log.info(f"Updating target network at training step {self.train_step_count}") + self.agent.update_target_network() + log.info("Target network update completed") def _maybe_checkpoint(self, episode: int) -> None: if episode > 0 and episode % cfg.training.checkpoint_interval == 0: @@ -108,137 +156,86 @@ def _log_metrics(self, metrics: Dict[str, Any]) -> None: log.info(f"Episode {metrics['Episode']:3d}: Reward={metrics['Reward']:6.1f}, Length={metrics['Length']:3d}, Loss={metrics['Loss']:.4f}, Memory={metrics['Memory']:2.2f}") def train_step(self) -> Dict[str, float]: - """Performs one full training step, including sampling and network update.""" - if not self.memory.is_ready(cfg.memory.min_size, cfg.memory.min_pct): - log.warning("Buffer not ready for training, skipping step.") - return {} log.debug(f"Starting training step {self.train_step_count}...") batch = self.memory.sample(cfg.training.batch_size) - observations, actions, targets = self._prepare_batch(batch) + ( + observations, + actions, + policy_targets, + reward_targets, + bootstrap_obs, + rewards_stack, + bootstrap_mask, + ) = self._prepare_batch(batch) params = nnx.state(self.agent.network, nnx.Param) - - grad_fn = jax.value_and_grad(self._loss_fn, has_aux=True) - (total_loss, losses), grads = grad_fn(params, observations, actions, targets) - - updates, self.agent.optimizer_state = self.agent.optimizer.update(grads, self.agent.optimizer_state, params) - updated_params = optax.apply_updates(params, updates) - - nnx.update(self.agent.network, updated_params) + total_loss, losses, new_params, new_opt_state = _jitted_train_step( + self.agent.network, + self.agent.optimizer, + params, + self.agent.optimizer_state, + observations, + actions, + policy_targets, + reward_targets, + bootstrap_obs, + rewards_stack, + bootstrap_mask, + ) + self.agent.optimizer_state = new_opt_state + nnx.update(self.agent.network, new_params) log.debug(f"Training step {self.train_step_count} complete.") - losses_float = {f"train/{k}": float(v) for k, v in losses.items()} losses_float["total_loss"] = float(total_loss) log.log_scalars(losses_float, self.train_step_count) - self.train_step_count += 1 return {"total_loss": float(total_loss), **losses_float} - def _loss_fn(self, params: nnx.State[Any, Any], observations: chex.Array, actions: chex.Array, targets: Dict[str, chex.Array]) -> Tuple[chex.Array, Dict[str, chex.Array]]: - """Computes the total loss for a batch.""" - temp_network = nnx.clone(self.agent.network) - nnx.update(temp_network, params) - - losses = self._compute_losses(temp_network, observations, actions, targets) - total_loss = jnp.sum(jnp.array(list(losses.values()))) - return total_loss, losses - - def _prepare_batch(self, batch: List[Any]) -> Tuple[chex.Array, chex.Array, Dict[str, chex.Array]]: - """Prepares a batch of trajectories for training.""" - observations, action_sequences, policy_targets, value_targets, reward_targets = [], [], [], [], [] - - for item in batch: - if not item: - log.critical("Encountered empty item in batch.") - raise RuntimeError("Encountered empty item in batch.") - - value = self._compute_n_step_return(item) - value_targets.append(value) - - policy_targets.append(item[0]["policy"]) - observations.append(item[0]["observation"]) - - actions = [step["action"] for step in item[: cfg.selfplay.num_unroll_steps]] - rewards = [step["reward"] for step in item[: cfg.selfplay.num_unroll_steps]] - action_sequences.append(actions) - reward_targets.append(rewards) - - for seq in action_sequences: - while len(seq) < cfg.selfplay.num_unroll_steps: - seq.append(0) - for seq in reward_targets: - while len(seq) < cfg.selfplay.num_unroll_steps: - seq.append(0.0) + def _prepare_batch(self, batch: List[Any]) -> Tuple[np.ndarray, ...]: + """Prepares a batch for training with pure NumPy, keeping it off the GPU.""" + batch_size = len(batch) + unroll_steps = cfg.selfplay.num_unroll_steps + n_steps = cfg.selfplay.td_steps + obs_shape = cfg.env.observation_shape + max_len = cfg.env.max_episode_steps + + observations = np.zeros((batch_size, *obs_shape), dtype=np.float32) + policy_targets = np.zeros((batch_size, cfg.env.action_space_size), dtype=np.float32) + action_sequences = np.zeros((batch_size, unroll_steps), dtype=np.int32) + reward_targets = np.zeros((batch_size, unroll_steps), dtype=np.float32) + rewards_stack = np.zeros((batch_size, max_len), dtype=np.float32) + bootstrap_obs_batch = np.zeros((batch_size, *obs_shape), dtype=np.float32) + bootstrap_mask = np.zeros((batch_size,), dtype=np.bool_) + + for i, item in enumerate(batch): + item_len = len(item) + observations[i] = item[0]["observation"] + policy_targets[i] = item[0]["policy"] + actions = [step["action"] for step in item[:unroll_steps]] + action_sequences[i, : len(actions)] = actions + full_rewards = [step["reward"] for step in item] + rewards_stack[i, :item_len] = full_rewards + unroll_rewards = full_rewards[:unroll_steps] + reward_targets[i, : len(unroll_rewards)] = unroll_rewards + if item_len > n_steps: + bootstrap_obs_batch[i] = item[n_steps]["observation"] + bootstrap_mask[i] = True return ( - jnp.array(observations), - jnp.array(action_sequences, dtype=jnp.int32), - { - "values": jnp.array(value_targets, dtype=jnp.float32), - "rewards": jnp.array(reward_targets, dtype=jnp.float32), - "policies": jnp.array(policy_targets, dtype=jnp.float32), - }, + observations, + action_sequences, + policy_targets, + reward_targets, + bootstrap_obs_batch, + rewards_stack, + bootstrap_mask, ) - def _compute_n_step_return(self, item: List[Dict[str, Any]]) -> float: - """Computes the n-step return for a item, with bootstrapping.""" - n_steps = cfg.selfplay.td_steps - discount = cfg.selfplay.discount - rewards = [step["reward"] for step in item] - n_step_return = 0.0 - - for i in range(min(len(rewards), n_steps)): - n_step_return += rewards[i] * (discount**i) - - if len(item) > n_steps: - last_obs = jnp.array(item[n_steps]["observation"])[None, :] - _, _, value = self.agent.target_network.initial_inference(last_obs) - n_step_return += (discount**n_steps) * float(jnp.asarray(value)[0, 0]) - - return n_step_return - - def _compute_losses(self, network: CuMindNetwork, observations: chex.Array, actions: chex.Array, targets: Dict[str, chex.Array]) -> Dict[str, chex.Array]: - """Computes the value, policy, and reward losses.""" - hidden_states, initial_policy_logits, initial_values = network.initial_inference(observations) - - value_loss = jnp.mean((jnp.asarray(initial_values).squeeze() - jnp.asarray(targets["values"])) ** 2) - policy_loss = -jnp.mean(jnp.sum(jnp.asarray(targets["policies"]) * jax.nn.log_softmax(initial_policy_logits, axis=-1), axis=-1)) - - reward_loss = jnp.array(0.0) - current_states = hidden_states - - # Accumulate value loss, policy loss, and reward loss for each unroll step - for step in range(cfg.selfplay.num_unroll_steps): - step_actions = jnp.asarray(actions)[:, step] - next_states, pred_rewards, pred_policy_logits, pred_values = network.recurrent_inference(current_states, step_actions) - - pred_rewards_squeezed = jnp.asarray(pred_rewards).squeeze() - target_rewards = jnp.asarray(targets["rewards"])[:, step] - reward_loss += jnp.mean((pred_rewards_squeezed - target_rewards) ** 2) - - pred_values_squeezed = jnp.asarray(pred_values).squeeze() - # Note: A more advanced implementation might use a different target for unrolled steps - value_loss += jnp.mean((pred_values_squeezed - jnp.asarray(targets["values"])) ** 2) - - policy_log_probs = jax.nn.log_softmax(pred_policy_logits, axis=-1) - policy_loss += -jnp.mean(jnp.sum(jnp.asarray(targets["policies"]) * policy_log_probs, axis=-1)) - - current_states = next_states - - if cfg.selfplay.num_unroll_steps > 0: - reward_loss /= cfg.selfplay.num_unroll_steps - value_loss /= cfg.selfplay.num_unroll_steps + 1 - policy_loss /= cfg.selfplay.num_unroll_steps + 1 - - return {"value_loss": value_loss, "policy_loss": policy_loss, "reward_loss": reward_loss} - def save_checkpoint(self, episode: int) -> None: - """Saves the agent's state to a checkpoint file.""" state = self.agent.save_state() path = f"{self.checkpoint_dir}/episode_{episode:05d}.pkl" save_checkpoint(state, path) def load_checkpoint(self, path: str) -> None: - """Loads the agent's state from a checkpoint file.""" log.info(f"Loading checkpoint from {path}") state = load_checkpoint(path) self.agent.load_state(state) diff --git a/src/cumind/cli.py b/src/cumind/cli.py index 3539cbb..60816fb 100644 --- a/src/cumind/cli.py +++ b/src/cumind/cli.py @@ -55,8 +55,6 @@ def select_checkpoint() -> Optional[str]: def main() -> None: """Main CLI entry point.""" - log.info("Welcome to CuMind!") - args = parse_arguments() config_path = args.config if not os.path.exists(config_path): @@ -69,7 +67,7 @@ def main() -> None: if checkpoint_path: log.info(f"Loading from checkpoint: {checkpoint_path}") - inference(checkpoint_path) + inference(checkpoint_path, 500) else: log.info("Starting a new run.") train() diff --git a/src/cumind/core/mlp.py b/src/cumind/core/mlp.py index c720a8b..ac0e6d0 100644 --- a/src/cumind/core/mlp.py +++ b/src/cumind/core/mlp.py @@ -1,4 +1,4 @@ -"""MLP neural network architectures.""" +"""MLP neural network architectures for reinforcement learning.""" from typing import Tuple @@ -10,55 +10,92 @@ class MLPWithEmbedding(nnx.Module): """MLP with embedding layer and residual connections.""" - def __init__(self, hidden_dim: int, embedding_size: int, num_blocks: int, rngs: nnx.Rngs): + def __init__(self, input_dim: int, hidden_dim: int, num_hidden_layers: int, embedding_size: int, rngs: nnx.Rngs): + """ + Initializes the MLP (Multi-Layer Perceptron) model. + + Args: + input_dim: Dimension of input state vectors + hidden_dim: Dimension of hidden layers + num_hidden_layers: Number of hidden layers in the network + embedding_size: Size of action embedding vocabulary + rngs: Random number generators for parameter initialization + """ + self.input_dim = input_dim self.hidden_dim = hidden_dim + self.num_hidden_layers = num_hidden_layers self.embedding_size = embedding_size - self.num_blocks = num_blocks - self.embedding = nnx.Embed(embedding_size, hidden_dim, rngs=rngs) - self.layers = [nnx.Linear(hidden_dim, hidden_dim, rngs=rngs) for _ in range(num_blocks)] - self.output_head = nnx.Linear(hidden_dim, 1, rngs=rngs) + self.action_embedding = nnx.Embed(embedding_size, hidden_dim, rngs=rngs) + self.input_projection = nnx.Linear(input_dim, hidden_dim, rngs=rngs) + self.hidden_layers = [nnx.Linear(hidden_dim, hidden_dim, rngs=rngs) for _ in range(num_hidden_layers)] + self.output_layer = nnx.Linear(hidden_dim, 1, rngs=rngs) def __call__(self, state: chex.Array, embedding_idx: chex.Array) -> Tuple[chex.Array, chex.Array]: - """Forward pass through the network. + """ + Forward pass through the network. Args: - state: Input state tensor. - embedding_idx: Embedding indices. + state: Input state tensor of shape (..., input_dim) + embedding_idx: Action embedding indices of shape (...,) Returns: - Tuple of (transformed_state, output). + Tuple of (hidden_features, output_value) """ - embedded = self.embedding(jnp.asarray(embedding_idx, dtype=jnp.int32)) - x = jnp.asarray(state, dtype=jnp.float32) + embedded + state = jnp.asarray(state, dtype=jnp.float32) + embedding_idx = jnp.asarray(embedding_idx, dtype=jnp.int32) + x = self.input_projection(state) + + action_emb = self.action_embedding(embedding_idx) + x = x + action_emb - for layer in self.layers: + for layer in self.hidden_layers: residual = x x = nnx.relu(layer(x)) x = x + residual + output = self.output_layer(x) - output = self.output_head(x) return x, output class MLPDual(nnx.Module): - """MLP with dual output heads.""" + """MLP with dual output heads for policy and value.""" - def __init__(self, hidden_dim: int, output_size: int, rngs: nnx.Rngs): - self.hidden_dim = hidden_dim - self.output_size = output_size - self.head1 = nnx.Linear(hidden_dim, output_size, rngs=rngs) - self.head2 = nnx.Linear(hidden_dim, 1, rngs=rngs) + def __init__(self, input_dim: int, hidden_dim: int, num_hidden_layers: int, num_actions: int, rngs: nnx.Rngs): + """ + Initialize dual-head MLP for policy and value. - def __call__(self, hidden_state: chex.Array) -> Tuple[chex.Array, chex.Array]: - """Forward pass through the network. + Args: + input_dim: Dimension of input state vectors + hidden_dim: Dimension of hidden layers + num_hidden_layers: Number of hidden layers + num_actions: Number of possible actions (for policy head) + rngs: Random number generators for parameter initialization + """ + self.input_dim = input_dim + self.hidden_dim = hidden_dim + self.num_hidden_layers = num_hidden_layers + self.num_actions = num_actions + self.input_projection = nnx.Linear(input_dim, hidden_dim, rngs=rngs) + self.hidden_layers = [nnx.Linear(hidden_dim, hidden_dim, rngs=rngs) for _ in range(num_hidden_layers)] + self.policy_head = nnx.Linear(hidden_dim, num_actions, rngs=rngs) + self.value_head = nnx.Linear(hidden_dim, 1, rngs=rngs) + + def __call__(self, state: chex.Array) -> Tuple[chex.Array, chex.Array]: + """ + Forward pass through the network. Args: - hidden_state: Input hidden state tensor. + state: Input state tensor of shape (..., input_dim) Returns: - Tuple of (output1, output2). + Tuple of (policy_logits, value) """ - x = jnp.asarray(hidden_state, dtype=jnp.float32) - output1 = self.head1(x) - output2 = self.head2(x) - return output1, output2 + x = jnp.asarray(state, dtype=jnp.float32) + x = self.input_projection(x) + for layer in self.hidden_layers: + x = nnx.relu(layer(x)) + + policy_logits = self.policy_head(x) + value = self.value_head(x) + + return policy_logits, value diff --git a/src/cumind/core/network.py b/src/cumind/core/network.py index 6d11418..5b253b9 100644 --- a/src/cumind/core/network.py +++ b/src/cumind/core/network.py @@ -1,17 +1,18 @@ """Unified CuMind neural network""" -from typing import Callable, Tuple +from typing import Callable, Optional, Tuple import chex +import jax from flax import nnx from cumind.utils.logger import log class CuMindNetwork(nnx.Module): - """The complete CuMind network, combining representation, dynamics, and prediction.""" + """The complete CuMind network, combining representation, dynamics, and prediction, with target prediction support.""" - def __init__(self, representation_network: Callable[[chex.Array], chex.Array], dynamics_network: Callable[[chex.Array, chex.Array], Tuple[chex.Array, chex.Array]], prediction_network: Callable[[chex.Array], Tuple[chex.Array, chex.Array]]): + def __init__(self, representation_network: Callable[[chex.Array], chex.Array], dynamics_network: Callable[[chex.Array, chex.Array], Tuple[chex.Array, chex.Array]], prediction_network: Callable[[chex.Array], Tuple[chex.Array, chex.Array]]) -> None: """Initializes the complete CuMind network. Args: @@ -23,8 +24,24 @@ def __init__(self, representation_network: Callable[[chex.Array], chex.Array], d self.representation_network = representation_network self.dynamics_network = dynamics_network self.prediction_network = prediction_network + # Target prediction network (initialized as a clone of the online prediction network) + self.target_prediction_network = nnx.clone(prediction_network) - def initial_inference(self, observation: chex.Array) -> Tuple[chex.Array, chex.Array, chex.Array]: + def update_target_prediction_network(self, hard: bool = True, tau: Optional[float] = None) -> None: + """Update the target prediction network. Hard copy by default, or soft update if tau is provided.""" + log.debug(f"Updating target prediction network: hard={hard}, tau={tau}") + if hard or tau is None: + self.target_prediction_network = nnx.clone(self.prediction_network) + log.debug("Target prediction network updated with hard copy") + else: + # Polyak averaging: target = tau * online + (1-tau) * target + online_params = nnx.state(self.prediction_network, nnx.Param) + target_params = nnx.state(self.target_prediction_network, nnx.Param) + new_params = jax.tree_util.tree_map(lambda o, t: tau * o + (1 - tau) * t, online_params, target_params) + nnx.update(self.target_prediction_network, new_params) + log.debug("Target prediction network updated with soft update (Polyak averaging)") + + def initial_inference(self, observation: chex.Array, use_target: bool = False) -> Tuple[chex.Array, chex.Array, chex.Array]: """Performs the initial inference step from an observation. Args: @@ -33,12 +50,15 @@ def initial_inference(self, observation: chex.Array) -> Tuple[chex.Array, chex.A Returns: A tuple of (hidden_state, policy_logits, value). """ - log.debug(f"Initial inference with observation shape: {observation.shape}") - hidden_state = self.representation_network(observation) - policy_logits, value = self.prediction_network(hidden_state) + log.debug(f"Initial inference with observation shape: {observation.shape}, use_target: {use_target}") + hidden_state: chex.Array = self.representation_network(observation) + if use_target: + policy_logits, value = self.target_prediction_network(hidden_state) + else: + policy_logits, value = self.prediction_network(hidden_state) return hidden_state, policy_logits, value - def recurrent_inference(self, hidden_state: chex.Array, action: chex.Array) -> Tuple[chex.Array, chex.Array, chex.Array, chex.Array]: + def recurrent_inference(self, hidden_state: chex.Array, action: chex.Array, use_target: bool = False) -> Tuple[chex.Array, chex.Array, chex.Array, chex.Array]: """Performs a recurrent inference step from a hidden state and action. Args: @@ -49,6 +69,11 @@ def recurrent_inference(self, hidden_state: chex.Array, action: chex.Array) -> T A tuple of (next_hidden_state, reward, policy_logits, value). """ log.debug(f"Recurrent inference with hidden state shape: {hidden_state.shape} and action shape: {action.shape}") + next_hidden_state: chex.Array + reward: chex.Array next_hidden_state, reward = self.dynamics_network(hidden_state, action) - policy_logits, value = self.prediction_network(next_hidden_state) + if use_target: + policy_logits, value = self.target_prediction_network(next_hidden_state) + else: + policy_logits, value = self.prediction_network(next_hidden_state) return next_hidden_state, reward, policy_logits, value diff --git a/src/cumind/core/resnet.py b/src/cumind/core/resnet.py index f0dec91..d134624 100644 --- a/src/cumind/core/resnet.py +++ b/src/cumind/core/resnet.py @@ -1,40 +1,63 @@ -"""ResNet architecture for CuMind.""" +"""ResNet architecture for reinforcement learning.""" -from typing import Tuple +from typing import Optional, Tuple, Union import chex +import jax.numpy as jnp from flax import nnx from cumind.core.encoder import BaseEncoder, ConvEncoder, VectorEncoder class ResNet(nnx.Module): - """ - General-purpose ResNet backbone supporting both vector and image inputs. - - Args: - input_shape: Shape of the input data (tuple). - hidden_dim: Dimension of hidden layers. - num_blocks: Number of residual blocks. - conv_channels: Number of channels for convolutional layers (used for image input). - rngs: Random number generators for parameter initialization. - - Raises: - ValueError: If input_shape is not 1D (vector) or 3D (image). - """ - - def __init__(self, hidden_dim: int, input_shape: Tuple[int, ...], num_blocks: int, conv_channels: int, rngs: nnx.Rngs): - self.input_shape = input_shape + """ResNet backbone supporting both vector and image inputs.""" + + def __init__(self, input_dim: Union[int, Tuple[int, int, int]], hidden_dim: int, num_hidden_layers: int, rngs: nnx.Rngs, conv_channels: Optional[int] = None): + """ + Initializes the ResNet model. + + Args: + input_dim: Input dimension - int for vector input, (H, W, C) for image input + hidden_dim: Dimension of hidden layers and output features + num_hidden_layers: Number of residual blocks/layers + conv_channels: Number of channels for convolutional layers (image input only) + rngs: Random number generators for parameter initialization + """ self.hidden_dim = hidden_dim - self.num_blocks = num_blocks + self.num_hidden_layers = num_hidden_layers self.conv_channels = conv_channels self.encoder: BaseEncoder - if len(input_shape) == 1: - self.encoder = VectorEncoder(input_shape, hidden_dim, num_blocks, rngs) - elif len(input_shape) == 3: - self.encoder = ConvEncoder(input_shape, hidden_dim, num_blocks, conv_channels, rngs) + + # Handle 1D tuples as integers (e.g., (4,) -> 4) + in_dim: Union[int, Tuple[int, int, int]] + if isinstance(input_dim, tuple) and len(input_dim) == 1: + in_dim = input_dim[0] + else: + in_dim = input_dim + + observation_shape: Tuple[int, ...] + if isinstance(in_dim, int): + # Vector input: (in_dim,) -> hidden_dim + observation_shape = (in_dim,) + self.encoder = VectorEncoder(observation_shape=observation_shape, hidden_dim=hidden_dim, num_blocks=num_hidden_layers, rngs=rngs) + elif isinstance(in_dim, tuple) and len(in_dim) == 3: + # Image input: (height, width, channels) -> hidden_dim + observation_shape = in_dim + assert conv_channels is not None, "conv_channels must be provided for image input" + self.encoder = ConvEncoder(observation_shape=observation_shape, hidden_dim=hidden_dim, num_blocks=num_hidden_layers, conv_channels=conv_channels, rngs=rngs) else: - raise ValueError("Unsupported observation shape") + raise ValueError(f"Unsupported input_dim: {in_dim}. Use int or (X,) for vector input or (H, W, C) tuple for image input.") def __call__(self, x: chex.Array) -> chex.Array: + """ + Forward pass through the ResNet. + + Args: + x: Input tensor of shape (..., input_dim) for vector input + or (..., H, W, C) for image input + + Returns: + Output tensor of shape (..., hidden_dim) + """ + x = jnp.asarray(x, dtype=jnp.float32) return self.encoder(x) diff --git a/src/cumind/utils/config.py b/src/cumind/utils/config.py index 6ceebed..a9c73d5 100644 --- a/src/cumind/utils/config.py +++ b/src/cumind/utils/config.py @@ -4,6 +4,7 @@ import inspect import json import re +import subprocess import threading from pathlib import Path from typing import Any, Optional, Tuple, Type, Union, cast, get_args, get_origin @@ -46,7 +47,7 @@ def extras(self) -> DictType[str, Any]: class GeneralNetworksConfig: """Configuration for general networks.""" - hidden_dim: int = 128 + hidden_state_dim: int = 128 @dataclasses.dataclass(frozen=True) @@ -54,13 +55,13 @@ class RepresentationConfig(HotSwappableConfig): """Configuration for representation network.""" type: Optional[Union[str, Type[Any]]] = "cumind.core.resnet.ResNet" - num_blocks: int = 2 + num_hidden_layers: int = 2 conv_channels: int = 32 seed: int = 42 def extras(self) -> DictType[str, Any]: - hidden_dim = cfg.networks.hidden_dim - input_shape = cfg.env.observation_shape + input_dim = cfg.env.observation_shape + hidden_dim = cfg.networks.hidden_state_dim rngs = nnx.Rngs(params=self.seed) return locals() @@ -70,11 +71,12 @@ class DynamicsConfig(HotSwappableConfig): """Configuration for dynamics network.""" type: Optional[Union[str, Type[Any]]] = "cumind.core.mlp.MLPWithEmbedding" - num_blocks: int = 2 + hidden_dim: int = 128 + num_hidden_layers: int = 2 seed: int = 42 def extras(self) -> DictType[str, Any]: - hidden_dim = cfg.networks.hidden_dim + input_dim = cfg.networks.hidden_state_dim embedding_size = cfg.env.action_space_size rngs = nnx.Rngs(params=self.seed) return locals() @@ -85,11 +87,13 @@ class PredictionConfig(HotSwappableConfig): """Configuration for prediction network.""" type: Optional[Union[str, Type[Any]]] = "cumind.core.mlp.MLPDual" + hidden_dim: int = 128 + num_hidden_layers: int = 2 seed: int = 42 def extras(self) -> DictType[str, Any]: - hidden_dim = cfg.networks.hidden_dim - output_size = cfg.env.action_space_size + input_dim = cfg.networks.hidden_state_dim + num_actions = cfg.env.action_space_size rngs = nnx.Rngs(params=self.seed) return locals() @@ -98,13 +102,13 @@ def extras(self) -> DictType[str, Any]: class MemoryConfig(HotSwappableConfig): """Configuration for memory buffer.""" - type: Optional[Union[str, Type[Any]]] = "cumind.data.memory.MemoryBuffer" + type: Optional[Union[str, Type[Any]]] = "cumind.data.memory.PrioritizedMemoryBuffer" capacity: int = 2000 - min_size: int = 100 + min_size: int = 200 min_pct: float = 0.1 - per_alpha: float = 0.6 - per_epsilon: float = 1e-6 - per_beta: float = 0.4 + alpha: float = 0.6 + epsilon: float = 1e-6 + beta: float = 0.4 def extras(self) -> DictType[str, Any]: return {} @@ -116,13 +120,15 @@ class TrainingConfig: optimizer: str = "optax.adamw" batch_size: int = 64 - learning_rate: float = 0.01 + num_batches: int = 1 + learning_rate: float = 0.001 weight_decay: float = 0.0001 - target_update_frequency: int = 250 + target_update_frequency: int = 100 checkpoint_interval: int = 50 - num_episodes: int = 1220 + num_episodes: int = 2000 train_frequency: int = 2 - checkpoint_root_dir: str = "checkpoints" + checkpoint_dir: str = "checkpoints" + debug: bool = False @dataclasses.dataclass(frozen=True) @@ -142,6 +148,7 @@ class EnvironmentConfig: name: str = "CartPole-v1" action_space_size: int = 2 observation_shape: Tuple[int, ...] = (4,) + max_episode_steps: int = 500 @dataclasses.dataclass(frozen=True) @@ -166,10 +173,14 @@ class DataTypesConfig: class LoggerConfig: """Configuration for logger.""" + wandb: bool = False + title: str = "spamEggs" + tags: list[str] = dataclasses.field(default_factory=lambda: ["foo", "bar"]) + dir: str = "logs" level: str = "INFO" console: bool = True - timestamps: bool = False + timestamps: bool = True tqdm: bool = False @@ -214,8 +225,18 @@ class Configuration(metaclass=ConfigMeta): # Global settings device: str = "cpu" seed: int = 42 + validate: bool = True + multi_device: bool = False + + @classmethod + def _get_instance(cls) -> "Configuration": + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = cls() + return cls._instance - def boot(self, path: Optional[str] = None) -> None: + def boot(self, path: Optional[str] = None) -> Tuple[str, str]: self._validate() from cumind.utils.logger import log @@ -223,27 +244,33 @@ def boot(self, path: Optional[str] = None) -> None: log(cfg=self) key.seed(self.seed) + if path is not None: log.info(f"Config location: {path}") else: log.info("Loaded default config") - log.info("Configuration validated and loaded successfully.") - @classmethod - def _get_instance(cls) -> "Configuration": - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = cls() - return cls._instance + # Log the full config as JSON + config_json = self._as_json_str() + log.info("Config values:\n" + config_json) + + timestamp = log.get_timestamp() + checkpoint_dir = log.get_checkpoint_dir() + + log.info(f"Logging directory: {self.logging.dir}/{timestamp}/training.log") + log.info(f"Checkpoint directory: {checkpoint_dir}/") + + log.info("Configuration booted successfully.") + + return timestamp, checkpoint_dir @classmethod - def load(cls, path: str) -> None: + def load(cls, path: str) -> Tuple[str, str]: """Load configuration from a JSON file and set as singleton instance. Automatically validates after loading.""" cfg = cls._from_json(path) with cls._lock: cls._instance = cfg - cfg.boot(path) + return cfg.boot(path) @classmethod def save(cls, path: str) -> None: @@ -252,22 +279,24 @@ def save(cls, path: str) -> None: def _validate(self) -> None: """Validates the configuration parameters.""" + if not self.validate: + return # 1. GeneralNetworksConfig - if self.networks.hidden_dim <= 0: - raise ValueError(f"networks.hidden_dim must be positive, got {self.networks.hidden_dim}") + if self.networks.hidden_state_dim <= 0: + raise ValueError(f"networks.hidden_dim must be positive, got {self.networks.hidden_state_dim}") # 2. RepresentationConfig - if self.representation.num_blocks <= 0: - raise ValueError(f"representation.num_blocks must be positive, got {self.representation.num_blocks}") + if self.representation.num_hidden_layers <= 0: + raise ValueError(f"representation.num_hidden_layers must be positive, got {self.representation.num_hidden_layers}") if self.representation.conv_channels <= 0: raise ValueError(f"representation.conv_channels must be positive, got {self.representation.conv_channels}") if self.representation.type is None: raise ValueError("representation.type must be specified") # 3. DynamicsConfig - if self.dynamics.num_blocks <= 0: - raise ValueError(f"dynamics.num_blocks must be positive, got {self.dynamics.num_blocks}") + if self.dynamics.num_hidden_layers <= 0: + raise ValueError(f"dynamics.num_hidden_layers must be positive, got {self.dynamics.num_hidden_layers}") if self.dynamics.type is None: raise ValueError("dynamics.type must be specified") @@ -282,12 +311,12 @@ def _validate(self) -> None: raise ValueError(f"memory.min_size must be positive, got {self.memory.min_size}") if not (0 < self.memory.min_pct < 1): raise ValueError(f"memory.min_pct must be between 0 and 1, got {self.memory.min_pct}") - if self.memory.per_alpha <= 0: - raise ValueError(f"memory.per_alpha must be positive, got {self.memory.per_alpha}") - if self.memory.per_epsilon <= 0: - raise ValueError(f"memory.per_epsilon must be positive, got {self.memory.per_epsilon}") - if self.memory.per_beta <= 0: - raise ValueError(f"memory.per_beta must be positive, got {self.memory.per_beta}") + if self.memory.alpha <= 0: + raise ValueError(f"memory.per_alpha must be positive, got {self.memory.alpha}") + if self.memory.epsilon <= 0: + raise ValueError(f"memory.per_epsilon must be positive, got {self.memory.epsilon}") + if self.memory.beta <= 0: + raise ValueError(f"memory.per_beta must be positive, got {self.memory.beta}") if self.memory.type is None: raise ValueError("memory.type must be specified") @@ -306,8 +335,8 @@ def _validate(self) -> None: raise ValueError(f"training.num_episodes must be positive, got {self.training.num_episodes}") if self.training.train_frequency <= 0: raise ValueError(f"training.train_frequency must be positive, got {self.training.train_frequency}") - if not isinstance(self.training.checkpoint_root_dir, str) or not self.training.checkpoint_root_dir: - raise ValueError("training.checkpoint_root_dir must be a non-empty string") + if not isinstance(self.training.checkpoint_dir, str) or not self.training.checkpoint_dir: + raise ValueError("training.checkpoint_dir must be a non-empty string") if not isinstance(self.training.optimizer, str) or not self.training.optimizer: raise ValueError("training.optimizer must be a non-empty string") @@ -328,6 +357,8 @@ def _validate(self) -> None: raise ValueError(f"env.action_space_size must be positive, got {self.env.action_space_size}") if not isinstance(self.env.observation_shape, tuple) or not all(isinstance(x, int) and x > 0 for x in self.env.observation_shape): raise ValueError("env.observation_shape must be a tuple of positive integers") + if not isinstance(self.env.max_episode_steps, int) or self.env.max_episode_steps <= 0: + raise ValueError(f"env.max_episode_steps must be a positive integer, got {self.env.max_episode_steps}") # 9. SelfPlayConfig if self.selfplay.num_unroll_steps <= 0: @@ -358,11 +389,28 @@ def _validate(self) -> None: raise ValueError(f"logging.timestamps must be a boolean, got {type(self.logging.timestamps)}") if not isinstance(self.logging.dir, str) or not self.logging.dir: raise ValueError("logging.dir must be a non-empty string") + if self.logging.title is not None and not isinstance(self.logging.title, str): + raise ValueError(f"logging.wandb_name must be a string, got {type(self.logging.title)}") + if not isinstance(self.logging.tags, list) or not all(isinstance(tag, str) for tag in self.logging.tags): + raise ValueError("logging.tags must be a list of strings") # 12. device - valid_devices = ["cpu", "gpu", "tpu"] + valid_devices = ["cpu", "cuda", "tpu", "rocm", "metal"] if self.device not in valid_devices: raise ValueError(f"device must be one of {valid_devices}, got {self.device}") + if not isinstance(self.multi_device, bool): + raise ValueError(f"multi_device must be a boolean, got {type(self.multi_device)}") + if self.multi_device and self.device == "cpu": + raise ValueError("multi_device cannot be True when device is 'cpu'") + + if self.device == "cuda": + subprocess.run(["uv", "pip", "install", "jax[cuda12]>=0.6.2"], check=True) + elif self.device == "tpu": + subprocess.run(["uv", "pip", "install", "jax[tpu]>=0.6.2"], check=True) + elif self.device == "rocm": + subprocess.run(["uv", "pip", "install", "jax[rocm]>=0.6.2"], check=True) + elif self.device == "metal": + subprocess.run(["uv", "pip", "install", "jax[metal]>=0.6.2"], check=True) # 13. seed if not isinstance(self.seed, int) or self.seed < 0: @@ -417,15 +465,10 @@ def _coerce_types(data: dict[str, Any], dataclass_type: type[Any]) -> dict[str, section_configs = _coerce_types(config_params, cls) return cls(**section_configs) - def _to_json(self, json_path: str) -> None: - """Saves the configuration to a JSON file.""" - json_file = Path(json_path) - json_file.parent.mkdir(parents=True, exist_ok=True) - + def _as_json_str(self) -> str: + """Returns the configuration as a JSON string.""" config_dict = dataclasses.asdict(self) organized_config = {} - - # Exclude internal fields (those starting with an underscore) for k, v in config_dict.items(): if k.startswith("_"): continue @@ -433,14 +476,16 @@ def _to_json(self, json_path: str) -> None: organized_config[k] = dataclasses.asdict(v) else: organized_config[k] = v - final_config = {"CuMind": organized_config} - - # Dump to string first json_str = json.dumps(final_config, indent=2) - # Compact single-element lists: [\n 4\n] -> [4] - json_str = re.sub(r"\[\s*([\d.eE+-]+)\s*\]", r"[\1]", json_str) + json_str = re.sub(r'\[\s*\n\s*(.*?)\s*\n\s*\]', lambda m: '[' + re.sub(r'\s*\n\s*', ' ', m.group(1).strip()) + ']', json_str, flags=re.DOTALL) + return json_str + def _to_json(self, json_path: str) -> None: + """Saves the configuration to a JSON file.""" + json_file = Path(json_path) + json_file.parent.mkdir(parents=True, exist_ok=True) + json_str = self._as_json_str() with open(json_file, "w", encoding="utf-8") as f: f.write(json_str) f.write("\n") diff --git a/src/cumind/utils/jax_utils.py b/src/cumind/utils/jax_utils.py index 343ac83..0928499 100644 --- a/src/cumind/utils/jax_utils.py +++ b/src/cumind/utils/jax_utils.py @@ -84,3 +84,39 @@ def safe_normalize(x: chex.Array, axis: int = -1, epsilon: float = 1e-8) -> chex """ norm = jnp.linalg.norm(x, axis=axis, keepdims=True) return x / jnp.maximum(norm, epsilon) + + +def vectorized_n_step_return(rewards: jnp.ndarray, values: jnp.ndarray, n_steps: int, discount: float) -> jnp.ndarray: + """Compute the n-step return for a sequence of rewards and values. + + This function calculates the n-step return using a vectorized approach, + replacing the need for a Python loop. It is designed to work with JAX for + efficient computation on accelerators. + + Args: + rewards: Array of rewards + values: Array of value estimates + n_steps: Number of steps to consider for the n-step return + discount: Discount factor for future rewards + + Returns: + Array containing the n-step returns + """ + idx = jnp.arange(n_steps) + mask = idx < rewards.shape[0] + rewards = jnp.where(mask, rewards[:n_steps], 0) + discounts = discount**idx + n_step_return = jnp.sum(rewards * discounts) + if rewards.shape[0] > n_steps: + n_step_return += (discount**n_steps) * values[n_steps] + return n_step_return + + +def vmap_n_step_return(rewards: jnp.ndarray, values: jnp.ndarray, n_steps: int, discount: float) -> chex.Array: + """Computes n-step returns for batches of rewards and values using JAX vmap.""" + return cast(chex.Array, jax.vmap(vectorized_n_step_return, in_axes=(0, 0, None, None))(rewards, values, n_steps, discount)) + + +def pmap_n_step_return(rewards: jnp.ndarray, values: jnp.ndarray, n_steps: int, discount: float) -> chex.Array: + """Computes n-step returns for batches of rewards and values using JAX vmap.""" + return cast(chex.Array, jax.pmap(vectorized_n_step_return, in_axes=(0, 0, None, None))(rewards, values, n_steps, discount)) diff --git a/src/cumind/utils/logger.py b/src/cumind/utils/logger.py index ec7c68d..e2a910a 100644 --- a/src/cumind/utils/logger.py +++ b/src/cumind/utils/logger.py @@ -7,9 +7,7 @@ from pathlib import Path from typing import Any, Dict, Optional -import tensorboard as tb # type: ignore import wandb - from cumind.utils.config import cfg @@ -20,6 +18,14 @@ class Logger: _initialized: bool = False _lock: threading.RLock = threading.RLock() + @classmethod + def _get_instance(cls) -> "Logger": + """Get the singleton instance, creating it if necessary.""" + if cls._instance is None: + cls() + assert cls._instance is not None + return cls._instance + def __new__(cls, *args: Any, **kwargs: Any) -> "Logger": if cls._instance is None: with cls._lock: @@ -30,23 +36,19 @@ def __new__(cls, *args: Any, **kwargs: Any) -> "Logger": cls._instance._initialize(*args, **kwargs) return cls._instance + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Constructor - initialization is handled in __new__.""" + pass + def _initialize( self, - cfg: Optional[cfg] = None, - dir: str = "logs", - level: str = "INFO", - console: bool = False, - timestamps: bool = True, - wandb_config: Optional[Dict[str, Any]] = None, - tensorboard_config: Optional[Dict[str, Any]] = None, + cfg: cfg, ) -> None: """Initialize the logger instance.""" - if cfg is not None: - dir = cfg.logging.dir - level = cfg.logging.level - console = cfg.logging.console - timestamps = cfg.logging.timestamps + level: str = cfg.logging.level + console: bool = cfg.logging.console + timestamps: bool = cfg.logging.timestamps self._logger = logging.getLogger("CuMindLogger") self.tb_writer: Optional[Any] = None @@ -62,11 +64,11 @@ def _initialize( # Single formatter for all handlers self._formatter = logging.Formatter(self.FORMAT, datefmt=self.DATEFMT) - # Setup file handler - + # Setup directories self.start_time = datetime.now().strftime("%Y%m%d_%H%M%S") - self.log_dir = Path(dir) / self.start_time - self.log_dir.mkdir(parents=True, exist_ok=True) + self._create_directories(cfg) + + # Setup file handler file_handler = logging.FileHandler(self.log_dir / "training.log") file_handler.setFormatter(self._formatter) self._logger.addHandler(file_handler) @@ -77,29 +79,40 @@ def _initialize( self.set_level(level) - # Integrations - self.use_wandb = wandb_config is not None - self.use_tensorboard = tensorboard_config is not None + # Setup wandb config + self.use_wandb = cfg.logging.wandb if self.use_wandb: - assert wandb_config is not None + wandb_config: Dict[str, Any] = { + "project": "CuMind", + "name": cfg.logging.title, + "tags": cfg.logging.tags, + } if wandb.run is None: wandb.init(**wandb_config) - if self.use_tensorboard: - self.tb_writer = tb.summary.create_file_writer(str(self.log_dir)) - type(self)._initialized = True - def __init__(self, *args: Any, **kwargs: Any) -> None: - """Constructor - initialization is handled in __new__.""" - pass + def _create_directories(self, cfg: cfg) -> None: + """Create both log and checkpoint directories with consistent timestamp.""" + # Log directory + self.log_dir = Path(cfg.logging.dir) / self.start_time + self.log_dir.mkdir(parents=True, exist_ok=True) + + # Checkpoint directory + self.checkpoint_dir = f"{cfg.training.checkpoint_dir}/{cfg.env.name}/{self.start_time}" + Path(self.checkpoint_dir).mkdir(parents=True, exist_ok=True) @classmethod - def _get_instance(cls) -> "Logger": - """Get the singleton instance, creating it if necessary.""" - if cls._instance is None: - cls() - assert cls._instance is not None - return cls._instance + def get_timestamp(cls) -> str: + """Get the current timestamp used for directories.""" + return cls._get_instance().start_time + + @classmethod + def get_checkpoint_dir(cls) -> str: + """Get the checkpoint directory path.""" + instance = cls._get_instance() + if instance.checkpoint_dir is None: + raise RuntimeError("Checkpoint directory not available - logger was initialized without config") + return instance.checkpoint_dir @classmethod def debug(cls, msg: str, *args: Any, **kwargs: Any) -> None: @@ -131,9 +144,6 @@ def log_scalar(cls, name: str, value: float, step: int) -> None: cls.info(f"Step {step:4d}: {name} = {value:.6f}") if instance.use_wandb: wandb.log({name: value}, step=step) - if instance.use_tensorboard and instance.tb_writer: - with instance.tb_writer.as_default(): - tb.summary.scalar(name, value, step=step) @classmethod def log_scalars(cls, metrics: Dict[str, float], step: int) -> None: @@ -190,8 +200,6 @@ def shutdown(cls) -> None: if instance.use_wandb and wandb.run is not None: wandb.finish() - if instance.use_tensorboard and instance.tb_writer is not None: - instance.tb_writer.close() cls.info("Closing logger handlers and shutting down logging system.") for handler in instance._logger.handlers[:]: @@ -218,3 +226,21 @@ def format(self, record: logging.LogRecord) -> str: # Alias log = Logger + + +class TqdmSink: + def __init__(self, mode: bool): + self.mode = mode + if mode: + self.sink = self._stdout_sink + else: + self.sink = self._logger_sink + + def write(self, msg: Any) -> None: + self.sink(msg) + + def _stdout_sink(self, msg: Any) -> None: + sys.stdout.write(str(msg)) + + def _logger_sink(self, msg: Any) -> None: + log.info(str(msg)) diff --git a/src/cumind/utils/prng.py b/src/cumind/utils/prng.py index 82f6695..b53c348 100644 --- a/src/cumind/utils/prng.py +++ b/src/cumind/utils/prng.py @@ -1,7 +1,7 @@ """PRNG utilities for JAX key management.""" import threading -from typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union, cast import jax import jax.numpy as jnp @@ -77,8 +77,18 @@ def get(cls, num: Union[int, Tuple[int, ...]] = 1) -> jax.Array: if prod == 1: return subkeys[0] else: + assert cls._key is not None return jnp.array(subkeys).reshape(shape + cls._key.shape) + @classmethod + def randint(cls, minval: int, maxval: int, shape: Tuple[int, ...] = ()) -> int: + """Return a random integer between minval and maxval.""" + with cls._lock: + if cls._key is None or cls._seed is None: + log.error("Attempted to use PRNG before initialization") + raise RuntimeError("PRNG not initialized. Call key.seed(value) first.") + return cast(int, jax.random.randint(cls._key, shape, minval, maxval).item()) + # Alias key = KeyManager diff --git a/src/cumind/utils/resolve.py b/src/cumind/utils/resolve.py index a6c03e8..35432e2 100644 --- a/src/cumind/utils/resolve.py +++ b/src/cumind/utils/resolve.py @@ -7,5 +7,8 @@ def resolve(dotted: str) -> Any: """Import and return a class/function from a dotted string path.""" module, attr = dotted.rsplit(".", 1) - mod = importlib.import_module(module) - return getattr(mod, attr) + try: + mod = importlib.import_module(module) + return getattr(mod, attr) + except (ImportError, AttributeError) as e: + raise ImportError(f"Could not resolve '{dotted}': {e}") diff --git a/tests/test_agent.py b/tests/test_agent.py index 8aaaecc..df62d08 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -7,8 +7,12 @@ from cumind.agent.agent import Agent from cumind.utils.config import cfg +from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_prng_manager_singleton(): diff --git a/tests/test_basic.py b/tests/test_basic.py index 1dcb57b..7ca6b9e 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -8,8 +8,12 @@ from cumind.core.network import CuMindNetwork from cumind.data.memory import Memory, MemoryBuffer, PrioritizedMemoryBuffer, TreeBuffer from cumind.utils.config import cfg +from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_prng_manager_singleton(): @@ -46,7 +50,7 @@ def test_network_inference(): obs = jnp.ones((batch_size, 4)) hidden_state, policy_logits, value = network.initial_inference(obs) - assert hidden_state.shape == (batch_size, cfg.networks.hidden_dim) + assert hidden_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert policy_logits.shape == (batch_size, cfg.env.action_space_size) assert value.shape == (batch_size, 1) @@ -54,7 +58,7 @@ def test_network_inference(): actions = jnp.array([0, 1]) next_state, reward, next_policy, next_value = network.recurrent_inference(hidden_state, actions) - assert next_state.shape == (batch_size, cfg.networks.hidden_dim) + assert next_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert reward.shape == (batch_size, 1) assert next_policy.shape == (batch_size, cfg.env.action_space_size) assert next_value.shape == (batch_size, 1) @@ -85,11 +89,11 @@ def test_memory_buffer_functionality(): _test_buffer_operations(memory_buffer, "MemoryBuffer") # Test PrioritizedMemoryBuffer - prioritized_buffer = PrioritizedMemoryBuffer(capacity=100, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon, beta=cfg.memory.per_beta) + prioritized_buffer = PrioritizedMemoryBuffer(capacity=100, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon, beta=cfg.memory.beta) _test_buffer_operations(prioritized_buffer, "PrioritizedMemoryBuffer") # Test TreeBuffer - tree_buffer = TreeBuffer(capacity=100, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon) + tree_buffer = TreeBuffer(capacity=100, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon) _test_buffer_operations(tree_buffer, "TreeBuffer") @@ -123,7 +127,7 @@ def _test_buffer_operations(buffer: Memory, buffer_name: str): def test_prioritized_buffer_priority_update(): """Test priority update functionality for prioritized buffers.""" # Test PrioritizedMemoryBuffer priority update - prioritized_buffer = PrioritizedMemoryBuffer(capacity=10, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon, beta=cfg.memory.per_beta) + prioritized_buffer = PrioritizedMemoryBuffer(capacity=10, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon, beta=cfg.memory.beta) sample = [{"observation": np.ones(4), "action": 0, "reward": 1.0}] prioritized_buffer.add(sample) @@ -132,7 +136,7 @@ def test_prioritized_buffer_priority_update(): assert prioritized_buffer.max_priority == 5.0 # Test TreeBuffer priority update - tree_buffer = TreeBuffer(capacity=10, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon) + tree_buffer = TreeBuffer(capacity=10, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon) tree_buffer.add(sample) # Update priority - TreeBuffer applies alpha exponent, so we need to account for that @@ -149,8 +153,8 @@ def test_basic_integration(): # Test with different buffer types buffer_types = [ (MemoryBuffer, {"capacity": 10}), - (PrioritizedMemoryBuffer, {"capacity": 10, "alpha": cfg.memory.per_alpha, "epsilon": cfg.memory.per_epsilon, "beta": cfg.memory.per_beta}), - (TreeBuffer, {"capacity": 10, "alpha": cfg.memory.per_alpha, "epsilon": cfg.memory.per_epsilon}), + (PrioritizedMemoryBuffer, {"capacity": 10, "alpha": cfg.memory.alpha, "epsilon": cfg.memory.epsilon, "beta": cfg.memory.beta}), + (TreeBuffer, {"capacity": 10, "alpha": cfg.memory.alpha, "epsilon": cfg.memory.epsilon}), ] for BufferClass, kwargs in buffer_types: # noqa: N806 diff --git a/tests/test_config.py b/tests/test_config.py index 160d26f..2e81099 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -48,157 +48,6 @@ def test_hot_swappable_config_extras_default(self): assert config.extras() == {} -class TestRepresentationConfig: - """Test RepresentationConfig functionality.""" - - def test_representation_config_init(self): - """Test RepresentationConfig initialization.""" - config = RepresentationConfig() - assert config.type == "cumind.core.resnet.ResNet" - assert config.num_blocks == 2 - assert config.conv_channels == 32 - - def test_representation_config_extras(self): - """Test RepresentationConfig extras method.""" - key.seed(cfg.seed) - config = RepresentationConfig() - extras = config.extras() - - assert "input_shape" in extras - assert "rngs" in extras - assert extras["input_shape"] == cfg.env.observation_shape - - def test_representation_config_instantiation(self): - """Test that RepresentationConfig can instantiate ResNet.""" - key.seed(cfg.seed) - config = RepresentationConfig() - network = config() - - # Use type() instead of isinstance() for nnx modules - assert type(network).__name__ == "ResNet" - assert network.num_blocks == 2 - assert network.conv_channels == 32 - assert network.hidden_dim == 128 - - def test_representation_config_custom_params(self): - """Test RepresentationConfig with custom parameters.""" - config = RepresentationConfig(num_blocks=5, conv_channels=64) - key.seed(cfg.seed) - network = config() - - assert network.num_blocks == 5 - assert network.conv_channels == 64 - - -class TestDynamicsConfig: - """Test DynamicsConfig functionality.""" - - def test_dynamics_config_init(self): - """Test DynamicsConfig initialization.""" - config = DynamicsConfig() - assert config.type == "cumind.core.mlp.MLPWithEmbedding" - assert config.num_blocks == 2 - - def test_dynamics_config_extras(self): - """Test DynamicsConfig extras method.""" - config = DynamicsConfig() - extras = config.extras() - - assert "hidden_dim" in extras - assert "embedding_size" in extras - assert "rngs" in extras - assert extras["hidden_dim"] == cfg.networks.hidden_dim - assert extras["embedding_size"] == cfg.env.action_space_size - - def test_dynamics_config_instantiation(self): - """Test that DynamicsConfig can instantiate MLPWithEmbedding.""" - key.seed(cfg.seed) - config = DynamicsConfig() - network = config() - - # Use type() instead of isinstance() for nnx modules - assert type(network).__name__ == "MLPWithEmbedding" - assert network.num_blocks == 2 - assert network.hidden_dim == cfg.networks.hidden_dim - assert network.embedding_size == cfg.env.action_space_size - - def test_dynamics_config_custom_params(self): - """Test DynamicsConfig with custom parameters.""" - config = DynamicsConfig(num_blocks=4) - key.seed(cfg.seed) - network = config() - - assert network.num_blocks == 4 - - -class TestPredictionConfig: - """Test PredictionConfig functionality.""" - - def test_prediction_config_init(self): - """Test PredictionConfig initialization.""" - config = PredictionConfig() - assert config.type == "cumind.core.mlp.MLPDual" - - def test_prediction_config_extras(self): - """Test PredictionConfig extras method.""" - config = PredictionConfig() - extras = config.extras() - - assert "hidden_dim" in extras - assert "output_size" in extras - assert "rngs" in extras - assert extras["hidden_dim"] == cfg.networks.hidden_dim - assert extras["output_size"] == cfg.env.action_space_size - - def test_prediction_config_instantiation(self): - """Test that PredictionConfig can instantiate MLPDual.""" - key.seed(cfg.seed) - config = PredictionConfig() - network = config() - - # Use type() instead of isinstance() for nnx modules - assert type(network).__name__ == "MLPDual" - assert network.hidden_dim == cfg.networks.hidden_dim - assert network.output_size == cfg.env.action_space_size - - -class TestMemoryConfig: - """Test MemoryConfig functionality.""" - - def test_memory_config_init(self): - """Test MemoryConfig initialization.""" - config = MemoryConfig() - assert config.type == "cumind.data.memory.MemoryBuffer" - assert config.capacity == 2000 - assert config.min_size == 100 - assert config.min_pct == 0.1 - - def test_memory_config_extras(self): - """Test MemoryConfig extras method.""" - config = MemoryConfig() - extras = config.extras() - - # MemoryConfig extras should be empty since dataclass fields are handled by base class - assert extras == {} - - def test_memory_config_instantiation(self): - """Test that MemoryConfig can instantiate MemoryBuffer.""" - config = MemoryConfig() - memory = config() - - # Use type() instead of isinstance() for consistency - assert type(memory).__name__ == "MemoryBuffer" - assert memory.capacity == 2000 - - def test_memory_config_custom_params(self): - """Test MemoryConfig with custom parameters.""" - config = MemoryConfig(capacity=5000, min_size=200) - memory = config() - - assert memory.capacity == 5000 - # min_size is not passed to constructor, only used for configuration - - class TestConfigurationIntegration: """Test integration of all config components.""" @@ -217,22 +66,6 @@ def test_configuration_hot_swappable_modules(self): assert isinstance(cfg.prediction, PredictionConfig) assert isinstance(cfg.memory, MemoryConfig) - def test_network_construction_integration(self): - """Test complete network construction using config.""" - key.seed(cfg.seed) - - # Test that all networks can be constructed - representation = cfg.representation() - dynamics = cfg.dynamics() - prediction = cfg.prediction() - memory = cfg.memory() - - # Use type() instead of isinstance() for nnx modules - assert type(representation).__name__ == "ResNet" - assert type(dynamics).__name__ == "MLPWithEmbedding" - assert type(prediction).__name__ == "MLPDual" - assert type(memory).__name__ == "MemoryBuffer" - def test_configuration_validation(self): """Test that configuration validation works.""" # This should not raise any errors @@ -256,7 +89,7 @@ def test_load_default_config(self): def test_load_from_json(self): """Test loading configuration from JSON file.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: - config_data = {"CuMind": {"networks": {"hidden_dim": 256}, "env": {"name": "TestEnv", "action_space_size": 4, "observation_shape": [8]}, "seed": 123}} + config_data = {"CuMind": {"networks": {"hidden_state_dim": 256}, "env": {"name": "TestEnv", "action_space_size": 4, "observation_shape": [8]}, "seed": 123}} import json json.dump(config_data, f) @@ -267,7 +100,7 @@ def test_load_from_json(self): cfg.load(config_path) # Verify the loaded values - assert cfg.networks.hidden_dim == 256 + assert cfg.networks.hidden_state_dim == 256 assert cfg.env.name == "TestEnv" assert cfg.env.action_space_size == 4 assert cfg.env.observation_shape == (8,) diff --git a/tests/test_data.py b/tests/test_data.py index 5d8c741..71b11ad 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -7,8 +7,12 @@ from cumind.data.memory import MemoryBuffer, PrioritizedMemoryBuffer, TreeBuffer from cumind.data.self_play import SelfPlay from cumind.utils.config import cfg +from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_prng_manager_singleton(): @@ -20,9 +24,9 @@ def reset_prng_manager_singleton(): def _create_buffer(BufferClass, capacity): # noqa: N803 """Helper function to create a buffer with the correct arguments.""" if BufferClass == PrioritizedMemoryBuffer: - return BufferClass(capacity=capacity, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon, beta=cfg.memory.per_beta) + return BufferClass(capacity=capacity, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon, beta=cfg.memory.beta) if BufferClass == TreeBuffer: - return BufferClass(capacity=capacity, alpha=cfg.memory.per_alpha, epsilon=cfg.memory.per_epsilon) + return BufferClass(capacity=capacity, alpha=cfg.memory.alpha, epsilon=cfg.memory.epsilon) return BufferClass(capacity=capacity) diff --git a/tests/test_mcts.py b/tests/test_mcts.py index 7b401cd..a155500 100644 --- a/tests/test_mcts.py +++ b/tests/test_mcts.py @@ -7,8 +7,12 @@ from cumind.core.mcts import MCTS, Node from cumind.core.network import CuMindNetwork from cumind.utils.config import cfg +from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_prng_manager_singleton(): @@ -193,7 +197,7 @@ def test_mcts_initialization(self, setup): def test_mcts_search(self, setup): """Test MCTS search returns a valid policy.""" mcts, _ = setup - root_hidden_state = jnp.ones(cfg.networks.hidden_dim) + root_hidden_state = jnp.ones(cfg.networks.hidden_state_dim) # Test search with default parameters policy = mcts.search(root_hidden_state) @@ -207,7 +211,7 @@ def test_mcts_search(self, setup): def test_mcts_search_basic(self, setup): """Test basic MCTS search functionality.""" mcts, _ = setup - root_hidden_state = jnp.ones(cfg.networks.hidden_dim) + root_hidden_state = jnp.ones(cfg.networks.hidden_state_dim) # Test search (uses default number of simulations from config) policy = mcts.search(root_hidden_state) @@ -260,7 +264,7 @@ def test_tree_statistics(self): def test_mcts_with_different_networks(self, setup): """Test MCTS with different network configurations.""" mcts, _ = setup - root_hidden_state = jnp.ones(cfg.networks.hidden_dim) + root_hidden_state = jnp.ones(cfg.networks.hidden_state_dim) # Test search (uses default number of simulations from config) policy = mcts.search(root_hidden_state) @@ -270,7 +274,7 @@ def test_mcts_with_different_networks(self, setup): def test_mcts_edge_cases(self, setup): """Test MCTS edge cases and error handling.""" mcts, _ = setup - root_hidden_state = jnp.ones(cfg.networks.hidden_dim) + root_hidden_state = jnp.ones(cfg.networks.hidden_state_dim) # Test search (uses default number of simulations from config) policy = mcts.search(root_hidden_state) diff --git a/tests/test_network.py b/tests/test_network.py index 189d88b..63e501e 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -11,8 +11,12 @@ from cumind.core.network import CuMindNetwork from cumind.core.resnet import ResNet from cumind.utils.config import cfg +from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_prng_manager_singleton(): @@ -95,13 +99,13 @@ def test_vector_encoder_initialization(self): observation_shape = (4,) hidden_dim = 64 - num_blocks = 2 + num_hidden_layers = 2 - encoder = VectorEncoder(observation_shape, hidden_dim, num_blocks, rngs) + encoder = VectorEncoder(observation_shape, hidden_dim, num_hidden_layers, rngs) # Test that encoder has required attributes assert hasattr(encoder, "layers") - assert len(encoder.layers) == num_blocks + assert len(encoder.layers) == num_hidden_layers assert encoder.hidden_dim == hidden_dim assert encoder.observation_shape == observation_shape @@ -112,10 +116,10 @@ def test_vector_encoder_forward(self): observation_shape = (4,) hidden_dim = 32 - num_blocks = 2 + num_hidden_layers = 2 batch_size = 3 - encoder = VectorEncoder(observation_shape, hidden_dim, num_blocks, rngs) + encoder = VectorEncoder(observation_shape, hidden_dim, num_hidden_layers, rngs) # Test forward pass obs = jnp.ones((batch_size, observation_shape[0])) @@ -136,9 +140,9 @@ def test_vector_encoder_gradients(self): observation_shape = (8,) hidden_dim = 16 - num_blocks = 1 + num_hidden_layers = 1 - encoder = VectorEncoder(observation_shape, hidden_dim, num_blocks, rngs) + encoder = VectorEncoder(observation_shape, hidden_dim, num_hidden_layers, rngs) # Test forward pass obs = jax.random.normal(key.get(), (2, observation_shape[0])) @@ -161,15 +165,15 @@ def test_conv_encoder_initialization(self): rngs = nnx.Rngs(params=key.get()) observation_shape = (84, 84, 3) hidden_dim = 256 - num_blocks = 2 + num_hidden_layers = 2 conv_channels = 32 - encoder = ConvEncoder(observation_shape, hidden_dim, num_blocks, conv_channels, rngs) + encoder = ConvEncoder(observation_shape, hidden_dim, num_hidden_layers, conv_channels, rngs) assert hasattr(encoder, "initial_conv") assert isinstance(encoder.initial_conv, nnx.Conv) assert hasattr(encoder, "residual_blocks") - assert len(encoder.residual_blocks) == num_blocks + assert len(encoder.residual_blocks) == num_hidden_layers assert isinstance(encoder.residual_blocks[0], ResidualBlock) assert hasattr(encoder, "final_dense") assert isinstance(encoder.final_dense, nnx.Linear) @@ -180,11 +184,11 @@ def test_conv_encoder_forward(self): rngs = nnx.Rngs(params=key.get()) observation_shape = (84, 84, 3) hidden_dim = 256 - num_blocks = 2 + num_hidden_layers = 2 conv_channels = 32 batch_size = 4 - encoder = ConvEncoder(observation_shape, hidden_dim, num_blocks, conv_channels, rngs) + encoder = ConvEncoder(observation_shape, hidden_dim, num_hidden_layers, conv_channels, rngs) obs = jnp.ones((batch_size, *observation_shape)) output = encoder(obs) @@ -196,10 +200,10 @@ def test_conv_encoder_with_residual_blocks(self): rngs = nnx.Rngs(params=key.get()) observation_shape = (10, 10, 4) hidden_dim = 128 - num_blocks = 4 + num_hidden_layers = 4 conv_channels = 16 - encoder = ConvEncoder(observation_shape, hidden_dim, num_blocks, conv_channels, rngs) + encoder = ConvEncoder(observation_shape, hidden_dim, num_hidden_layers, conv_channels, rngs) # Test forward pass batch_size = 2 @@ -224,14 +228,19 @@ def test_resnet_initialization(self): observation_shape = (4,) hidden_dim = 64 - num_blocks = 2 + num_hidden_layers = 2 conv_channels = 16 - resnet = ResNet(hidden_dim, observation_shape, num_blocks, conv_channels, rngs) + resnet = ResNet( + input_dim=observation_shape, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + rngs=rngs, + conv_channels=conv_channels, + ) assert hasattr(resnet, "encoder") assert resnet.hidden_dim == hidden_dim - assert resnet.input_shape == observation_shape def test_resnet_forward_1d(self): """Test ResNet forward pass with 1D observations.""" @@ -240,10 +249,16 @@ def test_resnet_forward_1d(self): observation_shape = (8,) hidden_dim = 32 - num_blocks = 1 + num_hidden_layers = 1 conv_channels = 8 - resnet = ResNet(hidden_dim, observation_shape, num_blocks, conv_channels, rngs) + resnet = ResNet( + input_dim=observation_shape, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + rngs=rngs, + conv_channels=conv_channels, + ) # Test forward pass batch_size = 3 @@ -259,10 +274,16 @@ def test_resnet_forward_3d(self): observation_shape = (10, 10, 3) hidden_dim = 64 - num_blocks = 2 + num_hidden_layers = 2 conv_channels = 16 - resnet = ResNet(hidden_dim, observation_shape, num_blocks, conv_channels, rngs) + resnet = ResNet( + input_dim=observation_shape, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + rngs=rngs, + conv_channels=conv_channels, + ) # Test forward pass batch_size = 2 @@ -282,12 +303,17 @@ def test_mlp_with_embedding_initialization(self): hidden_dim = 64 embedding_size = 4 - num_blocks = 2 + num_hidden_layers = 2 - mlp = MLPWithEmbedding(hidden_dim, embedding_size, num_blocks, rngs) + mlp = MLPWithEmbedding( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + embedding_size=embedding_size, + rngs=rngs, + ) - assert hasattr(mlp, "embedding") - assert hasattr(mlp, "layers") + assert hasattr(mlp, "action_embedding") assert mlp.hidden_dim == hidden_dim assert mlp.embedding_size == embedding_size @@ -298,9 +324,15 @@ def test_mlp_with_embedding_forward(self): hidden_dim = 32 embedding_size = 3 - num_blocks = 1 + num_hidden_layers = 1 - mlp = MLPWithEmbedding(hidden_dim, embedding_size, num_blocks, rngs) + mlp = MLPWithEmbedding( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + embedding_size=embedding_size, + rngs=rngs, + ) # Test forward pass batch_size = 2 @@ -319,9 +351,15 @@ def test_action_embedding(self): hidden_dim = 16 embedding_size = 2 - num_blocks = 1 + num_hidden_layers = 1 - mlp = MLPWithEmbedding(hidden_dim, embedding_size, num_blocks, rngs) + mlp = MLPWithEmbedding( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + embedding_size=embedding_size, + rngs=rngs, + ) # Test with different actions batch_size = 3 @@ -341,9 +379,15 @@ def test_reward_prediction(self): hidden_dim = 8 embedding_size = 2 - num_blocks = 1 + num_hidden_layers = 1 - mlp = MLPWithEmbedding(hidden_dim, embedding_size, num_blocks, rngs) + mlp = MLPWithEmbedding( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=num_hidden_layers, + embedding_size=embedding_size, + rngs=rngs, + ) # Test forward pass batch_size = 2 @@ -371,12 +415,17 @@ def test_mlp_dual_initialization(self): hidden_dim = 64 output_size = 4 - mlp = MLPDual(hidden_dim, output_size, rngs) + mlp = MLPDual( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=2, + num_actions=output_size, + rngs=rngs, + ) - assert hasattr(mlp, "head1") - assert hasattr(mlp, "head2") + assert hasattr(mlp, "policy_head") + assert hasattr(mlp, "value_head") assert mlp.hidden_dim == hidden_dim - assert mlp.output_size == output_size def test_mlp_dual_forward(self): """Test MLPDual forward pass.""" @@ -386,7 +435,13 @@ def test_mlp_dual_forward(self): hidden_dim = 32 output_size = 3 - mlp = MLPDual(hidden_dim, output_size, rngs) + mlp = MLPDual( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=1, + num_actions=output_size, + rngs=rngs, + ) # Test forward pass batch_size = 2 @@ -405,7 +460,13 @@ def test_policy_head(self): hidden_dim = 16 output_size = 2 - mlp = MLPDual(hidden_dim, output_size, rngs) + mlp = MLPDual( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=1, + num_actions=output_size, + rngs=rngs, + ) # Test policy head batch_size = 3 @@ -425,7 +486,13 @@ def test_value_head(self): hidden_dim = 8 output_size = 2 - mlp = MLPDual(hidden_dim, output_size, rngs) + mlp = MLPDual( + input_dim=hidden_dim, + hidden_dim=hidden_dim, + num_hidden_layers=1, + num_actions=output_size, + rngs=rngs, + ) # Test value head batch_size = 2 @@ -462,14 +529,26 @@ def setup_3d(self): from cumind.core.resnet import ResNet repre_net = ResNet( - hidden_dim=cfg.networks.hidden_dim, - input_shape=(10, 10, 3), # 3D observation shape - num_blocks=cfg.representation.num_blocks, + input_dim=(10, 10, 3), + hidden_dim=cfg.networks.hidden_state_dim, + num_hidden_layers=cfg.representation.num_hidden_layers, + rngs=rngs, conv_channels=cfg.representation.conv_channels, + ) + dyna_net = MLPWithEmbedding( + input_dim=cfg.networks.hidden_state_dim, + hidden_dim=cfg.networks.hidden_state_dim, + num_hidden_layers=cfg.dynamics.num_hidden_layers, + embedding_size=cfg.env.action_space_size, + rngs=rngs, + ) + pred_net = MLPDual( + input_dim=cfg.networks.hidden_state_dim, + hidden_dim=cfg.networks.hidden_state_dim, + num_hidden_layers=cfg.prediction.num_hidden_layers, + num_actions=cfg.env.action_space_size, rngs=rngs, ) - dyna_net = MLPWithEmbedding(hidden_dim=cfg.networks.hidden_dim, embedding_size=cfg.env.action_space_size, num_blocks=cfg.dynamics.num_blocks, rngs=rngs) - pred_net = MLPDual(hidden_dim=cfg.networks.hidden_dim, output_size=cfg.env.action_space_size, rngs=rngs) network = CuMindNetwork(repre_net, dyna_net, pred_net) return network, rngs @@ -490,7 +569,7 @@ def test_initial_inference(self, setup_1d): hidden_state, policy_logits, value = network.initial_inference(obs) - assert hidden_state.shape == (batch_size, cfg.networks.hidden_dim) + assert hidden_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert policy_logits.shape == (batch_size, cfg.env.action_space_size) assert value.shape == (batch_size, 1) @@ -499,12 +578,12 @@ def test_recurrent_inference(self, setup_1d): network, _ = setup_1d batch_size = 2 - hidden_state = jnp.ones((batch_size, cfg.networks.hidden_dim)) + hidden_state = jnp.ones((batch_size, cfg.networks.hidden_state_dim)) actions = jnp.array([0, 1]) next_state, reward, next_policy, next_value = network.recurrent_inference(hidden_state, actions) - assert next_state.shape == (batch_size, cfg.networks.hidden_dim) + assert next_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert reward.shape == (batch_size, 1) assert next_policy.shape == (batch_size, cfg.env.action_space_size) assert next_value.shape == (batch_size, 1) @@ -524,10 +603,10 @@ def test_network_integration(self, setup_1d): next_state, reward, next_policy, next_value = network.recurrent_inference(hidden_state, actions) # Verify all outputs have correct shapes - assert hidden_state.shape == (batch_size, cfg.networks.hidden_dim) + assert hidden_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert policy_logits.shape == (batch_size, cfg.env.action_space_size) assert value.shape == (batch_size, 1) - assert next_state.shape == (batch_size, cfg.networks.hidden_dim) + assert next_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert reward.shape == (batch_size, 1) assert next_policy.shape == (batch_size, cfg.env.action_space_size) assert next_value.shape == (batch_size, 1) @@ -543,7 +622,7 @@ def test_cumind_network_with_1d_observations(self, setup_1d): hidden_state, policy_logits, value = network.initial_inference(obs) # Verify shapes - assert hidden_state.shape == (batch_size, cfg.networks.hidden_dim) + assert hidden_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert policy_logits.shape == (batch_size, cfg.env.action_space_size) assert value.shape == (batch_size, 1) @@ -552,7 +631,7 @@ def test_cumind_network_with_1d_observations(self, setup_1d): next_state, reward, next_policy, next_value = network.recurrent_inference(hidden_state, actions) # Verify shapes - assert next_state.shape == (batch_size, cfg.networks.hidden_dim) + assert next_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert reward.shape == (batch_size, 1) assert next_policy.shape == (batch_size, cfg.env.action_space_size) assert next_value.shape == (batch_size, 1) @@ -568,7 +647,7 @@ def test_cumind_network_with_3d_observations(self, setup_3d): hidden_state, policy_logits, value = network.initial_inference(obs) # Verify shapes - assert hidden_state.shape == (batch_size, cfg.networks.hidden_dim) + assert hidden_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert policy_logits.shape == (batch_size, cfg.env.action_space_size) assert value.shape == (batch_size, 1) @@ -577,7 +656,7 @@ def test_cumind_network_with_3d_observations(self, setup_3d): next_state, reward, next_policy, next_value = network.recurrent_inference(hidden_state, actions) # Verify shapes - assert next_state.shape == (batch_size, cfg.networks.hidden_dim) + assert next_state.shape == (batch_size, cfg.networks.hidden_state_dim) assert reward.shape == (batch_size, 1) assert next_policy.shape == (batch_size, cfg.env.action_space_size) assert next_value.shape == (batch_size, 1) diff --git a/tests/test_utils.py b/tests/test_utils.py index 601f57e..d51bd68 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -13,12 +13,16 @@ from cumind.utils.logger import log from cumind.utils.prng import key +cfg.boot() +log.info(cfg.env.observation_shape) + @pytest.fixture(autouse=True) def reset_singletons(): """Reset the singletons before and after each test.""" log._instance = None log._initialized = False + log(cfg=cfg) key.seed(42) # Initialize with a default seed yield log._instance = None diff --git a/uv.lock b/uv.lock index 2793f50..5944bbb 100644 --- a/uv.lock +++ b/uv.lock @@ -10,11 +10,11 @@ resolution-markers = [ [[package]] name = "absl-py" -version = "2.3.0" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/15/18693af986560a5c3cc0b84a8046b536ffb2cdb536e03cce897f2759e284/absl_py-2.3.0.tar.gz", hash = "sha256:d96fda5c884f1b22178852f30ffa85766d50b99e00775ea626c23304f582fc4f", size = 116400, upload-time = "2025-05-27T09:15:50.143Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/04/9d75e1d3bb4ab8ec67ff10919476ccdee06c098bcfcf3a352da5f985171d/absl_py-2.3.0-py3-none-any.whl", hash = "sha256:9824a48b654a306168f63e0d97714665f8490b8d89ec7bf2efc24bf67cf579b3", size = 135657, upload-time = "2025-05-27T09:15:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, ] [[package]] @@ -46,11 +46,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.6.15" +version = "2025.7.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, ] [[package]] @@ -183,7 +183,7 @@ wheels = [ [[package]] name = "cumind" -version = "0.1.8" +version = "0.1.95" source = { editable = "." } dependencies = [ { name = "chex" }, @@ -195,7 +195,6 @@ dependencies = [ { name = "numpy" }, { name = "optax" }, { name = "pygame" }, - { name = "tensorboard" }, { name = "tqdm" }, { name = "wandb" }, ] @@ -218,7 +217,6 @@ requires-dist = [ { name = "numpy", specifier = ">=2.3.0" }, { name = "optax", specifier = ">=0.2.5" }, { name = "pygame", specifier = ">=2.6.1" }, - { name = "tensorboard", specifier = ">=2.19.0" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "wandb", specifier = ">=0.20.1" }, ] @@ -232,19 +230,19 @@ dev = [ [[package]] name = "debugpy" -version = "1.8.14" +version = "1.8.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/3a9a28ddb750a76eaec445c7f4d3147ea2c579a97dbd9e25d39001b92b21/debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00", size = 1643279, upload-time = "2025-07-15T16:43:29.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268, upload-time = "2025-04-10T19:46:26.044Z" }, - { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077, upload-time = "2025-04-10T19:46:27.464Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127, upload-time = "2025-04-10T19:46:29.467Z" }, - { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249, upload-time = "2025-04-10T19:46:31.538Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e4/395c792b243f2367d84202dc33689aa3d910fb9826a7491ba20fc9e261f5/debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f", size = 2485676, upload-time = "2025-04-10T19:46:32.96Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514, upload-time = "2025-04-10T19:46:34.336Z" }, - { url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756, upload-time = "2025-04-10T19:46:36.199Z" }, - { url = "https://files.pythonhosted.org/packages/e0/62/a7b4a57013eac4ccaef6977966e6bec5c63906dd25a86e35f155952e29a1/debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e", size = 5297119, upload-time = "2025-04-10T19:46:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4a/4508d256e52897f5cdfee6a6d7580974811e911c6d01321df3264508a5ac/debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba", size = 2511197, upload-time = "2025-07-15T16:43:42.343Z" }, + { url = "https://files.pythonhosted.org/packages/99/8d/7f6ef1097e7fecf26b4ef72338d08e41644a41b7ee958a19f494ffcffc29/debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc", size = 4229517, upload-time = "2025-07-15T16:43:44.14Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e8/e8c6a9aa33a9c9c6dacbf31747384f6ed2adde4de2e9693c766bdf323aa3/debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327", size = 5276132, upload-time = "2025-07-15T16:43:45.529Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ad/231050c6177b3476b85fcea01e565dac83607b5233d003ff067e2ee44d8f/debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa", size = 5317645, upload-time = "2025-07-15T16:43:46.968Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/2928aad2310726d5920b18ed9f54b9f06df5aa4c10cf9b45fa18ff0ab7e8/debugpy-1.8.15-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:f5e01291ad7d6649aed5773256c5bba7a1a556196300232de1474c3c372592bf", size = 2495538, upload-time = "2025-07-15T16:43:48.927Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c6/9b8ffb4ca91fac8b2877eef63c9cc0e87dd2570b1120054c272815ec4cd0/debugpy-1.8.15-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dc0f0d00e528d915e0ce1c78e771475b2335b376c49afcc7382ee0b146bab6", size = 4221874, upload-time = "2025-07-15T16:43:50.282Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/9b8d59674b4bf489318c7c46a1aab58e606e583651438084b7e029bf3c43/debugpy-1.8.15-cp313-cp313-win32.whl", hash = "sha256:fcf0748d4f6e25f89dc5e013d1129ca6f26ad4da405e0723a4f704583896a709", size = 5275949, upload-time = "2025-07-15T16:43:52.079Z" }, + { url = "https://files.pythonhosted.org/packages/72/83/9e58e6fdfa8710a5e6ec06c2401241b9ad48b71c0a7eb99570a1f1edb1d3/debugpy-1.8.15-cp313-cp313-win_amd64.whl", hash = "sha256:73c943776cb83e36baf95e8f7f8da765896fd94b05991e7bc162456d25500683", size = 5317720, upload-time = "2025-07-15T16:43:53.703Z" }, + { url = "https://files.pythonhosted.org/packages/07/d5/98748d9860e767a1248b5e31ffa7ce8cb7006e97bf8abbf3d891d0a8ba4e/debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d", size = 5282697, upload-time = "2025-07-15T16:44:07.996Z" }, ] [[package]] @@ -258,11 +256,11 @@ wheels = [ [[package]] name = "etils" -version = "1.12.2" +version = "1.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/12/1cc11e88a0201280ff389bc4076df7c3432e39d9f22cba8b71aa263f67b8/etils-1.12.2.tar.gz", hash = "sha256:c6b9e1f0ce66d1bbf54f99201b08a60ba396d3446d9eb18d4bc39b26a2e1a5ee", size = 104711, upload-time = "2025-03-10T15:14:13.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/a0/522bbff0f3cdd37968f90dd7f26c7aa801ed87f5ba335f156de7f2b88a48/etils-1.13.0.tar.gz", hash = "sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607", size = 106368, upload-time = "2025-07-15T10:29:10.563Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/71/40ee142e564b8a34a7ae9546e99e665e0001011a3254d5bbbe113d72ccba/etils-1.12.2-py3-none-any.whl", hash = "sha256:4600bec9de6cf5cb043a171e1856e38b5f273719cf3ecef90199f7091a6b3912", size = 167613, upload-time = "2025-03-10T15:14:12.333Z" }, + { url = "https://files.pythonhosted.org/packages/e7/98/87b5946356095738cb90a6df7b35ff69ac5750f6e783d5fbcc5cb3b6cbd7/etils-1.13.0-py3-none-any.whl", hash = "sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb", size = 170603, upload-time = "2025-07-15T10:29:09.076Z" }, ] [package.optional-dependencies] @@ -296,7 +294,7 @@ wheels = [ [[package]] name = "flax" -version = "0.10.6" +version = "0.10.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jax" }, @@ -310,18 +308,18 @@ dependencies = [ { name = "treescope" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/2eee448a8b64ddde6fca53b067e6dbfe974bb198f6b21dc13f52aaeab7e3/flax-0.10.6.tar.gz", hash = "sha256:8f3d1eb7de9bbaa18e08d0423dce890aef88a8b9dc6daa23baa631e8dfb09618", size = 5215148, upload-time = "2025-04-23T20:27:07.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/76/4ea55a60a47e98fcff591238ee26ed4624cb4fdc4893aa3ebf78d0d021f4/flax-0.10.7.tar.gz", hash = "sha256:2930d6671e23076f6db3b96afacf45c5060898f5c189ecab6dda7e05d26c2085", size = 5136099, upload-time = "2025-07-02T06:10:07.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/f8/aaf70a427f7e17afc1877d69c610b6b0c5093dba5addb63fb6990944e989/flax-0.10.6-py3-none-any.whl", hash = "sha256:86a5f0ba0f1603c687714999b58a4e362e784a6d2dc5a510b18a8e7a6c729e18", size = 447094, upload-time = "2025-04-23T20:27:05.036Z" }, + { url = "https://files.pythonhosted.org/packages/11/f6/560d338687d40182c8429cf35c64cc022e0d57ba3e52191c4a78ed239b4e/flax-0.10.7-py3-none-any.whl", hash = "sha256:4033223a9a9969ba0b252e085e9714d0a1e9124ac300aaf48e92c40769c420f6", size = 456944, upload-time = "2025-07-02T06:10:05.807Z" }, ] [[package]] name = "fsspec" -version = "2025.5.1" +version = "2025.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, ] [[package]] @@ -348,37 +346,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] -[[package]] -name = "grpcio" -version = "1.73.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/7b/ca3f561aeecf0c846d15e1b38921a60dffffd5d4113931198fbf455334ee/grpcio-1.73.0.tar.gz", hash = "sha256:3af4c30918a7f0d39de500d11255f8d9da4f30e94a2033e70fe2a720e184bd8e", size = 12786424, upload-time = "2025-06-09T10:08:23.365Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/4d/e938f3a0e51a47f2ce7e55f12f19f316e7074770d56a7c2765e782ec76bc/grpcio-1.73.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:fb9d7c27089d9ba3746f18d2109eb530ef2a37452d2ff50f5a6696cd39167d3b", size = 5334911, upload-time = "2025-06-09T10:03:33.494Z" }, - { url = "https://files.pythonhosted.org/packages/13/56/f09c72c43aa8d6f15a71f2c63ebdfac9cf9314363dea2598dc501d8370db/grpcio-1.73.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:128ba2ebdac41e41554d492b82c34586a90ebd0766f8ebd72160c0e3a57b9155", size = 10601460, upload-time = "2025-06-09T10:03:36.613Z" }, - { url = "https://files.pythonhosted.org/packages/20/e3/85496edc81e41b3c44ebefffc7bce133bb531120066877df0f910eabfa19/grpcio-1.73.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:068ecc415f79408d57a7f146f54cdf9f0acb4b301a52a9e563973dc981e82f3d", size = 5759191, upload-time = "2025-06-09T10:03:39.838Z" }, - { url = "https://files.pythonhosted.org/packages/88/cc/fef74270a6d29f35ad744bfd8e6c05183f35074ff34c655a2c80f3b422b2/grpcio-1.73.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ddc1cfb2240f84d35d559ade18f69dcd4257dbaa5ba0de1a565d903aaab2968", size = 6409961, upload-time = "2025-06-09T10:03:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/13cfea15e3b8f79c4ae7b676cb21fab70978b0fde1e1d28bb0e073291290/grpcio-1.73.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53007f70d9783f53b41b4cf38ed39a8e348011437e4c287eee7dd1d39d54b2f", size = 6003948, upload-time = "2025-06-09T10:03:44.96Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ed/b1a36dad4cc0dbf1f83f6d7b58825fefd5cc9ff3a5036e46091335649473/grpcio-1.73.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4dd8d8d092efede7d6f48d695ba2592046acd04ccf421436dd7ed52677a9ad29", size = 6103788, upload-time = "2025-06-09T10:03:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c8/d381433d3d46d10f6858126d2d2245ef329e30f3752ce4514c93b95ca6fc/grpcio-1.73.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:70176093d0a95b44d24baa9c034bb67bfe2b6b5f7ebc2836f4093c97010e17fd", size = 6749508, upload-time = "2025-06-09T10:03:51.185Z" }, - { url = "https://files.pythonhosted.org/packages/87/0a/ff0c31dbd15e63b34320efafac647270aa88c31aa19ff01154a73dc7ce86/grpcio-1.73.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:085ebe876373ca095e24ced95c8f440495ed0b574c491f7f4f714ff794bbcd10", size = 6284342, upload-time = "2025-06-09T10:03:54.467Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/f762430c0ba867403b9d6e463afe026bf019bd9206eee753785239719273/grpcio-1.73.0-cp312-cp312-win32.whl", hash = "sha256:cfc556c1d6aef02c727ec7d0016827a73bfe67193e47c546f7cadd3ee6bf1a60", size = 3669319, upload-time = "2025-06-09T10:03:56.751Z" }, - { url = "https://files.pythonhosted.org/packages/10/8b/3411609376b2830449cf416f457ad9d2aacb7f562e1b90fdd8bdedf26d63/grpcio-1.73.0-cp312-cp312-win_amd64.whl", hash = "sha256:bbf45d59d090bf69f1e4e1594832aaf40aa84b31659af3c5e2c3f6a35202791a", size = 4335596, upload-time = "2025-06-09T10:03:59.866Z" }, - { url = "https://files.pythonhosted.org/packages/60/da/6f3f7a78e5455c4cbe87c85063cc6da05d65d25264f9d4aed800ece46294/grpcio-1.73.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:da1d677018ef423202aca6d73a8d3b2cb245699eb7f50eb5f74cae15a8e1f724", size = 5335867, upload-time = "2025-06-09T10:04:03.153Z" }, - { url = "https://files.pythonhosted.org/packages/53/14/7d1f2526b98b9658d7be0bb163fd78d681587de6709d8b0c74b4b481b013/grpcio-1.73.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:36bf93f6a657f37c131d9dd2c391b867abf1426a86727c3575393e9e11dadb0d", size = 10595587, upload-time = "2025-06-09T10:04:05.694Z" }, - { url = "https://files.pythonhosted.org/packages/02/24/a293c398ae44e741da1ed4b29638edbb002258797b07a783f65506165b4c/grpcio-1.73.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:d84000367508ade791d90c2bafbd905574b5ced8056397027a77a215d601ba15", size = 5765793, upload-time = "2025-06-09T10:04:09.235Z" }, - { url = "https://files.pythonhosted.org/packages/e1/24/d84dbd0b5bf36fb44922798d525a85cefa2ffee7b7110e61406e9750ed15/grpcio-1.73.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c98ba1d928a178ce33f3425ff823318040a2b7ef875d30a0073565e5ceb058d9", size = 6415494, upload-time = "2025-06-09T10:04:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/5e/85/c80dc65aed8e9dce3d54688864bac45331d9c7600985541f18bd5cb301d4/grpcio-1.73.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a73c72922dfd30b396a5f25bb3a4590195ee45ecde7ee068acb0892d2900cf07", size = 6007279, upload-time = "2025-06-09T10:04:14.878Z" }, - { url = "https://files.pythonhosted.org/packages/37/fc/207c00a4c6fa303d26e2cbd62fbdb0582facdfd08f55500fd83bf6b0f8db/grpcio-1.73.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:10e8edc035724aba0346a432060fd192b42bd03675d083c01553cab071a28da5", size = 6105505, upload-time = "2025-06-09T10:04:17.39Z" }, - { url = "https://files.pythonhosted.org/packages/72/35/8fe69af820667b87ebfcb24214e42a1d53da53cb39edd6b4f84f6b36da86/grpcio-1.73.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f5cdc332b503c33b1643b12ea933582c7b081957c8bc2ea4cc4bc58054a09288", size = 6753792, upload-time = "2025-06-09T10:04:19.989Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d8/738c77c1e821e350da4a048849f695ff88a02b291f8c69db23908867aea6/grpcio-1.73.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:07ad7c57233c2109e4ac999cb9c2710c3b8e3f491a73b058b0ce431f31ed8145", size = 6287593, upload-time = "2025-06-09T10:04:22.878Z" }, - { url = "https://files.pythonhosted.org/packages/09/ec/8498eabc018fa39ae8efe5e47e3f4c1bc9ed6281056713871895dc998807/grpcio-1.73.0-cp313-cp313-win32.whl", hash = "sha256:0eb5df4f41ea10bda99a802b2a292d85be28958ede2a50f2beb8c7fc9a738419", size = 3668637, upload-time = "2025-06-09T10:04:25.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/35/347db7d2e7674b621afd21b12022e7f48c7b0861b5577134b4e939536141/grpcio-1.73.0-cp313-cp313-win_amd64.whl", hash = "sha256:38cf518cc54cd0c47c9539cefa8888549fcc067db0b0c66a46535ca8032020c4", size = 4335872, upload-time = "2025-06-09T10:04:29.032Z" }, -] - [[package]] name = "gymnasium" -version = "1.1.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cloudpickle" }, @@ -386,9 +356,9 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326, upload-time = "2025-03-06T16:30:36.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl", hash = "sha256:9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a", size = 965410, upload-time = "2025-03-06T16:30:34.443Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e2/a111dbb8625af467ea4760a1373d6ef27aac3137931219902406ccc05423/gymnasium-1.2.0-py3-none-any.whl", hash = "sha256:fc4a1e4121a9464c29b4d7dc6ade3fbeaa36dea448682f5f71a6d2c17489ea76", size = 944301, upload-time = "2025-06-27T08:21:18.83Z" }, ] [[package]] @@ -453,7 +423,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.3.0" +version = "9.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -467,9 +437,9 @@ dependencies = [ { name = "stack-data" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/09/4c7e06b96fbd203e06567b60fb41b06db606b6a82db6db7b2c85bb72a15c/ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8", size = 4426460, upload-time = "2025-05-31T16:34:55.678Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338, upload-time = "2025-07-01T11:11:30.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/99/9ed3d52d00f1846679e3aa12e2326ac7044b5e7f90dc822b60115fa533ca/ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04", size = 605320, upload-time = "2025-05-31T16:34:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021, upload-time = "2025-07-01T11:11:27.85Z" }, ] [[package]] @@ -564,15 +534,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, ] -[[package]] -name = "markdown" -version = "3.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -585,44 +546,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, -] - [[package]] name = "matplotlib-inline" version = "0.1.7" @@ -696,28 +619,28 @@ wheels = [ [[package]] name = "mypy" -version = "1.16.1" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" }, - { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" }, - { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" }, - { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480, upload-time = "2025-06-16T16:47:56.205Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538, upload-time = "2025-06-16T16:46:43.92Z" }, - { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839, upload-time = "2025-06-16T16:36:28.039Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634, upload-time = "2025-06-16T16:50:34.441Z" }, - { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584, upload-time = "2025-06-16T16:34:54.857Z" }, - { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886, upload-time = "2025-06-16T16:36:43.589Z" }, - { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, + { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, + { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, ] [[package]] @@ -806,7 +729,7 @@ wheels = [ [[package]] name = "orbax-checkpoint" -version = "0.11.16" +version = "0.11.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, @@ -822,9 +745,9 @@ dependencies = [ { name = "tensorstore" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/6d/e50d0b00c6b10628cdcc10ca6b5322a227ae9cbb5adeef47b9f6182c1d6c/orbax_checkpoint-0.11.16.tar.gz", hash = "sha256:f02d80059530c74a220201e19eac275ddb7394de8d939e69fac0283a37fcc265", size = 338088, upload-time = "2025-06-18T19:28:45.076Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/07/367e728c69dbb0dcf51bc9b1fd4476d9e31eb37aab763698991fabc0c2c7/orbax_checkpoint-0.11.19.tar.gz", hash = "sha256:6f7f2cd89644e1892f2adf17dfa2ad29253712b22374ca9d6b02129922d46795", size = 346028, upload-time = "2025-07-09T01:34:32.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/7f/cc728c462fc4d8894dafca17afa2357df827dc695ecd0b1bb382ffde0224/orbax_checkpoint-0.11.16-py3-none-any.whl", hash = "sha256:5891409943a856d22ce9ec2a418202c3789c46282601dd86d3e4859af91b1db3", size = 477559, upload-time = "2025-06-18T19:28:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/7f0e6bc634a22c5085e225fbad3e8df34d42a37458be3f4984d8eb6a4048/orbax_checkpoint-0.11.19-py3-none-any.whl", hash = "sha256:f9a9b007db8b7e7175d5be30afd754c4a29cae6d4a631cbe34cb9eb5b6cbd96f", size = 490278, upload-time = "2025-07-09T01:34:30.546Z" }, ] [[package]] @@ -1070,15 +993,18 @@ wheels = [ [[package]] name = "pywin32" -version = "310" +version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, - { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, - { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload-time = "2025-03-17T00:56:04.383Z" }, - { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload-time = "2025-03-17T00:56:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] [[package]] @@ -1167,27 +1093,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/90/5255432602c0b196a0da6720f6f76b93eb50baef46d3c9b0025e2f9acbf3/ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c", size = 4376101, upload-time = "2025-06-17T15:19:26.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/fd/b46bb20e14b11ff49dbc74c61de352e0dc07fb650189513631f6fb5fc69f/ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848", size = 10311554, upload-time = "2025-06-17T15:18:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d3/021dde5a988fa3e25d2468d1dadeea0ae89dc4bc67d0140c6e68818a12a1/ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6", size = 11118435, upload-time = "2025-06-17T15:18:49.064Z" }, - { url = "https://files.pythonhosted.org/packages/07/a2/01a5acf495265c667686ec418f19fd5c32bcc326d4c79ac28824aecd6a32/ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0", size = 10466010, upload-time = "2025-06-17T15:18:51.341Z" }, - { url = "https://files.pythonhosted.org/packages/4c/57/7caf31dd947d72e7aa06c60ecb19c135cad871a0a8a251723088132ce801/ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48", size = 10661366, upload-time = "2025-06-17T15:18:53.29Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ba/aa393b972a782b4bc9ea121e0e358a18981980856190d7d2b6187f63e03a/ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807", size = 10173492, upload-time = "2025-06-17T15:18:55.262Z" }, - { url = "https://files.pythonhosted.org/packages/d7/50/9349ee777614bc3062fc6b038503a59b2034d09dd259daf8192f56c06720/ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82", size = 11761739, upload-time = "2025-06-17T15:18:58.906Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/ad459de67c70ec112e2ba7206841c8f4eb340a03ee6a5cabc159fe558b8e/ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c", size = 12537098, upload-time = "2025-06-17T15:19:01.316Z" }, - { url = "https://files.pythonhosted.org/packages/ed/50/15ad9c80ebd3c4819f5bd8883e57329f538704ed57bac680d95cb6627527/ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165", size = 12154122, upload-time = "2025-06-17T15:19:03.727Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/79b91e41bc8cc3e78ee95c87093c6cacfa275c786e53c9b11b9358026b3d/ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2", size = 11363374, upload-time = "2025-06-17T15:19:05.875Z" }, - { url = "https://files.pythonhosted.org/packages/db/c3/82b292ff8a561850934549aa9dc39e2c4e783ab3c21debe55a495ddf7827/ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4", size = 11587647, upload-time = "2025-06-17T15:19:08.246Z" }, - { url = "https://files.pythonhosted.org/packages/2b/42/d5760d742669f285909de1bbf50289baccb647b53e99b8a3b4f7ce1b2001/ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514", size = 10527284, upload-time = "2025-06-17T15:19:10.37Z" }, - { url = "https://files.pythonhosted.org/packages/19/f6/fcee9935f25a8a8bba4adbae62495c39ef281256693962c2159e8b284c5f/ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88", size = 10158609, upload-time = "2025-06-17T15:19:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/37/fb/057febf0eea07b9384787bfe197e8b3384aa05faa0d6bd844b94ceb29945/ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51", size = 11141462, upload-time = "2025-06-17T15:19:15.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/7c/1be8571011585914b9d23c95b15d07eec2d2303e94a03df58294bc9274d4/ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a", size = 11641616, upload-time = "2025-06-17T15:19:17.6Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/b960ab4818f90ff59e571d03c3f992828d4683561095e80f9ef31f3d58b7/ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb", size = 10525289, upload-time = "2025-06-17T15:19:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/34/93/8b16034d493ef958a500f17cda3496c63a537ce9d5a6479feec9558f1695/ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0", size = 11598311, upload-time = "2025-06-17T15:19:21.785Z" }, - { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946, upload-time = "2025-06-17T15:19:23.952Z" }, +version = "0.12.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/ce/8d7dbedede481245b489b769d27e2934730791a9a82765cb94566c6e6abd/ruff-0.12.4.tar.gz", hash = "sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873", size = 5131435, upload-time = "2025-07-17T17:27:19.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/9f/517bc5f61bad205b7f36684ffa5415c013862dee02f55f38a217bdbe7aa4/ruff-0.12.4-py3-none-linux_armv6l.whl", hash = "sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a", size = 10188824, upload-time = "2025-07-17T17:26:31.412Z" }, + { url = "https://files.pythonhosted.org/packages/28/83/691baae5a11fbbde91df01c565c650fd17b0eabed259e8b7563de17c6529/ruff-0.12.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442", size = 10884521, upload-time = "2025-07-17T17:26:35.084Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8d/756d780ff4076e6dd035d058fa220345f8c458391f7edfb1c10731eedc75/ruff-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e", size = 10277653, upload-time = "2025-07-17T17:26:37.897Z" }, + { url = "https://files.pythonhosted.org/packages/8d/97/8eeee0f48ece153206dce730fc9e0e0ca54fd7f261bb3d99c0a4343a1892/ruff-0.12.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586", size = 10485993, upload-time = "2025-07-17T17:26:40.68Z" }, + { url = "https://files.pythonhosted.org/packages/49/b8/22a43d23a1f68df9b88f952616c8508ea6ce4ed4f15353b8168c48b2d7e7/ruff-0.12.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb", size = 10022824, upload-time = "2025-07-17T17:26:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/cd/70/37c234c220366993e8cffcbd6cadbf332bfc848cbd6f45b02bade17e0149/ruff-0.12.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c", size = 11524414, upload-time = "2025-07-17T17:26:46.219Z" }, + { url = "https://files.pythonhosted.org/packages/14/77/c30f9964f481b5e0e29dd6a1fae1f769ac3fd468eb76fdd5661936edd262/ruff-0.12.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a", size = 12419216, upload-time = "2025-07-17T17:26:48.883Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/af7fe0a4202dce4ef62c5e33fecbed07f0178f5b4dd9c0d2fcff5ab4a47c/ruff-0.12.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3", size = 11976756, upload-time = "2025-07-17T17:26:51.754Z" }, + { url = "https://files.pythonhosted.org/packages/09/d1/33fb1fc00e20a939c305dbe2f80df7c28ba9193f7a85470b982815a2dc6a/ruff-0.12.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045", size = 11020019, upload-time = "2025-07-17T17:26:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/64/f4/e3cd7f7bda646526f09693e2e02bd83d85fff8a8222c52cf9681c0d30843/ruff-0.12.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57", size = 11277890, upload-time = "2025-07-17T17:26:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d0/69a85fb8b94501ff1a4f95b7591505e8983f38823da6941eb5b6badb1e3a/ruff-0.12.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184", size = 10348539, upload-time = "2025-07-17T17:26:59.381Z" }, + { url = "https://files.pythonhosted.org/packages/16/a0/91372d1cb1678f7d42d4893b88c252b01ff1dffcad09ae0c51aa2542275f/ruff-0.12.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb", size = 10009579, upload-time = "2025-07-17T17:27:02.462Z" }, + { url = "https://files.pythonhosted.org/packages/23/1b/c4a833e3114d2cc0f677e58f1df6c3b20f62328dbfa710b87a1636a5e8eb/ruff-0.12.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1", size = 10942982, upload-time = "2025-07-17T17:27:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ce/ce85e445cf0a5dd8842f2f0c6f0018eedb164a92bdf3eda51984ffd4d989/ruff-0.12.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b", size = 11343331, upload-time = "2025-07-17T17:27:08.652Z" }, + { url = "https://files.pythonhosted.org/packages/35/cf/441b7fc58368455233cfb5b77206c849b6dfb48b23de532adcc2e50ccc06/ruff-0.12.4-py3-none-win32.whl", hash = "sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93", size = 10267904, upload-time = "2025-07-17T17:27:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7e/20af4a0df5e1299e7368d5ea4350412226afb03d95507faae94c80f00afd/ruff-0.12.4-py3-none-win_amd64.whl", hash = "sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a", size = 11209038, upload-time = "2025-07-17T17:27:14.417Z" }, + { url = "https://files.pythonhosted.org/packages/11/02/8857d0dfb8f44ef299a5dfd898f673edefb71e3b533b3b9d2db4c832dd13/ruff-0.12.4-py3-none-win_arm64.whl", hash = "sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e", size = 10469336, upload-time = "2025-07-17T17:27:16.913Z" }, ] [[package]] @@ -1230,59 +1156,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.30.0" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/4c/af31e0201b48469786ddeb1bf6fd3dfa3a291cc613a0fe6a60163a7535f9/sentry_sdk-2.30.0.tar.gz", hash = "sha256:436369b02afef7430efb10300a344fb61a11fe6db41c2b11f41ee037d2dd7f45", size = 326767, upload-time = "2025-06-12T10:34:34.733Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149, upload-time = "2025-06-12T10:34:32.896Z" }, -] - -[[package]] -name = "setproctitle" -version = "1.3.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, - { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, - { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, - { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, - { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, - { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, - { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, - { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/f1a2fdbf9b9602945a7489ba5c52e9863de37381ef1a85a2b9ed0ff8bc79/setproctitle-1.3.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2a9e62647dc040a76d55563580bf3bb8fe1f5b6ead08447c2ed0d7786e5e794", size = 17392, upload-time = "2025-04-29T13:33:30.925Z" }, - { url = "https://files.pythonhosted.org/packages/5c/5b/4e0db8b10b4543afcb3dbc0827793d46e43ec1de6b377e313af3703d08e0/setproctitle-1.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:751ba352ed922e0af60458e961167fa7b732ac31c0ddd1476a2dfd30ab5958c5", size = 11951, upload-time = "2025-04-29T13:33:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/dc/fe/d5d00aaa700fe1f6160b6e95c225b29c01f4d9292176d48fd968815163ea/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7890e291bf4708e3b61db9069ea39b3ab0651e42923a5e1f4d78a7b9e4b18301", size = 32087, upload-time = "2025-04-29T13:33:33.469Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b3/894b827b93ef813c082479bebf88185860f01ac243df737823dd705e7fff/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2b17855ed7f994f3f259cf2dfbfad78814538536fa1a91b50253d84d87fd88d", size = 33502, upload-time = "2025-04-29T13:33:34.831Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cd/5330734cca1a4cfcb721432c22cb7899ff15a4101ba868b2ef452ffafea1/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e51ec673513465663008ce402171192a053564865c2fc6dc840620871a9bd7c", size = 30713, upload-time = "2025-04-29T13:33:36.739Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d3/c2590c5daa2e9a008d3f2b16c0f4a351826193be55f147cb32af49c6d814/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cc10352dc6cf35a33951656aa660d99f25f574eb78132ce41a85001a638aa7", size = 31792, upload-time = "2025-04-29T13:33:37.974Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b1/c553ed5af8cfcecd5ae7737e63af58a17a03d26f3d61868c7eb20bf7e3cf/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dba8faee2e4a96e934797c9f0f2d093f8239bf210406a99060b3eabe549628e", size = 31927, upload-time = "2025-04-29T13:33:39.203Z" }, - { url = "https://files.pythonhosted.org/packages/70/78/2d5385206540127a3dca0ff83225b1ac66873f5cc89d4a6d3806c92f5ae2/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e3e44d08b61de0dd6f205528498f834a51a5c06689f8fb182fe26f3a3ce7dca9", size = 30981, upload-time = "2025-04-29T13:33:40.431Z" }, - { url = "https://files.pythonhosted.org/packages/31/62/e3e4a4e006d0e549748e53cded4ff3b667be0602860fc61b7de8b412b667/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:de004939fc3fd0c1200d26ea9264350bfe501ffbf46c8cf5dc7f345f2d87a7f1", size = 33244, upload-time = "2025-04-29T13:33:41.817Z" }, - { url = "https://files.pythonhosted.org/packages/aa/05/4b223fd4ef94e105dc7aff27fa502fb7200cf52be2bb0c064bd2406b5611/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f8194b4d631b003a1176a75d1acd545e04b1f54b821638e098a93e6e62830ef", size = 31630, upload-time = "2025-04-29T13:33:43.093Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ba/5f68eb969f7336f54b54a599fd3ffbd7662f9733b080bc8598705971b3dd/setproctitle-1.3.6-cp313-cp313-win32.whl", hash = "sha256:d714e002dd3638170fe7376dc1b686dbac9cb712cde3f7224440af722cc9866a", size = 11480, upload-time = "2025-04-29T13:34:01.257Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/7f47f0ca35c9c357f16187cee9229f3eda0237bc6fdd3061441336f361c0/setproctitle-1.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:b70c07409d465f3a8b34d52f863871fb8a00755370791d2bd1d4f82b3cdaf3d5", size = 12198, upload-time = "2025-04-29T13:34:02.293Z" }, - { url = "https://files.pythonhosted.org/packages/39/ad/c3941b8fc6b32a976c9e2d9615a90ae793b69cd010ca8c3575dbc822104f/setproctitle-1.3.6-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:23a57d3b8f1549515c2dbe4a2880ebc1f27780dc126c5e064167563e015817f5", size = 17401, upload-time = "2025-04-29T13:33:44.186Z" }, - { url = "https://files.pythonhosted.org/packages/04/38/a184f857b988d3a9c401e470a4e38182a5c99ee77bf90432d7665e9d35a3/setproctitle-1.3.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81c443310831e29fabbd07b75ebbfa29d0740b56f5907c6af218482d51260431", size = 11959, upload-time = "2025-04-29T13:33:45.71Z" }, - { url = "https://files.pythonhosted.org/packages/b7/b9/4878ef9d8483adfd1edf6bf95151362aaec0d05aac306a97ff0383f491b5/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88c63bd395c787b0aa81d8bbc22c1809f311032ce3e823a6517b711129818e4", size = 33463, upload-time = "2025-04-29T13:33:46.913Z" }, - { url = "https://files.pythonhosted.org/packages/cc/60/3ef49d1931aff2a36a7324a49cca10d77ef03e0278452fd468c33a52d7e3/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73f14b86d0e2858ece6bf5807c9889670e392c001d414b4293d0d9b291942c3", size = 34959, upload-time = "2025-04-29T13:33:48.216Z" }, - { url = "https://files.pythonhosted.org/packages/81/c6/dee0a973acecefb0db6c9c2e0ea7f18b7e4db773a72e534741ebdee8bbb8/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3393859eb8f19f5804049a685bf286cb08d447e28ba5c6d8543c7bf5500d5970", size = 32055, upload-time = "2025-04-29T13:33:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a5/5dd5c4192cf18d16349a32a07f728a9a48a2a05178e16966cabd6645903e/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785cd210c0311d9be28a70e281a914486d62bfd44ac926fcd70cf0b4d65dff1c", size = 32986, upload-time = "2025-04-29T13:33:51.519Z" }, - { url = "https://files.pythonhosted.org/packages/df/a6/1508d37eb8008670d33f13fcdb91cbd8ef54697276469abbfdd3d4428c59/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c051f46ed1e13ba8214b334cbf21902102807582fbfaf0fef341b9e52f0fafbf", size = 32736, upload-time = "2025-04-29T13:33:52.852Z" }, - { url = "https://files.pythonhosted.org/packages/1a/73/c84ec8880d543766a12fcd6b65dbd013770974a40577889f357409b0441e/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:49498ebf68ca3e75321ffe634fcea5cc720502bfaa79bd6b03ded92ce0dc3c24", size = 31945, upload-time = "2025-04-29T13:33:54.665Z" }, - { url = "https://files.pythonhosted.org/packages/95/0a/126b9ff7a406a69a62825fe5bd6d1ba8671919a7018c4f9e2c63f49bfcb6/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4431629c178193f23c538cb1de3da285a99ccc86b20ee91d81eb5f1a80e0d2ba", size = 34333, upload-time = "2025-04-29T13:33:56.101Z" }, - { url = "https://files.pythonhosted.org/packages/9a/fd/5474b04f1c013ff460129d2bc774557dd6e186da4667865efef9a83bf378/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d136fbf8ad4321716e44d6d6b3d8dffb4872626010884e07a1db54b7450836cf", size = 32508, upload-time = "2025-04-29T13:33:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/32/21/2503e38520cb076a7ecaef6a35d6a6fa89cf02af3541c84c811fd7500d20/setproctitle-1.3.6-cp313-cp313t-win32.whl", hash = "sha256:d483cc23cc56ab32911ea0baa0d2d9ea7aa065987f47de847a0a93a58bf57905", size = 11482, upload-time = "2025-04-29T13:33:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/65/23/7833d75a27fba25ddc5cd3b54cd03c4bf8e18b8e2dbec622eb6326278ce8/setproctitle-1.3.6-cp313-cp313t-win_amd64.whl", hash = "sha256:74973aebea3543ad033b9103db30579ec2b950a466e09f9c2180089e8346e0ec", size = 12209, upload-time = "2025-04-29T13:33:59.727Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/0b/6139f589436c278b33359845ed77019cd093c41371f898283bbc14d26c02/sentry_sdk-2.33.0.tar.gz", hash = "sha256:cdceed05e186846fdf80ceea261fe0a11ebc93aab2f228ed73d076a07804152e", size = 335233, upload-time = "2025-07-15T12:07:42.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/e5/f24e9f81c9822a24a2627cfcb44c10a3971382e67e5015c6e068421f5787/sentry_sdk-2.33.0-py2.py3-none-any.whl", hash = "sha256:a762d3f19a1c240e16c98796f2a5023f6e58872997d5ae2147ac3ed378b23ec2", size = 356397, upload-time = "2025-07-15T12:07:40.729Z" }, ] [[package]] @@ -1361,56 +1243,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] -[[package]] -name = "tensorboard" -version = "2.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "absl-py" }, - { name = "grpcio" }, - { name = "markdown" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "setuptools" }, - { name = "six" }, - { name = "tensorboard-data-server" }, - { name = "werkzeug" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/12/4f70e8e2ba0dbe72ea978429d8530b0333f0ed2140cc571a48802878ef99/tensorboard-2.19.0-py3-none-any.whl", hash = "sha256:5e71b98663a641a7ce8a6e70b0be8e1a4c0c45d48760b076383ac4755c35b9a0", size = 5503412, upload-time = "2025-02-12T08:17:27.21Z" }, -] - -[[package]] -name = "tensorboard-data-server" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, -] - [[package]] name = "tensorstore" -version = "0.1.75" +version = "0.1.76" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/4e/5be077c63d01af420ca8a009cad3b30fef137ef37f6530c266f4f2628382/tensorstore-0.1.75.tar.gz", hash = "sha256:515cc90f5b6c316443f44794168083326fb29a0e50b0cd8fbd4cb3e0f32a3922", size = 6831417, upload-time = "2025-05-14T00:38:05.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/ae/947a9f232de7319b664ed8d278e9e0363e9294da73fd422c687ac4eb070e/tensorstore-0.1.76.tar.gz", hash = "sha256:ed0d565e7a038a84b1b5b5d9f7397caec200b53941d8889f44b7f63dd6abffe7", size = 6869230, upload-time = "2025-07-02T21:34:03.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/fb/28a5f8035cadbae34bdcaf03a8e0d731fd8bc8c9804ed8f54413cbfddeda/tensorstore-0.1.75-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:bc092152673a993df1867bac16622f5f382816184f2244df9ff78ba7f781e642", size = 15644019, upload-time = "2025-05-14T00:37:41.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/52/b289ac969d7cee8c253b2f90e5cd6b37789f704147ff7fffa8a50e7b97c4/tensorstore-0.1.75-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d5c6c8ef6c6758f7e11a4cbc7fc4e23af5170128901df729185b7870f6dbc071", size = 13557511, upload-time = "2025-05-14T00:37:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/35/50/a2c4271e2512ace24290d2d7cf166aaf6e251ef14d20255d98a96c6a9514/tensorstore-0.1.75-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bbb30f24aef98d43657d132833f5577bfa91497769ef6b5238c5faccf7afe35", size = 17454887, upload-time = "2025-05-14T00:37:46.918Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1d/9b2610a0770a2115e4a20c1a9377e2e14efabeb55852d150832ff82346f4/tensorstore-0.1.75-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08d9c2b8b84c892c3c81f6025ec189f58bd7860bf624c32646e5bee81870f95", size = 18820501, upload-time = "2025-05-14T00:37:49.022Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9c/06f7318bd56fe62ccd7743159cd9e133b5e0ead5b8b229a6f1f392e65666/tensorstore-0.1.75-cp312-cp312-win_amd64.whl", hash = "sha256:39d4173bdbbc1cf41e168fe730fd457a6b0c4100ba707254260f63cb9ad3ef0b", size = 12607424, upload-time = "2025-05-14T00:37:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/c1/97/656252b262099fdc8b3f247c58ec147ba644f4fc4dec8f7af3ffb352704e/tensorstore-0.1.75-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:0d2f87ca268faf903d5ffba6157fd9aeb42e9f961cea01b98baa690f71f51a1e", size = 15644856, upload-time = "2025-05-14T00:37:53.28Z" }, - { url = "https://files.pythonhosted.org/packages/94/e1/66067a2aa5c2890c02397df65d748978de1dbbe91ce394f285f86390c149/tensorstore-0.1.75-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:17ee80f9869b5a4b0303cb37edca9c9665af7a9510fac85f59fb8de19f12efd1", size = 13557924, upload-time = "2025-05-14T00:37:55.249Z" }, - { url = "https://files.pythonhosted.org/packages/46/56/c1245f7bb674072bb0f9e8516bd60f7608ffe114e911c08ebcaefca58f46/tensorstore-0.1.75-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f00144d23eaf511104651b4479fcb111b9befc13db3018277d358144be503ef4", size = 17454695, upload-time = "2025-05-14T00:37:58.521Z" }, - { url = "https://files.pythonhosted.org/packages/db/78/8a103a9012662fb8d85c3d6daa9c9678d49f260a21b5426e0a1616e63c42/tensorstore-0.1.75-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8697cab7b24440a13df8d9e6d000c1067ed3f97204a3dae5388e9e60606834", size = 18820794, upload-time = "2025-05-14T00:38:01.253Z" }, - { url = "https://files.pythonhosted.org/packages/7d/3d/69d7997fe67fd9cb8fce07ea0f3f3e754a6ea0d2c16f1c46e178abe7da0e/tensorstore-0.1.75-cp313-cp313-win_amd64.whl", hash = "sha256:df410ca28e679c1c8a5b361267ce02fe60a9c4d78964cb984d884d15c538f2f2", size = 12607428, upload-time = "2025-05-14T00:38:03.32Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f2254b4ae1dabd95e258fa3eb4783ac4db4261bb8c90ff9bfe15549d1238/tensorstore-0.1.76-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:b68450983ccad9e7774e81b2fa37daef1b72c774fd939d9eb4065d6aa70e666a", size = 15712650, upload-time = "2025-07-02T21:33:39.716Z" }, + { url = "https://files.pythonhosted.org/packages/93/3c/1cae56cbbe9610ff48cb2d7c0921a4d4c333a0540918e3b2db08b521c5f6/tensorstore-0.1.76-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b7a3856f884279e40f90bad87d0da70869879e124835e650c6b16c80f64fbc4", size = 13624138, upload-time = "2025-07-02T21:33:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/b92d34a896f608a59dc76c290d4ec9f7d0264a02e4d74864987a6adbd3c9/tensorstore-0.1.76-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8709a98ae0b453eb23525c07372c2be1f6bbd978bba53319f26a1f2a83a77c2a", size = 17538270, upload-time = "2025-07-02T21:33:44.911Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/142b803541552b02a2fa033b1f48bcb50e1d2df6ac10131aab1857c5141d/tensorstore-0.1.76-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:267edea8f1596f2bd67017ff97b7b350bf3f95ff84947a8babadc5e17ca53663", size = 18910782, upload-time = "2025-07-02T21:33:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3e/c264cf1435c04fb998a1f30dd1f066deb370b841412f89e1cb36d37ee4fc/tensorstore-0.1.76-cp312-cp312-win_amd64.whl", hash = "sha256:f66ac63d0c63c3336ac4dc61f1f97b6afe8b512e586ddfdbc91f19175787f321", size = 12611059, upload-time = "2025-07-02T21:33:49.596Z" }, + { url = "https://files.pythonhosted.org/packages/5f/66/1e3b819e1de98b048dad7843f3a814c5e739ead57f511dafb6aa0748f04a/tensorstore-0.1.76-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:a471994b156daa3cadb0e4968e29202fa2e8c7ddcd28d825499bb5637caa0983", size = 15713110, upload-time = "2025-07-02T21:33:51.973Z" }, + { url = "https://files.pythonhosted.org/packages/58/d3/226344e8822c5e02af929c89bd61964e08980253cda15286a201850eb3b1/tensorstore-0.1.76-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98175dc64935b49467cb7664a431b9a06e9df9b5cab94f9a1fdb24a30b2d69d3", size = 13624514, upload-time = "2025-07-02T21:33:54.109Z" }, + { url = "https://files.pythonhosted.org/packages/94/9f/2b267c520dbbcf0a5ebc7a3c0a6cf852a445e22c8ea8b0f7450bf6b98783/tensorstore-0.1.76-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9e30577f1197ea3573102912482dced95e4c6ff72087ffeb99b5d8b496bf81a", size = 17539304, upload-time = "2025-07-02T21:33:56.172Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9a/9dcc01c8f87047b09602ea16379233b8a308d1d83d5432bf8bc89163ca3e/tensorstore-0.1.76-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20782f833bfa3c59dd3787f657388054c54ee0ab48dad181b360e3e5e81e4c4b", size = 18911982, upload-time = "2025-07-02T21:33:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/10/45/43d387027b3eac9f09de8bb736b1b432de287fbd807716877fe5fbaeee56/tensorstore-0.1.76-cp313-cp313-win_amd64.whl", hash = "sha256:e84fc11b36fcd55cfd1c5dfc60de9d54d7d95c3de074f4d854914067e82a6740", size = 12610851, upload-time = "2025-07-02T21:34:01.505Z" }, ] [[package]] @@ -1476,11 +1328,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] @@ -1506,7 +1358,7 @@ wheels = [ [[package]] name = "wandb" -version = "0.20.1" +version = "0.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1514,26 +1366,24 @@ dependencies = [ { name = "packaging" }, { name = "platformdirs" }, { name = "protobuf" }, - { name = "psutil" }, { name = "pydantic" }, { name = "pyyaml" }, { name = "requests" }, { name = "sentry-sdk" }, - { name = "setproctitle" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/1f/92be0ca87fb49eb48c16dcf0845a3579a57c4734fec2b95862cf5a0494a0/wandb-0.20.1.tar.gz", hash = "sha256:dbd3fc60dfe7bf83c4de24b206b99b44949fef323f817a783883db72fc5f3bfe", size = 40320062, upload-time = "2025-06-05T00:00:24.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/09/c84264a219e20efd615e4d5d150cc7d359d57d51328d3fa94ee02d70ed9c/wandb-0.21.0.tar.gz", hash = "sha256:473e01ef200b59d780416062991effa7349a34e51425d4be5ff482af2dc39e02", size = 40085784, upload-time = "2025-07-02T00:24:15.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/18/afcc37d0b93dd6f6d0f0c5683b9cfff9416ae1539931f58932a2938c0070/wandb-0.20.1-py3-none-any.whl", hash = "sha256:e6395cabf074247042be1cf0dc6ab0b06aa4c9538c2e1fdc5b507a690ce0cf17", size = 6458872, upload-time = "2025-06-04T23:59:55.441Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b5/70f9e2a3d1380b729ae5853763d938edc50072df357f79bbd19b9aae8e3f/wandb-0.20.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:2475a48c693adf677d40da9e1c8ceeaf86d745ffc3b7e3535731279d02f9e845", size = 22517483, upload-time = "2025-06-04T23:59:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/7e/4eb9aeb2fd974d410a8f2eb11b0219536503913a050d46a03206151705c8/wandb-0.20.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99cce804c31ec1e0d1e691650a7d51773ed7329c41745d56384fa3655a0e9b2c", size = 22034511, upload-time = "2025-06-05T00:00:01.301Z" }, - { url = "https://files.pythonhosted.org/packages/34/38/1df22c2273e6f7ab0aae4fd032085d6d92ab112f5b261646e7dc5e675cfe/wandb-0.20.1-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ce3ee412677a1679e04b21e03a91e1e02eb90faf658d682bee86c33cf5f32e09", size = 22720771, upload-time = "2025-06-05T00:00:04.122Z" }, - { url = "https://files.pythonhosted.org/packages/38/96/78fc7a7ea7158d136c84f481423f8736c9346a2387287ec8a6d92019975c/wandb-0.20.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e58ca32c7147161158f09b0fb5f5896876f8569d0d10ae7b64d0510c868ce33", size = 21537453, upload-time = "2025-06-05T00:00:09.474Z" }, - { url = "https://files.pythonhosted.org/packages/88/c9/41b8bdb493e5eda32b502bc1cc49d539335a92cacaf0ef304d7dae0240aa/wandb-0.20.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:591506ecbdd396648cc323ba270f3ab4aed3158e1dbfa7636c09f9f7f0253e1c", size = 23161349, upload-time = "2025-06-05T00:00:11.903Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/79e783cc50a47d373dfbda862eb5396de8139167e8c6443a16ef0166106f/wandb-0.20.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:382508532db09893f81cc926b1d333caa4c8a7db057878899fadf929bbdb3b56", size = 21550624, upload-time = "2025-06-05T00:00:14.28Z" }, - { url = "https://files.pythonhosted.org/packages/26/32/23890a726302e7be28bda9fff47ce9b491af64e339aba4d32b3b8d1a7aaf/wandb-0.20.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:29ea495e49393db860f17437fe37e48018da90436ce10949b471780f09293bd7", size = 23237996, upload-time = "2025-06-05T00:00:16.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/94/296e520b086b2a4f10e99bcea3cd5856421b9c004824663501e3789a713b/wandb-0.20.1-py3-none-win32.whl", hash = "sha256:455ee0a652e59ab1e4b546fa1dc833dd3063aa7e64eb8abf95d22f0e9f08c574", size = 22518456, upload-time = "2025-06-05T00:00:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/52/5f/c44ad7b2a062ca5f4da99ae475cea274c38f6ec37bdaca1b1c653ee87274/wandb-0.20.1-py3-none-win_amd64.whl", hash = "sha256:6d2431652f096b7e394c29a99135a6441c02ed3198b963f0b351a5b5e56aeca0", size = 22518459, upload-time = "2025-06-05T00:00:21.374Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/65eac086e1bc337bb5f0eed65ba1fe4a6dbc62c97f094e8e9df1ef83ffed/wandb-0.21.0-py3-none-any.whl", hash = "sha256:316e8cd4329738f7562f7369e6eabeeb28ef9d473203f7ead0d03e5dba01c90d", size = 6504284, upload-time = "2025-07-02T00:23:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/80556ce9097f59e10807aa68f4a9b29d736a90dca60852a9e2af1641baf8/wandb-0.21.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:701d9cbdfcc8550a330c1b54a26f1585519180e0f19247867446593d34ace46b", size = 21717388, upload-time = "2025-07-02T00:23:49.348Z" }, + { url = "https://files.pythonhosted.org/packages/23/ae/660bc75aa37bd23409822ea5ed616177d94873172d34271693c80405c820/wandb-0.21.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:01689faa6b691df23ba2367e0a1ecf6e4d0be44474905840098eedd1fbcb8bdf", size = 21141465, upload-time = "2025-07-02T00:23:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/23/ab/9861929530be56557c74002868c85d0d8ac57050cc21863afe909ae3d46f/wandb-0.21.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:55d3f42ddb7971d1699752dff2b85bcb5906ad098d18ab62846c82e9ce5a238d", size = 21793511, upload-time = "2025-07-02T00:23:55.447Z" }, + { url = "https://files.pythonhosted.org/packages/de/52/e5cad2eff6fbed1ac06f4a5b718457fa2fd437f84f5c8f0d31995a2ef046/wandb-0.21.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:893508f0c7da48917448daa5cd622c27ce7ce15119adaa861185034c2bd7b14c", size = 20704643, upload-time = "2025-07-02T00:23:58.255Z" }, + { url = "https://files.pythonhosted.org/packages/83/8f/6bed9358cc33767c877b221d4f565e1ddf00caf4bbbe54d2e3bbc932c6a7/wandb-0.21.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e8245a8912247ddf7654f7b5330f583a6c56ab88fee65589158490d583c57d", size = 22243012, upload-time = "2025-07-02T00:24:01.423Z" }, + { url = "https://files.pythonhosted.org/packages/be/61/9048015412ea5ca916844af55add4fed7c21fe1ad70bb137951e70b550c5/wandb-0.21.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c4f951e0d02755e315679bfdcb5bc38c1b02e2e5abc5432b91a91bb0cf246", size = 20716440, upload-time = "2025-07-02T00:24:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/fcd2273d8ec3f79323e40a031aba5d32d6fa9065702010eb428b5ffbab62/wandb-0.21.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:873749966eeac0069e0e742e6210641b6227d454fb1dae2cf5c437c6ed42d3ca", size = 22320652, upload-time = "2025-07-02T00:24:07.175Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/b8308db6b9c3c96dcd03be17c019aee105e1d7dc1e74d70756cdfb9241c6/wandb-0.21.0-py3-none-win32.whl", hash = "sha256:9d3cccfba658fa011d6cab9045fa4f070a444885e8902ae863802549106a5dab", size = 21484296, upload-time = "2025-07-02T00:24:10.147Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/71cc033e8abd00e54465e68764709ed945e2da2d66d764f72f4660262b22/wandb-0.21.0-py3-none-win_amd64.whl", hash = "sha256:28a0b2dad09d7c7344ac62b0276be18a2492a5578e4d7c84937a3e1991edaac7", size = 21484301, upload-time = "2025-07-02T00:24:12.658Z" }, ] [[package]] @@ -1545,18 +1395,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] -[[package]] -name = "werkzeug" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, -] - [[package]] name = "zipp" version = "3.23.0"