Compare commits
29 Commits
master
...
feature/al
Author | SHA1 | Date | |
---|---|---|---|
|
846ca0ff67 | ||
|
60e75e38dc | ||
|
7d479af0bb | ||
|
ed12b16d70 | ||
|
85e910ec5d | ||
|
f2e10b04bb | ||
|
929dd10267 | ||
|
ec2c5c19d1 | ||
|
9c645ff4a0 | ||
|
ce5fa0dfac | ||
|
9055eb5924 | ||
|
6d9b697efe | ||
|
18d8ffa576 | ||
|
e95fa26a14 | ||
|
dffe8cddf6 | ||
|
5a3f2291c2 | ||
|
85204b175c | ||
|
2e3f0b5e99 | ||
|
d9a201754c | ||
|
516c956d58 | ||
|
5259d5f3fb | ||
|
a7898b15b8 | ||
|
d9a612a997 | ||
|
f730782f29 | ||
|
6c1bd85521 | ||
|
5762d2c2e0 | ||
|
734119ec00 | ||
|
32cd3b4357 | ||
|
95cbfe6cdf |
@ -66,7 +66,13 @@ def main(
|
||||
replay_buffer_save_only_last_obs=True,
|
||||
)
|
||||
|
||||
env_factory = AtariEnvFactory(task, experiment_config.seed, frames_stack, scale=scale_obs)
|
||||
env_factory = AtariEnvFactory(
|
||||
task,
|
||||
sampling_config.train_seed,
|
||||
sampling_config.test_seed,
|
||||
frames_stack,
|
||||
scale=scale_obs,
|
||||
)
|
||||
|
||||
builder = (
|
||||
DQNExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -98,7 +104,7 @@ def main(
|
||||
)
|
||||
|
||||
experiment = builder.build()
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -65,7 +65,13 @@ def main(
|
||||
replay_buffer_save_only_last_obs=True,
|
||||
)
|
||||
|
||||
env_factory = AtariEnvFactory(task, experiment_config.seed, frames_stack, scale=scale_obs)
|
||||
env_factory = AtariEnvFactory(
|
||||
task,
|
||||
sampling_config.train_seed,
|
||||
sampling_config.test_seed,
|
||||
frames_stack,
|
||||
scale=scale_obs,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
IQNExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -90,7 +96,7 @@ def main(
|
||||
.with_epoch_stop_callback(AtariEpochStopCallback(task))
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -109,7 +109,7 @@ def main(
|
||||
),
|
||||
)
|
||||
experiment = builder.build()
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -65,7 +65,13 @@ def main(
|
||||
replay_buffer_save_only_last_obs=True,
|
||||
)
|
||||
|
||||
env_factory = AtariEnvFactory(task, experiment_config.seed, frames_stack, scale=scale_obs)
|
||||
env_factory = AtariEnvFactory(
|
||||
task,
|
||||
sampling_config.train_seed,
|
||||
sampling_config.test_seed,
|
||||
frames_stack,
|
||||
scale=scale_obs,
|
||||
)
|
||||
|
||||
builder = (
|
||||
DiscreteSACExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -96,7 +102,7 @@ def main(
|
||||
),
|
||||
)
|
||||
experiment = builder.build()
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -340,7 +340,7 @@ def make_atari_env(
|
||||
|
||||
:return: a tuple of (single env, training envs, test envs).
|
||||
"""
|
||||
env_factory = AtariEnvFactory(task, seed, frame_stack, scale=bool(scale))
|
||||
env_factory = AtariEnvFactory(task, seed, seed + training_num, frame_stack, scale=bool(scale))
|
||||
envs = env_factory.create_envs(training_num, test_num)
|
||||
return envs.env, envs.train_envs, envs.test_envs
|
||||
|
||||
@ -349,7 +349,8 @@ class AtariEnvFactory(EnvFactoryRegistered):
|
||||
def __init__(
|
||||
self,
|
||||
task: str,
|
||||
seed: int,
|
||||
train_seed: int,
|
||||
test_seed: int,
|
||||
frame_stack: int,
|
||||
scale: bool = False,
|
||||
use_envpool_if_available: bool = True,
|
||||
@ -366,7 +367,8 @@ class AtariEnvFactory(EnvFactoryRegistered):
|
||||
log.info("Not using envpool, because it is not available")
|
||||
super().__init__(
|
||||
task=task,
|
||||
seed=seed,
|
||||
train_seed=train_seed,
|
||||
test_seed=test_seed,
|
||||
venv_type=VectorEnvType.SUBPROC_SHARED_MEM,
|
||||
envpool_factory=envpool_factory,
|
||||
)
|
||||
|
67
examples/mujoco/launcher.py
Normal file
67
examples/mujoco/launcher.py
Normal file
@ -0,0 +1,67 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
from tianshou.highlevel.experiment import Experiment
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class JoblibConfig:
|
||||
n_jobs: int = -1
|
||||
"""The maximum number of concurrently running jobs. If -1, all CPUs are used."""
|
||||
backend: Literal["loky", "multiprocessing", "threading"] | None = None
|
||||
"""Allows to hard-code backend, otherwise inferred based on prefer and require."""
|
||||
verbose: int = 10
|
||||
"""If greater than zero, prints progress messages."""
|
||||
|
||||
|
||||
class ExpLauncher(ABC):
|
||||
@abstractmethod
|
||||
def launch(self, experiments: Sequence[Experiment]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class SequentialExpLauncher(ExpLauncher):
|
||||
def launch(self, experiments: Sequence[Experiment]) -> None:
|
||||
for exp in experiments:
|
||||
exp.run()
|
||||
|
||||
|
||||
class JoblibExpLauncher(ExpLauncher):
|
||||
def __init__(self, joblib_cfg: JoblibConfig | None = None) -> None:
|
||||
self.joblib_cfg = joblib_cfg or JoblibConfig()
|
||||
# Joblib's backend is hard-coded to loky since the threading backend produces different results
|
||||
self.joblib_cfg.backend = "loky"
|
||||
|
||||
def launch(self, experiments: Sequence[Experiment]) -> None:
|
||||
Parallel(**asdict(self.joblib_cfg))(delayed(self._safe_execute)(exp) for exp in experiments)
|
||||
|
||||
@staticmethod
|
||||
def _safe_execute(exp: Experiment):
|
||||
try:
|
||||
exp.run()
|
||||
except BaseException as e:
|
||||
log.error(e)
|
||||
|
||||
|
||||
class RegisteredExpLauncher(Enum):
|
||||
joblib = "joblib"
|
||||
sequential = "sequential"
|
||||
|
||||
def create_launcher(self):
|
||||
match self:
|
||||
case RegisteredExpLauncher.joblib:
|
||||
return JoblibExpLauncher()
|
||||
case RegisteredExpLauncher.sequential:
|
||||
return SequentialExpLauncher()
|
||||
case _:
|
||||
raise NotImplementedError(
|
||||
f"Launcher {self} is not yet implemented.",
|
||||
)
|
@ -54,7 +54,12 @@ def main(
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=True)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
A2CExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -78,7 +83,7 @@ def main(
|
||||
.with_critic_factory_default(hidden_sizes, nn.Tanh)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -51,7 +51,12 @@ def main(
|
||||
start_timesteps_random=True,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=False)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=False,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
DDPGExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -69,7 +74,7 @@ def main(
|
||||
.with_critic_factory_default(hidden_sizes)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -29,7 +29,7 @@ def make_mujoco_env(task: str, seed: int, num_train_envs: int, num_test_envs: in
|
||||
|
||||
:return: a tuple of (single env, training envs, test envs).
|
||||
"""
|
||||
envs = MujocoEnvFactory(task, seed, obs_norm=obs_norm).create_envs(
|
||||
envs = MujocoEnvFactory(task, seed, seed + num_train_envs, obs_norm=obs_norm).create_envs(
|
||||
num_train_envs,
|
||||
num_test_envs,
|
||||
)
|
||||
@ -65,13 +65,15 @@ class MujocoEnvFactory(EnvFactoryRegistered):
|
||||
def __init__(
|
||||
self,
|
||||
task: str,
|
||||
seed: int,
|
||||
train_seed: int,
|
||||
test_seed: int,
|
||||
obs_norm: bool = True,
|
||||
venv_type: VectorEnvType = VectorEnvType.SUBPROC_SHARED_MEM,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
task=task,
|
||||
seed=seed,
|
||||
train_seed=train_seed,
|
||||
test_seed=test_seed,
|
||||
venv_type=venv_type,
|
||||
envpool_factory=EnvPoolFactory() if envpool_is_available else None,
|
||||
)
|
||||
|
@ -56,7 +56,12 @@ def main(
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=True)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
NPGExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -80,7 +85,7 @@ def main(
|
||||
.with_critic_factory_default(hidden_sizes, torch.nn.Tanh)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -61,7 +61,12 @@ def main(
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=True)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
PPOExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -90,7 +95,7 @@ def main(
|
||||
.with_critic_factory_default(hidden_sizes, torch.nn.Tanh)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
132
examples/mujoco/mujoco_ppo_hl_multi.py
Normal file
132
examples/mujoco/mujoco_ppo_hl_multi.py
Normal file
@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from typing import Literal
|
||||
|
||||
import torch
|
||||
|
||||
from examples.mujoco.launcher import RegisteredExpLauncher
|
||||
from examples.mujoco.mujoco_env import MujocoEnvFactory
|
||||
from tianshou.highlevel.config import SamplingConfig
|
||||
from tianshou.highlevel.env import VectorEnvType
|
||||
from tianshou.highlevel.evaluation import RLiableExperimentResult
|
||||
from tianshou.highlevel.experiment import (
|
||||
ExperimentConfig,
|
||||
PPOExperimentBuilder,
|
||||
)
|
||||
from tianshou.highlevel.params.dist_fn import (
|
||||
DistributionFunctionFactoryIndependentGaussians,
|
||||
)
|
||||
from tianshou.highlevel.params.lr_scheduler import LRSchedulerFactoryLinear
|
||||
from tianshou.highlevel.params.policy_params import PPOParams
|
||||
from tianshou.utils import logging
|
||||
from tianshou.utils.logging import datetime_tag
|
||||
|
||||
|
||||
def main(
|
||||
experiment_config: ExperimentConfig,
|
||||
task: str = "Ant-v4",
|
||||
num_experiments: int = 5,
|
||||
buffer_size: int = 4096,
|
||||
hidden_sizes: Sequence[int] = (64, 64),
|
||||
lr: float = 3e-4,
|
||||
gamma: float = 0.99,
|
||||
epoch: int = 100,
|
||||
step_per_epoch: int = 30000,
|
||||
step_per_collect: int = 2048,
|
||||
repeat_per_collect: int = 10,
|
||||
batch_size: int = 64,
|
||||
training_num: int = 10,
|
||||
test_num: int = 10,
|
||||
rew_norm: bool = True,
|
||||
vf_coef: float = 0.25,
|
||||
ent_coef: float = 0.0,
|
||||
gae_lambda: float = 0.95,
|
||||
bound_action_method: Literal["clip", "tanh"] | None = "clip",
|
||||
lr_decay: bool = True,
|
||||
max_grad_norm: float = 0.5,
|
||||
eps_clip: float = 0.2,
|
||||
dual_clip: float | None = None,
|
||||
value_clip: bool = False,
|
||||
norm_adv: bool = False,
|
||||
recompute_adv: bool = True,
|
||||
run_sequential: bool = False,
|
||||
) -> str:
|
||||
"""Use the high-level API of TianShou to evaluate the PPO algorithm on a MuJoCo environment with multiple seeds for
|
||||
a given configuration. The results for each run are stored in separate sub-folders. After the agents are trained,
|
||||
the results are evaluated using rliable API.
|
||||
"""
|
||||
log_name = os.path.join("log", task, "ppo", datetime_tag())
|
||||
experiment_config.persistence_base_dir = log_name
|
||||
experiment_config.watch = False
|
||||
|
||||
sampling_config = SamplingConfig(
|
||||
num_epochs=epoch,
|
||||
step_per_epoch=step_per_epoch,
|
||||
batch_size=batch_size,
|
||||
num_train_envs=training_num,
|
||||
num_test_envs=test_num,
|
||||
num_test_episodes=test_num,
|
||||
buffer_size=buffer_size,
|
||||
step_per_collect=step_per_collect,
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
venv_type=VectorEnvType.SUBPROC_SHARED_MEM_FORK_CONTEXT
|
||||
if sys.platform == "darwin"
|
||||
else VectorEnvType.SUBPROC_SHARED_MEM,
|
||||
)
|
||||
|
||||
experiments = (
|
||||
PPOExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
.with_ppo_params(
|
||||
PPOParams(
|
||||
discount_factor=gamma,
|
||||
gae_lambda=gae_lambda,
|
||||
action_bound_method=bound_action_method,
|
||||
reward_normalization=rew_norm,
|
||||
ent_coef=ent_coef,
|
||||
vf_coef=vf_coef,
|
||||
max_grad_norm=max_grad_norm,
|
||||
value_clip=value_clip,
|
||||
advantage_normalization=norm_adv,
|
||||
eps_clip=eps_clip,
|
||||
dual_clip=dual_clip,
|
||||
recompute_advantage=recompute_adv,
|
||||
lr=lr,
|
||||
lr_scheduler_factory=LRSchedulerFactoryLinear(sampling_config)
|
||||
if lr_decay
|
||||
else None,
|
||||
dist_fn=DistributionFunctionFactoryIndependentGaussians(),
|
||||
),
|
||||
)
|
||||
.with_actor_factory_default(hidden_sizes, torch.nn.Tanh, continuous_unbounded=True)
|
||||
.with_critic_factory_default(hidden_sizes, torch.nn.Tanh)
|
||||
.build_default_seeded_experiments(num_experiments)
|
||||
)
|
||||
|
||||
if run_sequential:
|
||||
launcher = RegisteredExpLauncher.sequential.create_launcher()
|
||||
else:
|
||||
launcher = RegisteredExpLauncher.joblib.create_launcher()
|
||||
launcher.launch(experiments)
|
||||
|
||||
return log_name
|
||||
|
||||
|
||||
def eval_experiments(log_dir: str):
|
||||
"""Evaluate the experiments in the given log directory using the rliable API."""
|
||||
rliable_result = RLiableExperimentResult.load_from_disk(log_dir)
|
||||
rliable_result.eval_results(save_figure=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
log_dir = logging.run_cli(main)
|
||||
eval_experiments(log_dir)
|
@ -57,7 +57,12 @@ def main(
|
||||
start_timesteps_random=True,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=False)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=False,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
REDQExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -78,7 +83,7 @@ def main(
|
||||
.with_critic_ensemble_factory_default(hidden_sizes)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -49,7 +49,12 @@ def main(
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=True)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
PGExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -67,7 +72,7 @@ def main(
|
||||
.with_actor_factory_default(hidden_sizes, torch.nn.Tanh, continuous_unbounded=True)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -52,7 +52,12 @@ def main(
|
||||
start_timesteps_random=True,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=False)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=False,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
SACExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -75,7 +80,7 @@ def main(
|
||||
.with_common_critic_factory_default(hidden_sizes)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -58,7 +58,12 @@ def main(
|
||||
start_timesteps_random=True,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=False)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=False,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
TD3ExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -80,7 +85,7 @@ def main(
|
||||
.with_common_critic_factory_default(hidden_sizes, torch.nn.Tanh)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -58,7 +58,12 @@ def main(
|
||||
repeat_per_collect=repeat_per_collect,
|
||||
)
|
||||
|
||||
env_factory = MujocoEnvFactory(task, experiment_config.seed, obs_norm=True)
|
||||
env_factory = MujocoEnvFactory(
|
||||
task,
|
||||
train_seed=sampling_config.train_seed,
|
||||
test_seed=sampling_config.test_seed,
|
||||
obs_norm=True,
|
||||
)
|
||||
|
||||
experiment = (
|
||||
TRPOExperimentBuilder(env_factory, experiment_config, sampling_config)
|
||||
@ -84,7 +89,7 @@ def main(
|
||||
.with_critic_factory_default(hidden_sizes, torch.nn.Tanh)
|
||||
.build()
|
||||
)
|
||||
experiment.run(log_name)
|
||||
experiment.run(override_experiment_name=log_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
691
poetry.lock
generated
691
poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "absl-py"
|
||||
@ -152,6 +152,44 @@ files = [
|
||||
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arch"
|
||||
version = "5.3.0"
|
||||
description = "ARCH for Python"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "arch-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84acb49c1a364c05846d87ddcf5fd505c0e07aa862ed42eb4c5790d91e02cc06"},
|
||||
{file = "arch-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1b47437ce24a8943d23f407cf12374c66dbd06c69ea269188bbebd899956347"},
|
||||
{file = "arch-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60a908398695775eb4582ceb538565f36862449e1b9475d0707cf1c4fa130964"},
|
||||
{file = "arch-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d5601307d67150d522f3137b6bc4327da9d04c8217e17cfaa6ba8a31e8cd55"},
|
||||
{file = "arch-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9329b6c633ca40bf457605bc1eef0093b7c6bac15754903987914cb6ef1861d5"},
|
||||
{file = "arch-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:65d4b63ca395b546ed97f883a65144bb6decd827e4fecefcda1adc5fbdd91f66"},
|
||||
{file = "arch-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f795d4bfc6b5bd44e71f480bccd030fb914dab756ecca081f75fa25c72b4646"},
|
||||
{file = "arch-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c08638b948e6b5e5526d10cdbea23e847eb76a04667c42087373ebe55a097eda"},
|
||||
{file = "arch-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:2aaa969b2a49096d79abafac63bc4540203d8bbba69bbfbfb92f6d3f18488a29"},
|
||||
{file = "arch-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:60220d90428ed19ce13ff88045482177d9a95648292b75d2ce80748dc67163ea"},
|
||||
{file = "arch-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:95ceb0b0266b65052c66334fa6e5afa99deae56576beb7b63ad0c2afc6bfed89"},
|
||||
{file = "arch-5.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ce3ceba7786916619ddf06a062ed09e9e8e4e15c6982661daf6132a25103351e"},
|
||||
{file = "arch-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98dd5e07d83c9dfddba0441be3c982fd39831026d93bb029c6ff35a8598a583c"},
|
||||
{file = "arch-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dbcd0a7a1e4b697af1902d982d937774543f7b127a96f6cb94f0f41b8cd047a"},
|
||||
{file = "arch-5.3.0-cp38-cp38-win32.whl", hash = "sha256:3b599a2f8bf75bf1deace6045bab2790da1d41889d6c3f979f3fe0324a7dfc33"},
|
||||
{file = "arch-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e517ca337d084c47ac1f71a7559db6462623f12823e473e157178a9f650f194a"},
|
||||
{file = "arch-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fa36de059f24f3446cf752d16089b6320493bec00d171bb170f49b6747b7916"},
|
||||
{file = "arch-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2aa50b06dde2a803331494b1e0048a9e7f3e969a7d2463eca2cca86fde8a75"},
|
||||
{file = "arch-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7ffefb8401b202d012d86d857509b4d5921039906b631214097f9963e74fe5d"},
|
||||
{file = "arch-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:091e328d6604c1241431227322140ae354125b5219d8484ffdfa0968a050491d"},
|
||||
{file = "arch-5.3.0-cp39-cp39-win32.whl", hash = "sha256:0edd49c3524bd35f9e17ffdf5af7d1530d39d908406a4208754864e0e70b62bd"},
|
||||
{file = "arch-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bfb6c841414a83017f0dc7b13b4a491062fe170fc9f9eb33e04f54e9bead4af9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.17"
|
||||
pandas = ">=1.0"
|
||||
property-cached = ">=1.6.4"
|
||||
scipy = ">=1.3"
|
||||
statsmodels = ">=0.11"
|
||||
|
||||
[[package]]
|
||||
name = "argon2-cffi"
|
||||
version = "23.1.0"
|
||||
@ -719,6 +757,69 @@ traitlets = ">=4"
|
||||
[package.extras]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.2.0"
|
||||
description = "Python library for calculating contours of 2D quadrilateral grids"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"},
|
||||
{file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"},
|
||||
{file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"},
|
||||
{file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"},
|
||||
{file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"},
|
||||
{file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"},
|
||||
{file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"},
|
||||
{file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"},
|
||||
{file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.20,<2.0"
|
||||
|
||||
[package.extras]
|
||||
bokeh = ["bokeh", "selenium"]
|
||||
docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
|
||||
mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"]
|
||||
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
||||
test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.3.2"
|
||||
@ -798,6 +899,21 @@ files = [
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
description = "Composable style cycles"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
|
||||
{file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
|
||||
tests = ["pytest", "pytest-cov", "pytest-xdist"]
|
||||
|
||||
[[package]]
|
||||
name = "cython"
|
||||
version = "3.0.8"
|
||||
@ -1186,6 +1302,71 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
||||
typing = ["typing-extensions (>=4.8)"]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.50.0"
|
||||
description = "Tools to manipulate font files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effd303fb422f8ce06543a36ca69148471144c534cc25f30e5be752bc4f46736"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7913992ab836f621d06aabac118fc258b9947a775a607e1a737eb3a91c360335"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0a1c5bd2f63da4043b63888534b52c5a1fd7ae187c8ffc64cbb7ae475b9dab"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40fc98540fa5360e7ecf2c56ddf3c6e7dd04929543618fd7b5cc76e66390562"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fff65fbb7afe137bac3113827855e0204482727bddd00a806034ab0d3951d0d"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1aeae3dd2ee719074a9372c89ad94f7c581903306d76befdaca2a559f802472"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-win32.whl", hash = "sha256:e9623afa319405da33b43c85cceb0585a6f5d3a1d7c604daf4f7e1dd55c03d1f"},
|
||||
{file = "fonttools-4.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:778c5f43e7e654ef7fe0605e80894930bc3a7772e2f496238e57218610140f54"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3dfb102e7f63b78c832e4539969167ffcc0375b013080e6472350965a5fe8048"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e58fe34cb379ba3d01d5d319d67dd3ce7ca9a47ad044ea2b22635cd2d1247fc"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c673ab40d15a442a4e6eb09bf007c1dda47c84ac1e2eecbdf359adacb799c24"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b3ac35cdcd1a4c90c23a5200212c1bb74fa05833cc7c14291d7043a52ca2aaa"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8844e7a2c5f7ecf977e82eb6b3014f025c8b454e046d941ece05b768be5847ae"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f849bd3c5c2249b49c98eca5aaebb920d2bfd92b3c69e84ca9bddf133e9f83f0"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-win32.whl", hash = "sha256:39293ff231b36b035575e81c14626dfc14407a20de5262f9596c2cbb199c3625"},
|
||||
{file = "fonttools-4.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:c33d5023523b44d3481624f840c8646656a1def7630ca562f222eb3ead16c438"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b4a886a6dbe60100ba1cd24de962f8cd18139bd32808da80de1fa9f9f27bf1dc"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b2ca1837bfbe5eafa11313dbc7edada79052709a1fffa10cea691210af4aa1fa"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0493dd97ac8977e48ffc1476b932b37c847cbb87fd68673dee5182004906828"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77844e2f1b0889120b6c222fc49b2b75c3d88b930615e98893b899b9352a27ea"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3566bfb8c55ed9100afe1ba6f0f12265cd63a1387b9661eb6031a1578a28bad1"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35e10ddbc129cf61775d58a14f2d44121178d89874d32cae1eac722e687d9019"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-win32.whl", hash = "sha256:cc8140baf9fa8f9b903f2b393a6c413a220fa990264b215bf48484f3d0bf8710"},
|
||||
{file = "fonttools-4.50.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ccc85fd96373ab73c59833b824d7a73846670a0cb1f3afbaee2b2c426a8f931"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e270a406219af37581d96c810172001ec536e29e5593aa40d4c01cca3e145aa6"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac2463de667233372e9e1c7e9de3d914b708437ef52a3199fdbf5a60184f190c"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47abd6669195abe87c22750dbcd366dc3a0648f1b7c93c2baa97429c4dc1506e"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:074841375e2e3d559aecc86e1224caf78e8b8417bb391e7d2506412538f21adc"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0743fd2191ad7ab43d78cd747215b12033ddee24fa1e088605a3efe80d6984de"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3d7080cce7be5ed65bee3496f09f79a82865a514863197ff4d4d177389e981b0"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-win32.whl", hash = "sha256:a467ba4e2eadc1d5cc1a11d355abb945f680473fbe30d15617e104c81f483045"},
|
||||
{file = "fonttools-4.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:f77e048f805e00870659d6318fd89ef28ca4ee16a22b4c5e1905b735495fc422"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b6245eafd553c4e9a0708e93be51392bd2288c773523892fbd616d33fd2fda59"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a4062cc7e8de26f1603323ef3ae2171c9d29c8a9f5e067d555a2813cd5c7a7e0"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34692850dfd64ba06af61e5791a441f664cb7d21e7b544e8f385718430e8f8e4"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678dd95f26a67e02c50dcb5bf250f95231d455642afbc65a3b0bcdacd4e4dd38"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f2ce7b0b295fe64ac0a85aef46a0f2614995774bd7bc643b85679c0283287f9"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d346f4dc2221bfb7ab652d1e37d327578434ce559baf7113b0f55768437fe6a0"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-win32.whl", hash = "sha256:a51eeaf52ba3afd70bf489be20e52fdfafe6c03d652b02477c6ce23c995222f4"},
|
||||
{file = "fonttools-4.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:8639be40d583e5d9da67795aa3eeeda0488fb577a1d42ae11a5036f18fb16d93"},
|
||||
{file = "fonttools-4.50.0-py3-none-any.whl", hash = "sha256:48fa36da06247aa8282766cfd63efff1bb24e55f020f29a335939ed3844d20d3"},
|
||||
{file = "fonttools-4.50.0.tar.gz", hash = "sha256:fa5cf61058c7dbb104c2ac4e782bf1b2016a8cf2f69de6e4dd6a865d2c969bb5"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
|
||||
graphite = ["lz4 (>=1.7.4.2)"]
|
||||
interpolatable = ["munkres", "pycairo", "scipy"]
|
||||
lxml = ["lxml (>=4.0)"]
|
||||
pathops = ["skia-pathops (>=0.5.0)"]
|
||||
plot = ["matplotlib"]
|
||||
repacker = ["uharfbuzz (>=0.23.0)"]
|
||||
symfont = ["sympy"]
|
||||
type1 = ["xattr"]
|
||||
ufo = ["fs (>=2.2.0,<3)"]
|
||||
unicode = ["unicodedata2 (>=15.1.0)"]
|
||||
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "fqdn"
|
||||
version = "1.5.1"
|
||||
@ -1952,6 +2133,17 @@ MarkupSafe = ">=2.0"
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.3.2"
|
||||
description = "Lightweight pipelining with Python functions"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"},
|
||||
{file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.9.14"
|
||||
@ -2374,6 +2566,119 @@ files = [
|
||||
{file = "jupyterlab_widgets-3.0.9.tar.gz", hash = "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.5"
|
||||
description = "A fast implementation of the Cassowary constraint solver"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"},
|
||||
{file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "latexcodec"
|
||||
version = "2.0.1"
|
||||
@ -2549,6 +2854,54 @@ files = [
|
||||
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.8.3"
|
||||
description = "Python plotting package"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"},
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"},
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"},
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f"},
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635"},
|
||||
{file = "matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0"},
|
||||
{file = "matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7"},
|
||||
{file = "matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c4af3f7317f8a1009bbb2d0bf23dfaba859eb7dd4ccbd604eba146dccaaaf0a4"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c6e00a65d017d26009bac6808f637b75ceade3e1ff91a138576f6b3065eeeba"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b49ab49a3bea17802df6872f8d44f664ba8f9be0632a60c99b20b6db2165b7"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6728dde0a3997396b053602dbd907a9bd64ec7d5cf99e728b404083698d3ca01"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:813925d08fb86aba139f2d31864928d67511f64e5945ca909ad5bc09a96189bb"},
|
||||
{file = "matplotlib-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:cd3a0c2be76f4e7be03d34a14d49ded6acf22ef61f88da600a18a5cd8b3c5f3c"},
|
||||
{file = "matplotlib-3.8.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fa93695d5c08544f4a0dfd0965f378e7afc410d8672816aff1e81be1f45dbf2e"},
|
||||
{file = "matplotlib-3.8.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9764df0e8778f06414b9d281a75235c1e85071f64bb5d71564b97c1306a2afc"},
|
||||
{file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"},
|
||||
{file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
contourpy = ">=1.0.1"
|
||||
cycler = ">=0.10"
|
||||
fonttools = ">=4.22.0"
|
||||
kiwisolver = ">=1.3.1"
|
||||
numpy = ">=1.21,<2"
|
||||
packaging = ">=20.0"
|
||||
pillow = ">=8"
|
||||
pyparsing = ">=2.3.1"
|
||||
python-dateutil = ">=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.6"
|
||||
@ -3402,6 +3755,133 @@ files = [
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.1.0"
|
||||
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"},
|
||||
{file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""}
|
||||
python-dateutil = ">=2.8.2"
|
||||
pytz = ">=2020.1"
|
||||
tzdata = ">=2022.1"
|
||||
|
||||
[package.extras]
|
||||
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
|
||||
aws = ["s3fs (>=2022.05.0)"]
|
||||
clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
|
||||
compression = ["zstandard (>=0.17.0)"]
|
||||
computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
|
||||
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
|
||||
feather = ["pyarrow (>=7.0.0)"]
|
||||
fss = ["fsspec (>=2022.05.0)"]
|
||||
gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
|
||||
hdf5 = ["tables (>=3.7.0)"]
|
||||
html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
|
||||
mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
|
||||
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
|
||||
parquet = ["pyarrow (>=7.0.0)"]
|
||||
performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
|
||||
plot = ["matplotlib (>=3.6.1)"]
|
||||
postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
|
||||
spss = ["pyreadstat (>=1.1.5)"]
|
||||
sql-other = ["SQLAlchemy (>=1.4.36)"]
|
||||
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
|
||||
xml = ["lxml (>=4.8.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.2.1"
|
||||
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"},
|
||||
{file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"},
|
||||
{file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"},
|
||||
{file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"},
|
||||
{file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"},
|
||||
{file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}
|
||||
python-dateutil = ">=2.8.2"
|
||||
pytz = ">=2020.1"
|
||||
tzdata = ">=2022.7"
|
||||
|
||||
[package.extras]
|
||||
all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
|
||||
aws = ["s3fs (>=2022.11.0)"]
|
||||
clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
|
||||
compression = ["zstandard (>=0.19.0)"]
|
||||
computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
|
||||
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
|
||||
feather = ["pyarrow (>=10.0.1)"]
|
||||
fss = ["fsspec (>=2022.11.0)"]
|
||||
gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
|
||||
hdf5 = ["tables (>=3.8.0)"]
|
||||
html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
|
||||
mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
|
||||
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
|
||||
parquet = ["pyarrow (>=10.0.1)"]
|
||||
performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
|
||||
plot = ["matplotlib (>=3.6.3)"]
|
||||
postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
|
||||
pyarrow = ["pyarrow (>=10.0.1)"]
|
||||
spss = ["pyreadstat (>=1.2.0)"]
|
||||
sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
|
||||
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
|
||||
xml = ["lxml (>=4.9.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "pandocfilters"
|
||||
version = "1.5.0"
|
||||
@ -3460,6 +3940,24 @@ files = [
|
||||
{file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "patsy"
|
||||
version = "0.5.6"
|
||||
description = "A Python package for describing statistical models and for building design matrices."
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "patsy-0.5.6-py2.py3-none-any.whl", hash = "sha256:19056886fd8fa71863fa32f0eb090267f21fb74be00f19f5c70b2e9d76c883c6"},
|
||||
{file = "patsy-0.5.6.tar.gz", hash = "sha256:95c6d47a7222535f84bff7f63d7303f2e297747a598db89cf5c67f0c0c7d2cdb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.4"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "pytest-cov", "scipy"]
|
||||
|
||||
[[package]]
|
||||
name = "pettingzoo"
|
||||
version = "1.24.2"
|
||||
@ -3503,7 +4001,7 @@ ptyprocess = ">=0.5"
|
||||
name = "pillow"
|
||||
version = "10.2.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
|
||||
@ -3709,6 +4207,17 @@ files = [
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
name = "property-cached"
|
||||
version = "1.6.4"
|
||||
description = "A decorator for caching properties in classes (forked from cached-property)."
|
||||
optional = true
|
||||
python-versions = ">= 3.5"
|
||||
files = [
|
||||
{file = "property-cached-1.6.4.zip", hash = "sha256:3e9c4ef1ed3653909147510481d7df62a3cfb483461a6986a6f1dcd09b2ebb73"},
|
||||
{file = "property_cached-1.6.4-py2.py3-none-any.whl", hash = "sha256:135fc059ec969c1646424a0db15e7fbe1b5f8c36c0006d0b3c91ba568c11e7d8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "3.20.3"
|
||||
@ -4094,6 +4603,20 @@ files = [
|
||||
{file = "PyOpenGL-3.1.7.tar.gz", hash = "sha256:eef31a3888e6984fd4d8e6c9961b184c9813ca82604d37fe3da80eb000a76c86"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.1.2"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
files = [
|
||||
{file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
|
||||
{file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.3"
|
||||
@ -4157,6 +4680,17 @@ files = [
|
||||
{file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
|
||||
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "306"
|
||||
@ -4536,6 +5070,24 @@ files = [
|
||||
{file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rliable"
|
||||
version = "1.0.8"
|
||||
description = "rliable: Reliable evaluation on reinforcement learning and machine learning benchmarks."
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "rliable-1.0.8-py3-none-any.whl", hash = "sha256:0a868fada926d0fa410f368d2a01ade811ae11aa8d7e82a9f80de29b5a634fc3"},
|
||||
{file = "rliable-1.0.8.tar.gz", hash = "sha256:662ee1cd9a98c39340b4cf0c744bce5141171cf18d60f25811283d1f435d3852"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
absl-py = ">=0.9.0"
|
||||
arch = "5.3.0"
|
||||
numpy = ">=1.16.4"
|
||||
scipy = ">=1.7.0"
|
||||
seaborn = ">=0.11.2"
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "0.13.0"
|
||||
@ -4803,6 +5355,27 @@ dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyl
|
||||
doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"]
|
||||
test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
|
||||
|
||||
[[package]]
|
||||
name = "seaborn"
|
||||
version = "0.13.2"
|
||||
description = "Statistical data visualization"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987"},
|
||||
{file = "seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
matplotlib = ">=3.4,<3.6.1 || >3.6.1"
|
||||
numpy = ">=1.20,<1.24.0 || >1.24.0"
|
||||
pandas = ">=1.2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest-cov", "pytest-xdist"]
|
||||
docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"]
|
||||
stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "send2trash"
|
||||
version = "1.8.2"
|
||||
@ -5630,6 +6203,106 @@ pure-eval = "*"
|
||||
[package.extras]
|
||||
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "statsmodels"
|
||||
version = "0.14.0"
|
||||
description = "Statistical computations and models for Python"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16bfe0c96a53b20fa19067e3b6bd2f1d39e30d4891ea0d7bc20734a0ae95942d"},
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a6a0a1a06ff79be8aa89c8494b33903442859add133f0dda1daf37c3c71682e"},
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77b3cd3a5268ef966a0a08582c591bd29c09c88b4566c892a7c087935234f285"},
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c64ebe9cf376cba0c31aed138e15ed179a1d128612dd241cdf299d159e5e882"},
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:229b2f676b4a45cb62d132a105c9c06ca8a09ffba060abe34935391eb5d9ba87"},
|
||||
{file = "statsmodels-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb471f757fc45102a87e5d86e87dc2c8c78b34ad4f203679a46520f1d863b9da"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:582f9e41092e342aaa04920d17cc3f97240e3ee198672f194719b5a3d08657d6"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ebe885ccaa64b4bc5ad49ac781c246e7a594b491f08ab4cfd5aa456c363a6f6"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b587ee5d23369a0e881da6e37f78371dce4238cf7638a455db4b633a1a1c62d6"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef7fa4813c7a73b0d8a0c830250f021c102c71c95e9fe0d6877bcfb56d38b8c"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afe80544ef46730ea1b11cc655da27038bbaa7159dc5af4bc35bbc32982262f2"},
|
||||
{file = "statsmodels-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:a6ad7b8aadccd4e4dd7f315a07bef1bca41d194eeaf4ec600d20dea02d242fce"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0eea4a0b761aebf0c355b726ac5616b9a8b618bd6e81a96b9f998a61f4fd7484"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c815ce7a699047727c65a7c179bff4031cff9ae90c78ca730cfd5200eb025dd"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:575f61337c8e406ae5fa074d34bc6eb77b5a57c544b2d4ee9bc3da6a0a084cf1"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8be53cdeb82f49c4cb0fda6d7eeeb2d67dbd50179b3e1033510e061863720d93"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6f7d762df4e04d1dde8127d07e91aff230eae643aa7078543e60e83e7d5b40db"},
|
||||
{file = "statsmodels-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc2c7931008a911e3060c77ea8933f63f7367c0f3af04f82db3a04808ad2cd2c"},
|
||||
{file = "statsmodels-0.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3757542c95247e4ab025291a740efa5da91dc11a05990c033d40fce31c450dc9"},
|
||||
{file = "statsmodels-0.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:de489e3ed315bdba55c9d1554a2e89faa65d212e365ab81bc323fa52681fc60e"},
|
||||
{file = "statsmodels-0.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e290f4718177bffa8823a780f3b882d56dd64ad1c18cfb4bc8b5558f3f5757"},
|
||||
{file = "statsmodels-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71054f9dbcead56def14e3c9db6f66f943110fdfb19713caf0eb0f08c1ec03fd"},
|
||||
{file = "statsmodels-0.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:d7fda067837df94e0a614d93d3a38fb6868958d37f7f50afe2a534524f2660cb"},
|
||||
{file = "statsmodels-0.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c7724ad573af26139a98393ae64bc318d1b19762b13442d96c7a3e793f495c3"},
|
||||
{file = "statsmodels-0.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b0a135f3bfdeec987e36e3b3b4c53e0bb87a8d91464d2fcc4d169d176f46fdb"},
|
||||
{file = "statsmodels-0.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce28eb1c397dba437ec39b9ab18f2101806f388c7a0cf9cdfd8f09294ad1c799"},
|
||||
{file = "statsmodels-0.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b1c768dd94cc5ba8398121a632b673c625491aa7ed627b82cb4c880a25563f"},
|
||||
{file = "statsmodels-0.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d1e3e10dfbfcd58119ba5a4d3c7d519182b970a2aebaf0b6f539f55ae16058d"},
|
||||
{file = "statsmodels-0.14.0.tar.gz", hash = "sha256:6875c7d689e966d948f15eb816ab5616f4928706b180cf470fd5907ab6f647a4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.18"
|
||||
packaging = ">=21.3"
|
||||
pandas = ">=1.0"
|
||||
patsy = ">=0.5.2"
|
||||
scipy = ">=1.4,<1.9.2 || >1.9.2"
|
||||
|
||||
[package.extras]
|
||||
build = ["cython (>=0.29.26)"]
|
||||
develop = ["colorama", "cython (>=0.29.26)", "cython (>=0.29.28,<3.0.0)", "flake8", "isort", "joblib", "matplotlib (>=3)", "oldest-supported-numpy (>=2022.4.18)", "pytest (>=7.0.1,<7.1.0)", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=7.0.0,<7.1.0)"]
|
||||
docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "statsmodels"
|
||||
version = "0.14.1"
|
||||
description = "Statistical computations and models for Python"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43af9c0b07c9d72f275cf14ea54a481a3f20911f0b443181be4769def258fdeb"},
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16975ab6ad505d837ba9aee11f92a8c5b49c4fa1ff45b60fe23780b19e5705e"},
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e278fe74da5ed5e06c11a30851eda1af08ef5af6be8507c2c45d2e08f7550dde"},
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0564d92cb05b219b4538ed09e77d96658a924a691255e1f7dd23ee338df441b"},
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5385e22e72159a09c099c4fb975f350a9f3afeb57c1efce273b89dcf1fe44c0f"},
|
||||
{file = "statsmodels-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:0a8aae75a2e08ebd990e5fa394f8e32738b55785cb70798449a3f4207085e667"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b69a63ad6c979a6e4cde11870ffa727c76a318c225a7e509f031fbbdfb4e416a"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7562cb18a90a114f39fab6f1c25b9c7b39d9cd5f433d0044b430ca9d44a8b52c"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3abaca4b963259a2bf349c7609cfbb0ce64ad5fb3d92d6f08e21453e4890248"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f727fe697f6406d5f677b67211abe5a55101896abdfacdb3f38410405f6ad8"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6838ac6bdb286daabb5e91af90fd4258f09d0cec9aace78cc441cb2b17df428"},
|
||||
{file = "statsmodels-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:709bfcef2dbe66f705b17e56d1021abad02243ee1a5d1efdb90f9bad8b06a329"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f32a7cd424cf33304a54daee39d32cccf1d0265e652c920adeaeedff6d576457"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8c30181c084173d662aaf0531867667be2ff1bee103b84feb64f149f792dbd2"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de2b97413913d52ad6342dece2d653e77f78620013b7705fad291d4e4266ccb"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3420f88289c593ba2bca33619023059c476674c160733bd7d858564787c83d3"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c008e16096f24f0514e53907890ccac6589a16ad6c81c218f2ee6752fdada555"},
|
||||
{file = "statsmodels-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc0351d279c4e080f0ce638a3d886d312aa29eade96042e3ba0a73771b1abdfb"},
|
||||
{file = "statsmodels-0.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf293ada63b2859d95210165ad1dfcd97bd7b994a5266d6fbeb23659d8f0bf68"},
|
||||
{file = "statsmodels-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44ca8cb88fa3d3a4ffaff1fb8eb0e98bbf83fc936fcd9b9eedee258ecc76696a"},
|
||||
{file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5373d176239993c095b00d06036690a50309a4e00c2da553b65b840f956ae6"},
|
||||
{file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532dfe899f8b6632cd8caa0b089b403415618f51e840d1817a1e4b97e200c73"},
|
||||
{file = "statsmodels-0.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:4fe0a60695952b82139ae8750952786a700292f9e0551d572d7685070944487b"},
|
||||
{file = "statsmodels-0.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04293890f153ffe577e60a227bd43babd5f6c1fc50ea56a3ab1862ae85247a95"},
|
||||
{file = "statsmodels-0.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e70a2e93d54d40b2cb6426072acbc04f35501b1ea2569f6786964adde6ca572"},
|
||||
{file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab3a73d16c0569adbba181ebb967e5baaa74935f6d2efe86ac6fc5857449b07d"},
|
||||
{file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eefa5bcff335440ee93e28745eab63559a20cd34eea0375c66d96b016de909b3"},
|
||||
{file = "statsmodels-0.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:bc43765710099ca6a942b5ffa1bac7668965052542ba793dd072d26c83453572"},
|
||||
{file = "statsmodels-0.14.1.tar.gz", hash = "sha256:2260efdc1ef89f39c670a0bd8151b1d0843567781bcafec6cda0534eb47a94f6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.18,<2"
|
||||
packaging = ">=21.3"
|
||||
pandas = ">=1.0,<2.1.0 || >2.1.0"
|
||||
patsy = ">=0.5.4"
|
||||
scipy = ">=1.4,<1.9.2 || >1.9.2"
|
||||
|
||||
[package.extras]
|
||||
build = ["cython (>=0.29.33)"]
|
||||
develop = ["colorama", "cython (>=0.29.33)", "cython (>=0.29.33,<4.0.0)", "flake8", "isort", "joblib", "matplotlib (>=3)", "oldest-supported-numpy (>=2022.4.18)", "pytest (>=7.3.0)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=8.0,<9.0)"]
|
||||
docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "swig"
|
||||
version = "4.2.0"
|
||||
@ -5946,6 +6619,17 @@ files = [
|
||||
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.1"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
files = [
|
||||
{file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
|
||||
{file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uc-micro-py"
|
||||
version = "1.0.2"
|
||||
@ -6219,6 +6903,7 @@ atari = ["ale-py", "autorom", "opencv_python", "shimmy"]
|
||||
box2d = ["box2d_py", "pygame", "swig"]
|
||||
classic-control = ["pygame"]
|
||||
envpool = ["envpool"]
|
||||
eval = ["joblib", "rliable"]
|
||||
mujoco = ["imageio", "mujoco"]
|
||||
mujoco-py = ["cython", "mujoco-py"]
|
||||
pybullet = ["pybullet"]
|
||||
@ -6228,4 +6913,4 @@ vizdoom = ["vizdoom"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "0d4ff98ed02fe3f34c0d05b5d175822a82ac08a5ed52e57b7f847a48c302add6"
|
||||
content-hash = "bb0c689d4db6fe4de11d6bd55fe9fd4425739ef9a603ec5bd57f2c48e0ada9c7"
|
||||
|
@ -27,10 +27,12 @@ exclude = ["test/*", "examples/*", "docs/*"]
|
||||
python = "^3.11"
|
||||
gymnasium = "^0.28.0"
|
||||
h5py = "^3.9.0"
|
||||
matplotlib = ">=3.0.0"
|
||||
numba = "^0.57.1"
|
||||
numpy = "^1"
|
||||
overrides = "^7.4.0"
|
||||
packaging = "*"
|
||||
pandas = ">=2.0.0"
|
||||
pettingzoo = "^1.22"
|
||||
tensorboard = "^2.5.0"
|
||||
# Torch 2.0.1 causes problems, see https://github.com/pytorch/pytorch/issues/100974
|
||||
@ -56,12 +58,14 @@ docstring-parser = { version = "^0.15", optional = true }
|
||||
envpool = { version = "^0.8.2", optional = true, markers = "sys_platform != 'darwin'"}
|
||||
gymnasium-robotics = { version = "*", optional = true }
|
||||
imageio = { version = ">=2.14.1", optional = true }
|
||||
joblib = { version = "*", optional = true }
|
||||
jsonargparse = {version = "^4.24.1", optional = true}
|
||||
mujoco = { version = ">=2.1.5", optional = true }
|
||||
mujoco-py = { version = ">=2.1,<2.2", optional = true }
|
||||
opencv_python = { version = "*", optional = true }
|
||||
pybullet = { version = "*", optional = true }
|
||||
pygame = { version = ">=2.1.3", optional = true }
|
||||
rliable = { version = "*", optional = true }
|
||||
shimmy = { version = ">=0.1.0,<1.0", optional = true }
|
||||
swig = { version = "4.*", optional = true }
|
||||
vizdoom = { version = "*", optional = true }
|
||||
@ -76,6 +80,7 @@ pybullet = ["pybullet"]
|
||||
envpool = ["envpool"]
|
||||
robotics = ["gymnasium-robotics"]
|
||||
vizdoom = ["vizdoom"]
|
||||
eval = ["rliable", "joblib"]
|
||||
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
|
@ -2,11 +2,12 @@ from typing import Literal
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from tianshou.utils import BaseLogger
|
||||
from tianshou.utils import TensorboardLogger
|
||||
|
||||
|
||||
class TestBaseLogger:
|
||||
class TestTensorBoardLogger:
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
"input_dict, expected_output",
|
||||
@ -20,7 +21,8 @@ class TestBaseLogger:
|
||||
| dict[str, dict[str, dict[str, int]]],
|
||||
expected_output: dict[str, int],
|
||||
) -> None:
|
||||
result = BaseLogger.prepare_dict_for_logging(input_dict)
|
||||
logger = TensorboardLogger(SummaryWriter("log/logger"))
|
||||
result = logger.prepare_dict_for_logging(input_dict)
|
||||
assert result == expected_output
|
||||
|
||||
@staticmethod
|
||||
@ -36,7 +38,8 @@ class TestBaseLogger:
|
||||
delimiter: Literal["|", "."],
|
||||
expected_output: dict[str, int],
|
||||
) -> None:
|
||||
result = BaseLogger.prepare_dict_for_logging(input_dict, delimiter=delimiter)
|
||||
logger = TensorboardLogger(SummaryWriter("log/logger"))
|
||||
result = logger.prepare_dict_for_logging(input_dict, delimiter=delimiter)
|
||||
assert result == expected_output
|
||||
|
||||
@staticmethod
|
||||
@ -56,7 +59,8 @@ class TestBaseLogger:
|
||||
exclude_arrays: bool,
|
||||
expected_output: dict[str, np.ndarray],
|
||||
) -> None:
|
||||
result = BaseLogger.prepare_dict_for_logging(input_dict, exclude_arrays=exclude_arrays)
|
||||
logger = TensorboardLogger(SummaryWriter("log/logger"))
|
||||
result = logger.prepare_dict_for_logging(input_dict, exclude_arrays=exclude_arrays)
|
||||
assert result.keys() == expected_output.keys()
|
||||
for val1, val2 in zip(result.values(), expected_output.values(), strict=True):
|
||||
assert np.all(val1 == val2)
|
||||
@ -72,5 +76,6 @@ class TestBaseLogger:
|
||||
input_dict: dict[str, tuple[Literal[1]] | dict[str, str | dict[str, int]]],
|
||||
expected_output: dict[str, int],
|
||||
) -> None:
|
||||
result = BaseLogger.prepare_dict_for_logging(input_dict)
|
||||
logger = TensorboardLogger(SummaryWriter("log/logger"))
|
||||
result = logger.prepare_dict_for_logging(input_dict)
|
||||
assert result == expected_output
|
||||
|
@ -6,9 +6,19 @@ from tianshou.highlevel.env import (
|
||||
|
||||
class DiscreteTestEnvFactory(EnvFactoryRegistered):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(task="CartPole-v0", seed=42, venv_type=VectorEnvType.DUMMY)
|
||||
super().__init__(
|
||||
task="CartPole-v0",
|
||||
train_seed=42,
|
||||
test_seed=1337,
|
||||
venv_type=VectorEnvType.DUMMY,
|
||||
)
|
||||
|
||||
|
||||
class ContinuousTestEnvFactory(EnvFactoryRegistered):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(task="Pendulum-v1", seed=42, venv_type=VectorEnvType.DUMMY)
|
||||
super().__init__(
|
||||
task="Pendulum-v1",
|
||||
train_seed=42,
|
||||
test_seed=1337,
|
||||
venv_type=VectorEnvType.DUMMY,
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ def test_experiment_builder_continuous_default_params(builder_cls: type[Experime
|
||||
sampling_config=sampling_config,
|
||||
)
|
||||
experiment = builder.build()
|
||||
experiment.run("test")
|
||||
experiment.run(override_experiment_name="test")
|
||||
print(experiment)
|
||||
|
||||
|
||||
@ -77,5 +77,32 @@ def test_experiment_builder_discrete_default_params(builder_cls: type[Experiment
|
||||
sampling_config=sampling_config,
|
||||
)
|
||||
experiment = builder.build()
|
||||
experiment.run("test")
|
||||
experiment.run(override_experiment_name="test")
|
||||
print(experiment)
|
||||
|
||||
|
||||
def test_temp_builder_modification():
|
||||
env_factory = DiscreteTestEnvFactory()
|
||||
sampling_config = SamplingConfig(
|
||||
num_epochs=1,
|
||||
step_per_epoch=100,
|
||||
num_train_envs=2,
|
||||
num_test_envs=2,
|
||||
)
|
||||
builder = PPOExperimentBuilder(
|
||||
experiment_config=ExperimentConfig(persistence_enabled=False),
|
||||
env_factory=env_factory,
|
||||
sampling_config=sampling_config,
|
||||
)
|
||||
original_seed = builder.experiment_config.seed
|
||||
original_train_seed = builder.sampling_config.train_seed
|
||||
|
||||
with builder.temp_config_mutation():
|
||||
builder.experiment_config.seed += 12345
|
||||
builder.sampling_config.train_seed += 456
|
||||
exp = builder.build()
|
||||
|
||||
assert builder.experiment_config.seed == original_seed
|
||||
assert builder.sampling_config.train_seed == original_train_seed
|
||||
assert exp.config.seed == original_seed + 12345
|
||||
assert exp.sampling_config.train_seed == original_train_seed + 456
|
||||
|
@ -48,6 +48,9 @@ class SamplingConfig(ToStringMixin):
|
||||
num_train_envs: int = -1
|
||||
"""the number of training environments to use. If set to -1, use number of CPUs/threads."""
|
||||
|
||||
train_seed: int = 42
|
||||
"""the seed to use for the training environments."""
|
||||
|
||||
num_test_envs: int = 1
|
||||
"""the number of test environments to use"""
|
||||
|
||||
@ -122,6 +125,10 @@ class SamplingConfig(ToStringMixin):
|
||||
temporal aspects (e.g. velocities of moving objects for which only positions are observed).
|
||||
"""
|
||||
|
||||
@property
|
||||
def test_seed(self) -> int:
|
||||
return self.train_seed + self.num_train_envs + 1
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.num_train_envs == -1:
|
||||
self.num_train_envs = multiprocessing.cpu_count()
|
||||
|
@ -412,7 +412,8 @@ class EnvFactoryRegistered(EnvFactory):
|
||||
self,
|
||||
*,
|
||||
task: str,
|
||||
seed: int,
|
||||
train_seed: int,
|
||||
test_seed: int,
|
||||
venv_type: VectorEnvType,
|
||||
envpool_factory: EnvPoolFactory | None = None,
|
||||
render_mode_train: str | None = None,
|
||||
@ -434,7 +435,8 @@ class EnvFactoryRegistered(EnvFactory):
|
||||
super().__init__(venv_type)
|
||||
self.task = task
|
||||
self.envpool_factory = envpool_factory
|
||||
self.seed = seed
|
||||
self.train_seed = train_seed
|
||||
self.test_seed = test_seed
|
||||
self.render_modes = {
|
||||
EnvMode.TRAIN: render_mode_train,
|
||||
EnvMode.TEST: render_mode_test,
|
||||
@ -462,15 +464,16 @@ class EnvFactoryRegistered(EnvFactory):
|
||||
return gymnasium.make(self.task, **kwargs)
|
||||
|
||||
def create_venv(self, num_envs: int, mode: EnvMode) -> BaseVectorEnv:
|
||||
seed = self.train_seed if mode == EnvMode.TRAIN else self.test_seed
|
||||
if self.envpool_factory is not None:
|
||||
return self.envpool_factory.create_venv(
|
||||
self.task,
|
||||
num_envs,
|
||||
mode,
|
||||
self.seed,
|
||||
seed,
|
||||
self._create_kwargs(mode),
|
||||
)
|
||||
else:
|
||||
venv = super().create_venv(num_envs, mode)
|
||||
venv.seed(self.seed)
|
||||
venv.seed(seed)
|
||||
return venv
|
||||
|
181
tianshou/highlevel/evaluation.py
Normal file
181
tianshou/highlevel/evaluation.py
Normal file
@ -0,0 +1,181 @@
|
||||
import os
|
||||
from dataclasses import asdict, dataclass
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import scipy.stats as sst
|
||||
from rliable import library as rly
|
||||
from rliable import plot_utils
|
||||
|
||||
from tianshou.highlevel.experiment import Experiment
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoggedSummaryData:
|
||||
mean: np.ndarray
|
||||
std: np.ndarray
|
||||
max: np.ndarray
|
||||
min: np.ndarray
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoggedCollectStats:
|
||||
env_step: np.ndarray
|
||||
n_collected_episodes: np.ndarray
|
||||
n_collected_steps: np.ndarray
|
||||
collect_time: np.ndarray
|
||||
collect_speed: np.ndarray
|
||||
returns_stat: LoggedSummaryData
|
||||
lens_stat: LoggedSummaryData
|
||||
|
||||
@classmethod
|
||||
def from_data_dict(cls, data: dict) -> "LoggedCollectStats":
|
||||
return cls(
|
||||
env_step=np.array(data["env_step"]),
|
||||
n_collected_episodes=np.array(data["n_collected_episodes"]),
|
||||
n_collected_steps=np.array(data["n_collected_steps"]),
|
||||
collect_time=np.array(data["collect_time"]),
|
||||
collect_speed=np.array(data["collect_speed"]),
|
||||
returns_stat=LoggedSummaryData(**data["returns_stat"]),
|
||||
lens_stat=LoggedSummaryData(**data["lens_stat"]),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RLiableExperimentResult:
|
||||
"""The result of an experiment that can be used with the rliable library."""
|
||||
|
||||
exp_dir: str
|
||||
"""The base directory where each sub-directory contains the results of one experiment run."""
|
||||
|
||||
test_episode_returns_RE: np.ndarray
|
||||
"""The test episodes for each run of the experiment where each row corresponds to one run."""
|
||||
|
||||
env_steps_E: np.ndarray
|
||||
"""The number of environment steps at which the test episodes were evaluated."""
|
||||
|
||||
@classmethod
|
||||
def load_from_disk(cls, exp_dir: str) -> "RLiableExperimentResult":
|
||||
"""Load the experiment result from disk.
|
||||
|
||||
:param exp_dir: The directory from where the experiment results are restored.
|
||||
"""
|
||||
test_episode_returns = []
|
||||
env_step_at_test = None
|
||||
|
||||
for entry in os.scandir(exp_dir):
|
||||
if entry.name.startswith(".") or not entry.is_dir():
|
||||
continue
|
||||
|
||||
exp = Experiment.from_directory(entry.path)
|
||||
logger = exp.logger_factory.create_logger(
|
||||
entry.path,
|
||||
entry.name,
|
||||
None,
|
||||
asdict(exp.config),
|
||||
)
|
||||
data = logger.restore_logged_data(entry.path)
|
||||
|
||||
if "test" not in data or not data["test"]:
|
||||
continue
|
||||
test_data = LoggedCollectStats.from_data_dict(data["test"])
|
||||
|
||||
test_episode_returns.append(test_data.returns_stat.mean)
|
||||
env_step_at_test = test_data.env_step
|
||||
|
||||
if not test_episode_returns or env_step_at_test is None:
|
||||
raise ValueError(f"No experiment data found in {exp_dir}.")
|
||||
|
||||
return cls(
|
||||
test_episode_returns_RE=np.array(test_episode_returns),
|
||||
env_steps_E=np.array(env_step_at_test),
|
||||
exp_dir=exp_dir,
|
||||
)
|
||||
|
||||
def _get_rliable_data(
|
||||
self,
|
||||
algo_name: str | None = None,
|
||||
score_thresholds: np.ndarray | None = None,
|
||||
) -> tuple[dict, np.ndarray, np.ndarray]:
|
||||
"""Return the data in the format expected by the rliable library.
|
||||
|
||||
:param algo_name: The name of the algorithm to be shown in the figure legend. If None, the name of the algorithm
|
||||
is set to the experiment dir.
|
||||
:param score_thresholds: The score thresholds for the performance profile. If None, the thresholds are inferred
|
||||
from the minimum and maximum test episode returns.
|
||||
|
||||
:return: A tuple score_dict, env_steps, and score_thresholds.
|
||||
"""
|
||||
if score_thresholds is None:
|
||||
score_thresholds = np.linspace(
|
||||
np.min(self.test_episode_returns_RE),
|
||||
np.max(self.test_episode_returns_RE),
|
||||
101,
|
||||
)
|
||||
|
||||
if algo_name is None:
|
||||
algo_name = os.path.basename(self.exp_dir)
|
||||
|
||||
score_dict = {algo_name: self.test_episode_returns_RE}
|
||||
|
||||
return score_dict, self.env_steps_E, score_thresholds
|
||||
|
||||
def eval_results(
|
||||
self,
|
||||
algo_name: str | None = None,
|
||||
score_thresholds: np.ndarray | None = None,
|
||||
save_figure: bool = False,
|
||||
) -> tuple[plt.Figure, plt.Axes, plt.Figure, plt.Axes]:
|
||||
"""Evaluate the results of an experiment and create a sample efficiency curve and a performance profile.
|
||||
|
||||
:param algo_name: The name of the algorithm to be shown in the figure legend. If None, the name of the algorithm
|
||||
is set to the experiment dir.
|
||||
:param score_thresholds: The score thresholds for the performance profile. If None, the thresholds are inferred
|
||||
from the minimum and maximum test episode returns.
|
||||
:param save_figure: If True, the figures are saved to the experiment directory.
|
||||
|
||||
:return: The created figures and axes.
|
||||
"""
|
||||
score_dict, env_steps, score_thresholds = self._get_rliable_data(
|
||||
algo_name,
|
||||
score_thresholds,
|
||||
)
|
||||
|
||||
iqm = lambda scores: sst.trim_mean(scores, proportiontocut=0.25, axis=0)
|
||||
iqm_scores, iqm_cis = rly.get_interval_estimates(score_dict, iqm)
|
||||
|
||||
# Plot IQM sample efficiency curve
|
||||
fig1, ax1 = plt.subplots(ncols=1, figsize=(7, 5))
|
||||
plot_utils.plot_sample_efficiency_curve(
|
||||
env_steps,
|
||||
iqm_scores,
|
||||
iqm_cis,
|
||||
algorithms=None,
|
||||
xlabel=r"Number of env steps",
|
||||
ylabel="IQM episode return",
|
||||
ax=ax1,
|
||||
)
|
||||
|
||||
if save_figure:
|
||||
plt.savefig(os.path.join(self.exp_dir, "iqm_sample_efficiency_curve.png"))
|
||||
|
||||
final_score_dict = {algo: returns[:, [-1]] for algo, returns in score_dict.items()}
|
||||
score_distributions, score_distributions_cis = rly.create_performance_profile(
|
||||
final_score_dict,
|
||||
score_thresholds,
|
||||
)
|
||||
|
||||
# Plot score distributions
|
||||
fig2, ax2 = plt.subplots(ncols=1, figsize=(7, 5))
|
||||
plot_utils.plot_performance_profiles(
|
||||
score_distributions,
|
||||
score_thresholds,
|
||||
performance_profile_cis=score_distributions_cis,
|
||||
xlabel=r"Episode return $(\tau)$",
|
||||
ax=ax2,
|
||||
)
|
||||
|
||||
if save_figure:
|
||||
plt.savefig(os.path.join(self.exp_dir, "performance_profile.png"))
|
||||
|
||||
return fig1, ax1, fig2, ax2
|
@ -1,10 +1,12 @@
|
||||
import os
|
||||
import pickle
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Iterator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from copy import copy
|
||||
from dataclasses import dataclass
|
||||
from pprint import pformat
|
||||
from typing import Self
|
||||
from typing import Literal, Self
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
@ -144,6 +146,7 @@ class Experiment(ToStringMixin):
|
||||
agent_factory: AgentFactory,
|
||||
sampling_config: SamplingConfig,
|
||||
logger_factory: LoggerFactory | None = None,
|
||||
name: str | Literal["DATETIME_TAG"] = "DATETIME_TAG",
|
||||
):
|
||||
if logger_factory is None:
|
||||
logger_factory = LoggerFactoryDefault()
|
||||
@ -152,6 +155,22 @@ class Experiment(ToStringMixin):
|
||||
self.env_factory = env_factory
|
||||
self.agent_factory = agent_factory
|
||||
self.logger_factory = logger_factory
|
||||
if name == "DATETIME_TAG":
|
||||
name = datetime_tag()
|
||||
self.name = name
|
||||
|
||||
def get_seeding_info_as_str(self) -> str:
|
||||
"""Useful for creating unique experiment names based on seeds.
|
||||
|
||||
A typical example is to do `experiment.name = f"{experiment.name}_{experiment.get_seeding_info_as_str()}"`.
|
||||
"""
|
||||
return ",".join(
|
||||
[
|
||||
f"exp_seed={self.config.seed}",
|
||||
f"train_seed={self.sampling_config.train_seed}",
|
||||
f"test_seed={self.sampling_config.test_seed}",
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_directory(cls, directory: str, restore_policy: bool = True) -> "Experiment":
|
||||
@ -186,35 +205,42 @@ class Experiment(ToStringMixin):
|
||||
|
||||
def run(
|
||||
self,
|
||||
experiment_name: str | None = None,
|
||||
override_experiment_name: str | Literal["DATETIME_TAG"] | None = None,
|
||||
logger_run_id: str | None = None,
|
||||
raise_error_on_dirname_collision: bool = True,
|
||||
) -> ExperimentResult:
|
||||
"""Run the experiment and return the results.
|
||||
|
||||
:param experiment_name: the experiment name, which corresponds to the directory (within the logging
|
||||
:param override_experiment_name: if not None, will adjust the current instance's `name` name attribute.
|
||||
The name corresponds to the directory (within the logging
|
||||
directory) where all results associated with the experiment will be saved.
|
||||
The name may contain path separators (i.e. `os.path.sep`, as used by `os.path.join`), in which case
|
||||
a nested directory structure will be created.
|
||||
If None, use a name containing the current date and time.
|
||||
If "DATETIME_TAG" is passed, use a name containing the current date and time. This option
|
||||
is useful for preventing file-name collisions if a single experiment is executed repeatedly.
|
||||
:param logger_run_id: Run identifier to use for logger initialization/resumption (applies when
|
||||
using wandb, in particular).
|
||||
:param raise_error_on_dirname_collision: set to `False` e.g., when continuing a previously executed
|
||||
experiment with the same name.
|
||||
:return:
|
||||
"""
|
||||
if experiment_name is None:
|
||||
experiment_name = datetime_tag()
|
||||
if override_experiment_name is not None:
|
||||
if override_experiment_name == "DATETIME_TAG":
|
||||
override_experiment_name = datetime_tag()
|
||||
self.name = override_experiment_name
|
||||
|
||||
# initialize persistence directory
|
||||
use_persistence = self.config.persistence_enabled
|
||||
persistence_dir = os.path.join(self.config.persistence_base_dir, experiment_name)
|
||||
persistence_dir = os.path.join(self.config.persistence_base_dir, self.name)
|
||||
if use_persistence:
|
||||
os.makedirs(persistence_dir, exist_ok=True)
|
||||
os.makedirs(persistence_dir, exist_ok=not raise_error_on_dirname_collision)
|
||||
|
||||
with logging.FileLoggerContext(
|
||||
os.path.join(persistence_dir, self.LOG_FILENAME),
|
||||
enabled=use_persistence and self.config.log_file_enabled,
|
||||
):
|
||||
# log initial information
|
||||
log.info(f"Running experiment (name='{experiment_name}'):\n{self.pprints()}")
|
||||
log.info(f"Running experiment (name='{self.name}'):\n{self.pprints()}")
|
||||
log.info(f"Working directory: {os.getcwd()}")
|
||||
|
||||
self._set_seed()
|
||||
@ -245,7 +271,7 @@ class Experiment(ToStringMixin):
|
||||
if use_persistence:
|
||||
logger = self.logger_factory.create_logger(
|
||||
log_dir=persistence_dir,
|
||||
experiment_name=experiment_name,
|
||||
experiment_name=self.name,
|
||||
run_id=logger_run_id,
|
||||
config_dict=full_config,
|
||||
)
|
||||
@ -337,6 +363,32 @@ class ExperimentBuilder:
|
||||
self._optim_factory: OptimizerFactory | None = None
|
||||
self._policy_wrapper_factory: PolicyWrapperFactory | None = None
|
||||
self._trainer_callbacks: TrainerCallbacks = TrainerCallbacks()
|
||||
self._experiment_name: str = ""
|
||||
|
||||
@contextmanager
|
||||
def temp_config_mutation(self) -> Iterator[Self]:
|
||||
"""Returns the builder instance where the configs can be modified without affecting the current instance."""
|
||||
original_sampling_config = copy(self.sampling_config)
|
||||
original_experiment_config = copy(self.experiment_config)
|
||||
yield self
|
||||
self.sampling_config = original_sampling_config
|
||||
self.experiment_config = original_experiment_config
|
||||
|
||||
@property
|
||||
def experiment_config(self) -> ExperimentConfig:
|
||||
return self._config
|
||||
|
||||
@experiment_config.setter
|
||||
def experiment_config(self, experiment_config: ExperimentConfig) -> None:
|
||||
self._config = experiment_config
|
||||
|
||||
@property
|
||||
def sampling_config(self) -> SamplingConfig:
|
||||
return self._sampling_config
|
||||
|
||||
@sampling_config.setter
|
||||
def sampling_config(self, sampling_config: SamplingConfig) -> None:
|
||||
self._sampling_config = sampling_config
|
||||
|
||||
def with_logger_factory(self, logger_factory: LoggerFactory) -> Self:
|
||||
"""Allows to customize the logger factory to use.
|
||||
@ -414,6 +466,20 @@ class ExperimentBuilder:
|
||||
self._trainer_callbacks.epoch_stop_callback = callback
|
||||
return self
|
||||
|
||||
def with_experiment_name(
|
||||
self,
|
||||
experiment_name: str | Literal["DATETIME_TAG"] = "DATETIME_TAG",
|
||||
) -> Self:
|
||||
"""Sets the name of the experiment.
|
||||
|
||||
:param experiment_name: the name. If "DATETIME_TAG" (default) is given, the current date and time will be used.
|
||||
:return: the builder
|
||||
"""
|
||||
if experiment_name == "DATETIME_TAG":
|
||||
experiment_name = datetime_tag()
|
||||
self._experiment_name = experiment_name
|
||||
return self
|
||||
|
||||
@abstractmethod
|
||||
def _create_agent_factory(self) -> AgentFactory:
|
||||
pass
|
||||
@ -424,9 +490,12 @@ class ExperimentBuilder:
|
||||
else:
|
||||
return self._optim_factory
|
||||
|
||||
def build(self) -> Experiment:
|
||||
def build(self, add_seeding_info_to_name: bool = False) -> Experiment:
|
||||
"""Creates the experiment based on the options specified via this builder.
|
||||
|
||||
:param add_seeding_info_to_name: whether to add a postfix to the experiment name that contains
|
||||
info about the training seeds. Useful for creating multiple experiments that only differ
|
||||
by seeds.
|
||||
:return: the experiment
|
||||
"""
|
||||
agent_factory = self._create_agent_factory()
|
||||
@ -439,9 +508,27 @@ class ExperimentBuilder:
|
||||
agent_factory,
|
||||
self._sampling_config,
|
||||
self._logger_factory,
|
||||
name=self._experiment_name,
|
||||
)
|
||||
if add_seeding_info_to_name:
|
||||
experiment.name = f"{experiment.name}_{experiment.get_seeding_info_as_str()}"
|
||||
return experiment
|
||||
|
||||
def build_default_seeded_experiments(self, num_experiments: int) -> list[Experiment]:
|
||||
"""Creates a list of experiments with non-overlapping seeds, starting from the configured seed.
|
||||
|
||||
Each experiment will have a unique name that is created from the original experiment name and the seeds used.
|
||||
"""
|
||||
num_train_envs = self.sampling_config.num_train_envs
|
||||
|
||||
seeded_experiments = []
|
||||
for i in range(num_experiments):
|
||||
with self.temp_config_mutation():
|
||||
self.experiment_config.seed += i
|
||||
self.sampling_config.train_seed += i * num_train_envs
|
||||
seeded_experiments.append(self.build(add_seeding_info_to_name=True))
|
||||
return seeded_experiments
|
||||
|
||||
|
||||
class _BuilderMixinActorFactory(ActorFutureProviderProtocol):
|
||||
def __init__(self, continuous_actor_type: ContinuousActorType):
|
||||
|
@ -5,6 +5,7 @@ from typing import Literal, TypeAlias
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from tianshou.utils import BaseLogger, TensorboardLogger, WandbLogger
|
||||
from tianshou.utils.logger.pandas_logger import PandasLogger
|
||||
from tianshou.utils.string import ToStringMixin
|
||||
|
||||
TLogger: TypeAlias = BaseLogger
|
||||
@ -32,7 +33,7 @@ class LoggerFactory(ToStringMixin, ABC):
|
||||
class LoggerFactoryDefault(LoggerFactory):
|
||||
def __init__(
|
||||
self,
|
||||
logger_type: Literal["tensorboard", "wandb"] = "tensorboard",
|
||||
logger_type: Literal["tensorboard", "wandb", "pandas"] = "tensorboard",
|
||||
wandb_project: str | None = None,
|
||||
):
|
||||
if logger_type == "wandb" and wandb_project is None:
|
||||
@ -47,18 +48,21 @@ class LoggerFactoryDefault(LoggerFactory):
|
||||
run_id: str | None,
|
||||
config_dict: dict,
|
||||
) -> TLogger:
|
||||
writer = SummaryWriter(log_dir)
|
||||
writer.add_text(
|
||||
"args",
|
||||
str(
|
||||
dict(
|
||||
log_dir=log_dir,
|
||||
logger_type=self.logger_type,
|
||||
wandb_project=self.wandb_project,
|
||||
if self.logger_type in ["wandb", "tensorboard"]:
|
||||
writer = SummaryWriter(log_dir)
|
||||
writer.add_text(
|
||||
"args",
|
||||
str(
|
||||
dict(
|
||||
log_dir=log_dir,
|
||||
logger_type=self.logger_type,
|
||||
wandb_project=self.wandb_project,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
match self.logger_type:
|
||||
case "pandas":
|
||||
return PandasLogger(log_dir, exclude_arrays=False)
|
||||
case "wandb":
|
||||
wandb_logger = WandbLogger(
|
||||
save_interval=1,
|
||||
|
@ -3,7 +3,6 @@ from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from numbers import Number
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
@ -37,12 +36,14 @@ class BaseLogger(ABC):
|
||||
test_interval: int = 1,
|
||||
update_interval: int = 1000,
|
||||
info_interval: int = 1,
|
||||
exclude_arrays: bool = True,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.train_interval = train_interval
|
||||
self.test_interval = test_interval
|
||||
self.update_interval = update_interval
|
||||
self.info_interval = info_interval
|
||||
self.exclude_arrays = exclude_arrays
|
||||
self.last_log_train_step = -1
|
||||
self.last_log_test_step = -1
|
||||
self.last_log_update_step = -1
|
||||
@ -57,46 +58,15 @@ class BaseLogger(ABC):
|
||||
:param data: the data to write with format ``{key: value}``.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def prepare_dict_for_logging(
|
||||
input_dict: dict[str, Any],
|
||||
parent_key: str = "",
|
||||
delimiter: str = "/",
|
||||
exclude_arrays: bool = True,
|
||||
) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
"""Flattens and filters a nested dictionary by recursively traversing all levels and compressing the keys.
|
||||
@abstractmethod
|
||||
def prepare_dict_for_logging(self, log_data: dict) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
"""Prepare the dict for logging by filtering out invalid data types.
|
||||
|
||||
Filtering is performed with respect to valid logging data types.
|
||||
If necessary, reformulate the dict to be compatible with the writer.
|
||||
|
||||
:param input_dict: The nested dictionary to be flattened and filtered.
|
||||
:param parent_key: The parent key used as a prefix before the input_dict keys.
|
||||
:param delimiter: The delimiter used to separate the keys.
|
||||
:param exclude_arrays: Whether to exclude numpy arrays from the output.
|
||||
:return: A flattened dictionary where the keys are compressed and values are filtered.
|
||||
:param log_data: the dict to be prepared for logging.
|
||||
:return: the prepared dict.
|
||||
"""
|
||||
result = {}
|
||||
|
||||
def add_to_result(
|
||||
cur_dict: dict,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
for key, value in cur_dict.items():
|
||||
if exclude_arrays and isinstance(value, np.ndarray):
|
||||
continue
|
||||
|
||||
new_key = prefix + delimiter + key
|
||||
new_key = new_key.lstrip(delimiter)
|
||||
|
||||
if isinstance(value, dict):
|
||||
add_to_result(
|
||||
value,
|
||||
new_key,
|
||||
)
|
||||
elif isinstance(value, VALID_LOG_VALS):
|
||||
result[new_key] = value
|
||||
|
||||
add_to_result(input_dict, prefix=parent_key)
|
||||
return result
|
||||
|
||||
def log_train_data(self, log_data: dict, step: int) -> None:
|
||||
"""Use writer to log statistics generated during training.
|
||||
@ -106,8 +76,8 @@ class BaseLogger(ABC):
|
||||
"""
|
||||
# TODO: move interval check to calling method
|
||||
if step - self.last_log_train_step >= self.train_interval:
|
||||
log_data = self.prepare_dict_for_logging(log_data, parent_key=DataScope.TRAIN.value)
|
||||
self.write("train/env_step", step, log_data)
|
||||
log_data = self.prepare_dict_for_logging(log_data)
|
||||
self.write(f"{DataScope.TRAIN.value}/env_step", step, log_data)
|
||||
self.last_log_train_step = step
|
||||
|
||||
def log_test_data(self, log_data: dict, step: int) -> None:
|
||||
@ -118,8 +88,8 @@ class BaseLogger(ABC):
|
||||
"""
|
||||
# TODO: move interval check to calling method (stupid because log_test_data is only called from function in utils.py, not from BaseTrainer)
|
||||
if step - self.last_log_test_step >= self.test_interval:
|
||||
log_data = self.prepare_dict_for_logging(log_data, parent_key=DataScope.TEST.value)
|
||||
self.write(DataScope.TEST.value + "/env_step", step, log_data)
|
||||
log_data = self.prepare_dict_for_logging(log_data)
|
||||
self.write(f"{DataScope.TEST.value}/env_step", step, log_data)
|
||||
self.last_log_test_step = step
|
||||
|
||||
def log_update_data(self, log_data: dict, step: int) -> None:
|
||||
@ -130,8 +100,8 @@ class BaseLogger(ABC):
|
||||
"""
|
||||
# TODO: move interval check to calling method
|
||||
if step - self.last_log_update_step >= self.update_interval:
|
||||
log_data = self.prepare_dict_for_logging(log_data, parent_key=DataScope.UPDATE.value)
|
||||
self.write(DataScope.UPDATE.value + "/gradient_step", step, log_data)
|
||||
log_data = self.prepare_dict_for_logging(log_data)
|
||||
self.write(f"{DataScope.UPDATE.value}/gradient_step", step, log_data)
|
||||
self.last_log_update_step = step
|
||||
|
||||
def log_info_data(self, log_data: dict, step: int) -> None:
|
||||
@ -143,8 +113,8 @@ class BaseLogger(ABC):
|
||||
if (
|
||||
step - self.last_log_info_step >= self.info_interval
|
||||
): # TODO: move interval check to calling method
|
||||
log_data = self.prepare_dict_for_logging(log_data, parent_key=DataScope.INFO.value)
|
||||
self.write(DataScope.INFO.value + "/epoch", step, log_data)
|
||||
log_data = self.prepare_dict_for_logging(log_data)
|
||||
self.write(f"{DataScope.INFO.value}/epoch", step, log_data)
|
||||
self.last_log_info_step = step
|
||||
|
||||
@abstractmethod
|
||||
@ -166,7 +136,7 @@ class BaseLogger(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def restore_data(self) -> tuple[int, int, int]:
|
||||
"""Return the metadata from existing log.
|
||||
"""Restore internal data if present and return the metadata from existing log for continuation of training.
|
||||
|
||||
If it finds nothing or an error occurs during the recover process, it will
|
||||
return the default parameters.
|
||||
@ -174,6 +144,16 @@ class BaseLogger(ABC):
|
||||
:return: epoch, env_step, gradient_step.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def restore_logged_data(
|
||||
self,
|
||||
log_path: str,
|
||||
) -> dict[str, dict[str, VALID_LOG_VALS_TYPE | dict[str, VALID_LOG_VALS_TYPE]]]:
|
||||
"""Load the logged data from disk for post-processing.
|
||||
|
||||
:return: a dict containing the logged data.
|
||||
"""
|
||||
|
||||
|
||||
class LazyLogger(BaseLogger):
|
||||
"""A logger that does nothing. Used as the placeholder in trainer."""
|
||||
@ -181,6 +161,12 @@ class LazyLogger(BaseLogger):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
def prepare_dict_for_logging(
|
||||
self,
|
||||
data: dict[str, VALID_LOG_VALS_TYPE],
|
||||
) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
return data
|
||||
|
||||
def write(self, step_type: str, step: int, data: dict[str, VALID_LOG_VALS_TYPE]) -> None:
|
||||
"""The LazyLogger writes nothing."""
|
||||
|
||||
@ -195,3 +181,6 @@ class LazyLogger(BaseLogger):
|
||||
|
||||
def restore_data(self) -> tuple[int, int, int]:
|
||||
return 0, 0, 0
|
||||
|
||||
def restore_logged_data(self, log_path: str) -> dict:
|
||||
return {}
|
||||
|
125
tianshou/utils/logger/pandas_logger.py
Normal file
125
tianshou/utils/logger/pandas_logger.py
Normal file
@ -0,0 +1,125 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from tianshou.utils import BaseLogger, logging
|
||||
from tianshou.utils.logger.base import VALID_LOG_VALS, VALID_LOG_VALS_TYPE
|
||||
|
||||
|
||||
class PandasLogger(BaseLogger):
|
||||
def __init__(
|
||||
self,
|
||||
log_dir: str,
|
||||
train_interval: int = 1000,
|
||||
test_interval: int = 1,
|
||||
update_interval: int = 1000,
|
||||
info_interval: int = 1,
|
||||
exclude_arrays: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
train_interval,
|
||||
test_interval,
|
||||
update_interval,
|
||||
info_interval,
|
||||
exclude_arrays,
|
||||
)
|
||||
self.log_path = log_dir
|
||||
self.data: dict[str, list] = defaultdict(list)
|
||||
self.last_save_step = -1
|
||||
|
||||
def prepare_dict_for_logging(self, data: dict[str, Any]) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
"""Removes invalid data types from the log data."""
|
||||
filtered_dict = data.copy()
|
||||
|
||||
def filter_dict(data_dict: dict[str, Any]) -> None:
|
||||
"""Filter in place."""
|
||||
for key, value in data_dict.items():
|
||||
if isinstance(value, VALID_LOG_VALS):
|
||||
filter_dict(value)
|
||||
else:
|
||||
filtered_dict.pop(key)
|
||||
|
||||
filter_dict(data)
|
||||
return filtered_dict
|
||||
|
||||
def write(self, step_type: str, step: int, data: dict[str, Any]) -> None:
|
||||
scope, step_name = step_type.split("/")
|
||||
data[step_name] = step
|
||||
self.data[scope].append(data)
|
||||
|
||||
def save_data(
|
||||
self,
|
||||
epoch: int,
|
||||
env_step: int,
|
||||
gradient_step: int,
|
||||
save_checkpoint_fn: Callable[[int, int, int], str] | None = None,
|
||||
) -> None:
|
||||
self.last_save_step = epoch
|
||||
# create and dump a dataframe
|
||||
for k, v in self.data.items():
|
||||
df = pd.DataFrame(v)
|
||||
df.to_csv(os.path.join(self.log_path, k + "_log.csv"), index_label="index")
|
||||
df.to_pickle(os.path.join(self.log_path, k + "_log.pkl"))
|
||||
|
||||
def restore_data(self) -> tuple[int, int, int]:
|
||||
for scope in ["train", "test", "update", "info"]:
|
||||
try:
|
||||
self.data[scope].extend(
|
||||
list(
|
||||
pd.read_pickle(os.path.join(self.log_path, scope + "_log.pkl"))
|
||||
.T.to_dict()
|
||||
.values(),
|
||||
),
|
||||
)
|
||||
except FileNotFoundError:
|
||||
logging.warning(f"Failed to restore {scope} data")
|
||||
|
||||
try: # epoch / gradient_step
|
||||
epoch = self.data["info"][-1]["epoch"]
|
||||
self.last_save_step = self.last_log_test_step = epoch
|
||||
except (KeyError, IndexError):
|
||||
epoch = 0
|
||||
try:
|
||||
gradient_step = self.data["update"][-1]["gradient_step"]
|
||||
self.last_log_update_step = gradient_step
|
||||
except (KeyError, IndexError):
|
||||
gradient_step = 0
|
||||
try: # offline trainer doesn't have env_step
|
||||
env_step = self.data["train"][-1]["env_step"]
|
||||
self.last_log_train_step = env_step
|
||||
except (KeyError, IndexError):
|
||||
env_step = 0
|
||||
|
||||
return epoch, env_step, gradient_step
|
||||
|
||||
def restore_logged_data(
|
||||
self,
|
||||
log_path: str,
|
||||
) -> dict[str, dict[str, VALID_LOG_VALS_TYPE | dict[str, VALID_LOG_VALS_TYPE]]]:
|
||||
data = {}
|
||||
|
||||
def merge_dicts(list_of_dicts: list[dict]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = defaultdict(list)
|
||||
for d in list_of_dicts:
|
||||
for key, value in d.items():
|
||||
if isinstance(value, dict):
|
||||
result[key] = merge_dicts([result.get(key, {}), value])
|
||||
elif isinstance(value, list):
|
||||
result[key] = result.get(key, []) + value
|
||||
else:
|
||||
result[key].append(value)
|
||||
return result
|
||||
|
||||
for scope in ["train", "test", "update", "info"]:
|
||||
try:
|
||||
dict_list = list(
|
||||
pd.read_pickle(os.path.join(log_path, scope + "_log.pkl")).T.to_dict().values(),
|
||||
)
|
||||
data[scope] = merge_dicts(dict_list)
|
||||
except FileNotFoundError:
|
||||
logging.warning(f"Failed to restore {scope} data")
|
||||
data[scope] = {}
|
||||
return data
|
@ -1,10 +1,12 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from matplotlib.figure import Figure
|
||||
from tensorboard.backend.event_processing import event_accumulator
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from tianshou.utils.logger.base import VALID_LOG_VALS_TYPE, BaseLogger
|
||||
from tianshou.utils.logger.base import VALID_LOG_VALS, VALID_LOG_VALS_TYPE, BaseLogger
|
||||
from tianshou.utils.warning import deprecation
|
||||
|
||||
|
||||
@ -38,9 +40,58 @@ class TensorboardLogger(BaseLogger):
|
||||
self.last_save_step = -1
|
||||
self.writer = writer
|
||||
|
||||
def write(self, step_type: str, step: int, data: dict[str, VALID_LOG_VALS_TYPE]) -> None:
|
||||
def prepare_dict_for_logging(
|
||||
self,
|
||||
input_dict: dict[str, Any],
|
||||
parent_key: str = "",
|
||||
delimiter: str = "/",
|
||||
exclude_arrays: bool = True,
|
||||
) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
"""Flattens and filters a nested dictionary by recursively traversing all levels and compressing the keys.
|
||||
|
||||
Filtering is performed with respect to valid logging data types.
|
||||
|
||||
:param input_dict: The nested dictionary to be flattened and filtered.
|
||||
:param parent_key: The parent key used as a prefix before the input_dict keys.
|
||||
:param delimiter: The delimiter used to separate the keys.
|
||||
:param exclude_arrays: Whether to exclude numpy arrays from the output.
|
||||
:return: A flattened dictionary where the keys are compressed and values are filtered.
|
||||
"""
|
||||
result = {}
|
||||
|
||||
def add_to_result(
|
||||
cur_dict: dict,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
for key, value in cur_dict.items():
|
||||
if exclude_arrays and isinstance(value, np.ndarray):
|
||||
continue
|
||||
|
||||
new_key = prefix + delimiter + key
|
||||
new_key = new_key.lstrip(delimiter)
|
||||
|
||||
if isinstance(value, dict):
|
||||
add_to_result(
|
||||
value,
|
||||
new_key,
|
||||
)
|
||||
elif isinstance(value, VALID_LOG_VALS):
|
||||
result[new_key] = value
|
||||
|
||||
add_to_result(input_dict, prefix=parent_key)
|
||||
return result
|
||||
|
||||
def write(self, step_type: str, step: int, data: dict[str, Any]) -> None:
|
||||
scope, step_name = step_type.split("/")
|
||||
self.writer.add_scalar(step_type, step, global_step=step)
|
||||
for k, v in data.items():
|
||||
self.writer.add_scalar(k, v, global_step=step)
|
||||
scope_key = f"{scope}/{k}"
|
||||
if isinstance(v, np.ndarray):
|
||||
self.writer.add_histogram(scope_key, v, global_step=step, bins="auto")
|
||||
elif isinstance(v, Figure):
|
||||
self.writer.add_figure(scope_key, v, global_step=step)
|
||||
else:
|
||||
self.writer.add_scalar(scope_key, v, global_step=step)
|
||||
if self.write_flush: # issue 580
|
||||
self.writer.flush() # issue #482
|
||||
|
||||
@ -81,6 +132,55 @@ class TensorboardLogger(BaseLogger):
|
||||
|
||||
return epoch, env_step, gradient_step
|
||||
|
||||
def restore_logged_data(
|
||||
self,
|
||||
log_path: str,
|
||||
) -> dict[str, dict[str, VALID_LOG_VALS_TYPE | dict[str, VALID_LOG_VALS_TYPE]]]:
|
||||
ea = event_accumulator.EventAccumulator(log_path)
|
||||
ea.Reload()
|
||||
|
||||
def add_value_to_innermost_nested_dict(
|
||||
data_dict: dict[str, Any],
|
||||
key_string: str,
|
||||
value: Any,
|
||||
) -> None:
|
||||
"""A particular logic, walking through the keys in the
|
||||
key_string and adding the value to the data_dict in a nested manner,
|
||||
creating nested dictionaries on the fly if necessary, or updating existing ones.
|
||||
The value is added only to the innermost-nested dictionary.
|
||||
|
||||
|
||||
Example:
|
||||
-------
|
||||
>>> data_dict = {}
|
||||
>>> add_value_to_innermost_nested_dict(data_dict, "a/b/c", 1)
|
||||
>>> data_dict
|
||||
{"a": {"b": {"c": 1}}}
|
||||
"""
|
||||
keys = key_string.split("/")
|
||||
intermediate_keys = keys[:-1]
|
||||
last_key = keys[-1]
|
||||
|
||||
cur_nested_dict = data_dict
|
||||
for k in intermediate_keys:
|
||||
# on the right side, either the next nested dict is retrieved, or
|
||||
# a new one is created and set as the value of the current key
|
||||
# This nested dict is then reassigned to the current nested_dict
|
||||
# and used in the next iteration.
|
||||
cur_nested_dict = cur_nested_dict.setdefault(k, {})
|
||||
# this is the innermost nested dict, where the value is set directly
|
||||
cur_nested_dict[last_key] = value
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
for key_string in ea.scalars.Keys():
|
||||
add_value_to_innermost_nested_dict(
|
||||
data,
|
||||
key_string,
|
||||
np.array([s.value for s in ea.scalars.Items(key_string)]),
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class BasicLogger(TensorboardLogger):
|
||||
"""BasicLogger has changed its name to TensorboardLogger in #427.
|
||||
|
@ -82,6 +82,14 @@ class WandbLogger(BaseLogger):
|
||||
self.wandb_run._label(repo="tianshou") # type: ignore
|
||||
self.tensorboard_logger: TensorboardLogger | None = None
|
||||
|
||||
def prepare_dict_for_logging(self, log_data: dict) -> dict[str, VALID_LOG_VALS_TYPE]:
|
||||
if self.tensorboard_logger is None:
|
||||
raise Exception(
|
||||
"`logger` needs to load the Tensorboard Writer before "
|
||||
"preparing data for logging. Try `logger.load(SummaryWriter(log_path))`",
|
||||
)
|
||||
return self.tensorboard_logger.prepare_dict_for_logging(log_data)
|
||||
|
||||
def load(self, writer: SummaryWriter) -> None:
|
||||
self.writer = writer
|
||||
self.tensorboard_logger = TensorboardLogger(
|
||||
@ -156,3 +164,8 @@ class WandbLogger(BaseLogger):
|
||||
except KeyError:
|
||||
env_step = 0
|
||||
return epoch, env_step, gradient_step
|
||||
|
||||
def restore_logged_data(self, log_path: str) -> dict:
|
||||
# TODO: add support for restoring logged data from W&B
|
||||
assert self.tensorboard_logger is not None
|
||||
return self.tensorboard_logger.restore_logged_data(log_path)
|
||||
|
Loading…
x
Reference in New Issue
Block a user