diff --git a/FESurrogateModelTutorial/.agents/skills/harness-review/SKILL.md b/FESurrogateModelTutorial/.agents/skills/harness-review/SKILL.md new file mode 100644 index 0000000..4d3d784 --- /dev/null +++ b/FESurrogateModelTutorial/.agents/skills/harness-review/SKILL.md @@ -0,0 +1,42 @@ +--- +name: harness-review +description: "Use when reviewing this Harness repository: local changes, generated phase files, step outputs, implementation diffs, missing tests, build readiness, or compliance with AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and Harness acceptance criteria." +--- + +# Harness Review + +## Overview + +Use this skill to review Harness work against the repository's persistent rules, architecture docs, and executable verification requirements. Prioritize bugs, regressions, missing tests, and rule violations. + +## Review Process + +1. Read `/AGENTS.md`, `/docs/ARCHITECTURE.md`, and `/docs/ADR.md`. +2. Inspect the changed files with `git status --short` and `git diff`. +3. Check architecture, stack choices, tests, critical rules, and build readiness. +4. Run relevant verification commands when feasible. If a command cannot be run, report that as residual risk. +5. Lead with actionable findings. Keep summaries secondary. + +## Checklist + +| Item | Question | +| --- | --- | +| Architecture | Does the change follow `docs/ARCHITECTURE.md` directory and module boundaries? | +| Stack | Does the change stay within choices documented in `docs/ADR.md`? | +| Tests | Are new or changed behaviors covered by tests? | +| Critical Rules | Does the change violate any `AGENTS.md` CRITICAL rule? | +| Build | Do relevant build/test/lint commands pass? | + +## Output Format + +If there are findings, list them first in severity order with file and line references when possible. Then include this table: + +| 항목 | 결과 | 비고 | +| --- | --- | --- | +| 아키텍처 준수 | PASS/FAIL | {상세} | +| 기술 스택 준수 | PASS/FAIL | {상세} | +| 테스트 존재 | PASS/FAIL | {상세} | +| CRITICAL 규칙 | PASS/FAIL | {상세} | +| 빌드 가능 | PASS/FAIL | {상세} | + +When there are no findings, say that clearly, then mention any commands not run or remaining risk. diff --git a/FESurrogateModelTutorial/.agents/skills/harness-review/agents/openai.yaml b/FESurrogateModelTutorial/.agents/skills/harness-review/agents/openai.yaml new file mode 100644 index 0000000..9af296a --- /dev/null +++ b/FESurrogateModelTutorial/.agents/skills/harness-review/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Harness Review" + short_description: "Review Harness changes safely" + default_prompt: "Use $harness-review to review Harness repository changes." diff --git a/FESurrogateModelTutorial/.agents/skills/harness-workflow/SKILL.md b/FESurrogateModelTutorial/.agents/skills/harness-workflow/SKILL.md new file mode 100644 index 0000000..72519ba --- /dev/null +++ b/FESurrogateModelTutorial/.agents/skills/harness-workflow/SKILL.md @@ -0,0 +1,124 @@ +--- +name: harness-workflow +description: "Use when planning or running this Harness framework: reading AGENTS.md and docs/*.md, discussing implementation scope, creating or updating phases/index.json, phases/{task}/index.json, phases/{task}/stepN.md, or invoking scripts/execute.py for staged Codex execution." +--- + +# Harness Workflow + +## Overview + +Use this skill to turn a user-approved task into small, self-contained Harness steps that another Codex session can execute reliably. Keep the workflow grounded in repository docs and executable acceptance criteria. + +## Workflow + +1. Read `AGENTS.md` and relevant files under `docs/`, especially `docs/PRD.md`, `docs/ARCHITECTURE.md`, and `docs/ADR.md`. +2. Discuss unresolved product or technical decisions with the user before writing phase files. +3. When the user asks for an implementation plan, draft steps and get approval before creating files. +4. Create or update `phases/index.json`, `phases/{task-name}/index.json`, and one `phases/{task-name}/stepN.md` per step. +5. Run the phase with `python scripts/execute.py {task-name}` when asked to execute it. Use `--push` only when the user asks to push. + +## Step Design Rules + +- Scope each step to one layer or module. Split steps when multiple modules would otherwise change together. +- Make every step self-contained. Do not rely on prior conversation; include all required context and file paths. +- Force context gathering. Each step must tell Codex which docs and previous outputs to read before editing. +- Specify interfaces and signatures, not full implementations, unless exact code is required for a constraint. +- Put core invariants directly in the step: idempotency, security, data integrity, API contracts, or other non-negotiables. +- Use executable acceptance criteria such as `npm run build && npm test`, not abstract statements. +- Write cautions concretely: "Do not do X. Reason: Y." +- Name steps with kebab-case slugs such as `project-setup`, `api-layer`, or `auth-flow`. + +## Phase Files + +Create or update `phases/index.json`: + +```json +{ + "phases": [ + { + "dir": "0-mvp", + "status": "pending" + } + ] +} +``` + +Create `phases/{task-name}/index.json`: + +```json +{ + "project": "", + "phase": "", + "steps": [ + { "step": 0, "name": "project-setup", "status": "pending" }, + { "step": 1, "name": "core-types", "status": "pending" }, + { "step": 2, "name": "api-layer", "status": "pending" } + ] +} +``` + +Rules: + +- `project` comes from `AGENTS.md`. +- `phase` matches the task directory name. +- `steps[].step` starts at `0`. +- Initial status is always `pending`. +- Do not add timestamps when creating files. `scripts/execute.py` records `created_at`, `started_at`, `completed_at`, `failed_at`, and `blocked_at`. + +## Step Template + +```markdown +# Step {N}: {name} + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 설계 의도를 파악하라: + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- {previously created or modified files} + +이전 step에서 만들어진 코드를 꼼꼼히 읽고, 설계 의도를 이해한 뒤 작업하라. + +## 작업 + +{Concrete instructions with file paths, interfaces, signatures, and rules.} + +## Acceptance Criteria + +```bash +npm run build +npm test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - ARCHITECTURE.md 디렉토리 구조를 따르는가? + - ADR 기술 스택을 벗어나지 않았는가? + - AGENTS.md CRITICAL 규칙을 위반하지 않았는가? +3. 결과에 따라 `phases/{task-name}/index.json`의 해당 step을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "산출물 한 줄 요약"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- {Do not do X. Reason: Y.} +- 기존 테스트를 깨뜨리지 마라. +``` + +## Execution And Recovery + +Run: + +```bash +python scripts/execute.py {task-name} +python scripts/execute.py {task-name} --push +``` + +`scripts/execute.py` creates or checks out `feat-{task-name}`, injects `AGENTS.md` and `docs/*.md` into each prompt, carries completed step summaries forward, retries failed steps up to three times, separates code and metadata commits, and records timestamps. + +If a step is `error`, set it back to `pending` and remove `error_message` after fixing the cause. If a step is `blocked`, resolve `blocked_reason`, set it back to `pending`, remove `blocked_reason`, and rerun. diff --git a/FESurrogateModelTutorial/.agents/skills/harness-workflow/agents/openai.yaml b/FESurrogateModelTutorial/.agents/skills/harness-workflow/agents/openai.yaml new file mode 100644 index 0000000..3a671bf --- /dev/null +++ b/FESurrogateModelTutorial/.agents/skills/harness-workflow/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Harness Workflow" + short_description: "Plan staged Harness workflow steps" + default_prompt: "Use $harness-workflow to plan Harness phases and step files." diff --git a/FESurrogateModelTutorial/.codex/config.toml b/FESurrogateModelTutorial/.codex/config.toml new file mode 100644 index 0000000..b75aa36 --- /dev/null +++ b/FESurrogateModelTutorial/.codex/config.toml @@ -0,0 +1,4 @@ +#:schema https://developers.openai.com/codex/config-schema.json + +[features] +codex_hooks = true diff --git a/FESurrogateModelTutorial/.codex/hooks.json b/FESurrogateModelTutorial/.codex/hooks.json new file mode 100644 index 0000000..0229d62 --- /dev/null +++ b/FESurrogateModelTutorial/.codex/hooks.json @@ -0,0 +1,28 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "^Bash$", + "hooks": [ + { + "type": "command", + "command": "python -c \"import pathlib, runpy, subprocess; root = pathlib.Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()); runpy.run_path(str(root / '.codex' / 'hooks' / 'pre_commit_checks.py'), run_name='__main__')\"", + "timeout": 600, + "statusMessage": "Running pre-commit checks" + } + ] + }, + { + "matcher": "^(apply_patch|Edit|Write)$", + "hooks": [ + { + "type": "command", + "command": "python -c \"import pathlib, runpy, subprocess; root = pathlib.Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()); runpy.run_path(str(root / '.codex' / 'hooks' / 'tdd-guard.py'), run_name='__main__')\"", + "timeout": 30, + "statusMessage": "Checking TDD guard" + } + ] + } + ] + } +} diff --git a/FESurrogateModelTutorial/.codex/hooks/pre_commit_checks.py b/FESurrogateModelTutorial/.codex/hooks/pre_commit_checks.py new file mode 100644 index 0000000..1cca8d8 --- /dev/null +++ b/FESurrogateModelTutorial/.codex/hooks/pre_commit_checks.py @@ -0,0 +1,111 @@ +import json +import re +import shutil +import subprocess +import sys +from pathlib import Path + + +CHECKS = ("lint", "build", "test") + + +def _repo_root(cwd: Path) -> Path: + try: + root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + cwd=cwd, + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return cwd + return Path(root) + + +def _load_scripts(root: Path) -> dict[str, str]: + package_json = root / "package.json" + if not package_json.exists(): + return {} + + try: + package = json.loads(package_json.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + _deny(f"Invalid package.json: {exc}") + raise SystemExit(0) from exc + + scripts = package.get("scripts", {}) + if not isinstance(scripts, dict): + return {} + return {str(name): str(command) for name, command in scripts.items()} + + +def _is_git_commit(command: str) -> bool: + return re.search(r"\bgit(?:\s+(?:-[A-Za-z]\s+\S+|--[A-Za-z0-9-]+(?:=\S+)?))*\s+commit\b", command) is not None + + +def _deny(reason: str) -> None: + print( + json.dumps( + { + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": reason, + } + } + ) + ) + + +def _tail(text: str, limit: int = 1200) -> str: + text = text.strip() + if len(text) <= limit: + return text + return text[-limit:] + + +def _run_checks(root: Path, scripts: dict[str, str]) -> str | None: + npm = shutil.which("npm") or shutil.which("npm.cmd") + if npm is None: + return "npm was not found, so pre-commit checks could not run." + + for check in CHECKS: + if check not in scripts: + continue + result = subprocess.run( + [npm, "run", check], + cwd=root, + capture_output=True, + text=True, + ) + + if result.returncode != 0: + details = _tail(result.stdout + "\n" + result.stderr) + if details: + return f"npm run {check} failed:\n{details}" + return f"npm run {check} failed with exit code {result.returncode}." + + return None + + +def main() -> int: + try: + payload = json.load(sys.stdin) + except json.JSONDecodeError: + return 0 + + command = payload.get("tool_input", {}).get("command", "") + if not isinstance(command, str) or not _is_git_commit(command): + return 0 + + cwd = Path(payload.get("cwd") or Path.cwd()) + root = _repo_root(cwd) + failure = _run_checks(root, _load_scripts(root)) + if failure: + _deny(f"PRE-COMMIT CHECKS: {failure}") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/FESurrogateModelTutorial/.codex/hooks/tdd-guard.py b/FESurrogateModelTutorial/.codex/hooks/tdd-guard.py new file mode 100644 index 0000000..8f7e23c --- /dev/null +++ b/FESurrogateModelTutorial/.codex/hooks/tdd-guard.py @@ -0,0 +1,189 @@ +import json +import subprocess +import sys +from pathlib import Path + + +SOURCE_SUFFIXES = {".ts", ".tsx", ".js", ".jsx"} +TEST_SUFFIXES = ("ts", "tsx", "js", "jsx") +CONFIG_SUFFIXES = {".json", ".css", ".scss", ".md", ".yml", ".yaml"} +NEXT_SPECIAL_FILES = { + "layout.ts", + "layout.tsx", + "page.ts", + "page.tsx", + "loading.tsx", + "error.tsx", + "not-found.tsx", + "globals.css", +} + + +def _repo_root(cwd: Path) -> Path: + try: + root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + cwd=cwd, + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return cwd + return Path(root) + + +def _extract_patch_paths(command: str) -> list[str]: + prefixes = ( + "*** Add File: ", + "*** Update File: ", + "*** Delete File: ", + "*** Move to: ", + ) + paths: list[str] = [] + for raw_line in command.splitlines(): + line = raw_line.strip() + for prefix in prefixes: + if line.startswith(prefix): + paths.append(line[len(prefix) :].strip()) + break + return paths + + +def _touched_paths(payload: dict) -> list[str]: + tool_input = payload.get("tool_input", {}) + if not isinstance(tool_input, dict): + return [] + + file_path = tool_input.get("file_path") + if isinstance(file_path, str) and file_path: + return [file_path] + + command = tool_input.get("command") + if isinstance(command, str): + return _extract_patch_paths(command) + + return [] + + +def _normalize(path_text: str) -> str: + return path_text.replace("\\", "/").lower() + + +def _is_test_path(path_text: str) -> bool: + normalized = _normalize(path_text) + name = normalized.rsplit("/", 1)[-1] + return ( + "__tests__/" in normalized + or ".test." in name + or ".spec." in name + or "test" in name + or "spec" in name + ) + + +def _is_exempt(path_text: str) -> bool: + normalized = _normalize(path_text) + path = Path(path_text) + name = path.name.lower() + + if _is_test_path(path_text): + return True + if name in NEXT_SPECIAL_FILES: + return True + if path.suffix.lower() in CONFIG_SUFFIXES: + return True + if ".env" in name or ".config." in name: + return True + if any(token in name for token in ("tailwind", "postcss", "next.config", "tsconfig")): + return True + if "/types/" in normalized or name in {"types.ts", "types.d.ts"}: + return True + + return False + + +def _resolve_path(path_text: str, cwd: Path) -> Path: + path = Path(path_text) + if path.is_absolute(): + return path + return (cwd / path).resolve() + + +def _base_name(path: Path) -> str: + for suffix in (".tsx", ".ts", ".jsx", ".js"): + if path.name.endswith(suffix): + return path.name[: -len(suffix)] + return path.stem + + +def _has_existing_test(path: Path, root: Path) -> bool: + directory = path.parent + parent = directory.parent + base = _base_name(path) + + for ext in TEST_SUFFIXES: + if (directory / f"{base}.test.{ext}").exists(): + return True + if (directory / f"{base}.spec.{ext}").exists(): + return True + + for ext in TEST_SUFFIXES: + if (parent / "__tests__" / f"{base}.test.{ext}").exists(): + return True + if (directory / "__tests__" / f"{base}.test.{ext}").exists(): + return True + + for ext in TEST_SUFFIXES: + if (root / "src" / "__tests__" / f"{base}.test.{ext}").exists(): + return True + + return False + + +def _guarded_paths(paths: list[str], cwd: Path, root: Path) -> list[str]: + missing_tests: list[str] = [] + for path_text in paths: + if _is_exempt(path_text): + continue + + path = _resolve_path(path_text, cwd) + if path.suffix.lower() not in SOURCE_SUFFIXES: + continue + if not _has_existing_test(path, root): + missing_tests.append(_base_name(path)) + + return missing_tests + + +def main() -> int: + try: + payload = json.load(sys.stdin) + except json.JSONDecodeError: + return 0 + + cwd = Path(payload.get("cwd") or Path.cwd()) + root = _repo_root(cwd) + missing_tests = _guarded_paths(_touched_paths(payload), cwd, root) + if not missing_tests: + return 0 + + names = ", ".join(sorted(set(missing_tests))) + print( + json.dumps( + { + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": ( + "TDD GUARD: missing test file for " + f"{names}. Write or add the test first." + ), + } + } + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/FESurrogateModelTutorial/.gitignore b/FESurrogateModelTutorial/.gitignore new file mode 100644 index 0000000..93deae3 --- /dev/null +++ b/FESurrogateModelTutorial/.gitignore @@ -0,0 +1,19 @@ +node_modules/ +.next/ +out/ +next-env.d.ts +tsconfig.tsbuildinfo + +# Python +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.venv/ +*.egg-info/ +.ipynb_checkpoints/ +*.nbconvert.ipynb + +# phase execution outputs +phases/**/phase*-output.json +phases/**/step*-output.json diff --git a/FESurrogateModelTutorial/AGENTS.md b/FESurrogateModelTutorial/AGENTS.md new file mode 100644 index 0000000..3e59373 --- /dev/null +++ b/FESurrogateModelTutorial/AGENTS.md @@ -0,0 +1,168 @@ +# FEMSurrogateTutorial Agent Instructions + +## Project Purpose +This repository is a Korean tutorial project for studying surrogate models in finite element method (FEM) structural analysis. The tutorial combines: +- Theory documents on surrogate modeling methods. +- NumPy/SciPy implementation of a 2D Euler-Bernoulli beam/frame element. +- Jupyter notebooks that build, validate, and compare surrogate models from generated FEM data. + +The primary audience is graduate students and researchers who already know basic FEM and Python. + +## Tech Stack +- Python `>=3.12,<3.15` +- Environment and dependency management: `uv`, `pyproject.toml`, `uv.lock` +- Numerical computing: NumPy, SciPy +- Machine learning: scikit-learn +- Data and plotting: pandas, matplotlib +- Notebook workflow: JupyterLab, ipykernel, nbconvert +- Testing and quality: pytest, ruff +- Model/result persistence: joblib, CSV, JSON + +## Architecture Rules +- CRITICAL: At the start of every new task or session, read `AGENTS.md`, `PROGRESS.md`, and `WORKNOTES.md` before planning or editing. Use them to understand current scope, completed work, open tasks, and known pitfalls. +- CRITICAL: Keep `PROGRESS.md` current while working. Update it whenever the task state materially changes, including completed milestones, in-progress work, blockers, verification status, and next actions. +- CRITICAL: Record meaningful trial-and-error, mistakes, false starts, debugging discoveries, and corrective decisions in `WORKNOTES.md`. Do not log routine command output; log lessons future agents need. +- CRITICAL: Core calculation logic must live under `src/femsurrogate/`; notebooks should explain and orchestrate, not hide reusable implementation in cells. +- CRITICAL: FEM data for the tutorial must be generated with the in-repository 2D beam/frame implementation using NumPy/SciPy. Do not introduce Abaqus, ANSYS, OpenSeesPy, CalculiX, or other external solvers for v1. +- CRITICAL: Keep the structural model linear static and Euler-Bernoulli based for v1. Do not add Timoshenko shear deformation, geometric nonlinearity, material nonlinearity, dynamics, contact, or shell/solid elements unless explicitly requested. +- CRITICAL: Use `BeamExamples/CantileverBeam.txt` and `BeamExamples/CantileverBeam_Displacements.txt` as the canonical solver verification fixture. The solver must reproduce the listed `Ux`, `Uy`, and `Rz` values within documented floating-point tolerance before it is used to generate surrogate training data. +- CRITICAL: All random sampling, train/test splits, and model training examples must use fixed seeds for reproducibility. +- CRITICAL: Keep the project document and Python notebook based. Do not add browser app or design-system work. +- Keep public helper interfaces small and stable: + - `run_beam2d_case(params)` + - `generate_lhs_samples(bounds, n, seed)` + - `build_dataset(samples)` + - `make_model(model_name, random_state)` + - `evaluate_model(...)` + +## Expected Repository Shape +```text +docs/ + PRD.md + ARCHITECTURE.md + ADR.md + theory/ + 00_surrogate_modeling_for_fem.md + 01_doe_sampling_validation.md + 02_response_surface_methodology.md + 03_gaussian_process_kriging.md + 04_random_forest.md + 05_gradient_boosting.md + 06_mlp_neural_network.md + +PROGRESS.md +WORKNOTES.md + +BeamExamples/ + CantileverBeam.txt + CantileverBeam_Displacements.txt + +notebooks/ + 00_beam2d_fea_dataset.ipynb + 01_response_surface_surrogate.ipynb + 02_gaussian_process_kriging_surrogate.ipynb + 03_random_forest_surrogate.ipynb + 04_gradient_boosting_surrogate.ipynb + 05_mlp_surrogate.ipynb + 06_compare_surrogate_models.ipynb + +src/femsurrogate/ + fea/ + io.py + data/ + surrogates/ + plotting/ + +tests/ +``` + +## Development Process +- Start each session by reading `PROGRESS.md` and `WORKNOTES.md` after `AGENTS.md`. +- Before making changes, update `PROGRESS.md` with the current task, intended scope, and success criteria when they are not already recorded. +- During longer tasks, update `PROGRESS.md` after each meaningful milestone instead of waiting until the end. +- When you encounter an implementation mistake, incorrect assumption, failed approach, non-obvious parser/detail issue, or verification problem, add a concise note to `WORKNOTES.md` with the date and lesson learned. +- At the end of a task, update `PROGRESS.md` with completed work, verification commands/results, remaining risks, and recommended next step. +- Use TDD for code changes: write a failing pytest first, implement the smallest passing change, then refactor only if needed. +- For documentation changes, verify that links, filenames, and referenced commands match the repository. +- Prefer simple educational implementations over production abstractions. +- Keep every change traceable to the tutorial goal. Do not add speculative tooling, dashboards, APIs, or deployment layers. +- Commit messages should follow Conventional Commits, for example `docs: define tutorial architecture` or `feat: add beam2d solver`. + +## Commands +Use these commands once the Python project files exist: + +```powershell +uv sync +uv run pytest +uv run ruff check . +uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb +``` + +For full notebook verification: + +```powershell +uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/01_response_surface_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/02_gaussian_process_kriging_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/03_random_forest_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/04_gradient_boosting_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/05_mlp_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/06_compare_surrogate_models.ipynb +``` + +## Documentation Standards +- Write tutorial content in Korean. +- Keep technical terms such as surrogate model, response surface, Gaussian process, Kriging, Random Forest, Gradient Boosting, MLP, DOE, LHS, RMSE, MAE, and R2 in English when that is clearer. +- Theory documents must include citations and links to primary or official sources where possible. +- Avoid long quotations. Summarize sources in your own words. +- When adding equations, define symbols and units near the equation. +- Each model-specific theory document should explain: + - Core idea and assumptions. + - Mathematical form. + - FEM structural-analysis use case. + - Strengths, weaknesses, and failure modes. + - Hyperparameters used in the matching notebook. + - References. + +## Progress And Work Notes +`PROGRESS.md` and `WORKNOTES.md` are mandatory handoff files for future agents. + +`PROGRESS.md` should answer: +- What is the current project/task state? +- What has been completed? +- What is in progress? +- What is blocked or intentionally deferred? +- What verification has been run, with command names and results? +- What should the next agent do first? + +`WORKNOTES.md` should answer: +- What assumptions turned out to be wrong? +- What approaches were tried and rejected? +- What parsing, numerical, notebook, or tooling pitfalls were discovered? +- What should future agents avoid repeating? + +Keep both files concise and factual. Do not use them as a full chat transcript. + +## Data And Result Conventions +- Treat files under `BeamExamples/` as checked-in verification fixtures, not generated data. +- `BeamExamples/CantileverBeam.txt` defines a six-node, five-element cantilever beam with fixed node 1 and a downward nodal load at node 6. +- `BeamExamples/CantileverBeam_Displacements.txt` defines expected nodal `Ux`, `Uy`, and `Rz` values for that example. +- The BeamExamples fixture uses clockwise-positive `Rz`; keep element stiffness, solver output, and regression comparisons in that convention. +- For the 2D in-plane beam/frame solver, use `Area`, `ElasticModulus`, and `Izz` from the example input. Preserve `J`, `Iyy`, and `Poisson'sRatio` when parsing metadata, but do not use them in the v1 Euler-Bernoulli in-plane solve. +- Store generated reference data under `data/reference/`. +- Store derived or regenerated data under `data/processed/`. +- Store metrics under `reports/results/`. +- Store predictions under `reports/predictions/`. +- Store generated figures under `reports/figures/`. +- Use SI units in data columns and encode units in column names where practical, for example `tip_uy_m`, `max_abs_bending_stress_pa`, `mass_kg`, `compliance_j`. +- Include metadata JSON for generated datasets with seed, sample count, bounds, and code version notes. + +## Review Checklist +- Does the change keep notebooks thin and reusable code in `src/femsurrogate/`? +- Does the FEM implementation remain a linear static 2D Euler-Bernoulli beam/frame model? +- Does the solver pass the `BeamExamples/CantileverBeam.txt` displacement regression check? +- Are random seeds fixed? +- Are tests added or updated for new code behavior? +- Do docs and notebooks refer to the same paths and artifact names? +- Are citations present for theory claims? +- Has browser-app and design-system work been avoided? diff --git a/FESurrogateModelTutorial/BeamExamples/CantileverBeam.txt b/FESurrogateModelTutorial/BeamExamples/CantileverBeam.txt new file mode 100644 index 0000000..4b1d10b --- /dev/null +++ b/FESurrogateModelTutorial/BeamExamples/CantileverBeam.txt @@ -0,0 +1,29 @@ +Area, 1.0 +J, 0.140625 +Iyy, 0.0833333 +Izz, 0.0833333 + +ElasticModulus, 2.05e+011 +Poisson'sRatio, 0.3 + +# Node, NodeID, X, Y +Node, 1, 0.0, 0.0, +Node, 2, 1.0, 0.0, +Node, 3, 2.0, 0.0, +Node, 4, 3.0, 0.0, +Node, 5, 4.0, 0.0, +Node, 6, 5.0, 0.0, + +# Beam, BeamID, NodeID1, NodeID2 +Beam, 1, 1, 2, +Beam, 2, 2, 3, +Beam, 3, 3, 4, +Beam, 4, 4, 5, +Beam, 5, 5, 6, + +# Fix, NodeID +Fix, 1, + +# NodalLoad, NodeID, Fx, Fy, Mz +NodeLoad, 6, 0.0, -100000.0, 0.0, + diff --git a/FESurrogateModelTutorial/BeamExamples/CantileverBeam_Displacements.txt b/FESurrogateModelTutorial/BeamExamples/CantileverBeam_Displacements.txt new file mode 100644 index 0000000..0348a8c --- /dev/null +++ b/FESurrogateModelTutorial/BeamExamples/CantileverBeam_Displacements.txt @@ -0,0 +1,7 @@ +# NodeID, Ux, Uy, Rz +1 0.000000, 0.000000, 0.000000 +2 0.000000, -0.000014, 0.000026 +3 0.000000, -0.000051, 0.000047 +4 0.000000, -0.000105, 0.000061 +5 0.000000, -0.000172, 0.000070 +6 0.000000, -0.000244, 0.000073 \ No newline at end of file diff --git a/FESurrogateModelTutorial/PROGRESS.md b/FESurrogateModelTutorial/PROGRESS.md new file mode 100644 index 0000000..7ba1a4a --- /dev/null +++ b/FESurrogateModelTutorial/PROGRESS.md @@ -0,0 +1,91 @@ +# Progress + +## Current State +- Project direction is defined as a Korean FEM surrogate tutorial using NumPy/SciPy 2D Euler-Bernoulli beam/frame analysis and scikit-learn surrogate models. +- Documentation files have been populated for PRD, architecture, ADR, and model-specific theory. +- `BeamExamples/CantileverBeam.txt` and `BeamExamples/CantileverBeam_Displacements.txt` are designated as canonical solver verification fixtures. +- Implementation work is active on branch `codex/harness-implementation`. + +## Completed +- Replaced template project instructions in `AGENTS.md`. +- Removed UI-oriented documentation from the planned scope. +- Added theory document structure under `docs/theory/`. +- Updated PRD and architecture to require BeamExamples-based solver regression verification. +- Added `PROGRESS.md` and `WORKNOTES.md` as mandatory handoff files and documented their workflow in `AGENTS.md`. +- Created Harness phase files under `phases/`. +- Completed phase `0-project-foundation` step 0: `pyproject.toml`, package skeleton, artifact directories, and lint configuration. +- Completed phase `0-project-foundation` step 1 with TDD: package import smoke tests and `femsurrogate.__version__`. +- Completed phase `1-beam2d-solver` step 0 with TDD: BeamExamples input and displacement parser. +- Completed phase `1-beam2d-solver` step 1 with TDD: frame local stiffness and transformation matrix. +- Completed phase `1-beam2d-solver` step 2 with TDD: sparse global assembly, constrained DOF handling, load vector assembly, and linear static solve. +- Completed phase `1-beam2d-solver` step 3 with TDD: BeamExamples displacement regression and analytical cantilever tip check. +- Completed phase `2-dataset-and-surrogates` step 0 with TDD: LHS sampling, dataset schema, Beam2D case runner, and dataset builder. +- Completed phase `2-dataset-and-surrogates` step 1 with TDD: common split/evaluate utilities, metrics, timings, and prediction table. +- Completed phase `2-dataset-and-surrogates` step 2 with TDD: scikit-learn model builders and registry for RSM, GPR, Random Forest, Gradient Boosting, and MLP. +- Completed phase `2-dataset-and-surrogates` step 3 with TDD: plotting diagnostics and metric comparison helpers. +- Completed phase `3-notebooks-and-final-verification` step 0: dataset notebook, reference CSV, and metadata JSON. +- Completed phase `3-notebooks-and-final-verification` step 1: five surrogate model notebooks with metrics, predictions, and diagnostic figures. +- Completed phase `3-notebooks-and-final-verification` step 2: comparison notebook and model comparison outputs. +- Completed phase `3-notebooks-and-final-verification` step 3: full pytest, ruff, and all notebook execution checks. + +## In Progress +- None. + +## Blockers +- None recorded. + +## Verification +- Documentation checks were run manually with PowerShell/`rg` to confirm required BeamExamples references and absence of stale template terms. +- Verified that `AGENTS.md` references `PROGRESS.md` and `WORKNOTES.md`, both files exist, and stale template terms are absent. +- `uv sync` passed. +- `uv run python -c "import femsurrogate; print(femsurrogate.__name__)"` printed `femsurrogate`. +- `uv run ruff check .` passed after excluding existing local harness infrastructure from ruff scope. +- RED: `uv run pytest tests/test_project_structure.py -q` failed because `femsurrogate.__version__` was missing. +- GREEN: `uv run pytest tests/test_project_structure.py -q` passed with 2 tests. +- `uv run ruff check .` passed. +- RED: `uv run pytest tests/test_beamexamples_io.py -q` failed because `femsurrogate.fea.io` did not exist. +- GREEN: `uv run pytest tests/test_beamexamples_io.py tests/test_project_structure.py -q` passed with 4 tests. +- `uv run ruff check .` passed. +- RED: `uv run pytest tests/test_frame_element.py -q` failed because `femsurrogate.fea.element` did not exist. +- GREEN: `uv run pytest tests/test_frame_element.py tests/test_beamexamples_io.py tests/test_project_structure.py -q` passed with 8 tests. +- `uv run ruff check .` passed. +- RED: `uv run pytest tests/test_beam_solver.py -q` failed because `femsurrogate.fea.assembly` did not exist. +- GREEN: `uv run pytest tests/test_beam_solver.py tests/test_frame_element.py tests/test_beamexamples_io.py -q` passed with 9 tests. +- `uv run ruff check .` passed after solver import formatting was corrected. +- RED: `uv run pytest tests/test_cantilever_fixture_regression.py -q` failed because `femsurrogate.fea.benchmark` did not exist. +- Regression discovery: fixture comparison then exposed an `Rz` sign convention mismatch; the element convention was aligned to BeamExamples clockwise-positive `Rz`. +- GREEN: `uv run pytest tests/test_cantilever_fixture_regression.py tests/test_beam_solver.py tests/test_frame_element.py tests/test_beamexamples_io.py -q` passed with 11 tests. +- `uv run ruff check .` passed. +- GREEN: `uv run pytest -q` passed with 13 tests after completing phase 1. +- RED: `uv run pytest tests/test_sampling.py tests/test_dataset_builder.py -q` failed because `femsurrogate.data.bounds` did not exist. +- GREEN: `uv run pytest tests/test_sampling.py tests/test_dataset_builder.py -q` passed with 4 tests. +- `uv run ruff check .` passed. +- GREEN: `uv run pytest -q` passed with 17 tests after completing dataset builder work. +- RED: `uv run pytest tests/test_surrogate_common.py -q` failed because `femsurrogate.surrogates.common` did not exist. +- GREEN: `uv run pytest tests/test_surrogate_common.py -q` passed with 2 tests. +- `uv run ruff check .` passed. +- GREEN: `uv run pytest -q` passed with 19 tests after completing surrogate common utilities. +- RED: `uv run pytest tests/test_surrogate_models.py -q` failed because `femsurrogate.surrogates.registry` did not exist. +- GREEN: `uv run pytest tests/test_surrogate_models.py -q` passed with 2 tests. +- `uv run ruff check .` passed. +- GREEN: `uv run pytest -q` passed with 21 tests after completing surrogate model builders. +- RED: `uv run pytest tests/test_plotting_and_results.py -q` failed because `femsurrogate.plotting.comparison` did not exist. +- GREEN: `uv run pytest tests/test_plotting_and_results.py -q` passed with 2 tests. +- `uv run ruff check .` passed. +- Initial `uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb` failed because nbconvert executed with `notebooks/` as the working directory; notebooks now locate the repository root by checking for `pyproject.toml`. +- `uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb` passed. +- `uv run jupyter nbconvert --to notebook --execute` passed for notebooks 01 through 05. +- `uv run jupyter nbconvert --to notebook --execute notebooks/06_compare_surrogate_models.ipynb` exited 0 and wrote comparison output; Windows/Jupyter emitted a transient zmq connection-reset message after execution. +- `uv run ruff check .` passed after notebook cell style fixes. +- FINAL: `uv run pytest` passed with 23 tests. +- FINAL: `uv run ruff check .` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/01_response_surface_surrogate.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/02_gaussian_process_kriging_surrogate.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/03_random_forest_surrogate.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/04_gradient_boosting_surrogate.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/05_mlp_surrogate.ipynb` passed. +- FINAL: `uv run jupyter nbconvert --to notebook --execute notebooks/06_compare_surrogate_models.ipynb` passed and `reports/results/model_comparison.csv` exists. + +## Next Actions +- Review generated tutorial outputs and continue with documentation polish or additional explanatory notebook text if desired. diff --git a/FESurrogateModelTutorial/WORKNOTES.md b/FESurrogateModelTutorial/WORKNOTES.md new file mode 100644 index 0000000..707aa4b --- /dev/null +++ b/FESurrogateModelTutorial/WORKNOTES.md @@ -0,0 +1,16 @@ +# Work Notes + +## 2026-05-21 +- Initial repository documents were generic templates for a web project. They were replaced with FEM surrogate tutorial-specific instructions before implementation work. +- The previous design-guide document was removed from the active scope because the project is notebook based. +- BeamExamples fixture files use a simple comma/space text format rather than JSON/CSV. Future parser work should handle comments, trailing commas, and labels such as `Poisson'sRatio`. +- For the v1 in-plane Euler-Bernoulli frame solver, use `Area`, `ElasticModulus`, and `Izz` from `BeamExamples/CantileverBeam.txt`; preserve `J`, `Iyy`, and `Poisson'sRatio` as metadata but do not use them in the in-plane solve. +- Avoid writing removed/stale file names into handoff notes when the project uses simple `rg` checks for stale terms; describe the lesson without preserving obsolete filenames unless the exact name matters. +- `scripts/execute.py` expects `git rev-parse --abbrev-ref HEAD` to succeed. On an unborn repository with no initial commit, it returns `HEAD` plus a fatal stderr and nonzero exit code, so create an initial commit before running Harness execution. +- `ruff check .` should exclude existing local agent/harness infrastructure (`.agents`, `.codex`, `scripts/execute.py`) so linting focuses on tutorial package code and future project scripts. +- Use `Izz` as the code parameter name for the in-plane bending inertia instead of bare `I`; ruff flags `I` as an ambiguous variable name and the fixture also uses `Izz`. +- The BeamExamples displacement fixture reports downward cantilever rotations as positive `Rz`, so this project treats `Rz` as clockwise-positive. A textbook counterclockwise-positive Euler-Bernoulli matrix reproduces `Uy` but flips every `Rz`; keep the element stiffness convention aligned instead of flipping only solver output. +- `jupyter nbconvert --execute notebooks/*.ipynb` can run cells with `Path.cwd()` equal to `notebooks/` on Windows. Notebook code should discover the repository root by walking/checking for `pyproject.toml`, not by assuming the shell working directory. +- `ruff check .` lints notebook code cells too. Generated notebooks must keep imports sorted and line lengths under the configured limit. +- nbconvert writes `*.nbconvert.ipynb` output files by default; keep them ignored because they are verification artifacts, not source notebooks. +- On Windows, nbconvert may emit a zmq Proactor event loop warning, and comparison execution once printed a libzmq connection-reset assertion after finishing with exit code 0. Treat this as an environment warning only when the command exit code is 0 and expected output files exist. diff --git a/FESurrogateModelTutorial/data/processed/.gitkeep b/FESurrogateModelTutorial/data/processed/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/data/processed/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/data/reference/.gitkeep b/FESurrogateModelTutorial/data/reference/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/data/reference/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/data/reference/beam2d_lhs_300.csv b/FESurrogateModelTutorial/data/reference/beam2d_lhs_300.csv new file mode 100644 index 0000000..04c1618 --- /dev/null +++ b/FESurrogateModelTutorial/data/reference/beam2d_lhs_300.csv @@ -0,0 +1,301 @@ +L_m,b_m,h_m,E_pa,P_n,A_m2,I_m4,tip_uy_m,max_abs_bending_stress_pa,mass_kg,compliance_j +1.8314998429486489,0.02288848310526338,0.0815229398597667,172594545239.26154,1204.4650264605164,0.0018659364316716727,1.0334161957807233e-06,-0.013829015619417438,87011299.20020096,26.827078910236825,16.656565663964518 +1.485780501096186,0.0703451958583708,0.15227975734470864,134603011411.74176,430.61187485571196,0.01071214935567871,2.070044708657494e-05,-0.00016896401045085464,2353277.332637697,124.93983570435536,0.07275790932338262 +1.912481924636748,0.047325586440789394,0.13412538162234836,137384268072.70396,1491.016058175157,0.006347562341872312,9.51585181099103e-06,-0.0026593065863062455,20096154.678896487,95.29584621803525,3.965068823793571 +1.9005563087063533,0.03892541267357975,0.07695136609138858,204751900490.29495,1095.028618079069,0.002995363680903012,1.4780903506148138e-06,-0.008279755549199318,54174082.80724987,44.68893012378931,9.066569277072231 +1.5947212929865011,0.028377670567845643,0.060651928533480566,143550315733.9896,285.4873970880711,0.0017211604472276289,5.27629829547462e-07,-0.005095497266108835,26167185.46189781,21.54645402864454,1.4547002513707934 +1.07237473124558,0.060835540739851784,0.1272283305923341,194807902395.3725,841.5626396886561,0.0077400042890132725,1.0440651813465087e-05,-0.00017008684513586912,5498690.043104498,65.15645240127142,0.14313873436885768 +1.719404317170917,0.05385550434598768,0.12141457596271288,180228444125.88403,780.2860073349921,0.006538843223426135,8.03269604288166e-06,-0.0009132337131101088,10139378.371577015,88.25688485115214,0.7125834877663964 +1.8443936254162967,0.06781937032695569,0.0906637700929121,206850241685.69434,664.8332582220359,0.0061487597991691754,4.2118590646042665e-06,-0.0015959616691765433,13197640.606252536,89.02475701575838,1.0610483965161202 +1.1133087091552474,0.07276333760631655,0.08972554699620917,109961391102.16035,527.9000060948273,0.00652873026799659,4.380056467488244e-06,-0.0005041471272986309,6019683.850035296,57.057664296625695,0.2661392715736369 +1.7653262594338854,0.05980665036951069,0.10875237157317413,126953548532.28677,896.458597440227,0.006504115063531939,6.410389856402397e-06,-0.0020200152084294265,13423924.977437384,90.13279816085453,1.8108600005565714 +2.4184005785926237,0.046583251237609244,0.1568083429212308,150170594507.19855,1012.1064382265317,0.007304642434452879,1.4967733662081683e-05,-0.00212299367486471,12821461.97409473,138.67457919566064,2.148695566644777 +1.0122542173163664,0.0257631638203873,0.05996802707039356,122101793297.94275,196.1163789614812,0.0015449661053999694,4.629959923088148e-07,-0.0011993946892279361,12856299.886894634,12.276602878045393,0.23522094339701388 +2.99885756490169,0.034208042406339394,0.06827291867439919,212556641451.73248,222.15212458178422,0.0023354828972184083,9.071777376802585e-07,-0.010356897291806043,25068733.85015985,54.979677349072226,2.30080673745004 +2.5090373124694736,0.0644222822855815,0.06190015136572917,213676707662.4631,1509.0204900058407,0.003987749024803228,1.2732978139795645e-06,-0.029201695277680767,92030965.56296098,78.54247710356118,44.06595651692707 +1.8803275265391401,0.03546362707534823,0.141638741145562,188165807683.9365,370.8129344289211,0.005023023495407992,8.39746263160384e-06,-0.0005200502893437317,5880203.563159812,74.14269535721769,0.19284137384215863 +2.2767602373665152,0.05078719396396178,0.1319282799922206,113377538465.96469,502.63221905928657,0.006700267145296765,9.718218816358546e-06,-0.001794598357182278,7767635.770409364,119.75097925673768,0.9020229545906786 +1.8387426022120184,0.03598261817712693,0.042756728124461955,139276750527.0553,693.7486407804699,0.0015384990226057391,2.3438235152907022e-07,-0.04403927624425049,116351676.43422394,22.20689401616478,30.552188035404416 +1.3011131084669427,0.05481935324204158,0.08484687238436737,115209362593.83258,1734.3755729455568,0.004651250668721058,2.7903596005967974e-06,-0.003961130049810408,34308668.80130053,47.50665524433302,6.870087199651787 +2.6198549887683336,0.07887793897606994,0.13647070121249977,207909603351.69885,1561.5862492494184,0.01076452764226103,1.670677321550229e-05,-0.0026946933734432015,16709370.142657414,221.38178634570068,4.2079961179124314 +1.9163836401280836,0.0692936197063689,0.09353918410176623,173905530654.12637,1950.663751507745,0.006481668650791817,4.725989305697665e-06,-0.005568040616383383,36994376.79622444,97.50770554040177,10.861374997301908 +2.460080176958872,0.03952084747455992,0.0688781284182868,107735300872.5662,974.7000408118613,0.002722122007552264,1.0761901586120548e-06,-0.041720592641427086,76733069.55438615,52.56861136183609,40.66506335029402 +2.7755844200326263,0.05125709604641546,0.05692408518502546,175283604103.8452,1997.850111329783,0.0029177633016831852,7.878815513196515e-07,-0.10311046485275611,200319153.39364558,63.57321213773421,205.99925368534448 +1.4102930025734708,0.022265414249600112,0.08705755680218691,186941727856.41827,117.79790590776206,0.0019383725657587836,1.2242467456018725e-06,-0.00048124824409759455,5906829.594940002,21.45933513707948,0.05669003537648415 +2.7187095888004675,0.04325268945427481,0.08898072117551989,134150937902.88864,326.1913240502886,0.0038486555004221762,2.53933287372463e-06,-0.0064139601803737635,15537513.364638543,82.13750641195135,2.092178163641946 +1.5015451743314063,0.0280474634849621,0.11212059965520282,163640968179.4723,1011.4454299974409,0.0031446984247413554,3.294341222963827e-06,-0.002117267779164323,25844473.813744865,37.06696794352397,2.1415008193165854 +1.5613872984144939,0.039382290755115124,0.11412904353427888,149887803869.72943,1234.6779455652597,0.004494663176070163,4.878746594076833e-06,-0.002142346946215878,22548738.685472872,55.09059345107331,2.6451085262418284 +2.2823683011586327,0.021672540627220464,0.05066818292659669,184397309151.04816,1023.8895143480386,0.001098108252984105,2.3492788514504826e-07,-0.09366986044671798,252005249.36306617,19.674356622635106,95.90758792183861 +2.6010016269455036,0.06835079352389664,0.1545043674209379,186248249676.83844,589.1393859193188,0.01056049611612879,2.100799452968336e-05,-0.0008831650596319473,5634882.319220892,215.62276049831084,0.520307320896964 +2.6988235184899976,0.050858874149295734,0.14880865969476118,102787761774.47253,1136.1743909684308,0.007568240895741235,1.396593804751516e-05,-0.005186037815692381,16336069.803628998,160.33897020574037,5.892243356783542 +1.601688330241449,0.03963062111434279,0.1144439338431775,113729873278.4416,1702.5081209908894,0.004535484180973879,4.950259498266031e-06,-0.004141898486083398,31521147.53931131,57.02579186458539,7.051615808876856 +1.9382531257107587,0.03620970064654632,0.15745742612342728,141534717182.76892,1897.3497040557022,0.005701486264504984,1.1779670221306714e-05,-0.0027622405372338702,24578642.735462748,86.74975005098388,5.240936265851348 +1.5591287409274512,0.049392939146356715,0.06940672689731209,100163372270.41394,1158.927649116863,0.0034282022379867358,1.376221431308448e-06,-0.01062144848682015,45563969.61218766,41.9583178158119,12.309490325046339 +1.0169440865973296,0.05593646414679583,0.07417492434149953,100476764002.75151,160.82272485226002,0.004149082996019581,1.902326686036506e-06,-0.0002949623926723835,3188500.632999652,33.122175528188606,0.04743665571851501 +1.2608420992915117,0.021894964398804215,0.040383667280905984,106701680904.53453,680.973354795578,0.0008841989574085911,1.2016562860306466e-07,-0.035484561670924356,144273416.82930407,8.751456866755749,24.16404100449994 +1.1681065315811268,0.03651047386470185,0.06101704401387274,172176203613.8894,1116.1525285212006,0.0022277611907698634,6.911776980931584e-07,-0.004982953168829409,57548956.32287725,20.427759822268623,5.5617357788916735 +2.613091893660169,0.0428529520046492,0.08746146285403349,188757426541.5191,1856.724475458743,0.0037479818699403056,2.3891846138442986e-06,-0.02448705173017026,88805359.56713,76.8814951791214,45.46570827923148 +1.957399449788186,0.02352144812631788,0.13224525759397504,144858300254.10114,1813.4632198003533,0.0031105999664482295,4.533390505297857e-06,-0.0069033416455300395,51774414.247427806,47.79619030326978,12.518956167884774 +1.0754425740145133,0.05632837492058661,0.10197971033879796,127852674408.22131,1402.1610993072868,0.0057443513582566335,4.978371458644394e-06,-0.0009133583838071284,15444776.195958031,48.49510208452314,1.28067559550053 +2.748190193991543,0.029189783685797673,0.13914471791153568,194550648608.6056,1991.810225336548,0.004061604216859064,6.553145407482807e-06,-0.010808976488024223,58114120.24030992,87.62217791307721,21.529429894268976 +2.737376665709003,0.059793119208386175,0.15846534399138273,197171418728.4136,1360.1770486512287,0.009475137203674669,1.982772363055239e-05,-0.0023788171016291743,14878578.766606314,203.60560296298277,3.23561242457504 +2.127996843275138,0.07648227551987294,0.065583255277641,210719969283.56403,1765.8392275775352,0.0050159565996347,1.7978707338303775e-06,-0.014971960976924529,68537246.1786959,83.79042750871774,26.438076006813407 +2.339542531006592,0.05110218729810469,0.11375155805330195,217519353948.91022,1324.3401998970633,0.005812953425091065,6.268019011143268e-06,-0.0041461301601332,28114318.46768957,106.75726638477374,5.490886845070045 +2.4807541533989843,0.07280280445677859,0.1434690996526244,188989500502.69434,1030.2910044573773,0.010444952807600093,1.791603828409672e-05,-0.0015484963132998005,10233637.532757176,203.40417646715596,1.5954018220281971 +1.611259544606639,0.020965647782533448,0.0793032577437884,132195515076.3166,301.11512726835883,0.0016626441698637357,8.713650254441947e-07,-0.003644945025297345,22077962.1869384,21.02976761062358,1.0975480851785815 +1.1380693108621813,0.054688929246585666,0.1302823502190609,172923054616.7614,1611.8077500652946,0.007125002233209116,1.0078013308574489e-05,-0.0004544334545482174,11856666.740908664,63.65365909430245,0.7324593639297616 +1.1970560162717296,0.07686991602122166,0.07875653775878153,161018911947.34885,236.11945674620944,0.006054008443639709,3.129212149325244e-06,-0.00026794277217479195,3556868.926398274,56.88884975564729,0.06326650180498523 +2.2366756486113366,0.032494419533240844,0.04880067870206303,133425120710.35454,1317.3621792082665,0.0015857497272517274,3.1470607277950625e-07,-0.11701734408260855,228454093.01701942,27.842441227927658,154.15422340582876 +1.5799282239183674,0.07445087941133813,0.07008585625845357,190051464709.8472,885.9142139521327,0.005217953632738504,2.135894201675166e-06,-0.0028690064822059374,22964113.163278602,64.71533889136836,2.5416936225070463 +2.875760889847622,0.027160509123923204,0.11772326594936802,198436241667.45178,1085.3627463884377,0.0031974238389158483,3.6926960907836247e-06,-0.011742144368100255,49752673.59576146,72.18095743013201,12.744486059850818 +2.43133244952272,0.07060028264520024,0.10016988958032483,196552139300.14716,467.0925737092751,0.007072022516909432,5.913393489384802e-06,-0.001925307900863989,9618729.896012884,134.97633695857036,0.8992970225973624 +2.454532752038515,0.060416097529133087,0.09619491473650338,212976693312.94727,472.14750470019595,0.005811721350527229,4.481545039166519e-06,-0.0024383895768227836,12437723.079946205,111.98072414463617,1.151279554183844 +2.2659447659220726,0.028952339083094296,0.04094356756849021,124497478994.26439,1125.413412036063,0.0011854120515145112,1.6559966563004738e-07,-0.21169898568446774,315251844.6210834,21.08571413368349,238.2488778037305 +2.8870356750940136,0.07720109864779011,0.07803697159449194,118508819724.02277,1241.3343766667094,0.006024539942241167,3.057338015981658e-06,-0.027480830284222508,45737022.472930476,136.53553465334076,34.11289933114898 +1.1291014037948819,0.03573057699007314,0.052549547438675834,183667874579.9599,1353.1837966588705,0.0018776256505511077,4.320815517243893e-07,-0.008181547419697236,92910114.81121816,16.642233599032494,11.07113739993049 +2.2414313057299413,0.06872856088386628,0.12359238421025875,146548487465.68164,812.8953468340021,0.008494326702976963,1.0812624845355039e-05,-0.0019256493507311863,10413354.435320249,149.45968087623032,1.5653513968432986 +1.4065306561811344,0.020273390641434193,0.1040998919792323,135341893943.94872,397.70883693499695,0.0021104577758260788,1.90588187213665e-06,-0.0014300899817837975,15277023.517729312,23.30212494816057,0.5687594233676251 +1.826249320085565,0.04697611579152199,0.11924746491239926,144621923926.2337,1653.5705281560415,0.005601782719570324,6.638092864183832e-06,-0.0034970690839348415,27124305.438361306,80.30747728062661,5.7826503721203 +2.2246456016622407,0.05005609518099281,0.15198244049392126,197936724240.38977,142.72425932125824,0.007607647507203299,1.4643873337184473e-05,-0.00018070772012295034,1647654.2683857938,132.85590859228773,0.025791375508181322 +1.042833035705912,0.058779886893946726,0.14493211466677136,126379582748.83847,1972.5755796896055,0.008519093307413338,1.4912188568033188e-05,-0.00039567484010969616,9996355.198026434,69.73933669156939,0.7804985270979758 +2.899760811553612,0.0318220486590499,0.13490142046062267,194336063708.38275,1659.3238440922435,0.004292839566072884,6.510231879122651e-06,-0.010659738945541567,49852093.38463715,97.71843236028197,17.68795900413583 +1.780938884510343,0.03761604152904589,0.11829859744482052,216411005745.66534,307.6507833259193,0.004449924954312251,5.189561130770031e-06,-0.0005157910996826229,6244907.491086512,62.21159841665765,0.15868353584989625 +2.4755986030522696,0.07137351869202158,0.05303918024990824,138485060383.08636,688.5633491549236,0.0037855929229763273,8.874548585343163e-07,-0.028334407686754395,50938325.87525222,73.56712713198132,19.510034653112616 +2.9282979381162413,0.07048741960062826,0.06795280784918846,205214703573.28278,479.0341318296734,0.004789818079906613,1.8431156484791915e-06,-0.010600587323396578,25858691.500124387,110.10421309763807,5.078043145347921 +2.102098815519008,0.026402623716520364,0.12228369270545006,151914192477.86105,253.96104698542044,0.003228610325168604,4.023198635307189e-06,-0.0012865758997511547,8113108.99264617,53.276834831428495,0.3267401625270126 +1.438691137842491,0.053003422601885564,0.06601178728785918,110575643852.93709,942.6032728618675,0.0034988506583241777,1.2705364910413264e-06,-0.006659839671566904,35229044.54530091,39.51505866290807,6.2775866711542685 +1.414403183805858,0.04882944216761645,0.14845767749078817,149309506519.0759,610.60676382933,0.007249105577375095,1.3313998479414166e-05,-0.00028971038864845423,4815030.022969181,80.48729036581547,0.1768991228603701 +2.0353686403612703,0.02332481098639935,0.15539943358306796,179856019927.0757,1945.7396345704922,0.0036246624157185796,7.29432621084417e-06,-0.004168526314270902,42185389.05134458,57.91356507087033,8.110866867426946 +2.885249561289233,0.07502641998074575,0.05528161931795067,147988357964.81046,606.4353783401605,0.004147581988164274,1.0562707308145347e-06,-0.031060656337359775,45787130.56863121,93.93945152733245,18.836280877440476 +2.351414219740206,0.02986481013474012,0.09828716313145244,214871839997.58304,505.5376364361669,0.0029353274656030563,2.3630282440532682e-06,-0.0043149050563687525,24721824.882794213,54.18204032637211,2.181346903643125 +1.4311801416075802,0.02434680593601473,0.1293718902803398,191379545728.0371,1304.2210211934942,0.0031497923062308235,4.393195391237984e-06,-0.001515782214629715,27483681.44445964,35.387173561097285,1.976915027871303 +1.8605504351019462,0.042054043274588296,0.14538206538300838,155548354819.96948,404.43763353660887,0.006113903668966059,1.0768594273778364e-05,-0.0005183584838648528,5079428.273869284,89.29552513200956,0.20964367853792554 +2.838279252077004,0.06353785559677937,0.15836925718719325,157717901217.78394,355.8809369288714,0.010062442994129098,2.1031194818127544e-05,-0.0008177186490437612,3803091.596639364,224.19618192723718,0.2910104789659047 +2.0111673108402313,0.06485092449088713,0.12682787158977285,111271833012.24095,1986.3541833732334,0.008224904723808288,1.1025011173054024e-05,-0.004390517963427739,22977911.960835844,129.85202719509425,8.721123723830017 +1.8574788390230883,0.0685463759352212,0.0947230269330848,105468411318.24774,613.2931684395832,0.0064929202138773135,4.854784488659686e-06,-0.0025587398737420674,11113417.203661704,94.67462592082764,1.5692576843799715 +2.6392631901607957,0.050586520941824586,0.07477415415586579,205756877676.9475,1792.692131769722,0.0037825643151129244,1.7624146452234193e-06,-0.030294940628359013,100369516.79162282,78.36798467615259,54.30950169689008 +2.111956910778975,0.04150066578872504,0.15400899877518737,152541561771.2149,316.0008881453181,0.006391475986625215,1.263316331472268e-05,-0.0005148988350637654,4067966.312636845,105.96339675824329,0.1627084891851395 +1.0014264214742683,0.061764448541219166,0.11708875672061811,125718315392.91345,1376.2922971645733,0.007231922489225947,8.262337020810443e-06,-0.00044355257253967935,9765894.263652556,56.85157033130386,0.6104579889738913 +1.6856494847041383,0.0663499842356854,0.1189675311235549,108626234296.58911,263.5662911187316,0.00789349381460628,9.309898043819937e-06,-0.0004160933527119218,2838642.271951645,104.44946068168507,0.10966818173343947 +1.1458396446677404,0.026891163534892886,0.08406813850038895,178256591425.2339,1547.380576946341,0.002260690060487984,1.331443190666247e-06,-0.003269483729673584,55975731.78183147,20.33454812056554,5.059135619938985 +1.6712647923371784,0.04365954073872256,0.12457052002277555,128972751590.80008,1905.650002103744,0.005438691693778223,7.03305071686388e-06,-0.003269007846047917,28205248.893309005,71.35252746187737,6.229584808698369 +2.1384126500867273,0.05709307593456131,0.055745934746160644,215490403396.62256,970.0232332545547,0.0031827068855056493,8.242174439925595e-07,-0.0178018726648786,70147961.63245386,53.42663422404477,17.268230080371417 +1.2557271906680043,0.044839093908410316,0.12255691330320925,160243710423.25885,1111.453854069051,0.0054953409447275016,6.878425296936459e-06,-0.0006655582310943875,12433847.260623487,54.17009501334135,0.739737261057237 +2.588034071579322,0.021174945313894144,0.15511863778583707,207517772888.27136,1141.6966984823966,0.0032846286722808538,6.586170934589154e-06,-0.004826709522544038,34795391.17509136,66.73073769334123,5.510638326422073 +2.807867229176492,0.0666381271839097,0.10374811733425016,113085413208.90451,1067.323026027182,0.0069135802380109485,6.201292416114119e-06,-0.011230925047518122,25069199.434559733,152.38746078475626,11.987024906801516 +1.227061247834137,0.029723238153739916,0.1029526971865092,177496282147.6928,1883.8957752126732,0.0030600875370444826,2.7028880727597e-06,-0.0024183380949151356,44025346.65861299,29.476081428746827,4.5558969200464885 +2.9710431244066564,0.060186340327272714,0.13692333426772887,166872332657.3057,729.6871195986578,0.008240914394982451,1.2875054890388292e-05,-0.0029689908975776454,11527735.869587757,192.20027960848614,2.1664344161680655 +2.210021847705903,0.051760835238663316,0.13811991091366066,176150450348.11322,1826.625937519196,0.007149201951980845,1.1365509212876897e-05,-0.003282807450351345,24529211.211943835,124.02915618418395,5.996461236693027 +2.4487914057713125,0.053673046310513686,0.14252070565784486,213317360550.77258,191.55717833418424,0.0076495204349805975,1.2948184857742409e-05,-0.00033946692094041287,2581602.071297701,147.04682721070228,0.0650273255131391 +2.045876965049992,0.04954726517142617,0.0833183854162591,117833768369.22206,1959.1727057772932,0.004128198135874477,2.3881465726731752e-06,-0.019872809059518277,69920110.49397166,66.29941596578111,38.93426509653193 +1.3947201725217278,0.07977939782851484,0.1589662855852927,142043082185.1145,332.6222866889177,0.01268223453903037,2.6706968102347878e-05,-7.929515677140202e-05,1380666.7640673867,138.852121502264,0.026375336368659956 +2.727914991379722,0.07249126009796847,0.07971050709530414,120733477883.6434,265.75679548591074,0.005778315102386654,3.0595046598209796e-06,-0.004868293923976997,9443862.782197664,123.73760628282379,1.2937821927196567 +1.5080205001189793,0.07929796780301963,0.059519692129124196,104269368673.55035,1047.7877266045866,0.004719790630100931,1.3933583994093103e-06,-0.008244307928712086,33748027.387947045,55.87269705772418,8.638284662053405 +2.7117035361833945,0.06135319406939463,0.12096633619886818,108056962302.81097,583.7158701538976,0.007421671100672796,9.050019442820886e-06,-0.00396738732995687,10578612.956230627,157.98416837946354,2.315826947543323 +1.0939706323345735,0.06893894354764854,0.1361355202715921,157283226091.914,536.4549517916284,0.009385038946833051,1.4494316622265394e-05,-0.00010269489179134353,2756021.731196738,80.59561238053969,0.055091183225171696 +1.1622913272795432,0.05786893010930312,0.0460773074504991,157178725492.1706,1425.8497974086044,0.0026664444844778047,4.717647482754067e-07,-0.010064177004606168,80932021.23996015,24.32860459700026,14.35000474310204 +2.7716878936706366,0.04312541237999208,0.12480506453875065,156706246149.03607,1682.697094517661,0.005382269875345148,6.9863227093708594e-06,-0.010908944065388938,41658520.38609861,117.10608219360417,18.356448483085646 +2.5389745767420147,0.05546185075884183,0.1291954309889122,169475506176.02783,1536.9320110250876,0.007165417712231298,9.966773228725662e-06,-0.0049641733799332816,25291558.42784295,142.81358521427373,7.629596975898065 +2.691255638827559,0.06297069741791503,0.0765125118485241,182849657583.16687,1188.249760943137,0.00481804623229805,2.3504695887419873e-06,-0.017963979523115015,52048775.4075278,101.7877636128114,21.345694373928826 +2.471636471146515,0.05778734751839677,0.08634307603146052,201290879335.22626,314.4498614731415,0.004989537340437364,3.099802786616029e-06,-0.002536446909736171,10824291.000963096,96.80873134846313,0.7975853794005168 +1.1749551739564756,0.07225278999907067,0.06712687228516774,217670736453.52356,165.16932008621438,0.004850103806514662,1.821220843370476e-06,-0.00022527311363690155,3576469.151747768,44.734438309269635,0.03720820701311154 +2.3809623473625416,0.04852277304787561,0.06134584352093114,140821987726.1037,1039.775234610213,0.0029766704425966324,9.335117613737227e-07,-0.03558652100852212,81344341.81955363,55.635620917987154,37.00198323059736 +2.0875962313292424,0.03274970697390237,0.04426037959548125,190994540317.8186,1457.9021947833785,0.0014495144623056986,2.3663096529928056e-07,-0.09782596018411872,284635101.82364684,23.754107290817885,142.6206820592181 +1.7941800678946425,0.045497545343850115,0.10729594440554388,103020088720.08171,995.1496827382779,0.0048817020958024536,4.683350275692161e-06,-0.003970879214337942,20452721.60720234,68.75542289185287,3.9516191903404247 +2.0574912934882548,0.034019059204692954,0.14686011359844534,189703355518.88113,498.1916558730003,0.004996042899313445,8.979509875556454e-06,-0.0008491053312872404,8382154.9957609605,80.69262092276516,0.4230171910045828 +2.5323886742521076,0.071607908391047,0.07490476862260115,137178636631.16434,622.871006540016,0.005363773809579794,2.507888032320121e-06,-0.009801066218384816,23555905.66826902,106.6278063660451,6.104799980610698 +2.3301750347380024,0.05527413751107069,0.09549247389991775,123981563453.13733,1556.5889191219576,0.005278264133616382,4.010958444788195e-06,-0.01320118883679208,43177099.64580963,96.54934259061136,20.548824262587036 +1.677122085801654,0.07462903940988234,0.11497922397710428,206518586292.924,1232.4381175880685,0.008580789037505004,9.453327962571316e-06,-0.0009926451687196605,12569976.11805454,112.96959184597424,1.2233737431697491 +2.0323791598945036,0.04277015701138904,0.09154170388539487,140161625063.5206,554.0170695932885,0.003915253048268421,2.7341137178513925e-06,-0.004045483107103573,18849520.242546406,62.46463780296122,2.2412666960866736 +2.8062624068569506,0.07985373881967696,0.11337690514066068,200031319360.05948,205.79991536540234,0.009053569771285607,9.698125558017938e-06,-0.0007814891818280886,3375827.68091437,199.44253610156997,0.16083040747919816 +2.2717225155456804,0.04716473377826938,0.0949863969839479,127466196746.35666,1090.6494644510033,0.0044800081263049124,3.3683746050520937e-06,-0.009926916544691682,34934256.698401086,79.89208234328272,10.826786213117787 +1.7378500598700533,0.0738777679130561,0.14607640565505262,219006087605.34467,1760.7324236745358,0.010791798794557413,1.9189901333882387e-05,-0.0007329596853289192,11646166.685165578,147.2230462226333,1.290545883204913 +1.5137154358328395,0.05407141725293492,0.10531225799488872,198347582797.3896,491.23308203901007,0.005694383043890359,5.262877731885171e-06,-0.0005440626066401296,7439735.481965973,67.66445276199077,0.26726155108200844 +1.4492631580499715,0.025535568028259188,0.10835483609802105,181044649435.6347,962.4982454017294,0.002766902288371891,2.7071303983189564e-06,-0.0019926250153884852,27916201.656973228,31.47826095542309,1.917898081055011 +1.2689404943203408,0.06687857997010405,0.058791498763259065,123206849125.83661,272.9127958972913,0.003931891951600894,1.132529158521066e-06,-0.0013321163869399325,8988770.643501224,39.16629479592707,0.3635516076203749 +1.4258128606535507,0.0758595259575483,0.06400342688102204,202271809950.72742,1362.0811610946794,0.004855269622852936,1.6574428459527883e-06,-0.003925503713462117,37497316.15409862,54.34324108110321,5.346854655913956 +1.0313069010984408,0.03073462810610016,0.143667461943138,203435921628.17838,653.2340954951508,0.004415566013769644,7.594898511996559e-06,-0.0001545830051510399,6371815.872576612,35.74735906271248,0.10097888954876179 +2.904521170293873,0.03156970614678105,0.14293141312972282,121103625103.24872,1964.0599277206998,0.004512302711649512,7.681965562248249e-06,-0.017243587282486036,53070675.69776106,102.8827182091696,33.8674387916851 +1.4573945036854046,0.06471900711662265,0.14999607121470343,145928435034.80405,104.33495908973752,0.00970759680040983,1.820079053666575e-05,-4.0533098182527294e-05,626565.6961965796,111.06021603415373,0.0042290191406542995 +2.3995696313896944,0.03826366264328562,0.08648192895824242,106970536155.77838,1569.0855127466498,0.0033091153543987816,2.0624403488417235e-06,-0.032755170141048756,78939422.57360174,62.332553782768194,51.39566293587124 +1.5831564779291536,0.06158952125377512,0.14651519020975837,134859695898.89427,1524.898180279426,0.009023800421424817,1.6142602099109667e-05,-0.0009264786171953522,10955792.648574464,112.14579152784042,1.4127855574289916 +2.763846605279553,0.07855458480595,0.14787570706278394,135672389234.38428,649.6311811736562,0.011616314771203281,2.11680471453804e-05,-0.0015918976721288796,6271433.8012254285,252.029840348054,1.0341463650526777 +1.3259034621897743,0.023928018494800956,0.1309319860687389,139710905166.88168,1446.3098788607458,0.0031329429842138056,4.475718424778278e-06,-0.0017971434688577397,28049573.410042908,32.60874260445615,2.599226352739018 +1.4630438878749488,0.0753596491752525,0.12043197556765828,203664306156.47577,1650.7582276289302,0.009075711428261307,1.0969404555335327e-05,-0.0007713230369176848,13257742.691050088,104.23358844589103,1.2732678493516012 +1.054406551847298,0.027783601365745487,0.05215405511524665,147170484989.8246,1287.711424834247,0.0014490274769291323,3.284517181024652e-07,-0.010409486893534211,107798617.8929205,11.993732914024568,13.404415199466358 +2.7894324144038096,0.07145804127893124,0.08227478525214643,148956404054.46942,849.1079141955836,0.0058791950007630826,3.3164246451966493e-06,-0.012435402609648991,29379564.91368695,128.7369942799756,10.558998772061372 +2.2539066242101127,0.0380942901797728,0.11154464927677643,115056077385.51292,826.4707556456384,0.004249214237550506,4.405800892019677e-06,-0.006222710058892296,23580732.457847882,75.1820571239686,5.142887884536431 +2.402994457545718,0.024450172665569057,0.07333284694924996,119617979556.28087,1486.8203416185795,0.0017930007699669106,8.035193158826713e-07,-0.0715488673316161,163035992.62936315,33.822281663954676,106.38031136841587 +2.742828039600342,0.023609133476818346,0.09170812089936714,159918493491.65118,395.0861585155586,0.002165149267221353,1.5174772399310487e-06,-0.011198127962772995,32745076.440472394,46.61826214243065,4.424225359377641 +1.5697906829164001,0.07549512638840895,0.07604579502760658,181453987934.30493,1258.2507780822061,0.0057410869069162,2.7667077354952207e-06,-0.0032317770495882414,27145064.58942552,70.74659217987953,4.066385987232621 +1.0361433009419025,0.06182983510372808,0.04805833918607037,111760346401.94025,706.8580808633781,0.0029714391872337647,5.719039774627004e-07,-0.0041007149991116746,30772848.329627056,24.168868942866858,2.8986235344397473 +1.1891332822946319,0.0527875598961165,0.08766354684184721,139024803572.45477,1499.9087014354996,0.004627544729620024,2.963517222398649e-06,-0.0020404944982919558,26380130.898834437,43.196724508391526,3.0605554532193686 +2.544032769922248,0.06594414947234263,0.05742754543697271,120109802061.00427,156.39458008854078,0.0037870106401254764,1.0407724496932926e-06,-0.006866482356673922,10976878.837448228,75.62909147290901,1.073880624857392 +2.4113491041746977,0.06806925253289528,0.15637898691304603,165932504055.44736,1719.6287239408757,0.010644600751022457,2.1692266004892448e-05,-0.0022328385307194484,14946457.070863962,201.4926106094112,3.8396532733471047 +1.5923443831004058,0.04005190618973452,0.05773069148673035,151035260497.287,1282.6360973889136,0.0023122242396950293,6.421880539337018e-07,-0.017797239006710864,91802603.88603026,28.90257965229384,22.82738118386537 +2.14854194002951,0.05439898896951202,0.11195997362774329,147207832368.7976,1631.9826419816247,0.006090509370402464,6.362062696420984e-06,-0.005760987394660233,30852774.00784508,102.7228613248515,9.401831428760444 +2.9217862660688345,0.032843445417127595,0.048757074670193176,195326934051.26315,761.0501315585215,0.001601350320629304,3.1723448179120516e-07,-0.10211624123203614,170879107.52690297,36.72860648574005,77.71557882390282 +2.650969177806023,0.033149857480434794,0.07285282237491812,112263045235.50276,562.2891172867971,0.002415060678775967,1.0681680006457648e-06,-0.029118974501895014,50832465.96639513,50.25767366243609,16.3732824689673 +2.645330657566194,0.05809176556486251,0.062398521644742064,192519600897.1284,1836.6219985253722,0.0036248402909803547,1.176132446739775e-06,-0.05005036379772565,128880680.4817552,75.27287403151523,91.92359918510083 +1.7116485574723677,0.04607224077507914,0.05080289837571628,133938373623.35886,414.7005979668885,0.0023406033660378772,5.034119949422421e-07,-0.01028084050401299,35816586.66371313,31.44937944448555,4.263470704616394 +2.3179594419089993,0.0698117402640165,0.12983737616156665,109203043249.62506,1619.773916426538,0.009064173181152699,1.273345944268063e-05,-0.004835812948398948,19141834.59741644,164.93152859555715,7.832923678534328 +2.2170807507995125,0.06206830874195027,0.09726977055680387,219837925483.5011,1265.2639654958202,0.0060373701501783665,4.760168652730884e-06,-0.00439217324060257,28660793.27513042,105.07489659148435,5.557258531549435 +1.5294680246631351,0.04242086202547611,0.1352559504127669,140463337062.4261,856.1338645748704,0.0057376740105846225,8.747166327492526e-06,-0.0008310195877148452,10123742.215554321,68.88837314076949,0.711464011167726 +2.2316117653477456,0.07494208675424054,0.09992226275683394,208295443337.3362,956.7772193039224,0.007488382884202668,6.230620737223157e-06,-0.0027310799449712883,17121050.84331536,131.18263228035056,2.6130350754463385 +1.2445404504507112,0.020747849981456956,0.10212715532645167,196038222403.36148,1775.016121825097,0.002118918897746172,1.8416858803310756e-06,-0.0031590156914121205,61250127.86547549,20.701080193835935,5.607303781354969 +2.595558640536703,0.07828353095980646,0.04130940703342244,116560683003.23747,124.8123838731956,0.0032338462444321723,4.5987102109595957e-07,-0.013571905660408055,14550283.171452533,65.89005486093926,1.6939418991776465 +1.316696947387901,0.04519978117597574,0.15359480367917966,136273168756.41151,1435.5960470836774,0.006942451516065875,1.364849156707118e-05,-0.0005873182212224315,10636039.812315397,71.75767204095068,0.8431517167671394 +1.626844127395485,0.05282349110856836,0.047938376313797666,167988424833.00262,793.6237756270751,0.0025322723949710952,4.849487175130852e-07,-0.013981574273287148,63814316.719322376,32.338957926586744,11.096109763976525 +1.0617097056098412,0.05157370872018972,0.12594127202734143,132787328876.609,1712.9943483542802,0.0064952584793882846,8.585218314452442e-06,-0.000599437708845018,13339773.44982657,54.13421989888761,1.0268334074419545 +1.690444257651822,0.021496426584195448,0.0818724117236866,121600668008.59973,1672.1450161760738,0.0017599642878892516,9.83100182416688e-07,-0.022522754868352028,117702206.53730504,23.35470396445711,37.66131230367025 +2.197356544114635,0.0249116415865122,0.15997594369687082,183594002331.03647,1251.4457206043853,0.003985263371840501,8.499338833351692e-06,-0.0028362775317182136,25879274.133217223,68.74279971855255,3.549447379515127 +1.4693122091591027,0.059152260883236454,0.1331021331716896,191936918720.63373,1417.963699048954,0.007873292105487064,1.1623720270335684e-05,-0.0006720203919551751,11928588.864011742,90.81134510241475,0.952900520813088 +2.8602167128648457,0.07186473302707089,0.09702997350637749,143986230123.234,698.2192085353996,0.006973033141659579,5.470818525639466e-06,-0.006913438243374578,17709827.89240488,156.56342955941122,4.827095378547361 +2.2941834394689877,0.037284593794104204,0.11294452893614798,144254417101.51257,1604.7796995369506,0.004211090882650726,4.476553355336487e-06,-0.010002434203830587,46444575.73239496,75.83896747584768,16.051703356261367 +2.0844917675866554,0.05949897487368812,0.14722733877245786,202799760420.5213,1572.01388989613,0.008759875730342438,1.5823174704596453e-05,-0.0014790219398015017,15244788.603835296,143.3401274331005,2.3250430328290785 +2.304222089828586,0.03906286972043847,0.12026734480875395,107303479831.53624,230.63138968017876,0.004697987621887408,5.662732780013312e-06,-0.0015478550699345377,5643316.537703733,84.97787382033972,0.3569839658025127 +2.288668294374408,0.07636508084636587,0.13870445205023224,115956690685.08931,1784.523748927207,0.010592176694566862,1.6981841100838293e-05,-0.0036213379155577586,16679415.125940835,190.29953490874354,6.4623635132033685 +1.619280685381442,0.06031216365010704,0.12285413804816706,208559810329.4909,1179.1318813706505,0.007409598879053893,9.319508963432962e-06,-0.000858586154413993,12584943.811277138,94.18602975751622,1.0123863075729633 +1.9764133743797743,0.04862186406220104,0.04718223796736001,209818554532.6043,914.5179335817894,0.002294088360599399,4.255846630641964e-07,-0.026355694473561814,100191893.13257454,35.592425305496334,24.102755248074736 +2.5811156078482758,0.04791637475969256,0.07180650089040813,162818322054.708,1394.271398804625,0.0034407072068469934,1.4784069635400225e-06,-0.0332009996887741,87396601.42829902,69.71477512798624,46.29120427777899 +2.4240966074345485,0.0550835490300282,0.1164652314828088,137726245762.51544,291.74655046003613,0.006415318288676882,7.251528374280895e-06,-0.0013870368085180226,5679268.500204659,122.07810769867636,0.40466320424623076 +1.8119409846568342,0.03615284614821878,0.043978129880797225,168655136048.6181,1384.97239798188,0.0015899345634668451,2.562545111832703e-07,-0.06354493181927395,215337868.01679626,22.61481064797419,88.00797660133492 +1.5203382549442104,0.0661720352183438,0.13349617672348985,153446858345.07272,1758.8207431663848,0.008833713707661018,1.3118969722462246e-05,-0.0010234420659766342,13605073.890493095,105.42732813140951,1.8000511350687638 +1.119274932276804,0.03118191689650051,0.15397494615710128,130820606053.8404,1267.9718041324911,0.004801233975213873,9.48575157053905e-06,-0.00047758510126295325,11518467.262977421,42.185121534752895,0.6055644424751853 +2.8264732580246443,0.03711519596359855,0.04504328364823122,210362584278.08685,1195.6522622459536,0.001671790299448056,2.826575829108088e-07,-0.1513523398706324,269270748.2849036,37.0933740091573,180.96476756254006 +1.9700378017525204,0.02010190573151893,0.06320283381208486,176721792183.19147,378.1281448349977,0.001270497407255387,4.229272215427543e-07,-0.012893941091676782,55661445.40198045,19.64798416667518,4.875562024607487 +2.3540189868785317,0.05728838900570228,0.10253006432107029,166687539871.52856,860.9030418047646,0.005873782209605151,5.145635904998451e-06,-0.004364361156086619,20190467.95615102,108.5419095426689,3.7572917948095292 +2.681783428693869,0.06279757642523184,0.0903787048143618,131752029800.08102,579.2534526212464,0.005675563622793355,3.863313725592832e-06,-0.007316463209146168,18170566.797023073,119.48196490602393,4.2380865748742425 +1.1228746478704057,0.03003018546318814,0.10893177537958146,164989127617.54022,1929.469855839357,0.003271241417483183,3.2347484534134203e-06,-0.0017061402656593592,36479875.439676814,28.8345733298308,3.291946212423486 +2.3452421725929256,0.06721105485541519,0.08017800091707836,171857774746.8822,798.8693930488431,0.005388848017835283,2.8868561032027542e-06,-0.006923461670399869,26017380.52327449,99.20950602000063,5.530941622429273 +2.1921406572154005,0.04161487558830651,0.11868647536109089,125111735826.98828,1697.0981515337937,0.004939122906166404,5.7979044364939595e-06,-0.008215270238589898,38078125.56060209,84.99392924869139,13.942119936261504 +1.6538092114275944,0.023076624935595744,0.15312730231001884,170280287585.6021,1465.4676397641606,0.0035336613228078885,6.90476560049627e-06,-0.0018793076552264289,26874187.02850412,45.87541291894207,2.7540645538953936 +2.572389637442625,0.06310861419853078,0.15585331463983307,117112596843.1493,989.0783852809141,0.009835686705167455,1.99092787415357e-05,-0.0024069139974230015,9958592.990530154,198.61456567641395,2.3806266100811726 +1.9501819587052847,0.06965944772581867,0.05461591488166573,102058969811.37593,756.6883347626233,0.0038045144676971555,9.45706599929415e-07,-0.019382669354814672,42611318.31850184,58.24303949080942,14.666639797349244 +2.169380236533605,0.07309913637665019,0.14042989389535024,185356904469.80307,1328.859299956813,0.010265303965214724,1.686979103822627e-05,-0.0014462597918054655,11998709.765359618,174.81437822155294,1.921875774494297 +1.9210801752994098,0.03752749610171206,0.09940157815533544,105927512798.03633,675.2751286959088,0.003730292336728378,3.0714834610985046e-06,-0.004905000029512263,20991397.246766213,56.25459665085755,3.31222452618233 +1.8672525327673226,0.06439535012713746,0.14110818184384483,161447928371.44077,871.9607816125334,0.009086710775638168,1.5077517842054512e-05,-0.0007773606289221339,7618901.494090041,133.19239212612575,0.6778279815897544 +2.5139736529173735,0.0523306246636633,0.08575743461063907,158554436079.10913,1902.691856398136,0.004487740122728002,2.7503629902673907e-06,-0.023107925436530653,74572886.04475248,88.56417437297526,43.967261546342215 +2.8287887582563256,0.0655810217625155,0.1502194765651921,185857541823.98007,132.24380229654287,0.00985154676177555,1.8525744315176733e-05,-0.00028980092717855843,1516688.6192816324,218.7633661395054,0.0383243765191561 +1.1491168951070976,0.043562541919502956,0.13726800646592155,214735079097.06833,1303.003086355074,0.005979743285878311,9.38944552879265e-06,-0.0003268691068341117,10944830.456825476,53.940678699917086,0.42591145503897393 +1.816452532974277,0.031336992307416156,0.053829698863246477,169898635912.34576,1175.1395559848966,0.001686860859188083,4.073257968145525e-07,-0.03392411627878146,141046897.41507798,24.05320604151176,39.865570941027244 +1.7201861883903216,0.05216015809158496,0.08834191964205884,124165131519.08154,1368.5047738109579,0.004607928494643884,2.9968026860878492e-06,-0.006240129876809475,34697681.819949664,62.22298538557642,8.539647525614152 +1.4466566045237335,0.027334226317785183,0.07884479362491571,159208365270.32013,739.752259166604,0.0021551614329225126,1.1164636867654345e-06,-0.004200015783817907,37787675.500095166,24.47456138790466,3.1069711646146914 +1.8527855752852664,0.03493335622852884,0.14129445290853324,116134563701.037,335.50969196930976,0.004935889456568885,8.211725114663548e-06,-0.000745870451627361,5347994.409986015,71.78938657271767,0.25024676547450586 +2.7971497624395587,0.024165381669526993,0.09574930693548259,118806388105.07796,1437.322937855852,0.0023138185466886245,1.7677438297101707e-06,-0.049925352234107614,108882075.16564503,50.805961435856894,71.75885394661577 +1.2963080909812095,0.04750360625822915,0.09387910852407244,145324504696.18393,752.151047413974,0.0044595962072011015,3.2753084462676986e-06,-0.001147406876847494,13973334.399871632,45.38093357034563,0.8630232842308393 +1.9936770076287835,0.03342559856298535,0.07239437329396463,180532790604.77344,953.707162368382,0.0024198252599429686,1.0568476488097286e-06,-0.013203519451001034,65122681.703268915,37.871147368336985,12.592291068889933 +1.3869643241945444,0.058219887610593324,0.13360766982317238,104935850950.06586,547.9444957150688,0.007778623521018357,1.157135682245887e-05,-0.0004013312796635827,4387518.563440152,84.69108552269515,0.21990726564994506 +2.5564017033384347,0.03856683579322107,0.1511509527738792,164293231268.08487,627.6901227004914,0.005829413975619112,1.1098529223897001e-05,-0.0019170181887193482,10926721.058913376,116.98324196138995,1.2032933820963216 +1.3502967241920518,0.07867742122415634,0.049844588613273774,148097496367.31027,983.0855480332484,0.003921643694071328,8.11938095415363e-07,-0.006709452211138715,40746060.66989111,41.56875367339051,6.595965503990194 +2.8669274755305993,0.04082263006107567,0.08536535809262197,174577218859.83533,1923.2652766415831,0.0034848384334463588,2.116239102299512e-06,-0.04088987199949751,111209742.10429634,78.42761556316417,78.64207098295249 +2.321092882149906,0.0624418907370763,0.11083112427626327,156235495722.25974,790.1308036373971,0.006920504952325756,7.084023859237722e-06,-0.002975744043477069,14346410.090740222,126.09560806795292,2.351227032491734 +1.6420849752535778,0.05664594997241158,0.1006076067015132,203193406480.75067,1199.838353027825,0.005699013456057977,4.807065859132985e-06,-0.0018130036740156116,20617647.849358328,73.46237530419205,2.175311342264287 +2.6727701007027393,0.06108791293093188,0.0710279279220952,103940414216.20375,454.08890509393973,0.004338947876569457,1.8241539064487798e-06,-0.015242581589106612,23628718.435804565,91.0365297010695,6.921487184602466 +1.7744739384736667,0.06564742067871134,0.1216052643240628,123074084192.20244,511.90463757274773,0.007983071943827637,9.837699424928616e-06,-0.0007874382793362332,5614195.354110834,111.2011619392673,0.40309330699452256 +2.960533583025227,0.07947322771835542,0.15159347388603736,178840793298.61615,1346.2324845207952,0.012047622670761614,2.307178104636529e-05,-0.0028220264349158876,13093611.348032258,279.9890233723858,3.7991036588601776 +1.2157155733362761,0.06378382198624324,0.06471442790537063,109114310589.13678,149.76433221439063,0.0041277335494577325,1.4405642793286307e-06,-0.0005706485592192375,4089581.3660352468,39.39247717546836,0.08546280040057326 +2.389810404232901,0.05241290466780049,0.13599722167844264,162288211966.3734,1937.3023089560381,0.007128009414917944,1.0986189627661704e-05,-0.004943475859068718,28655882.859444786,133.72153983074168,9.577007196042262 +2.0263533456094995,0.051833773855205115,0.12841653159329114,215618442447.67346,1634.7370694283527,0.006656313457876456,9.147330929693026e-06,-0.002298750802032596,23251963.77338491,105.88113790162892,3.757853149460842 +2.1801583211376006,0.0544373241338348,0.08478722391231747,182444307143.85284,1227.1349606572255,0.004615589590522854,2.765074079163017e-06,-0.008402298097986923,41017955.65846277,78.99232101396575,10.310753745903463 +2.14563415133425,0.04819637311475541,0.08252572251278449,205188393280.99457,596.2179361203249,0.003977440513790931,2.2573615199825473e-06,-0.0042383507360532644,23384006.977611676,66.9929377801286,2.5269807284037373 +1.7926478098633127,0.04021839785799433,0.0836200901763734,167578973472.24176,659.5016413182734,0.0033630660556347486,1.959636024754766e-06,-0.003856422518740346,25224123.077278662,47.32602504261536,2.543316980726008 +2.3756280674227153,0.0483290729257396,0.09665149195898387,200871859581.95782,130.53595113399155,0.004671077003267266,3.636243060911639e-06,-0.0007986789177711991,4121300.169104623,87.10942182732785,0.1042563121819305 +1.6958661415634104,0.06526374561339524,0.062458407865225524,168178476222.96375,1870.3868278064733,0.004076269642333763,1.3251452289488016e-06,-0.013644291790094198,74751591.11529726,54.265540211985474,25.520103638940196 +2.4433997055693606,0.04408042722693588,0.09428494243490745,131315942784.47629,1341.246356802283,0.004156120543597776,3.078871543997218e-06,-0.016131019880204368,50179213.1978636,79.71725014341997,21.635671645829305 +1.1028063868576052,0.06406947408643922,0.046615887590654936,190473321972.5782,1310.082184245514,0.00298665540200583,5.408437141121633e-07,-0.005685506510251672,62262992.35227599,25.855565823497166,7.448480787492602 +2.0518223806710534,0.05934782620461518,0.04929105853191075,161709968680.2527,1056.1085577582949,0.0029253171751933535,5.922812776066097e-07,-0.03174985874842365,90169252.42114542,47.11751531739171,33.53129753182728 +1.0223759978198008,0.03397969986648991,0.12554263886747186,142541733129.8655,523.4450425052518,0.004265901189163824,5.602889429367168e-06,-0.00023346817289116728,5995576.4022467835,34.23663663124551,0.12220775768264053 +1.5385843620956283,0.0569991138377577,0.14001589228789396,146082075158.87045,877.2234445545054,0.00798078178361289,1.3038236515113336e-05,-0.0005591613347830274,7247029.445275793,96.39098248908093,0.49050943216006226 +2.5662646359499606,0.028666054976278867,0.04563183452125549,129906583615.10869,175.8268955552439,0.0013080846770547698,2.2698150440767e-07,-0.033592841401824065,45355944.012306854,26.351597863296394,5.9065250165623935 +2.5475806232002713,0.026145266709118133,0.060181120434581035,141646259842.15485,456.35486046361933,0.0015734514446156804,4.7488957685110224e-07,-0.03739103507369861,73666197.535083,31.46668113301765,17.063580593648027 +1.8023601030541823,0.04529786577896972,0.04061234069047676,199529001178.22357,1584.56457706168,0.0018396523575670068,2.528544241506192e-07,-0.061296658313751695,229355403.64641982,26.028370700231466,97.12851345622427 +2.0785411776654135,0.030505920460044238,0.11257009285054553,184566302940.21112,1074.432761558658,0.0034340542986785365,3.6263687238748333e-06,-0.004805178356806669,34662425.026948035,56.03191263921675,5.1628410516856835 +1.383765571277426,0.0456377171355702,0.06848166717680429,101575719368.99918,425.77229281039746,0.0031253469555872567,1.221421724284707e-06,-0.003031015502007379,16516522.223900557,33.94926799619116,1.2905224198335397 +1.8869495499350393,0.02206603407158994,0.13149700401976216,176890774227.61542,917.97242336677,0.0029016173710120713,4.181100558450406e-06,-0.002779671352508547,27238624.543780982,42.98036389967212,2.5516616476254583 +2.9070650191280567,0.07764107771594184,0.05129676553297728,201713576323.0376,1684.3832381599352,0.0039827361593223354,8.733337723868289e-07,-0.07830121620547195,143805463.9610624,90.88787280808175,131.88925610403405 +1.7054965108590037,0.0670474566809185,0.11559254638985875,154576172017.00507,1878.5585276090378,0.007750186246711116,8.629597801067986e-06,-0.002328750055407863,21457782.839533187,103.76063747784498,4.37469327525646 +1.184295505330851,0.027483044815957383,0.045438835799037305,104640068317.19223,716.4567217412143,0.001248797560649871,2.1486475721748903e-07,-0.017643540894781685,89718510.65179947,11.609720904444318,12.640833469382338 +2.6278699871233693,0.02582792346346185,0.09088122248710997,197355116711.1208,738.0035598900417,0.0023472732586629244,1.615588397802971e-06,-0.01400134349726241,54547615.502999134,48.421382241937906,10.333041344222945 +2.7024519568205987,0.03539519932942396,0.04210185934832423,133110101253.87187,248.86983309758128,0.0014902037037733077,2.2012377114750704e-07,-0.055878789497748604,64318302.56899091,31.613550735289312,13.906545015999573 +1.3297143707186265,0.020598701447028227,0.08283703529892925,204128818615.94937,1890.6208917107308,0.0017063353588795823,9.757357983043522e-07,-0.007439122734166268,106714916.20655371,17.81116838654492,14.064560857215 +2.493970388921495,0.029519715390018526,0.12384489925720596,211627243968.80576,772.5088228459828,0.0036558661785782366,4.67267195611426e-06,-0.004039421782807421,25531573.26245612,71.57333266258458,3.1204889664149817 +1.2376104313828369,0.07799389912769801,0.15785801418451584,152877308824.47632,1841.6809879495952,0.01231196203480585,2.5566971772605712e-05,-0.00029773007909170217,7036484.147785957,119.61368926376164,0.5483238262039172 +1.4771859313584725,0.040513737564428005,0.10497583568425264,189411673010.8775,632.8111067480272,0.004252963457518328,3.905611909384729e-06,-0.0009190994374579616,12562599.497313634,49.31697962031793,0.5816163322292619 +2.819157854789256,0.05351171996944851,0.14819831316475116,155105351085.70502,184.8837000512838,0.007930346634016799,1.4514345119172876e-05,-0.0006133541746896295,2660932.375508624,175.50165718524644,0.11339918925852019 +1.3369512392761456,0.053296070850150104,0.11593498395800672,103501600362.37758,1152.4278222710052,0.006178879119036942,6.920818587591752e-06,-0.0012815473411189504,12904950.645225698,64.84775174994141,1.4768908114629091 +2.8501593284628433,0.02277771271481304,0.0532787527568028,138339274506.69586,1866.5618870210176,0.0012135681240980074,2.8707211811440863e-07,-0.36273715876780366,493678907.641683,27.152070700541252,677.071355562274 +2.0951792560264026,0.07406533974700003,0.07146009099466157,110929048849.82607,1740.7797644535306,0.005292715917871146,2.25229082633491e-06,-0.02136074396510656,57859425.41469475,87.05013050344027,37.18435084813037 +1.0836687756578331,0.04222388045854136,0.041856923487206044,193639384841.11835,726.7213463140549,0.0017673617336861002,2.5803511413136545e-07,-0.006169697030512495,63873832.58763539,15.034592599791754,4.483650532363868 +2.7546212839409696,0.04461943357315988,0.12642371509089032,192860071445.43384,767.1257728362566,0.005640954557570071,7.513260584185761e-06,-0.0036885887754046563,17778641.534867678,121.97874386530671,2.8296115150074383 +1.5467854062971749,0.049870594991660216,0.04470165073126904,218740964751.01474,640.2836832422215,0.00222929791907777,3.712222395568458e-07,-0.009726963089415511,59629625.15415055,27.068727077017517,6.228015753652102 +2.173910077761913,0.06642213976014666,0.13974731270509982,211435093105.7926,417.4666239245434,0.00928231553560306,1.5106435890405407e-05,-0.0004475959511004225,4197732.824075782,158.40451640933568,0.1868563705881884 +2.9881608632106027,0.06226482992396756,0.06380558300294158,187433213647.60504,446.12442249273533,0.003972843773877753,1.3478377128018105e-06,-0.015705888459199183,31553755.344870836,93.1912458038986,7.006780418595552 +1.928995177822485,0.021303088247859245,0.11962160800408958,130279639299.75061,485.0840592149928,0.002548309671661946,3.0387168120254663e-06,-0.0029317193820578113,18417791.69843448,38.58806498563921,1.4221303383278734 +1.6519785274161694,0.0302512897817867,0.10102083746353827,110336460642.15823,1386.2454826876135,0.00305601062810827,2.5989357504399295e-06,-0.007264696769490036,44507168.68080702,39.63044190694513,10.070653079800861 +1.7318884807611248,0.07204950073348351,0.15261615406608325,154159461726.3519,708.8711369355949,0.010995917704325698,2.134279264448713e-05,-0.00037306489081996894,4389413.364616604,149.49307017902404,0.264454933306305 +1.660836317721548,0.029259975287818117,0.06563276847501871,216087586514.02155,1599.6674590351797,0.0019204131836501354,6.893739688650444e-07,-0.01639850496561314,126471418.98158444,25.037511889433635,26.232154770318147 +1.3605777007928852,0.05032300096904793,0.10614835262365957,106246645726.0461,1625.703309806559,0.005341703651943262,5.01562503462157e-06,-0.0025612371482731057,23405764.992079355,57.05225255366169,4.1638117091471 +2.9558431518635926,0.07374489336359635,0.12823646225845173,206271115408.20898,901.4139582943543,0.009456784234574371,1.295941182037301e-05,-0.0029028424886419295,13182625.226640377,219.4292517095832,2.616662737991756 +2.249765552000813,0.03329480317374009,0.12404159838985881,148666125744.9088,1060.8610296626157,0.004129940603746465,5.295381665986321e-06,-0.005114930511678461,27953477.905458976,72.93747510162815,5.426230449271942 +1.7725347730110497,0.04188773776384526,0.08091372269812258,192280991525.7248,1538.9560461677734,0.0033892927978754527,1.8491502826542483e-06,-0.008034903989242845,59681668.88542053,47.15996882017868,12.365364074622839 +2.940011731978143,0.07566834988588603,0.059190285389112834,162741747361.5662,1126.6770492461765,0.004478831224668837,1.3076283241468332e-06,-0.04484789766891998,74969502.74749662,103.36735831669986,50.52909701051324 +2.624776066505209,0.043805932405658055,0.08922955060496712,174865528976.846,881.0331896034272,0.003908783662388435,2.5934495239728607e-06,-0.011710239589151218,39781892.249888845,80.53850217854237,10.317109736250226 +2.7240323279965777,0.05656983266985362,0.10595797521122305,174184264326.22763,211.23376340607712,0.005994024927735386,5.607956031341586e-06,-0.001457019403494844,5435939.931264098,128.17415377205305,0.3077716919558935 +1.4930080857952328,0.05746940956810506,0.08857761921629925,181986546190.57208,1726.7551338255507,0.0050905034773091554,3.328338577228582e-06,-0.0031624902764392274,34305158.0410692,59.66127839127186,5.460846320514821 +1.4978591215046884,0.030966805920008115,0.10651328394486723,101910433057.51341,381.2722785189818,0.0032983761918234196,3.1183617195054307e-06,-0.0013439457646466806,9753342.762155218,38.78294749085136,0.5124092638927752 +2.4904040188857453,0.04111838637386729,0.11614233792704334,125431018377.34157,927.0671449994952,0.0047755855252484295,5.368173072711737e-06,-0.007088721321710404,24975549.821073905,93.36112846919856,6.571720637415113 +1.6202029806615617,0.07332331449794896,0.0674029628912004,218117586637.73328,1645.1576020070886,0.00494220864616507,1.8711034749810364e-06,-0.005714865914334549,48009603.80757185,62.85794225960871,9.401855103418674 +1.5406629516324877,0.04120566430531498,0.07240269404633617,119564303692.352,1748.06148661587,0.002983401105673756,1.303286368322009e-06,-0.013674673808985646,74808202.8299052,36.081862093962,23.90417062752255 +1.960630680308092,0.03506576023661653,0.08072497564950115,118234799547.0885,215.9390093583068,0.0028306826412321153,1.5371837366101303e-06,-0.002984874888866303,11116780.867309257,43.566897376029544,0.6445509265602756 +2.4397064110239333,0.02535178025825994,0.1106155953071023,160769926256.55527,1799.7399897197276,0.0028043022653622667,2.8594091243021e-06,-0.018950475123467307,84929271.86628518,53.707142589738105,34.105927903893004 +2.0647253922351423,0.046344053185831235,0.11735671690144217,163550742253.26984,815.8856469688461,0.005438785929794975,6.242184805680148e-06,-0.002344799933806798,15835541.745614268,88.15235538560438,1.9130886110064667 +1.0523732677245807,0.032248275964980415,0.05625056779683694,158803880645.26825,278.6476441083222,0.0018139838334992379,4.783061746560689e-07,-0.0014252020732370275,17243130.329282057,14.985556541504364,0.3971292000857942 +1.7401156422876118,0.04582209130992622,0.05172123384202792,173440774897.3228,1694.7117816808484,0.002369975099771449,5.283239400985305e-07,-0.03248309717520949,144348591.41050282,32.37364133211387,55.04948748831141 +1.206060508769472,0.0735780027774754,0.06659308000994577,166028251316.1757,1498.1950736613505,0.00489978582593243,1.810731492584172e-06,-0.002914193282266561,33226340.64116436,46.38908976077129,4.366030019188764 +1.7583912519298883,0.025094062051144405,0.07049933995135561,150585897268.83624,1576.7306984844304,0.0017691148113040415,7.327315204746838e-07,-0.025897249241152977,133377698.35273038,24.41974866167448,40.83298788482852 +1.9922434745333981,0.07701808187849038,0.07756755245216204,219344889732.8167,387.5516471728011,0.0059741041058747136,2.995378556826978e-06,-0.0015547348521333635,9997015.741973927,93.42967888072982,0.6025400528612463 +2.9755934765842085,0.03475764166615617,0.05659374519233602,170948948382.8667,905.0359449688766,0.001967065115940964,5.25018202556666e-07,-0.08855734201116047,145145476.28413817,45.94751109703079,80.14757771100261 +1.3094892725297502,0.07093992867841134,0.09238199660591137,195849893830.10547,1852.1065781860923,0.006553572250392591,4.660918768946262e-06,-0.0015186429556610496,24035531.69588962,67.36738558530635,2.8126886080958 +1.8981167520140687,0.038721206016067364,0.13766089031344603,121475057997.73196,1454.1325339922068,0.005330395694182196,8.417814504873018e-06,-0.003241641893466258,22568782.972723186,79.42404989162846,4.713776940841385 +1.3574827743366096,0.03447660181414077,0.1421915841094498,114059411442.89397,1911.9193096375288,0.004902282626663407,8.25971162199173e-06,-0.0016922139691061726,22339986.12159546,52.23989913190785,3.2353765635724563 +2.3614452255990823,0.056194595351063276,0.15040608205209757,170741500984.13037,1146.7202174889098,0.008452008919256444,1.593343809830657e-05,-0.0018502109703872754,12780894.536866957,156.67780545642705,2.1216743263628635 +1.2835769902090703,0.03370516955796104,0.06515841870363565,117335088497.90169,1786.1736193459988,0.0021961755505346594,7.770104836812091e-07,-0.013810627192923274,96130069.42665954,22.128839164539308,24.668177958622035 +2.844162452744898,0.07675297437317298,0.09281253990843882,128746469431.01382,1804.5579421198604,0.0071236384971014996,5.113684640101157e-06,-0.02102052230264616,46576617.9910677,159.04716335201582,37.932750468747784 +1.7485215564374665,0.028478863066171697,0.1012479627143423,153814855172.39337,1078.618158304916,0.002883426865870612,2.4632034319057656e-06,-0.005072953519384799,38760978.732904285,39.57761214637765,5.471779782245274 +1.6367506690640443,0.031673975209392455,0.10710018393981813,126460361692.25813,804.6497488754809,0.0033922885710311714,3.2425895336681313e-06,-0.002868050609130363,21749919.389263835,43.585795118104606,2.3077762023989163 +2.8593012729562552,0.039935660012452856,0.06986382492509467,187784073528.28076,1274.6650253987286,0.0027900579593781107,1.134845387465724e-06,-0.04660785489340355,112186772.41173568,62.624287757741776,59.409402541480496 +1.2894122392511047,0.07003745071138104,0.07587294613660116,182345905342.1501,1666.9298081007958,0.005313947725369472,2.549235324139676e-06,-0.0025625094730750667,31985719.588014014,53.787173501280925,4.271523424209492 +2.5007519169881003,0.02472886936083557,0.10324005577111053,198895061932.1864,1528.157161891395,0.0025530098519691707,2.2676065649571843e-06,-0.017663098512832365,86993972.04948102,50.11788760900107,26.991990493578026 +2.575713827835502,0.06074189800573748,0.13939833856790468,178616507363.1539,571.5920948948395,0.008467319663460928,1.3711340152901193e-05,-0.0013294018832493678,7483961.0802418385,171.20372988375613,0.759875607403651 +2.3127451009760245,0.03796653861409344,0.12617502498012312,193413776489.3423,1101.938323212827,0.004790428958042049,6.355357079950439e-06,-0.003696510805289008,25298134.89069752,86.97047266864062,4.073326918518267 +1.0867103616976994,0.058812886692577795,0.07546259379684746,112590649803.91116,1594.1365388368088,0.004438172978502014,2.106136119624612e-06,-0.0028757883752170105,31035205.190956067,37.8606172175473,4.584399326895575 +2.1545894431208374,0.04435398512393907,0.13450475818736907,114782616266.9025,1729.1938392131376,0.00596582204374159,8.99423736084342e-06,-0.0055843874647924854,27858101.40911099,100.9030929806182,9.656488399898238 +2.9161579355363765,0.047608153993703106,0.06288634706975409,185042648477.68683,564.93738666509,0.0029939028953983125,9.866638140493067e-07,-0.025578268905592966,52501115.328370355,68.53594544013282,14.450120390942622 +2.000970817284346,0.03692262640850094,0.10948735191872908,155690935171.92496,1166.5925641960014,0.004042560591351303,4.038342930675903e-06,-0.004955114455666087,31643952.152809307,63.49900929762199,5.780599678720175 +2.5257575434441066,0.049619483679336954,0.15926963098288882,128319429680.0518,347.56102305797657,0.007902876855169471,1.670590149549538e-05,-0.0008708080825570729,4184617.698189847,156.69189403005493,0.30265894806069116 +1.8742592357417815,0.06944088769281627,0.0986166055119026,165387780700.4831,1036.5394879150635,0.006848024627998794,5.549887332477966e-06,-0.002478369121151686,17260421.557063296,100.75454123406925,2.5689274597030747 +1.2481383635438636,0.07808945345119186,0.04746855694708957,209349288623.42667,1479.3639341378,0.003706793668115001,6.96032029338957e-07,-0.006580232727711145,62962877.45763502,36.318742355889135,9.734558975609067 +1.275326027497872,0.0321316650017885,0.1304934908462004,175661232869.35196,1412.3338300614255,0.004192973132784065,5.950021456310129e-06,-0.0009342977827756744,19751446.500165913,41.97715098538465,1.3195403659654659 +2.9333622363830667,0.07107695027394666,0.05409851593936968,158363799031.54465,1518.188155176256,0.0038451575273168895,9.377856727866733e-07,-0.08600860998612192,128452646.89814313,88.54203308606732,130.57725292410456 +1.3703702447732584,0.027947534902596813,0.07398694652962075,179263668998.52936,109.23131917603185,0.00206775277047314,9.432516503091359e-07,-0.0005541404200699495,5870601.936981746,22.243656931100304,0.060529489093001015 +1.1567270365111966,0.06386407424978399,0.10457363801910008,171509667256.70508,1405.6795964765386,0.0066784985830218415,6.086141230807372e-06,-0.0006947476967621571,13969067.724642783,60.64281901312218,0.9765926620376335 +2.950898911344147,0.0774297823386892,0.05496187934167775,211191670593.61926,178.12924338747985,0.004255686354351407,1.0713010128343616e-06,-0.006743551272841987,13483716.681648755,98.58108680611,1.20122368597602 +1.2245838548143226,0.046656130713799045,0.14458165377891735,214231300322.82773,1931.3916143840818,0.006745620537526405,1.175078924724582e-05,-0.00046963960449313805,14550403.131653015,64.84553730752617,0.9070579939007035 +2.654042778904257,0.06908777372321699,0.15664819627399507,177931027615.85968,1216.535491985191,0.010822475138327854,2.2130750799585856e-05,-0.0019252159470979626,11426992.90055299,225.47799912738665,2.342093529380555 +1.341647441716586,0.06518563156775864,0.1276817747379519,199710785525.73175,1296.2575257759631,0.008323017125985684,1.1307259608286994e-05,-0.00046208979063604963,9819090.191774791,87.65745388036989,0.5989873686962185 +2.119507188432695,0.0445034505665908,0.10768973330179513,169075513542.73682,441.7098327760789,0.004792564722525787,4.63164583863495e-06,-0.0017902118817806182,10883809.28827098,79.73932173631543,0.7907541909350664 +1.9833354161451644,0.026723614080772236,0.09760623526288784,164792672870.41846,1005.0305193964633,0.002608391363042477,2.070840412147525e-06,-0.007658821840670438,46976034.399680264,40.61052251049103,7.6973496924939875 +2.9830555649770574,0.07432930167586123,0.043372498172153065,186610332176.64044,933.5025178522313,0.003223847501073705,5.053847335008139e-07,-0.08758313029815197,119492090.63789581,75.49279239541507,81.75907265470491 +2.2059194362242263,0.06750011366567416,0.05817678090920038,209145047096.00708,1830.4582337522565,0.003926939324074048,1.1075728938136135e-06,-0.028274023080775923,106046623.13896349,68.00071747180812,51.75441834950763 +2.372138401379674,0.04908727021159006,0.13277350189106182,216988375018.40353,834.7645356514713,0.006517488764265615,9.574627017102846e-06,-0.0017877404859739238,13729779.534080055,121.36402521945894,1.492342356639358 +2.12015487262597,0.03674782348618326,0.14563549833092412,129541030934.79411,1470.121780419921,0.005351787585987137,9.45915003841848e-06,-0.003811319465878837,23994186.74255478,89.07095544236498,5.603103758926898 +2.016354373847353,0.06773755385580861,0.0776701506922517,136685689093.0688,542.9227248488031,0.005261186015505169,2.644908830182127e-06,-0.004103775526658433,16073791.377157208,83.27606115680874,2.2280329911012284 +2.6656097913052337,0.040681731393672725,0.04307193176899242,143020562662.46283,1208.9931467032854,0.0017522407588327486,2.7089515186277925e-07,-0.19701196119820402,256202601.8744024,36.66570246922919,238.18611090720225 +2.7834542390347785,0.026360463582715998,0.14929065592923285,152346166625.45764,240.32891356571506,0.003935370898862327,7.30919714808926e-06,-0.001551442980384774,6831609.964977957,85.98830976330774,0.3728566059350277 +1.2116712982771045,0.07911760326114853,0.11039028427685474,122653054422.75237,942.102232455435,0.008733814715301595,8.86919966074582e-06,-0.0005135330986130388,7103939.887531149,83.07272402775914,0.4838006786431009 +1.3743417983180422,0.07603784896307911,0.05026209108819763,130676803187.83884,1976.5708030753901,0.003821821290732896,8.045818570399575e-07,-0.016266969392967665,84849141.58795275,41.23203665261483,32.15281675666089 +2.0690065573514795,0.02248557684543056,0.14406425226894867,151244101150.64072,341.9052137577071,0.003239367815072939,5.602623980454012e-06,-0.0011912424166293971,9094992.56856866,52.61284502081509,0.4072919930949216 +2.6751069756974353,0.06320983032140148,0.09257444072869406,100880770512.19016,362.8125614439693,0.00585161469055939,4.1790413614801725e-06,-0.00549160714740934,10749986.04889828,122.88150793080075,1.992424055595592 +2.161070148505975,0.05567257776565458,0.09899735765114717,200734643086.90546,831.3373741828162,0.005511438092427811,4.5012267733521605e-06,-0.0030953475561482768,19756448.814332534,93.49824404454299,2.573278109511506 +1.9431887660878542,0.058471811981890304,0.10969641005462585,212336367484.17975,1817.640943138909,0.006414147863802424,6.431948395661203e-06,-0.0032551040880256102,30119166.95645154,97.84161557279313,5.916610464574188 diff --git a/FESurrogateModelTutorial/data/reference/beam2d_lhs_300_metadata.json b/FESurrogateModelTutorial/data/reference/beam2d_lhs_300_metadata.json new file mode 100644 index 0000000..5a6da1b --- /dev/null +++ b/FESurrogateModelTutorial/data/reference/beam2d_lhs_300_metadata.json @@ -0,0 +1,36 @@ +{ + "dataset_name": "beam2d_lhs_300", + "sample_count": 300, + "random_seed": 20260521, + "unit_system": "SI", + "fea_model": "2D Euler-Bernoulli beam/frame, linear static", + "target_columns": [ + "tip_uy_m", + "max_abs_bending_stress_pa", + "mass_kg", + "compliance_j" + ], + "parameter_bounds": { + "L_m": { + "lower": 1.0, + "upper": 3.0 + }, + "b_m": { + "lower": 0.02, + "upper": 0.08 + }, + "h_m": { + "lower": 0.04, + "upper": 0.16 + }, + "E_pa": { + "lower": 100000000000.0, + "upper": 220000000000.0 + }, + "P_n": { + "lower": 100.0, + "upper": 2000.0 + } + }, + "notes": "Generated by notebooks/00_beam2d_fea_dataset.ipynb using src/femsurrogate." +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/docs/ADR.md b/FESurrogateModelTutorial/docs/ADR.md new file mode 100644 index 0000000..189f810 --- /dev/null +++ b/FESurrogateModelTutorial/docs/ADR.md @@ -0,0 +1,76 @@ +# Architecture Decision Records + +## 철학 +이 프로젝트는 연구용 개념 전달과 재현성을 우선한다. 구현은 작고 명시적이어야 하며, notebook 교육 흐름과 테스트 가능한 Python 모듈 사이의 경계를 분명히 둔다. + +--- + +## ADR-001: Python tutorial project로 구성 +**결정**: Python `>=3.12,<3.15` 기반의 문서 + notebook + src package 구조를 사용한다. + +**이유**: 대상 사용자는 FEM/CAE 연구자이며 Python scientific stack에 익숙하다. NumPy, SciPy, scikit-learn, pandas, matplotlib로 FEM 데이터 생성과 surrogate 모델링을 모두 재현할 수 있다. + +**트레이드오프**: 브라우저 대시보드 기반 상호작용은 제공하지 않는다. 시각적 결과는 notebook과 저장된 figure로 제한한다. + +--- + +## ADR-002: `uv`와 `pyproject.toml` 사용 +**결정**: 의존성 관리는 `uv`, 프로젝트 설정은 `pyproject.toml`, lock은 `uv.lock`으로 관리한다. + +**이유**: 튜토리얼 재현성을 높이고, pytest/ruff/jupyter 실행 명령을 하나의 환경에서 고정하기 쉽다. + +**트레이드오프**: conda 기반 연구실 환경보다 초기 학습 비용이 있을 수 있다. 대신 repository-local workflow가 단순해진다. + +--- + +## ADR-003: 자체 2D Euler-Bernoulli beam/frame solver 사용 +**결정**: FEM 데이터는 NumPy/SciPy로 구현한 2-node 2D Euler-Bernoulli beam/frame element에서 생성한다. + +**이유**: 외부 CAE solver 없이 해석 데이터 생성 과정을 완전히 설명할 수 있다. 요소 강성, 조립, 경계조건, 응답 계산이 surrogate 학습 데이터와 직접 연결된다. + +**트레이드오프**: 산업용 고충실도 해석과 차이가 있다. v1은 선형 정적, slender beam 가정에 제한된다. + +--- + +## ADR-004: Notebook은 orchestration layer로 제한 +**결정**: 핵심 계산과 학습 helper는 `src/femsurrogate/`에 두고 notebook은 설명, 호출, 시각화, 해석을 담당한다. + +**이유**: notebook hidden state 문제를 줄이고, pytest로 핵심 로직을 검증할 수 있다. 모델별 notebook도 같은 helper를 사용하므로 비교 조건이 일관된다. + +**트레이드오프**: 독자가 처음 볼 파일 수는 늘어난다. 대신 코드 재사용성과 검증 가능성이 높아진다. + +--- + +## ADR-005: scikit-learn 중심 surrogate 모델 +**결정**: Response Surface, Gaussian Process/Kriging, Random Forest, Gradient Boosting, MLP를 모두 scikit-learn API로 구현한다. + +**이유**: 공통 `fit/predict` 인터페이스를 사용하면 모델별 비교가 단순하다. 연구자에게 익숙하고 설치 부담이 낮다. + +**트레이드오프**: PyTorch 기반 deep learning, GPU 학습, 복잡한 Bayesian optimization framework는 제외한다. + +--- + +## ADR-006: 기준 데이터셋은 CSV + metadata JSON +**결정**: 기준 FEM 데이터는 CSV로 저장하고, 생성 조건은 JSON metadata로 별도 저장한다. + +**이유**: CSV는 notebook, pandas, spreadsheet, 다른 ML 도구에서 쉽게 읽을 수 있다. Metadata JSON은 seed, bounds, 단위, target 정보를 명확히 남긴다. + +**트레이드오프**: 대용량/버전관리형 데이터셋에는 Parquet, DVC, database가 더 적합할 수 있다. v1에서는 단순성과 가독성을 우선한다. + +--- + +## ADR-007: 모델별 이론 문서와 모델별 notebook 분리 +**결정**: 각 surrogate 모델은 별도 이론 문서와 별도 실습 notebook으로 구성한다. 최종 비교는 별도 notebook에서 수행한다. + +**이유**: 모델별 가정, 장점, 실패 모드, hyperparameter가 다르므로 독립적으로 학습하는 편이 좋다. 최종 비교 notebook은 같은 입력 데이터와 metric을 기준으로 모델 선택을 돕는다. + +**트레이드오프**: notebook 수가 늘어난다. 대신 각 notebook의 목표가 분명하고 실행 실패 범위가 작아진다. + +--- + +## ADR-008: Notebook 중심 범위 고정 +**결정**: 브라우저 앱과 디자인 시스템 계획을 프로젝트 범위에서 제외한다. + +**이유**: 튜토리얼은 notebook 기반이다. 브라우저 앱 설계 지침은 현재 목표에 직접 기여하지 않으며 미래 agent에게 잘못된 구현 방향을 줄 수 있다. + +**트레이드오프**: 웹 기반 인터랙티브 학습 환경은 제공하지 않는다. diff --git a/FESurrogateModelTutorial/docs/ARCHITECTURE.md b/FESurrogateModelTutorial/docs/ARCHITECTURE.md new file mode 100644 index 0000000..dbabc69 --- /dev/null +++ b/FESurrogateModelTutorial/docs/ARCHITECTURE.md @@ -0,0 +1,226 @@ +# 아키텍처 + +## 개요 +이 프로젝트는 문서와 Jupyter notebook이 중심인 교육용 Python 프로젝트다. 재사용 가능한 계산 로직은 `src/femsurrogate/`에 모듈화하고, notebook은 설명, 시각화, 실행 순서, 결과 해석을 담당한다. + +## 기술 스택 +- Python `>=3.12,<3.15` +- `uv` + `pyproject.toml` + `uv.lock` +- NumPy, SciPy +- pandas, matplotlib +- scikit-learn +- JupyterLab, ipykernel, nbconvert +- pytest, ruff +- joblib, CSV, JSON + +## 디렉토리 구조 +```text +docs/ + PRD.md + ARCHITECTURE.md + ADR.md + theory/ + 00_surrogate_modeling_for_fem.md + 01_doe_sampling_validation.md + 02_response_surface_methodology.md + 03_gaussian_process_kriging.md + 04_random_forest.md + 05_gradient_boosting.md + 06_mlp_neural_network.md + +BeamExamples/ + CantileverBeam.txt + CantileverBeam_Displacements.txt + +notebooks/ + 00_beam2d_fea_dataset.ipynb + 01_response_surface_surrogate.ipynb + 02_gaussian_process_kriging_surrogate.ipynb + 03_random_forest_surrogate.ipynb + 04_gradient_boosting_surrogate.ipynb + 05_mlp_surrogate.ipynb + 06_compare_surrogate_models.ipynb + +src/femsurrogate/ + fea/ + element.py + model.py + io.py + assembly.py + solver.py + responses.py + benchmark.py + data/ + bounds.py + sampling.py + dataset.py + schema.py + surrogates/ + common.py + rsm.py + gpr.py + random_forest.py + boosting.py + mlp.py + registry.py + plotting/ + diagnostics.py + comparison.py + +tests/ +data/ + reference/ + processed/ +reports/ + results/ + predictions/ + figures/ +``` + +## 핵심 모듈 책임 +### `femsurrogate.fea` +2D Euler-Bernoulli beam/frame 요소 기반 선형 정적 해석을 담당한다. + +- `element.py`: local 6x6 stiffness matrix와 좌표 변환 행렬. +- `model.py`: node, element, support, load, material, section, parameter dataclass. +- `io.py`: `BeamExamples/*.txt`의 간단한 텍스트 fixture parser. +- `assembly.py`: global sparse stiffness matrix 조립. +- `solver.py`: 경계조건 적용과 `K u = f` 풀이. +- `responses.py`: tip displacement, bending stress, mass, compliance 계산. +- `benchmark.py`: cantilever analytical solution 등 검증 helper. + +### `femsurrogate.data` +입력 공간 정의, 샘플링, batch 해석, dataset schema를 담당한다. + +- `bounds.py`: 설계변수 범위와 단위. +- `sampling.py`: Latin Hypercube Sampling. +- `dataset.py`: 샘플별 FEM 실행과 CSV/metadata 저장. +- `schema.py`: 컬럼명, target명, 단위, split seed. + +### `femsurrogate.surrogates` +scikit-learn 기반 모델 생성, 학습, 평가를 담당한다. + +- `common.py`: train/test split, scaling, metric, timing, JSON 저장. +- `rsm.py`: `PolynomialFeatures` + `Ridge`. +- `gpr.py`: `GaussianProcessRegressor`. +- `random_forest.py`: `RandomForestRegressor`. +- `boosting.py`: `GradientBoostingRegressor`. +- `mlp.py`: `MLPRegressor`. +- `registry.py`: notebook에서 모델명을 통해 builder를 가져오는 작은 registry. + +### `femsurrogate.plotting` +모델 진단과 최종 비교 그림을 담당한다. + +- `diagnostics.py`: parity plot, residual plot, error histogram. +- `comparison.py`: 모델별 metric table, bar plot, prediction-time comparison. + +## 주요 인터페이스 +```python +def run_beam2d_case(params: BeamParameters) -> AnalysisResult: + ... + +def generate_lhs_samples(bounds: ParameterBounds, n: int, seed: int) -> pd.DataFrame: + ... + +def build_dataset(samples: pd.DataFrame) -> pd.DataFrame: + ... + +def make_model(model_name: str, random_state: int): + ... + +def evaluate_model(model, X_train, X_test, y_train, y_test) -> MetricsReport: + ... +``` + +## 데이터 흐름 +```text +Parameter bounds + -> Latin Hypercube samples + -> Beam2D FEM batch analysis + -> data/reference/beam2d_lhs_300.csv + -> model-specific notebooks + -> reports/results/_metrics.json + -> reports/predictions/_predictions.csv + -> notebooks/06_compare_surrogate_models.ipynb +``` + +## FEM 해석 설계 +- 요소: 2-node 2D Euler-Bernoulli frame element. +- 노드 DOF: `[ux, uy, rz]`. +- Local stiffness matrix: axial `EA/L` 항과 bending `EI` 항을 포함한 6x6 matrix. +- Global assembly: sparse matrix 기반. +- Solver: constrained DOF 제거 후 `scipy.sparse.linalg.spsolve`. +- 기준 검증: cantilever tip displacement `P L^3 / (3 E I)`. + +## Solver Verification Fixture +`BeamExamples/`의 cantilever 예제는 solver 구현의 canonical regression fixture다. + +```text +BeamExamples/CantileverBeam.txt +BeamExamples/CantileverBeam_Displacements.txt +``` + +입력 파일은 다음 항목을 포함한다. + +- Section/material metadata: `Area`, `J`, `Iyy`, `Izz`, `ElasticModulus`, `Poisson'sRatio`. +- Geometry: `Node, NodeID, X, Y`. +- Connectivity: `Beam, BeamID, NodeID1, NodeID2`. +- Boundary condition: `Fix, NodeID`. +- Load: `NodeLoad, NodeID, Fx, Fy, Mz`. + +2D in-plane Euler-Bernoulli frame solver는 `Area`, `Izz`, `ElasticModulus`, node coordinates, beam connectivity, fixed nodes, nodal loads를 사용한다. `J`, `Iyy`, `Poisson'sRatio`는 fixture metadata로 보존하되 v1 해석에는 사용하지 않는다. + +기준 변위 파일은 다음 형식을 가진다. + +```text +# NodeID, Ux, Uy, Rz +1 0.000000, 0.000000, 0.000000 +... +``` + +검증 test는 solver 결과의 모든 node별 `[Ux, Uy, Rz]`를 기준 파일과 비교한다. 기준 파일 값은 소수점 6자리로 반올림되어 있으므로 기본 허용오차는 `atol=5e-7`, `rtol=1e-6`로 둔다. Tip displacement는 별도로 해석해 `P L^3 / (3 E I)`와 비교해 부호와 크기를 확인한다. + +## Dataset Schema +기본 dataset은 SI 단위 컬럼을 사용한다. + +```text +L_m +b_m +h_m +E_pa +P_n +A_m2 +I_m4 +tip_uy_m +max_abs_bending_stress_pa +mass_kg +compliance_j +``` + +Metadata JSON에는 다음을 포함한다. + +```text +dataset_name +created_by +sample_count +random_seed +parameter_bounds +target_columns +unit_system +fea_model +notes +``` + +## Notebook 책임 +- Notebook은 한 번에 위에서 아래로 실행 가능해야 한다. +- Notebook 셀에 핵심 FEM 또는 ML helper 구현을 길게 두지 않는다. +- 각 모델별 notebook은 같은 dataset, target, split seed를 사용한다. +- 각 모델별 notebook은 metric JSON과 prediction CSV를 저장한다. +- 최종 비교 notebook은 이전 notebook 결과물을 읽어 비교만 수행한다. + +## 검증 전략 +- Unit tests: FEM element, solver, dataset generation, model factory. +- Regression tests: `BeamExamples/CantileverBeam.txt`를 해석하고 `BeamExamples/CantileverBeam_Displacements.txt`와 비교. +- Integration tests: 작은 sample count로 dataset 생성과 모든 surrogate smoke test. +- Notebook tests: `nbconvert --execute`로 순차 실행. +- Documentation checks: 문서의 경로와 notebook/file 이름이 실제 구조와 일치하는지 검토. diff --git a/FESurrogateModelTutorial/docs/PRD.md b/FESurrogateModelTutorial/docs/PRD.md new file mode 100644 index 0000000..ff23e8f --- /dev/null +++ b/FESurrogateModelTutorial/docs/PRD.md @@ -0,0 +1,60 @@ +# PRD: FEM Surrogate Tutorial + +## 목표 +FEM 기반 구조해석 데이터로 surrogate model을 구축하고 검증하는 전체 절차를 학습할 수 있는 한국어 튜토리얼을 만든다. 독자는 2D beam 요소로 해석 데이터를 직접 생성하고, 여러 surrogate 모델을 같은 데이터셋에서 학습, 평가, 비교한다. + +## 사용자 +- FEM과 선형 정적 구조해석의 기본 개념을 아는 대학원생. +- CAE 해석 결과를 기계학습 모델로 근사하고 싶은 연구자. +- surrogate 기반 설계 탐색, 민감도 분석, 최적화의 입문 파이프라인을 재현하고 싶은 엔지니어. + +## 사용자 문제 +고정밀 구조해석은 반복 설계, 불확실성 분석, 최적화에서 계산 비용이 커진다. Surrogate model은 해석 데이터에서 입력 변수와 응답의 관계를 학습하여 빠른 근사 예측을 제공하지만, 모델별 가정, 데이터 설계, 검증 방법, 외삽 위험을 함께 이해해야 실무적으로 사용할 수 있다. + +## 학습 성과 +튜토리얼을 완료한 독자는 다음을 할 수 있어야 한다. + +1. 2D Euler-Bernoulli beam/frame 요소를 이용해 선형 정적 FEM 해석 데이터를 생성한다. +2. DOE와 Latin Hypercube Sampling으로 입력 공간을 정의하고 해석 케이스를 만든다. +3. Response Surface, Gaussian Process/Kriging, Random Forest, Gradient Boosting, MLP를 같은 데이터셋에 적용한다. +4. RMSE, MAE, R2, residual, parity plot, uncertainty 또는 feature importance로 모델을 진단한다. +5. 최종 비교표를 통해 구조해석 surrogate 문제에 맞는 모델을 선택하는 기준을 설명한다. + +## 핵심 산출물 +- `docs/theory/`: surrogate 모델별 이론 문서와 출처. +- `notebooks/`: 데이터 생성, 모델별 학습, 최종 비교 notebook. +- `src/femsurrogate/`: notebook에서 재사용하는 FEM, 데이터, 모델, plotting helper. +- `tests/`: FEM 공식 검증, dataset schema, surrogate pipeline smoke test. +- `BeamExamples/`: 2D beam solver 검증용 cantilever 예제와 기준 변위 파일. +- `data/reference/`: 재현 가능한 기준 해석 데이터. +- `reports/`: 모델별 metric, 예측값, 그림. + +## MVP 범위 +- 선형 정적 2D Euler-Bernoulli beam/frame 요소. +- 기준 구조: fixed-free cantilever beam 또는 간단한 planar frame. +- Solver 검증 기준 fixture: `BeamExamples/CantileverBeam.txt`와 `BeamExamples/CantileverBeam_Displacements.txt`. +- 입력 변수: `L`, `b`, `h`, `E`, `P`. +- 응답 변수: `tip_uy_m`, `max_abs_bending_stress_pa`, `mass_kg`, `compliance_j`. +- Surrogate 모델: + - Response Surface Methodology: polynomial features + Ridge regression. + - Gaussian Process Regression/Kriging. + - Random Forest Regressor. + - Gradient Boosting Regressor. + - Multi-layer Perceptron Regressor. + +## MVP 제외 사항 +- 상용 CAE 연동. +- 3D beam, shell, solid element. +- Timoshenko beam, geometric nonlinearity, material nonlinearity, contact, buckling, modal, transient analysis. +- PyTorch/TensorFlow 기반 deep learning framework. +- Bayesian optimization, active learning, MLflow, DVC, browser dashboard. +- 배포용 Python package publishing. + +## 성공 기준 +- 이론 문서가 모델별로 분리되어 있고 각 문서에 출처가 있다. +- FEM solver가 `BeamExamples/CantileverBeam.txt`를 해석했을 때 `BeamExamples/CantileverBeam_Displacements.txt`의 모든 노드 `Ux`, `Uy`, `Rz` 기준값과 허용오차 내에서 일치한다. +- `00_beam2d_fea_dataset.ipynb`가 데이터셋을 생성하고, 생성 데이터가 동일 seed에서 재현된다. +- 모든 모델별 notebook이 같은 dataset과 split을 사용한다. +- 최종 비교 notebook이 모든 모델의 metric JSON을 읽어 하나의 비교표와 그림을 만든다. +- FEM solver는 cantilever beam 해석해와 BeamExamples regression fixture를 모두 비교하는 pytest를 통과한다. +- 전체 notebook은 `nbconvert --execute`로 순서대로 실행 가능하다. diff --git a/FESurrogateModelTutorial/docs/theory/00_surrogate_modeling_for_fem.md b/FESurrogateModelTutorial/docs/theory/00_surrogate_modeling_for_fem.md new file mode 100644 index 0000000..34d7c52 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/00_surrogate_modeling_for_fem.md @@ -0,0 +1,98 @@ +# FEM 구조해석을 위한 Surrogate Modeling 개요 + +## 목적 +Surrogate model은 계산 비용이 큰 해석 모델을 반복 호출하기 어려울 때, 입력 변수와 출력 응답 사이의 관계를 데이터 기반으로 근사하는 모델이다. FEM 구조해석에서는 설계변수 변화에 따른 변위, 응력, 질량, compliance 같은 응답을 빠르게 예측하기 위해 사용한다. + +이 튜토리얼의 surrogate는 고충실도 산업용 solver를 대체하는 범용 도구가 아니라, 제한된 입력 범위 안에서 반복 설계 탐색과 모델 비교를 학습하기 위한 교육용 근사 모델이다. + +## 문제 정의 +FEM 해석 모델을 다음 함수로 본다. + +```text +y = f(x) +``` + +- `x`: 설계변수 벡터. 예: beam 길이 `L`, 폭 `b`, 높이 `h`, 탄성계수 `E`, 하중 `P`. +- `f`: FEM solver. 이 튜토리얼에서는 2D Euler-Bernoulli beam/frame solver. +- `y`: 응답. 예: tip displacement, maximum bending stress, mass, compliance. + +Surrogate model은 해석 데이터 `{x_i, y_i}`를 이용해 `f`를 근사하는 `\hat{f}`를 만든다. + +```text +\hat{y} = \hat{f}(x) +``` + +좋은 surrogate는 빠른 예측뿐 아니라, 어떤 영역에서 믿을 수 있고 어떤 영역에서 위험한지도 설명할 수 있어야 한다. + +## 왜 FEM에서 필요한가 +구조해석에서는 다음 작업이 반복 평가를 요구한다. + +- 설계변수 sweep. +- 민감도 분석. +- 최적화. +- 불확실성 전파. +- 신뢰성 분석. +- inverse problem 또는 model calibration. + +Queipo et al.은 surrogate-based analysis and optimization이 비싼 high-fidelity model의 계산 부담을 줄이고 민감도/최적화 연구를 가능하게 한다고 정리한다. Forrester와 Keane도 긴 계산 시간이 필요한 aerospace design evaluation에서 surrogate 기반 방법이 효율적 최적화를 가능하게 하는 배경을 설명한다. + +## 전체 workflow +```text +1. 해석 문제 정의 +2. 입력 변수와 범위 설정 +3. DOE로 샘플 생성 +4. FEM batch 해석 +5. 데이터 정리와 train/test split +6. surrogate 모델 학습 +7. 검증과 진단 +8. 모델 비교 +9. 설계 탐색 또는 최적화에 활용 +``` + +이 workflow에서 가장 중요한 판단은 데이터의 범위다. Surrogate는 학습한 입력 영역 안에서만 신뢰할 수 있다. 학습 범위를 벗어난 외삽은 모델별로 다르게 실패하며, 예측값이 그럴듯해 보여도 구조적으로 틀릴 수 있다. + +## 이 튜토리얼의 FEM 모델 +기준 해석 모델은 2D Euler-Bernoulli beam/frame element이다. + +- 노드당 자유도: `ux`, `uy`, `rz`. +- 요소: 축방향 변형과 굽힘 변형을 포함한 2-node frame element. +- 해석: 선형 정적 해석. +- solver: sparse global stiffness matrix를 조립하고 `scipy.sparse.linalg.spsolve`로 선형 시스템을 푼다. +- 검증: cantilever beam tip displacement 해석해 `P L^3 / (3 E I)`와 비교한다. + +이 선택은 학습 목적에 맞다. Beam 요소는 수식이 충분히 작아 직접 구현할 수 있고, 단면 형상과 하중 변화가 변위/응력에 비선형적인 영향을 주므로 surrogate 비교에도 적합하다. + +## 모델 비교 관점 +이 튜토리얼은 다음 질문에 답하도록 설계한다. + +| 모델 | 핵심 질문 | +| --- | --- | +| Response Surface | 낮은 차수 다항식으로 구조 응답을 충분히 설명할 수 있는가? | +| Gaussian Process/Kriging | 적은 데이터에서 예측 불확실성을 함께 얻을 수 있는가? | +| Random Forest | 비선형성과 변수 상호작용을 튼튼하게 포착하는가? | +| Gradient Boosting | 작은 tree를 순차적으로 더해 높은 정확도를 얻는가? | +| MLP | scaling과 충분한 데이터가 있을 때 매끄러운 비선형 근사가 가능한가? | + +## 검증 원칙 +Surrogate 검증은 단순히 test R2 하나로 끝내지 않는다. + +- Train/test split은 고정 seed로 재현한다. +- K-fold cross validation으로 표본 의존성을 확인한다. +- RMSE, MAE, R2를 함께 본다. +- Parity plot으로 편향과 outlier를 본다. +- Residual plot으로 입력 영역별 오류 구조를 본다. +- 모델별 uncertainty 또는 feature importance를 해석한다. +- 해석 데이터의 물리 단위와 target 정의를 metadata에 남긴다. + +## 실패 모드 +- 학습 영역 밖에서 외삽한다. +- 입력 변수 범위가 물리적으로 의미 없는 조합을 포함한다. +- 응력처럼 국소적이고 비선형적인 응답을 너무 단순한 모델로 근사한다. +- train/test split이 샘플링 편향을 숨긴다. +- feature scaling을 하지 않아 MLP나 GPR 학습이 불안정하다. +- 예측 정확도만 보고 물리 제약을 확인하지 않는다. + +## References +- Queipo, N. V. et al. (2005), "Surrogate-based analysis and optimization", Progress in Aerospace Sciences. https://doi.org/10.1016/j.paerosci.2005.02.001 +- Forrester, A. I. J. and Keane, A. J. (2009), "Recent advances in surrogate-based optimization", Progress in Aerospace Sciences. https://doi.org/10.1016/j.paerosci.2008.11.001 +- SciPy `spsolve` documentation. https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.spsolve.html diff --git a/FESurrogateModelTutorial/docs/theory/01_doe_sampling_validation.md b/FESurrogateModelTutorial/docs/theory/01_doe_sampling_validation.md new file mode 100644 index 0000000..9ec8560 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/01_doe_sampling_validation.md @@ -0,0 +1,114 @@ +# DOE, Sampling, Validation + +## 목적 +Surrogate model의 품질은 모델 종류만큼이나 데이터 설계에 좌우된다. FEM surrogate에서는 입력 변수 범위, 샘플링 방법, train/test split, 검증 지표가 모두 모델 해석에 영향을 준다. + +## 입력 공간 정의 +이 튜토리얼의 기본 입력 변수는 다음과 같다. + +| 변수 | 의미 | 단위 | 예시 범위 | +| --- | --- | --- | --- | +| `L_m` | beam length | m | 1.0-3.0 | +| `b_m` | rectangular section width | m | 0.02-0.08 | +| `h_m` | rectangular section height | m | 0.04-0.16 | +| `E_pa` | Young's modulus | Pa | 100e9-220e9 | +| `P_n` | tip point load magnitude | N | 100-2000 | + +파생 변수는 FEM 해석 직전에 계산한다. + +```text +A = b h +I = b h^3 / 12 +``` + +`h`는 bending stiffness에 세제곱으로 들어가므로, tip displacement와 bending stress에 큰 영향을 준다. 이런 구조적 비선형성 때문에 단순 선형 회귀보다 다양한 surrogate 비교가 의미를 가진다. + +## Latin Hypercube Sampling +Latin Hypercube Sampling(LHS)은 각 입력 변수의 marginal distribution을 층화하여, 적은 샘플에서도 각 변수 범위를 비교적 고르게 덮도록 설계한다. McKay, Beckman, Conover의 1979년 논문은 computer code output 분석에서 sampling plan을 비교한 고전적 출처다. + +SciPy의 `scipy.stats.qmc.LatinHypercube`는 `[0, 1)^d` 단위 hypercube에 샘플을 만들고, 이후 사용자가 물리 범위로 scaling한다. + +```text +u ~ LHS([0,1)^d) +x_j = lower_j + u_j (upper_j - lower_j) +``` + +이 튜토리얼은 다음을 기본값으로 사용한다. + +- `n_samples = 300` +- `seed = 20260521` +- `target = tip_uy_m` +- 동일 dataset과 동일 split을 모든 model notebook에서 사용 + +## Train/Test Split +단일 test set만으로 모델을 판단하면 샘플 배치에 민감할 수 있다. 따라서 다음 두 가지를 함께 사용한다. + +- Hold-out test set: 최종 성능 비교용. +- K-fold cross validation: 학습 데이터 안에서 모델 안정성 확인용. + +추천 기본값: + +```text +test_size = 0.2 +cv_folds = 5 +random_state = 20260521 +``` + +## 평가 지표 +### RMSE +큰 오차에 민감하다. 구조해석 surrogate에서 위험한 outlier를 확인하는 데 유용하다. + +```text +RMSE = sqrt(mean((y - y_hat)^2)) +``` + +### MAE +평균적인 절대 오차를 직관적으로 보여준다. + +```text +MAE = mean(abs(y - y_hat)) +``` + +### R2 +분산 설명력을 나타낸다. 다만 target scale이나 test set 분포에 따라 해석이 왜곡될 수 있으므로 RMSE/MAE와 함께 본다. + +```text +R2 = 1 - sum((y - y_hat)^2) / sum((y - mean(y))^2) +``` + +## Plot 기반 진단 +- Parity plot: 예측값과 실제값이 `y=x` 선 주변에 있는지 확인. +- Residual plot: 예측값 또는 주요 입력 변수에 따른 오차 패턴 확인. +- Error histogram: 오차 분포와 outlier 확인. +- Model comparison bar plot: RMSE, MAE, R2, 학습 시간, 예측 시간 비교. + +## Dataset Metadata +CSV만으로는 dataset의 생성 조건을 알기 어렵다. 따라서 metadata JSON을 함께 저장한다. + +```json +{ + "dataset_name": "beam2d_lhs_300", + "sample_count": 300, + "random_seed": 20260521, + "unit_system": "SI", + "fea_model": "2D Euler-Bernoulli beam/frame, linear static", + "target_columns": [ + "tip_uy_m", + "max_abs_bending_stress_pa", + "mass_kg", + "compliance_j" + ] +} +``` + +## 주의점 +- DOE 범위는 surrogate의 유효 영역이다. 범위 밖 예측은 별도 경고를 둔다. +- 물리적으로 불가능한 조합을 허용하지 않는다. +- FEM solver 실패 케이스는 조용히 버리지 말고 metadata에 기록한다. +- 단위가 섞이면 모델 비교가 무의미해진다. +- 동일 데이터셋을 쓰지 않으면 모델별 비교가 공정하지 않다. + +## References +- McKay, M. D., Beckman, R. J., and Conover, W. J. (1979), "Comparison of Three Methods for Selecting Values of Input Variables in the Analysis of Output from a Computer Code", Technometrics. https://doi.org/10.1080/00401706.1979.10489755 +- OSTI bibliographic record for McKay et al. https://www.osti.gov/biblio/5236110 +- SciPy `LatinHypercube` documentation. https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.qmc.LatinHypercube.html diff --git a/FESurrogateModelTutorial/docs/theory/02_response_surface_methodology.md b/FESurrogateModelTutorial/docs/theory/02_response_surface_methodology.md new file mode 100644 index 0000000..387a050 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/02_response_surface_methodology.md @@ -0,0 +1,77 @@ +# Response Surface Methodology + +## 핵심 아이디어 +Response Surface Methodology(RSM)는 입력 변수와 응답 사이의 관계를 낮은 차수 다항식으로 근사한다. 구조해석 surrogate에서는 물리 응답이 충분히 매끄럽고 입력 범위가 좁을 때 강력한 기준선이 된다. + +기본 2차 response surface는 다음 형태다. + +```text +y = beta_0 + sum beta_i x_i + sum beta_ii x_i^2 + sum beta_ij x_i x_j + epsilon +``` + +- `x_i`: scaling된 설계변수. +- `beta`: 회귀 계수. +- `x_i x_j`: 변수 상호작용. +- `epsilon`: 모델이 설명하지 못한 잔차. + +## FEM surrogate에서의 의미 +Beam 문제에서는 `I = b h^3 / 12` 때문에 응답이 입력 변수에 대해 비선형이다. 낮은 차수 다항식은 이 관계를 완벽히 재현하지 못할 수 있지만, 다음 장점이 있다. + +- 빠르다. +- 계수 해석이 가능하다. +- 적은 데이터에서도 안정적이다. +- 복잡한 모델의 baseline으로 적합하다. + +RSM은 특히 설계변수 범위가 좁고 응답이 단조롭고 매끄러울 때 유용하다. + +## 구현 선택 +이 튜토리얼에서는 scikit-learn의 `PolynomialFeatures`와 `Ridge`를 조합한다. + +```text +StandardScaler + -> PolynomialFeatures(degree=2, include_bias=False) + -> Ridge(alpha=...) +``` + +`PolynomialFeatures`는 입력 변수의 다항 조합을 만들고, `Ridge`는 L2 regularization으로 계수 크기를 제어한다. 일반 선형회귀 대신 Ridge를 쓰는 이유는 다항 feature가 늘어나면 feature 간 상관성이 커지고 계수가 불안정해질 수 있기 때문이다. + +## Notebook 실습 포인트 +`notebooks/01_response_surface_surrogate.ipynb`는 다음을 보여준다. + +1. 동일 FEM dataset 로드. +2. target `tip_uy_m` 선택. +3. degree 1, 2, 3 비교. +4. Ridge `alpha` 변화에 따른 train/test error 비교. +5. parity plot과 residual plot. +6. 다항 feature 이름과 계수 크기 해석. + +## 장점 +- 학습과 예측이 매우 빠르다. +- 작은 데이터에서 baseline으로 좋다. +- 변수 간 interaction을 명시적으로 볼 수 있다. +- extrapolation이 tree model보다 연속적이다. + +## 한계와 실패 모드 +- 실제 응답이 강하게 비선형이면 낮은 차수 다항식으로 부족하다. +- 높은 차수는 과적합과 수치 불안정을 만든다. +- 입력 scaling 없이 다항 feature를 만들면 큰 단위의 변수가 학습을 지배할 수 있다. +- 설계공간 경계 밖에서는 다항식이 물리적으로 말이 안 되는 큰 값을 낼 수 있다. + +## 구조해석 해석 기준 +RSM이 좋은 선택인 경우: + +- 설계변수 수가 적다. +- 입력 범위가 좁다. +- 응답이 매끄럽고 단조적이다. +- 설명 가능한 surrogate가 중요하다. + +RSM을 피해야 하는 경우: + +- 응답에 threshold, discontinuity, contact-like behavior가 있다. +- 입력 범위가 넓고 상호작용이 복잡하다. +- 국소 응력 peak처럼 고차 비선형성이 크다. + +## References +- Box, G. E. P. and Wilson, K. B. (1951), "On the Experimental Attainment of Optimum Conditions", Journal of the Royal Statistical Society Series B. https://doi.org/10.1111/j.2517-6161.1951.tb00067.x +- scikit-learn `PolynomialFeatures` documentation. https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html +- scikit-learn `Ridge` documentation. https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html diff --git a/FESurrogateModelTutorial/docs/theory/03_gaussian_process_kriging.md b/FESurrogateModelTutorial/docs/theory/03_gaussian_process_kriging.md new file mode 100644 index 0000000..7579346 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/03_gaussian_process_kriging.md @@ -0,0 +1,86 @@ +# Gaussian Process Regression and Kriging + +## 핵심 아이디어 +Gaussian Process Regression(GPR)은 함수값을 확률변수로 보고, 입력 위치 사이의 상관관계를 kernel로 정의한다. Engineering design 분야에서는 Kriging이라는 이름으로 널리 사용된다. + +GPR은 예측 평균뿐 아니라 예측 표준편차를 제공한다. + +```text +y(x) ~ GP(m(x), k(x, x')) +``` + +- `m(x)`: mean function. +- `k(x, x')`: covariance function 또는 kernel. +- 예측 결과: `mean`, `standard deviation`. + +## FEM surrogate에서의 의미 +FEM 해석 데이터는 deterministic한 computer experiment인 경우가 많다. 같은 입력을 넣으면 같은 출력이 나온다. 이런 상황에서 GPR/Kriging은 적은 해석점으로 매끄러운 응답면을 만들고, 학습점에서 먼 영역의 불확실성을 크게 줄 수 있어 surrogate 신뢰도 설명에 유리하다. + +Sacks et al.의 computer experiment 연구는 계산 모델 입력과 출력 사이의 응답을 stochastic process로 모델링하는 관점을 제시했다. Rasmussen과 Williams의 GPML은 Gaussian process의 수학적 기반과 kernel 관점을 체계적으로 설명한다. + +## 구현 선택 +이 튜토리얼에서는 다음 kernel을 기본으로 사용한다. + +```text +ConstantKernel * RBF + WhiteKernel +``` + +- `ConstantKernel`: 응답 scale. +- `RBF`: 입력 공간에서 가까운 점은 비슷한 응답을 가진다는 smoothness 가정. +- `WhiteKernel`: 수치 noise 또는 모델 불일치 허용. + +Notebook pipeline: + +```text +StandardScaler + -> GaussianProcessRegressor(kernel=..., normalize_y=True) +``` + +## Hyperparameter 의미 +- `length_scale`: 각 입력 방향에서 함수가 얼마나 빨리 변하는지 나타낸다. +- `noise_level`: 해석 데이터의 noise 또는 모델 불일치 허용량이다. +- `alpha`: numerical stability를 위한 diagonal jitter로 사용할 수 있다. +- `n_restarts_optimizer`: kernel hyperparameter 최적화의 local optimum 위험을 줄인다. + +## Notebook 실습 포인트 +`notebooks/02_gaussian_process_kriging_surrogate.ipynb`는 다음을 보여준다. + +1. 동일 dataset과 split 로드. +2. RBF kernel 기반 GPR 학습. +3. 예측 평균과 표준편차 계산. +4. parity plot에 uncertainty band 또는 error coloring 추가. +5. 입력 공간에서 학습점과 먼 샘플의 uncertainty 확인. +6. 학습 sample 수 증가에 따른 RMSE와 예측 표준편차 변화. + +## 장점 +- 작은 데이터에서 강력하다. +- 예측 uncertainty를 제공한다. +- Smooth response surface에 적합하다. +- Bayesian optimization과 active learning으로 확장하기 좋다. + +## 한계와 실패 모드 +- 기본 exact GPR은 학습 데이터 수가 커지면 계산 비용이 급격히 증가한다. +- Kernel 선택이 성능을 크게 좌우한다. +- 입력 scaling이 중요하다. +- 고차원 입력에서는 length scale 학습이 불안정할 수 있다. +- 예측 표준편차는 모델 가정 하의 불확실성이지, 모든 물리 오류를 보장하지 않는다. + +## 구조해석 해석 기준 +GPR/Kriging이 좋은 선택인 경우: + +- FEM 해석이 비싸고 sample 수가 작다. +- 응답이 매끄럽다. +- uncertainty 기반 추가 샘플링을 설명하고 싶다. +- 설계공간 탐색에서 신뢰도 경고가 필요하다. + +주의할 경우: + +- sample 수가 수천 개 이상이다. +- 응답이 불연속적이다. +- 입력 차원이 많고 상호작용이 복잡하다. + +## References +- Sacks, J., Welch, W. J., Mitchell, T. J., and Wynn, H. P. (1989), "Design and Analysis of Computer Experiments", Statistical Science. https://doi.org/10.1214/ss/1177012413 +- Sacks, J., Schiller, S. B., and Welch, W. J. (1989), "Designs for Computer Experiments", Technometrics. https://doi.org/10.1080/00401706.1989.10488474 +- Rasmussen, C. E. and Williams, C. K. I. (2006), Gaussian Processes for Machine Learning. https://gaussianprocess.org/gpml/chapters/RW.pdf +- scikit-learn Gaussian Process documentation. https://scikit-learn.org/stable/modules/gaussian_process.html diff --git a/FESurrogateModelTutorial/docs/theory/04_random_forest.md b/FESurrogateModelTutorial/docs/theory/04_random_forest.md new file mode 100644 index 0000000..3792caa --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/04_random_forest.md @@ -0,0 +1,76 @@ +# Random Forest Surrogate + +## 핵심 아이디어 +Random Forest는 여러 decision tree를 bootstrap sample로 학습하고 평균하여 예측하는 ensemble 모델이다. 각 tree는 입력 공간을 여러 영역으로 나누고, 회귀 문제에서는 leaf의 평균값을 예측한다. + +```text +\hat{f}(x) = (1 / T) sum_{t=1}^{T} h_t(x) +``` + +- `T`: tree 개수. +- `h_t`: 개별 regression tree. + +Breiman의 2001년 Random Forest 논문은 bagging과 random feature selection을 결합한 ensemble 방법을 제시했다. + +## FEM surrogate에서의 의미 +Random Forest는 비선형성과 변수 상호작용을 명시적 feature engineering 없이 포착할 수 있다. Beam surrogate에서는 `h`, `b`, `L`, `P`, `E`의 비선형 영향과 interaction을 빠르게 잡는 비교 모델로 좋다. + +## 구현 선택 +이 튜토리얼에서는 scikit-learn의 `RandomForestRegressor`를 사용한다. + +기본 설정 예: + +```text +RandomForestRegressor( + n_estimators=300, + max_depth=None, + min_samples_leaf=2, + random_state=20260521, + n_jobs=-1 +) +``` + +Tree model은 feature scaling이 필수는 아니지만, 다른 모델과 공통 전처리 구조를 설명하기 위해 입력 column 관리는 동일하게 유지한다. + +## Notebook 실습 포인트 +`notebooks/03_random_forest_surrogate.ipynb`는 다음을 보여준다. + +1. 동일 dataset과 split 로드. +2. `n_estimators`, `max_depth`, `min_samples_leaf` 영향 비교. +3. test metric 계산. +4. parity plot과 residual plot. +5. permutation importance로 입력 변수 영향 확인. +6. feature importance와 beam 물리 관계 비교. + +## 장점 +- scaling에 덜 민감하다. +- 비선형성과 interaction을 잘 포착한다. +- outlier에 비교적 튼튼하다. +- feature importance를 계산하기 쉽다. +- 학습이 안정적이고 baseline으로 좋다. + +## 한계와 실패 모드 +- 예측이 piecewise constant라 매끄러운 응답면이 필요한 경우 부자연스러울 수 있다. +- 학습 데이터 범위 밖으로 외삽하지 못한다. +- 물리적으로 단조여야 하는 관계를 자동으로 보장하지 않는다. +- uncertainty는 GPR처럼 원래 제공되는 개념이 아니다. +- feature importance는 correlated feature에서 오해될 수 있다. + +## 구조해석 해석 기준 +Random Forest가 좋은 선택인 경우: + +- 빠르고 안정적인 비선형 baseline이 필요하다. +- 변수 중요도 분석을 보고 싶다. +- 데이터가 중간 규모 이상이다. +- target이 매끄러운 global response다. + +주의할 경우: + +- 설계 최적화에서 매끄러운 gradient-like response가 필요하다. +- 외삽이 필요하다. +- 응답면의 국소 smoothness가 중요하다. + +## References +- Breiman, L. (2001), "Random Forests", Machine Learning, 45, 5-32. https://doi.org/10.1023/A:1010933404324 +- CiNii bibliographic record for Breiman (2001). https://cir.nii.ac.jp/crid/1360574092892023168 +- scikit-learn `RandomForestRegressor` documentation. https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html diff --git a/FESurrogateModelTutorial/docs/theory/05_gradient_boosting.md b/FESurrogateModelTutorial/docs/theory/05_gradient_boosting.md new file mode 100644 index 0000000..9205cf8 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/05_gradient_boosting.md @@ -0,0 +1,77 @@ +# Gradient Boosting Surrogate + +## 핵심 아이디어 +Gradient Boosting은 약한 예측기, 보통 shallow decision tree를 순차적으로 더해 손실함수를 줄이는 additive model이다. 각 단계의 tree는 이전 모델의 오차를 보정하는 방향으로 학습된다. + +```text +F_M(x) = F_0(x) + sum_{m=1}^{M} nu h_m(x) +``` + +- `F_M`: 최종 모델. +- `h_m`: m번째 weak learner. +- `nu`: learning rate. + +Friedman의 2001년 논문은 gradient boosting machine을 함수공간에서의 greedy approximation으로 설명한다. + +## FEM surrogate에서의 의미 +Gradient Boosting은 Random Forest보다 bias를 더 적극적으로 줄이는 경우가 많다. Beam surrogate에서 낮은 차수 RSM이 놓치는 비선형성을 tree ensemble이 순차적으로 보정할 수 있다. + +## 구현 선택 +이 튜토리얼에서는 scikit-learn의 `GradientBoostingRegressor`를 사용한다. + +기본 설정 예: + +```text +GradientBoostingRegressor( + loss="squared_error", + learning_rate=0.05, + n_estimators=300, + max_depth=3, + subsample=0.9, + random_state=20260521 +) +``` + +`learning_rate`와 `n_estimators`는 함께 조정해야 한다. 작은 learning rate는 더 많은 tree를 요구하지만 과적합을 완화할 수 있다. + +## Notebook 실습 포인트 +`notebooks/04_gradient_boosting_surrogate.ipynb`는 다음을 보여준다. + +1. 동일 dataset과 split 로드. +2. `learning_rate`와 `n_estimators` tradeoff. +3. staged prediction으로 train/test error curve 확인. +4. parity plot과 residual plot. +5. permutation importance 또는 built-in feature importance 비교. +6. Random Forest와 오차 패턴 비교. + +## 장점 +- tabular regression에서 높은 성능을 내기 쉽다. +- 비선형성과 interaction을 잘 포착한다. +- model size와 성능의 tradeoff를 조절하기 쉽다. +- staged prediction으로 과적합 진행을 관찰할 수 있다. + +## 한계와 실패 모드 +- hyperparameter에 Random Forest보다 민감하다. +- noise가 큰 데이터에서는 오차를 과도하게 따라갈 수 있다. +- tree 기반 모델이므로 외삽은 약하다. +- 학습 순서가 sequential하므로 Random Forest보다 병렬화 이점이 작다. +- 너무 복잡한 설정은 교육용 notebook을 흐리게 만들 수 있다. + +## 구조해석 해석 기준 +Gradient Boosting이 좋은 선택인 경우: + +- 정확도가 중요한 tabular surrogate가 필요하다. +- 입력 변수 수가 적거나 중간 규모다. +- 해석 응답이 비선형이지만 불연속은 아니다. +- 최종 비교에서 강한 classical ML baseline이 필요하다. + +주의할 경우: + +- uncertainty가 핵심 요구사항이다. +- 모델 해석성이 RSM 수준으로 필요하다. +- 데이터 수가 매우 적다. + +## References +- Friedman, J. H. (2001), "Greedy Function Approximation: A Gradient Boosting Machine", The Annals of Statistics. https://doi.org/10.1214/aos/1013203451 +- CiNii bibliographic record for Friedman (2001). https://cir.nii.ac.jp/crid/1360292617909870720 +- scikit-learn `GradientBoostingRegressor` documentation. https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html diff --git a/FESurrogateModelTutorial/docs/theory/06_mlp_neural_network.md b/FESurrogateModelTutorial/docs/theory/06_mlp_neural_network.md new file mode 100644 index 0000000..b2a99a2 --- /dev/null +++ b/FESurrogateModelTutorial/docs/theory/06_mlp_neural_network.md @@ -0,0 +1,90 @@ +# MLP Neural Network Surrogate + +## 핵심 아이디어 +Multi-layer Perceptron(MLP)은 affine transform과 nonlinear activation을 여러 층으로 쌓아 입력과 출력의 비선형 관계를 학습한다. + +단일 hidden layer MLP는 다음처럼 표현할 수 있다. + +```text +\hat{y} = W_2 phi(W_1 x + b_1) + b_2 +``` + +- `x`: scaling된 입력. +- `phi`: activation function. +- `W`, `b`: 학습되는 weight와 bias. + +Cybenko의 universal approximation theorem은 충분한 hidden unit을 가진 신경망이 넓은 함수군을 근사할 수 있음을 보인 고전적 결과다. 다만 "근사 가능하다"는 말이 적은 데이터에서 항상 잘 학습된다는 뜻은 아니다. + +## FEM surrogate에서의 의미 +MLP는 beam 응답처럼 매끄러운 비선형 함수를 학습할 수 있다. 그러나 데이터 수, scaling, regularization, optimization 설정에 민감하다. 이 튜토리얼에서는 deep learning framework를 도입하지 않고 scikit-learn의 `MLPRegressor`로 작은 neural surrogate의 특성을 보여준다. + +## 구현 선택 +기본 pipeline: + +```text +StandardScaler + -> TransformedTargetRegressor( + regressor=MLPRegressor(...), + transformer=StandardScaler() + ) +``` + +추천 기본 설정: + +```text +MLPRegressor( + hidden_layer_sizes=(64, 32), + activation="relu", + solver="adam", + alpha=1e-4, + learning_rate_init=1e-3, + early_stopping=True, + max_iter=2000, + random_state=20260521 +) +``` + +입력뿐 아니라 target scaling도 중요하다. FEM 응답은 단위와 scale이 크게 다를 수 있기 때문이다. + +## Notebook 실습 포인트 +`notebooks/05_mlp_surrogate.ipynb`는 다음을 보여준다. + +1. 동일 dataset과 split 로드. +2. 입력 scaling과 target scaling의 효과. +3. hidden layer 크기 비교. +4. train/test learning curve. +5. parity plot과 residual plot. +6. RSM, GPR, tree ensemble 대비 MLP의 장단점 정리. + +## 장점 +- 매끄러운 비선형 함수 근사에 적합하다. +- 다중 출력 surrogate로 확장하기 쉽다. +- 충분한 데이터와 tuning이 있으면 복잡한 관계를 학습할 수 있다. +- 동일 pipeline 구조로 다른 neural framework로 확장 가능하다. + +## 한계와 실패 모드 +- 작은 데이터에서는 과적합하거나 불안정할 수 있다. +- scaling 없이 학습하면 convergence가 나빠질 수 있다. +- hyperparameter 선택이 성능에 큰 영향을 준다. +- GPR처럼 기본 uncertainty를 제공하지 않는다. +- 모델 해석성이 낮다. + +## 구조해석 해석 기준 +MLP가 좋은 선택인 경우: + +- 데이터 수가 충분하다. +- 응답이 매끄럽지만 단순 다항식으로 부족하다. +- 다중 출력 또는 복잡한 feature representation 확장을 고려한다. +- 최종 정확도가 중요하고 해석성 요구가 낮다. + +주의할 경우: + +- sample 수가 매우 작다. +- 예측 불확실성이 중요하다. +- 실험자가 scaling과 validation을 엄격히 관리하기 어렵다. + +## References +- Cybenko, G. (1989), "Approximation by Superpositions of a Sigmoidal Function", Mathematics of Control, Signals, and Systems. https://doi.org/10.1007/BF02551274 +- CiNii bibliographic record for Cybenko (1989). https://cir.nii.ac.jp/crid/1361981471344652032 +- Glorot, X. and Bengio, Y. (2010), "Understanding the difficulty of training deep feedforward neural networks", AISTATS. https://proceedings.mlr.press/v9/glorot10a.html +- scikit-learn `MLPRegressor` documentation. https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPRegressor.html diff --git a/FESurrogateModelTutorial/notebooks/.gitkeep b/FESurrogateModelTutorial/notebooks/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/notebooks/00_beam2d_fea_dataset.ipynb b/FESurrogateModelTutorial/notebooks/00_beam2d_fea_dataset.ipynb new file mode 100644 index 0000000..41ef2de --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/00_beam2d_fea_dataset.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7e3ade70", + "metadata": {}, + "source": [ + "# 00 Beam2D FEM Dataset\n", + "\n", + "BeamExamples ?? ??? ?? ??? ? LHS sample? ???, repository solver? FEM surrogate ??? dataset? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71929240", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "\n", + "from femsurrogate.data.bounds import DEFAULT_PARAMETER_BOUNDS\n", + "from femsurrogate.data.dataset import build_dataset\n", + "from femsurrogate.data.sampling import generate_lhs_samples\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, TARGET_COLUMNS\n", + "from femsurrogate.fea.io import read_beam_example, read_expected_displacements\n", + "from femsurrogate.fea.solver import solve_linear_static\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "REFERENCE_DIR = ROOT / \"data\" / \"reference\"\n", + "REFERENCE_DIR.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "df256ebc", + "metadata": {}, + "source": [ + "## BeamExamples ?? ??\n", + "\n", + "Fixture? ?? node? ?? `Ux`, `Uy`, `Rz`? ???? ???? ?? ????? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a8c947c", + "metadata": {}, + "outputs": [], + "source": [ + "model = read_beam_example(ROOT / \"BeamExamples\" / \"CantileverBeam.txt\")\n", + "expected = read_expected_displacements(ROOT / \"BeamExamples\" / \"CantileverBeam_Displacements.txt\")\n", + "actual = solve_linear_static(model)\n", + "\n", + "max_abs_error = 0.0\n", + "for node_id, expected_displacement in expected.items():\n", + " actual_displacement = actual[node_id]\n", + " actual_values = np.array(\n", + " [actual_displacement.ux, actual_displacement.uy, actual_displacement.rz]\n", + " )\n", + " expected_values = np.array(\n", + " [expected_displacement.ux, expected_displacement.uy, expected_displacement.rz]\n", + " )\n", + " error = float(np.max(np.abs(actual_values - expected_values)))\n", + " max_abs_error = max(max_abs_error, error)\n", + "\n", + "assert max_abs_error <= 5e-7\n", + "max_abs_error" + ] + }, + { + "cell_type": "markdown", + "id": "6620d0ee", + "metadata": {}, + "source": [ + "## LHS sampling? FEM batch ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9923fd04", + "metadata": {}, + "outputs": [], + "source": [ + "N_SAMPLES = 300\n", + "\n", + "samples = generate_lhs_samples(DEFAULT_PARAMETER_BOUNDS, n=N_SAMPLES, seed=DEFAULT_RANDOM_SEED)\n", + "dataset = build_dataset(samples)\n", + "\n", + "assert len(dataset) == N_SAMPLES\n", + "assert set(TARGET_COLUMNS).issubset(dataset.columns)\n", + "dataset.head()" + ] + }, + { + "cell_type": "markdown", + "id": "c0efee15", + "metadata": {}, + "source": [ + "## Dataset ??\n", + "\n", + "?? notebook? ?? CSV? metadata? ??? `data/reference/`? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d80890bd", + "metadata": {}, + "outputs": [], + "source": [ + "dataset_path = REFERENCE_DIR / \"beam2d_lhs_300.csv\"\n", + "metadata_path = REFERENCE_DIR / \"beam2d_lhs_300_metadata.json\"\n", + "\n", + "dataset.to_csv(dataset_path, index=False)\n", + "metadata = {\n", + " \"dataset_name\": \"beam2d_lhs_300\",\n", + " \"sample_count\": N_SAMPLES,\n", + " \"random_seed\": DEFAULT_RANDOM_SEED,\n", + " \"unit_system\": \"SI\",\n", + " \"fea_model\": \"2D Euler-Bernoulli beam/frame, linear static\",\n", + " \"target_columns\": list(TARGET_COLUMNS),\n", + " \"parameter_bounds\": {\n", + " name: {\"lower\": bound.lower, \"upper\": bound.upper}\n", + " for name, bound in DEFAULT_PARAMETER_BOUNDS.items()\n", + " },\n", + " \"notes\": \"Generated by notebooks/00_beam2d_fea_dataset.ipynb using src/femsurrogate.\",\n", + "}\n", + "metadata_path.write_text(json.dumps(metadata, indent=2), encoding=\"utf-8\")\n", + "\n", + "{\"dataset_path\": str(dataset_path), \"metadata_path\": str(metadata_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "d56bcddb", + "metadata": {}, + "source": [ + "## ?? sanity check" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e560e50c", + "metadata": {}, + "outputs": [], + "source": [ + "dataset.describe().T.loc[[\"L_m\", \"b_m\", \"h_m\", \"E_pa\", \"P_n\", *TARGET_COLUMNS]]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/01_response_surface_surrogate.ipynb b/FESurrogateModelTutorial/notebooks/01_response_surface_surrogate.ipynb new file mode 100644 index 0000000..3f90dc3 --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/01_response_surface_surrogate.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "260fc3ce", + "metadata": {}, + "source": [ + "# Response Surface Surrogate\n", + "\n", + "Response Surface? polynomial feature? Ridge regression?? ?? baseline? ???.\n", + "\n", + "?? ?? notebook? ?? dataset, target, train/test split seed? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56528a9c", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, PARAMETER_COLUMNS\n", + "from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals\n", + "from femsurrogate.surrogates.common import evaluate_model, metrics_to_dict, split_dataset\n", + "from femsurrogate.surrogates.registry import make_model\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "DATASET_PATH = ROOT / \"data\" / \"reference\" / \"beam2d_lhs_300.csv\"\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "PREDICTIONS_DIR = ROOT / \"reports\" / \"predictions\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "for directory in [RESULTS_DIR, PREDICTIONS_DIR, FIGURES_DIR]:\n", + " directory.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "7cd36ad9", + "metadata": {}, + "source": [ + "## Dataset? split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50689a07", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = pd.read_csv(DATASET_PATH)\n", + "target_column = \"tip_uy_m\"\n", + "split = split_dataset(\n", + " dataset,\n", + " feature_columns=list(PARAMETER_COLUMNS),\n", + " target_column=target_column,\n", + " test_size=0.2,\n", + " random_state=DEFAULT_RANDOM_SEED,\n", + ")\n", + "\n", + "len(split.X_train), len(split.X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "0473847f", + "metadata": {}, + "source": [ + "## ?? ??? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "539ffb4d", + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"rsm\"\n", + "model = make_model(MODEL_NAME, random_state=DEFAULT_RANDOM_SEED, **{})\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=ConvergenceWarning)\n", + " result = evaluate_model(\n", + " model,\n", + " split.X_train,\n", + " split.X_test,\n", + " split.y_train,\n", + " split.y_test,\n", + " model_name=MODEL_NAME,\n", + " target_column=target_column,\n", + " )\n", + "\n", + "metrics = metrics_to_dict(result.metrics)\n", + "metrics" + ] + }, + { + "cell_type": "markdown", + "id": "78c34610", + "metadata": {}, + "source": [ + "## ?? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e15bf80d", + "metadata": {}, + "outputs": [], + "source": [ + "metrics_path = RESULTS_DIR / f\"{MODEL_NAME}_metrics.json\"\n", + "predictions_path = PREDICTIONS_DIR / f\"{MODEL_NAME}_predictions.csv\"\n", + "\n", + "metrics_path.write_text(json.dumps(metrics, indent=2), encoding=\"utf-8\")\n", + "result.predictions.to_csv(predictions_path, index=False)\n", + "\n", + "{\"metrics_path\": str(metrics_path), \"predictions_path\": str(predictions_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "2142e40b", + "metadata": {}, + "source": [ + "## ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b89510", + "metadata": {}, + "outputs": [], + "source": [ + "parity_fig = plot_parity(result.predictions, title=f\"{MODEL_NAME} parity\")\n", + "residual_fig = plot_residuals(result.predictions, title=f\"{MODEL_NAME} residuals\")\n", + "parity_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_parity.png\", dpi=150, bbox_inches=\"tight\")\n", + "residual_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_residuals.png\", dpi=150, bbox_inches=\"tight\")\n", + "parity_fig" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/02_gaussian_process_kriging_surrogate.ipynb b/FESurrogateModelTutorial/notebooks/02_gaussian_process_kriging_surrogate.ipynb new file mode 100644 index 0000000..e316b84 --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/02_gaussian_process_kriging_surrogate.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "144f56e0", + "metadata": {}, + "source": [ + "# Gaussian Process / Kriging Surrogate\n", + "\n", + "GPR? smooth response? ?? ???? ??? ???? ????.\n", + "\n", + "?? ?? notebook? ?? dataset, target, train/test split seed? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a5a2974", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, PARAMETER_COLUMNS\n", + "from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals\n", + "from femsurrogate.surrogates.common import evaluate_model, metrics_to_dict, split_dataset\n", + "from femsurrogate.surrogates.registry import make_model\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "DATASET_PATH = ROOT / \"data\" / \"reference\" / \"beam2d_lhs_300.csv\"\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "PREDICTIONS_DIR = ROOT / \"reports\" / \"predictions\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "for directory in [RESULTS_DIR, PREDICTIONS_DIR, FIGURES_DIR]:\n", + " directory.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "a2c2b3fe", + "metadata": {}, + "source": [ + "## Dataset? split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef13216f", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = pd.read_csv(DATASET_PATH)\n", + "target_column = \"tip_uy_m\"\n", + "split = split_dataset(\n", + " dataset,\n", + " feature_columns=list(PARAMETER_COLUMNS),\n", + " target_column=target_column,\n", + " test_size=0.2,\n", + " random_state=DEFAULT_RANDOM_SEED,\n", + ")\n", + "\n", + "len(split.X_train), len(split.X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "6c3be3f4", + "metadata": {}, + "source": [ + "## ?? ??? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5e09c8a", + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"gpr\"\n", + "model = make_model(MODEL_NAME, random_state=DEFAULT_RANDOM_SEED, **{})\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=ConvergenceWarning)\n", + " result = evaluate_model(\n", + " model,\n", + " split.X_train,\n", + " split.X_test,\n", + " split.y_train,\n", + " split.y_test,\n", + " model_name=MODEL_NAME,\n", + " target_column=target_column,\n", + " )\n", + "\n", + "metrics = metrics_to_dict(result.metrics)\n", + "metrics" + ] + }, + { + "cell_type": "markdown", + "id": "4b9e74af", + "metadata": {}, + "source": [ + "## ?? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5af40658", + "metadata": {}, + "outputs": [], + "source": [ + "metrics_path = RESULTS_DIR / f\"{MODEL_NAME}_metrics.json\"\n", + "predictions_path = PREDICTIONS_DIR / f\"{MODEL_NAME}_predictions.csv\"\n", + "\n", + "metrics_path.write_text(json.dumps(metrics, indent=2), encoding=\"utf-8\")\n", + "result.predictions.to_csv(predictions_path, index=False)\n", + "\n", + "{\"metrics_path\": str(metrics_path), \"predictions_path\": str(predictions_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "37a881b4", + "metadata": {}, + "source": [ + "## ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d156a54d", + "metadata": {}, + "outputs": [], + "source": [ + "parity_fig = plot_parity(result.predictions, title=f\"{MODEL_NAME} parity\")\n", + "residual_fig = plot_residuals(result.predictions, title=f\"{MODEL_NAME} residuals\")\n", + "parity_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_parity.png\", dpi=150, bbox_inches=\"tight\")\n", + "residual_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_residuals.png\", dpi=150, bbox_inches=\"tight\")\n", + "parity_fig" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/03_random_forest_surrogate.ipynb b/FESurrogateModelTutorial/notebooks/03_random_forest_surrogate.ipynb new file mode 100644 index 0000000..921bd8f --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/03_random_forest_surrogate.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1638813f", + "metadata": {}, + "source": [ + "# Random Forest Surrogate\n", + "\n", + "Random Forest? feature scaling ??? ????? interaction? ??? ???? tree ensemble??.\n", + "\n", + "?? ?? notebook? ?? dataset, target, train/test split seed? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8bdcff3", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, PARAMETER_COLUMNS\n", + "from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals\n", + "from femsurrogate.surrogates.common import evaluate_model, metrics_to_dict, split_dataset\n", + "from femsurrogate.surrogates.registry import make_model\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "DATASET_PATH = ROOT / \"data\" / \"reference\" / \"beam2d_lhs_300.csv\"\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "PREDICTIONS_DIR = ROOT / \"reports\" / \"predictions\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "for directory in [RESULTS_DIR, PREDICTIONS_DIR, FIGURES_DIR]:\n", + " directory.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "6957e06b", + "metadata": {}, + "source": [ + "## Dataset? split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "209f40be", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = pd.read_csv(DATASET_PATH)\n", + "target_column = \"tip_uy_m\"\n", + "split = split_dataset(\n", + " dataset,\n", + " feature_columns=list(PARAMETER_COLUMNS),\n", + " target_column=target_column,\n", + " test_size=0.2,\n", + " random_state=DEFAULT_RANDOM_SEED,\n", + ")\n", + "\n", + "len(split.X_train), len(split.X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "fdc2fbed", + "metadata": {}, + "source": [ + "## ?? ??? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63ed6060", + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"random_forest\"\n", + "model = make_model(MODEL_NAME, random_state=DEFAULT_RANDOM_SEED, **{})\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=ConvergenceWarning)\n", + " result = evaluate_model(\n", + " model,\n", + " split.X_train,\n", + " split.X_test,\n", + " split.y_train,\n", + " split.y_test,\n", + " model_name=MODEL_NAME,\n", + " target_column=target_column,\n", + " )\n", + "\n", + "metrics = metrics_to_dict(result.metrics)\n", + "metrics" + ] + }, + { + "cell_type": "markdown", + "id": "25dafcbf", + "metadata": {}, + "source": [ + "## ?? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8357de5d", + "metadata": {}, + "outputs": [], + "source": [ + "metrics_path = RESULTS_DIR / f\"{MODEL_NAME}_metrics.json\"\n", + "predictions_path = PREDICTIONS_DIR / f\"{MODEL_NAME}_predictions.csv\"\n", + "\n", + "metrics_path.write_text(json.dumps(metrics, indent=2), encoding=\"utf-8\")\n", + "result.predictions.to_csv(predictions_path, index=False)\n", + "\n", + "{\"metrics_path\": str(metrics_path), \"predictions_path\": str(predictions_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "069f56dd", + "metadata": {}, + "source": [ + "## ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00159cc2", + "metadata": {}, + "outputs": [], + "source": [ + "parity_fig = plot_parity(result.predictions, title=f\"{MODEL_NAME} parity\")\n", + "residual_fig = plot_residuals(result.predictions, title=f\"{MODEL_NAME} residuals\")\n", + "parity_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_parity.png\", dpi=150, bbox_inches=\"tight\")\n", + "residual_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_residuals.png\", dpi=150, bbox_inches=\"tight\")\n", + "parity_fig" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/04_gradient_boosting_surrogate.ipynb b/FESurrogateModelTutorial/notebooks/04_gradient_boosting_surrogate.ipynb new file mode 100644 index 0000000..76a6577 --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/04_gradient_boosting_surrogate.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ad3ce4ad", + "metadata": {}, + "source": [ + "# Gradient Boosting Surrogate\n", + "\n", + "Gradient Boosting? shallow tree? ????? ?? residual pattern? ??? ensemble??.\n", + "\n", + "?? ?? notebook? ?? dataset, target, train/test split seed? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0fec75b1", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, PARAMETER_COLUMNS\n", + "from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals\n", + "from femsurrogate.surrogates.common import evaluate_model, metrics_to_dict, split_dataset\n", + "from femsurrogate.surrogates.registry import make_model\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "DATASET_PATH = ROOT / \"data\" / \"reference\" / \"beam2d_lhs_300.csv\"\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "PREDICTIONS_DIR = ROOT / \"reports\" / \"predictions\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "for directory in [RESULTS_DIR, PREDICTIONS_DIR, FIGURES_DIR]:\n", + " directory.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "1d93d9fb", + "metadata": {}, + "source": [ + "## Dataset? split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c905b0aa", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = pd.read_csv(DATASET_PATH)\n", + "target_column = \"tip_uy_m\"\n", + "split = split_dataset(\n", + " dataset,\n", + " feature_columns=list(PARAMETER_COLUMNS),\n", + " target_column=target_column,\n", + " test_size=0.2,\n", + " random_state=DEFAULT_RANDOM_SEED,\n", + ")\n", + "\n", + "len(split.X_train), len(split.X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "c5e7af1a", + "metadata": {}, + "source": [ + "## ?? ??? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5168f3e", + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"gradient_boosting\"\n", + "model = make_model(MODEL_NAME, random_state=DEFAULT_RANDOM_SEED, **{})\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=ConvergenceWarning)\n", + " result = evaluate_model(\n", + " model,\n", + " split.X_train,\n", + " split.X_test,\n", + " split.y_train,\n", + " split.y_test,\n", + " model_name=MODEL_NAME,\n", + " target_column=target_column,\n", + " )\n", + "\n", + "metrics = metrics_to_dict(result.metrics)\n", + "metrics" + ] + }, + { + "cell_type": "markdown", + "id": "f02aead6", + "metadata": {}, + "source": [ + "## ?? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1daaf0f6", + "metadata": {}, + "outputs": [], + "source": [ + "metrics_path = RESULTS_DIR / f\"{MODEL_NAME}_metrics.json\"\n", + "predictions_path = PREDICTIONS_DIR / f\"{MODEL_NAME}_predictions.csv\"\n", + "\n", + "metrics_path.write_text(json.dumps(metrics, indent=2), encoding=\"utf-8\")\n", + "result.predictions.to_csv(predictions_path, index=False)\n", + "\n", + "{\"metrics_path\": str(metrics_path), \"predictions_path\": str(predictions_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "14f71882", + "metadata": {}, + "source": [ + "## ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "807c1025", + "metadata": {}, + "outputs": [], + "source": [ + "parity_fig = plot_parity(result.predictions, title=f\"{MODEL_NAME} parity\")\n", + "residual_fig = plot_residuals(result.predictions, title=f\"{MODEL_NAME} residuals\")\n", + "parity_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_parity.png\", dpi=150, bbox_inches=\"tight\")\n", + "residual_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_residuals.png\", dpi=150, bbox_inches=\"tight\")\n", + "parity_fig" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/05_mlp_surrogate.ipynb b/FESurrogateModelTutorial/notebooks/05_mlp_surrogate.ipynb new file mode 100644 index 0000000..9ec7f35 --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/05_mlp_surrogate.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "69c9cc92", + "metadata": {}, + "source": [ + "# MLP Neural Network Surrogate\n", + "\n", + "MLP? scaled input? target? ??? ???? ??? ??? ????.\n", + "\n", + "?? ?? notebook? ?? dataset, target, train/test split seed? ????." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eed089f5", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "\n", + "from femsurrogate.data.schema import DEFAULT_RANDOM_SEED, PARAMETER_COLUMNS\n", + "from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals\n", + "from femsurrogate.surrogates.common import evaluate_model, metrics_to_dict, split_dataset\n", + "from femsurrogate.surrogates.registry import make_model\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "DATASET_PATH = ROOT / \"data\" / \"reference\" / \"beam2d_lhs_300.csv\"\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "PREDICTIONS_DIR = ROOT / \"reports\" / \"predictions\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "for directory in [RESULTS_DIR, PREDICTIONS_DIR, FIGURES_DIR]:\n", + " directory.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b2547495", + "metadata": {}, + "source": [ + "## Dataset? split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "039279d4", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = pd.read_csv(DATASET_PATH)\n", + "target_column = \"tip_uy_m\"\n", + "split = split_dataset(\n", + " dataset,\n", + " feature_columns=list(PARAMETER_COLUMNS),\n", + " target_column=target_column,\n", + " test_size=0.2,\n", + " random_state=DEFAULT_RANDOM_SEED,\n", + ")\n", + "\n", + "len(split.X_train), len(split.X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "71b3323f", + "metadata": {}, + "source": [ + "## ?? ??? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "605253ef", + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"mlp\"\n", + "model = make_model(MODEL_NAME, random_state=DEFAULT_RANDOM_SEED, **{'max_iter': 500})\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=ConvergenceWarning)\n", + " result = evaluate_model(\n", + " model,\n", + " split.X_train,\n", + " split.X_test,\n", + " split.y_train,\n", + " split.y_test,\n", + " model_name=MODEL_NAME,\n", + " target_column=target_column,\n", + " )\n", + "\n", + "metrics = metrics_to_dict(result.metrics)\n", + "metrics" + ] + }, + { + "cell_type": "markdown", + "id": "f70ad9fd", + "metadata": {}, + "source": [ + "## ?? ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb08797b", + "metadata": {}, + "outputs": [], + "source": [ + "metrics_path = RESULTS_DIR / f\"{MODEL_NAME}_metrics.json\"\n", + "predictions_path = PREDICTIONS_DIR / f\"{MODEL_NAME}_predictions.csv\"\n", + "\n", + "metrics_path.write_text(json.dumps(metrics, indent=2), encoding=\"utf-8\")\n", + "result.predictions.to_csv(predictions_path, index=False)\n", + "\n", + "{\"metrics_path\": str(metrics_path), \"predictions_path\": str(predictions_path)}" + ] + }, + { + "cell_type": "markdown", + "id": "39a34166", + "metadata": {}, + "source": [ + "## ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "694a1081", + "metadata": {}, + "outputs": [], + "source": [ + "parity_fig = plot_parity(result.predictions, title=f\"{MODEL_NAME} parity\")\n", + "residual_fig = plot_residuals(result.predictions, title=f\"{MODEL_NAME} residuals\")\n", + "parity_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_parity.png\", dpi=150, bbox_inches=\"tight\")\n", + "residual_fig.savefig(FIGURES_DIR / f\"{MODEL_NAME}_residuals.png\", dpi=150, bbox_inches=\"tight\")\n", + "parity_fig" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/notebooks/06_compare_surrogate_models.ipynb b/FESurrogateModelTutorial/notebooks/06_compare_surrogate_models.ipynb new file mode 100644 index 0000000..411e0bf --- /dev/null +++ b/FESurrogateModelTutorial/notebooks/06_compare_surrogate_models.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "741ede83", + "metadata": {}, + "source": [ + "# 06 Compare Surrogate Models\n", + "\n", + "? notebook? ?? model notebook?? ??? metrics JSON? ?? ??? ????. ??? ?? ???? ???." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10f43a71", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "\n", + "from femsurrogate.plotting.comparison import plot_metric_comparison\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "if not (ROOT / \"pyproject.toml\").exists():\n", + " ROOT = ROOT.parent\n", + "assert (ROOT / \"pyproject.toml\").exists(), ROOT\n", + "RESULTS_DIR = ROOT / \"reports\" / \"results\"\n", + "FIGURES_DIR = ROOT / \"reports\" / \"figures\"\n", + "FIGURES_DIR.mkdir(parents=True, exist_ok=True)\n", + "MODEL_NAMES = [\"rsm\", \"gpr\", \"random_forest\", \"gradient_boosting\", \"mlp\"]" + ] + }, + { + "cell_type": "markdown", + "id": "9798e3bb", + "metadata": {}, + "source": [ + "## Metrics ??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55d2447d", + "metadata": {}, + "outputs": [], + "source": [ + "records = []\n", + "for model_name in MODEL_NAMES:\n", + " metrics_path = RESULTS_DIR / f\"{model_name}_metrics.json\"\n", + " assert metrics_path.exists(), metrics_path\n", + " records.append(json.loads(metrics_path.read_text(encoding=\"utf-8\")))\n", + "\n", + "comparison = pd.DataFrame(records).sort_values(\"rmse\").reset_index(drop=True)\n", + "comparison_path = RESULTS_DIR / \"model_comparison.csv\"\n", + "comparison.to_csv(comparison_path, index=False)\n", + "comparison" + ] + }, + { + "cell_type": "markdown", + "id": "731bb2f7", + "metadata": {}, + "source": [ + "## Metric ?? plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64657b84", + "metadata": {}, + "outputs": [], + "source": [ + "figures = {}\n", + "for metric in [\"rmse\", \"mae\", \"r2\", \"fit_time_s\", \"predict_time_s\"]:\n", + " figure = plot_metric_comparison(comparison, metric=metric, title=f\"Surrogate {metric}\")\n", + " figure.savefig(FIGURES_DIR / f\"comparison_{metric}.png\", dpi=150, bbox_inches=\"tight\")\n", + " figures[metric] = figure\n", + "\n", + "figures[\"rmse\"]" + ] + }, + { + "cell_type": "markdown", + "id": "63cb5b8d", + "metadata": {}, + "source": [ + "## ?? ???\n", + "\n", + "- `rmse`? `mae`? ?? ??? ?? ????.\n", + "- `r2`? ??? residual plot?? ?? ?? ??? bias? ??? ????? ????.\n", + "- `fit_time_s`, `predict_time_s`? ?? ??? ??? loop?? ????.\n", + "- GPR? ?? dataset?? ???? sample ?? ??? ?? ??? ??? ?? ? ??." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FESurrogateModelTutorial/phases/0-project-foundation/index.json b/FESurrogateModelTutorial/phases/0-project-foundation/index.json new file mode 100644 index 0000000..f6062c0 --- /dev/null +++ b/FESurrogateModelTutorial/phases/0-project-foundation/index.json @@ -0,0 +1,18 @@ +{ + "project": "FEMSurrogateTutorial", + "phase": "0-project-foundation", + "steps": [ + { + "step": 0, + "name": "python-project-skeleton", + "status": "completed", + "summary": "Created uv/pyproject foundation, src package skeleton, artifact directories, and ruff configuration." + }, + { + "step": 1, + "name": "import-smoke-test", + "status": "completed", + "summary": "Added package import smoke tests and exposed femsurrogate.__version__." + } + ] +} diff --git a/FESurrogateModelTutorial/phases/0-project-foundation/step0.md b/FESurrogateModelTutorial/phases/0-project-foundation/step0.md new file mode 100644 index 0000000..e82853a --- /dev/null +++ b/FESurrogateModelTutorial/phases/0-project-foundation/step0.md @@ -0,0 +1,40 @@ +# Step 0: python-project-skeleton + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/PROGRESS.md` +- `/WORKNOTES.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` + +## 작업 + +Python tutorial project의 최소 기반을 만든다. + +- `/pyproject.toml` 생성: Python `>=3.12,<3.15`, setuptools src layout, pytest `pythonpath = ["src"]`, ruff target `py312`. +- runtime dependencies: `numpy`, `scipy`, `pandas`, `scikit-learn`, `matplotlib`, `joblib`. +- dev dependencies: `pytest`, `ruff`, `jupyterlab`, `ipykernel`, `nbconvert`. +- Python package directories: `/src/femsurrogate/`, `/src/femsurrogate/fea/`, `/src/femsurrogate/data/`, `/src/femsurrogate/surrogates/`, `/src/femsurrogate/plotting/`. +- artifact directories: `/tests/`, `/data/reference/`, `/data/processed/`, `/reports/results/`, `/reports/predictions/`, `/reports/figures/`, `/notebooks/`. +- 빈 artifact directory에는 `.gitkeep`을 둔다. +- Python cache, virtualenv, notebook checkpoint를 `.gitignore`에 추가한다. +- `/PROGRESS.md`에 phase 0 시작 상태를 기록한다. + +## Acceptance Criteria + +```powershell +uv sync +uv run python -c "import femsurrogate; print(femsurrogate.__name__)" +uv run ruff check . +``` + +## 검증 절차 + +AC 커맨드를 실행하고 성공하면 `phases/0-project-foundation/index.json`의 step 0을 `completed`로 갱신한다. + +## 금지사항 + +- Beam solver, parser, dataset, surrogate 모델 구현을 이 step에서 만들지 마라. +- Notebook 내용을 만들지 마라. diff --git a/FESurrogateModelTutorial/phases/0-project-foundation/step1.md b/FESurrogateModelTutorial/phases/0-project-foundation/step1.md new file mode 100644 index 0000000..51ba7ee --- /dev/null +++ b/FESurrogateModelTutorial/phases/0-project-foundation/step1.md @@ -0,0 +1,36 @@ +# Step 1: import-smoke-test + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/PROGRESS.md` +- `/WORKNOTES.md` +- `/docs/ARCHITECTURE.md` +- `/pyproject.toml` +- `/src/femsurrogate/__init__.py` + +## 작업 + +TDD로 package import smoke test를 추가한다. + +- 먼저 `/tests/test_project_structure.py`를 작성한다. +- 테스트는 `femsurrogate.__version__`이 문자열인지 확인한다. +- 테스트는 `femsurrogate.fea`, `femsurrogate.data`, `femsurrogate.surrogates`, `femsurrogate.plotting` import 가능성을 확인한다. +- 테스트 실패를 먼저 확인한 뒤 최소 구현으로 통과시킨다. +- `/PROGRESS.md`에 검증 결과를 기록한다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_project_structure.py -q +uv run ruff check . +``` + +## 검증 절차 + +테스트가 먼저 실패한 기록을 확인하고, 구현 후 AC가 통과하면 `phases/0-project-foundation/index.json`의 step 1을 `completed`로 갱신한다. + +## 금지사항 + +- solver API를 만들지 마라. +- 테스트를 구현 후에만 작성하지 마라. diff --git a/FESurrogateModelTutorial/phases/1-beam2d-solver/index.json b/FESurrogateModelTutorial/phases/1-beam2d-solver/index.json new file mode 100644 index 0000000..18f03ca --- /dev/null +++ b/FESurrogateModelTutorial/phases/1-beam2d-solver/index.json @@ -0,0 +1,30 @@ +{ + "project": "FEMSurrogateTutorial", + "phase": "1-beam2d-solver", + "steps": [ + { + "step": 0, + "name": "beamexamples-parser", + "status": "completed", + "summary": "Added BeamExamples parser dataclasses and tests for cantilever input and displacement fixtures." + }, + { + "step": 1, + "name": "frame-element", + "status": "completed", + "summary": "Added 2D Euler-Bernoulli frame local stiffness and transformation matrix tests and implementation." + }, + { + "step": 2, + "name": "assembly-and-solver", + "status": "completed", + "summary": "Added sparse global stiffness assembly, load vector assembly, constrained DOF handling, and linear static solve." + }, + { + "step": 3, + "name": "fixture-regression", + "status": "completed", + "summary": "Added BeamExamples displacement regression, analytical cantilever tip check, and clockwise-positive Rz convention alignment." + } + ] +} diff --git a/FESurrogateModelTutorial/phases/1-beam2d-solver/step0.md b/FESurrogateModelTutorial/phases/1-beam2d-solver/step0.md new file mode 100644 index 0000000..99284b4 --- /dev/null +++ b/FESurrogateModelTutorial/phases/1-beam2d-solver/step0.md @@ -0,0 +1,32 @@ +# Step 0: beamexamples-parser + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/PROGRESS.md` +- `/WORKNOTES.md` +- `/docs/ARCHITECTURE.md` +- `/BeamExamples/CantileverBeam.txt` +- `/BeamExamples/CantileverBeam_Displacements.txt` + +## 작업 + +TDD로 `BeamExamples` parser를 구현한다. + +- `/tests/test_beamexamples_io.py`를 먼저 작성한다. +- `read_beam_example()`은 metadata, 6 nodes, 5 beams, fixed node 1, node 6 load를 읽는다. +- `read_expected_displacements()`는 node별 `Ux`, `Uy`, `Rz` 기준값을 읽는다. +- parser는 comment, blank line, trailing comma, `Poisson'sRatio` label을 처리한다. +- 구현 파일: `/src/femsurrogate/fea/model.py`, `/src/femsurrogate/fea/io.py`. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_beamexamples_io.py -q +uv run ruff check . +``` + +## 금지사항 + +- solver 구현을 이 step에 넣지 마라. +- fixture 파일을 수정하지 마라. diff --git a/FESurrogateModelTutorial/phases/1-beam2d-solver/step1.md b/FESurrogateModelTutorial/phases/1-beam2d-solver/step1.md new file mode 100644 index 0000000..dce1ae6 --- /dev/null +++ b/FESurrogateModelTutorial/phases/1-beam2d-solver/step1.md @@ -0,0 +1,28 @@ +# Step 1: frame-element + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/src/femsurrogate/fea/model.py` +- `/src/femsurrogate/fea/io.py` + +## 작업 + +TDD로 2-node 2D Euler-Bernoulli frame element 행렬을 구현한다. + +- `/tests/test_frame_element.py`를 먼저 작성한다. +- `local_frame_stiffness(E, A, I, L)`의 shape, symmetry, axial/bending 계수를 검증한다. +- `transformation_matrix(x1, y1, x2, y2)`의 shape와 orthogonality를 검증한다. +- 구현 파일: `/src/femsurrogate/fea/element.py`. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_frame_element.py tests/test_beamexamples_io.py -q +uv run ruff check . +``` + +## 금지사항 + +- Timoshenko beam, 3D beam DOF를 추가하지 마라. diff --git a/FESurrogateModelTutorial/phases/1-beam2d-solver/step2.md b/FESurrogateModelTutorial/phases/1-beam2d-solver/step2.md new file mode 100644 index 0000000..52bde88 --- /dev/null +++ b/FESurrogateModelTutorial/phases/1-beam2d-solver/step2.md @@ -0,0 +1,31 @@ +# Step 2: assembly-and-solver + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/src/femsurrogate/fea/model.py` +- `/src/femsurrogate/fea/io.py` +- `/src/femsurrogate/fea/element.py` + +## 작업 + +TDD로 global assembly와 constrained linear static solver를 구현한다. + +- `/tests/test_beam_solver.py`를 먼저 작성한다. +- BeamExamples model의 global stiffness shape `(18, 18)`을 검증한다. +- fixed node 1의 3 DOF가 constrained 처리되는지 검증한다. +- `solve_linear_static(model)`이 모든 node displacement를 finite 값으로 반환하는지 검증한다. +- 구현 파일: `/src/femsurrogate/fea/assembly.py`, `/src/femsurrogate/fea/solver.py`. +- solver는 `scipy.sparse`와 `scipy.sparse.linalg.spsolve`를 사용한다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_beam_solver.py tests/test_frame_element.py tests/test_beamexamples_io.py -q +uv run ruff check . +``` + +## 금지사항 + +- expected displacement 값을 solver에 hard-code하지 마라. diff --git a/FESurrogateModelTutorial/phases/1-beam2d-solver/step3.md b/FESurrogateModelTutorial/phases/1-beam2d-solver/step3.md new file mode 100644 index 0000000..82c03d7 --- /dev/null +++ b/FESurrogateModelTutorial/phases/1-beam2d-solver/step3.md @@ -0,0 +1,30 @@ +# Step 3: fixture-regression + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/BeamExamples/CantileverBeam.txt` +- `/BeamExamples/CantileverBeam_Displacements.txt` +- `/src/femsurrogate/fea/` + +## 작업 + +TDD로 BeamExamples displacement regression을 완성한다. + +- `/tests/test_cantilever_fixture_regression.py`를 먼저 작성한다. +- solver 결과의 모든 node별 `Ux`, `Uy`, `Rz`를 기준 displacement file과 `atol=5e-7`, `rtol=1e-6`으로 비교한다. +- tip displacement 부호와 해석해 `P L^3 / (3 E I)` 대비 크기를 검증한다. +- 필요한 helper는 `/src/femsurrogate/fea/benchmark.py`, `/src/femsurrogate/fea/responses.py`에 둔다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_cantilever_fixture_regression.py tests/test_beam_solver.py tests/test_frame_element.py tests/test_beamexamples_io.py -q +uv run ruff check . +``` + +## 금지사항 + +- 허용오차를 문서 기준보다 느슨하게 키우지 마라. diff --git a/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/index.json b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/index.json new file mode 100644 index 0000000..348eb5f --- /dev/null +++ b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/index.json @@ -0,0 +1,30 @@ +{ + "project": "FEMSurrogateTutorial", + "phase": "2-dataset-and-surrogates", + "steps": [ + { + "step": 0, + "name": "sampling-and-dataset", + "status": "completed", + "summary": "Added reproducible LHS sampling, BeamParameters/AnalysisResult schema, and Beam2D dataset builder using the in-repository solver." + }, + { + "step": 1, + "name": "surrogate-common", + "status": "completed", + "summary": "Added reproducible dataset split helper, evaluation result dataclasses, regression metrics, fit/predict timing, and prediction table generation." + }, + { + "step": 2, + "name": "surrogate-models", + "status": "completed", + "summary": "Added scikit-learn builders and registry for RSM, GPR, Random Forest, Gradient Boosting, and MLP models." + }, + { + "step": 3, + "name": "plotting-and-results", + "status": "completed", + "summary": "Added parity and residual diagnostic plots, metrics table creation, and metric comparison plot helpers returning matplotlib figures." + } + ] +} diff --git a/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step0.md b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step0.md new file mode 100644 index 0000000..554d167 --- /dev/null +++ b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step0.md @@ -0,0 +1,28 @@ +# Step 0: sampling-and-dataset + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/docs/theory/01_doe_sampling_validation.md` +- `/src/femsurrogate/fea/` + +## 작업 + +TDD로 LHS sampling과 Beam2D dataset builder를 구현한다. + +- 테스트: `/tests/test_sampling.py`, `/tests/test_dataset_builder.py`. +- 구현: `/src/femsurrogate/data/bounds.py`, `schema.py`, `sampling.py`, `dataset.py`. +- 기본 seed는 `20260521`. +- dataset schema는 문서의 SI 단위 컬럼을 따른다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_sampling.py tests/test_dataset_builder.py -q +uv run ruff check . +``` + +## 금지사항 + +- surrogate 학습을 이 step에 넣지 마라. diff --git a/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step1.md b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step1.md new file mode 100644 index 0000000..0fa5ff4 --- /dev/null +++ b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step1.md @@ -0,0 +1,26 @@ +# Step 1: surrogate-common + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/src/femsurrogate/data/` + +## 작업 + +TDD로 surrogate 공통 split/evaluate/result helper를 구현한다. + +- 테스트: `/tests/test_surrogate_common.py`. +- 구현: `/src/femsurrogate/surrogates/common.py`. +- metrics는 `rmse`, `mae`, `r2`, `fit_time_s`, `predict_time_s`를 포함한다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_surrogate_common.py -q +uv run ruff check . +``` + +## 금지사항 + +- 개별 모델 builder를 이 step에 섞지 마라. diff --git a/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step2.md b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step2.md new file mode 100644 index 0000000..9feb8d5 --- /dev/null +++ b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step2.md @@ -0,0 +1,30 @@ +# Step 2: surrogate-models + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/theory/02_response_surface_methodology.md` +- `/docs/theory/03_gaussian_process_kriging.md` +- `/docs/theory/04_random_forest.md` +- `/docs/theory/05_gradient_boosting.md` +- `/docs/theory/06_mlp_neural_network.md` +- `/src/femsurrogate/surrogates/common.py` + +## 작업 + +TDD로 모델별 scikit-learn pipeline builder와 registry를 구현한다. + +- 테스트: `/tests/test_surrogate_models.py`. +- 구현: `rsm.py`, `gpr.py`, `random_forest.py`, `boosting.py`, `mlp.py`, `registry.py`. +- 모델명: `rsm`, `gpr`, `random_forest`, `gradient_boosting`, `mlp`. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_surrogate_models.py -q +uv run ruff check . +``` + +## 금지사항 + +- PyTorch/TensorFlow, AutoML, MLflow를 추가하지 마라. diff --git a/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step3.md b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step3.md new file mode 100644 index 0000000..9a07828 --- /dev/null +++ b/FESurrogateModelTutorial/phases/2-dataset-and-surrogates/step3.md @@ -0,0 +1,26 @@ +# Step 3: plotting-and-results + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/src/femsurrogate/surrogates/` + +## 작업 + +TDD로 diagnostics plotting과 result comparison helper를 구현한다. + +- 테스트: `/tests/test_plotting_and_results.py`. +- 구현: `/src/femsurrogate/plotting/diagnostics.py`, `/src/femsurrogate/plotting/comparison.py`. +- plotting 함수는 matplotlib Figure를 반환한다. + +## Acceptance Criteria + +```powershell +uv run pytest tests/test_plotting_and_results.py -q +uv run ruff check . +``` + +## 금지사항 + +- browser dashboard를 만들지 마라. diff --git a/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/index.json b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/index.json new file mode 100644 index 0000000..f8edac3 --- /dev/null +++ b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/index.json @@ -0,0 +1,30 @@ +{ + "project": "FEMSurrogateTutorial", + "phase": "3-notebooks-and-final-verification", + "steps": [ + { + "step": 0, + "name": "dataset-notebook", + "status": "completed", + "summary": "Added and executed dataset notebook that validates BeamExamples, generates LHS samples, builds FEM dataset, and writes reference CSV/metadata." + }, + { + "step": 1, + "name": "model-notebooks", + "status": "completed", + "summary": "Added and executed five model notebooks sharing the same dataset, target, and split seed while saving metrics, predictions, and diagnostic figures." + }, + { + "step": 2, + "name": "comparison-notebook", + "status": "completed", + "summary": "Added and executed comparison notebook that reads saved metrics and writes model comparison CSV and figures without retraining." + }, + { + "step": 3, + "name": "final-verification", + "status": "completed", + "summary": "Ran full pytest, ruff, all seven nbconvert executions, and verified comparison outputs exist." + } + ] +} diff --git a/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step0.md b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step0.md new file mode 100644 index 0000000..a54531f --- /dev/null +++ b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step0.md @@ -0,0 +1,23 @@ +# Step 0: dataset-notebook + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/src/femsurrogate/` + +## 작업 + +`notebooks/00_beam2d_fea_dataset.ipynb`를 만든다. Notebook은 BeamExamples regression 검증, LHS sampling, FEM batch run, dataset 저장을 설명하고 실행한다. + +## Acceptance Criteria + +```powershell +uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb +uv run ruff check . +``` + +## 금지사항 + +- 핵심 solver 구현을 notebook cell에 넣지 마라. diff --git a/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step1.md b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step1.md new file mode 100644 index 0000000..a93d66d --- /dev/null +++ b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step1.md @@ -0,0 +1,39 @@ +# Step 1: model-notebooks + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/theory/02_response_surface_methodology.md` +- `/docs/theory/03_gaussian_process_kriging.md` +- `/docs/theory/04_random_forest.md` +- `/docs/theory/05_gradient_boosting.md` +- `/docs/theory/06_mlp_neural_network.md` +- `/src/femsurrogate/surrogates/` +- `/src/femsurrogate/plotting/` + +## 작업 + +모델별 notebook 5개를 만든다. + +- `notebooks/01_response_surface_surrogate.ipynb` +- `notebooks/02_gaussian_process_kriging_surrogate.ipynb` +- `notebooks/03_random_forest_surrogate.ipynb` +- `notebooks/04_gradient_boosting_surrogate.ipynb` +- `notebooks/05_mlp_surrogate.ipynb` + +각 notebook은 같은 dataset, target, split seed를 쓰고 metric JSON과 prediction CSV를 저장한다. + +## Acceptance Criteria + +```powershell +uv run jupyter nbconvert --to notebook --execute notebooks/01_response_surface_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/02_gaussian_process_kriging_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/03_random_forest_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/04_gradient_boosting_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/05_mlp_surrogate.ipynb +uv run ruff check . +``` + +## 금지사항 + +- 모델별로 다른 split을 쓰지 마라. diff --git a/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step2.md b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step2.md new file mode 100644 index 0000000..166ebdd --- /dev/null +++ b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step2.md @@ -0,0 +1,24 @@ +# Step 2: comparison-notebook + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/reports/results/` +- `/reports/predictions/` +- `/src/femsurrogate/plotting/comparison.py` + +## 작업 + +`notebooks/06_compare_surrogate_models.ipynb`를 만든다. 이전 notebook 결과물을 읽어 RMSE, MAE, R2, 학습 시간, 예측 시간을 비교하고 모델 선택 가이드를 정리한다. + +## Acceptance Criteria + +```powershell +uv run jupyter nbconvert --to notebook --execute notebooks/06_compare_surrogate_models.ipynb +uv run ruff check . +``` + +## 금지사항 + +- 비교 notebook에서 모델을 다시 학습하지 마라. diff --git a/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step3.md b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step3.md new file mode 100644 index 0000000..4e308dd --- /dev/null +++ b/FESurrogateModelTutorial/phases/3-notebooks-and-final-verification/step3.md @@ -0,0 +1,37 @@ +# Step 3: final-verification + +## 읽어야 할 파일 + +- `/AGENTS.md` +- `/PROGRESS.md` +- `/WORKNOTES.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` + +## 작업 + +전체 검증과 handoff 문서 정리를 수행한다. + +- `uv run pytest` +- `uv run ruff check .` +- 모든 notebook 순차 `nbconvert --execute` +- `/PROGRESS.md` 최종 업데이트 +- `/WORKNOTES.md`에 남은 주의점 기록 + +## Acceptance Criteria + +```powershell +uv run pytest +uv run ruff check . +uv run jupyter nbconvert --to notebook --execute notebooks/00_beam2d_fea_dataset.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/01_response_surface_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/02_gaussian_process_kriging_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/03_random_forest_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/04_gradient_boosting_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/05_mlp_surrogate.ipynb +uv run jupyter nbconvert --to notebook --execute notebooks/06_compare_surrogate_models.ipynb +``` + +## 금지사항 + +- 실패한 검증을 성공으로 기록하지 마라. diff --git a/FESurrogateModelTutorial/phases/index.json b/FESurrogateModelTutorial/phases/index.json new file mode 100644 index 0000000..a86aa12 --- /dev/null +++ b/FESurrogateModelTutorial/phases/index.json @@ -0,0 +1,20 @@ +{ + "phases": [ + { + "dir": "0-project-foundation", + "status": "completed" + }, + { + "dir": "1-beam2d-solver", + "status": "completed" + }, + { + "dir": "2-dataset-and-surrogates", + "status": "completed" + }, + { + "dir": "3-notebooks-and-final-verification", + "status": "completed" + } + ] +} diff --git a/FESurrogateModelTutorial/pyproject.toml b/FESurrogateModelTutorial/pyproject.toml new file mode 100644 index 0000000..cbc84fa --- /dev/null +++ b/FESurrogateModelTutorial/pyproject.toml @@ -0,0 +1,46 @@ +[project] +name = "femsurrogate-tutorial" +version = "0.1.0" +description = "Korean tutorial for FEM structural-analysis surrogate models." +readme = "docs/PRD.md" +requires-python = ">=3.12,<3.15" +dependencies = [ + "joblib", + "matplotlib", + "numpy", + "pandas", + "scikit-learn", + "scipy", +] + +[dependency-groups] +dev = [ + "ipykernel", + "jupyterlab", + "nbconvert", + "pytest", + "ruff", +] + +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] + +[tool.ruff] +target-version = "py312" +line-length = 100 +extend-exclude = [ + ".agents", + ".codex", + "scripts/execute.py", +] + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] diff --git a/FESurrogateModelTutorial/reports/figures/.gitkeep b/FESurrogateModelTutorial/reports/figures/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/reports/figures/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/reports/figures/comparison_fit_time_s.png b/FESurrogateModelTutorial/reports/figures/comparison_fit_time_s.png new file mode 100644 index 0000000..d79208b Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/comparison_fit_time_s.png differ diff --git a/FESurrogateModelTutorial/reports/figures/comparison_mae.png b/FESurrogateModelTutorial/reports/figures/comparison_mae.png new file mode 100644 index 0000000..625f9ee Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/comparison_mae.png differ diff --git a/FESurrogateModelTutorial/reports/figures/comparison_predict_time_s.png b/FESurrogateModelTutorial/reports/figures/comparison_predict_time_s.png new file mode 100644 index 0000000..3331a61 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/comparison_predict_time_s.png differ diff --git a/FESurrogateModelTutorial/reports/figures/comparison_r2.png b/FESurrogateModelTutorial/reports/figures/comparison_r2.png new file mode 100644 index 0000000..5d8db5d Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/comparison_r2.png differ diff --git a/FESurrogateModelTutorial/reports/figures/comparison_rmse.png b/FESurrogateModelTutorial/reports/figures/comparison_rmse.png new file mode 100644 index 0000000..25efe96 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/comparison_rmse.png differ diff --git a/FESurrogateModelTutorial/reports/figures/gpr_parity.png b/FESurrogateModelTutorial/reports/figures/gpr_parity.png new file mode 100644 index 0000000..00cead6 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/gpr_parity.png differ diff --git a/FESurrogateModelTutorial/reports/figures/gpr_residuals.png b/FESurrogateModelTutorial/reports/figures/gpr_residuals.png new file mode 100644 index 0000000..8183a2f Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/gpr_residuals.png differ diff --git a/FESurrogateModelTutorial/reports/figures/gradient_boosting_parity.png b/FESurrogateModelTutorial/reports/figures/gradient_boosting_parity.png new file mode 100644 index 0000000..02141bd Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/gradient_boosting_parity.png differ diff --git a/FESurrogateModelTutorial/reports/figures/gradient_boosting_residuals.png b/FESurrogateModelTutorial/reports/figures/gradient_boosting_residuals.png new file mode 100644 index 0000000..5e48f11 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/gradient_boosting_residuals.png differ diff --git a/FESurrogateModelTutorial/reports/figures/mlp_parity.png b/FESurrogateModelTutorial/reports/figures/mlp_parity.png new file mode 100644 index 0000000..cfc8f41 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/mlp_parity.png differ diff --git a/FESurrogateModelTutorial/reports/figures/mlp_residuals.png b/FESurrogateModelTutorial/reports/figures/mlp_residuals.png new file mode 100644 index 0000000..14c29c2 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/mlp_residuals.png differ diff --git a/FESurrogateModelTutorial/reports/figures/random_forest_parity.png b/FESurrogateModelTutorial/reports/figures/random_forest_parity.png new file mode 100644 index 0000000..eafac97 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/random_forest_parity.png differ diff --git a/FESurrogateModelTutorial/reports/figures/random_forest_residuals.png b/FESurrogateModelTutorial/reports/figures/random_forest_residuals.png new file mode 100644 index 0000000..e7fd7e7 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/random_forest_residuals.png differ diff --git a/FESurrogateModelTutorial/reports/figures/rsm_parity.png b/FESurrogateModelTutorial/reports/figures/rsm_parity.png new file mode 100644 index 0000000..79fa1b0 Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/rsm_parity.png differ diff --git a/FESurrogateModelTutorial/reports/figures/rsm_residuals.png b/FESurrogateModelTutorial/reports/figures/rsm_residuals.png new file mode 100644 index 0000000..c38127a Binary files /dev/null and b/FESurrogateModelTutorial/reports/figures/rsm_residuals.png differ diff --git a/FESurrogateModelTutorial/reports/predictions/.gitkeep b/FESurrogateModelTutorial/reports/predictions/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/reports/predictions/gpr_predictions.csv b/FESurrogateModelTutorial/reports/predictions/gpr_predictions.csv new file mode 100644 index 0000000..ba028c4 --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/gpr_predictions.csv @@ -0,0 +1,61 @@ +y_true,y_pred,residual +-0.0098010662183848,-0.015250421684545492,0.005449355466160691 +-0.0038113194658788,-0.0017486039076851003,-0.0020627155581936997 +-0.0027310799449712,-0.00530647071310256,0.0025753907681313605 +-0.0100024342038305,-0.010316738327277896,0.00031430412344739603 +-0.0499253522341076,-0.04382392281863989,-0.006101429415467707 +-0.001425202073237,0.005078412534373628,-0.006503614607610628 +-0.0082152702385898,-0.009571524248486946,0.0013562540098971468 +-0.030294940628359,-0.02508246148372859,-0.005212479144630411 +-0.0003394669209404,-0.0023962303315858673,0.0020567634106454674 +-0.0104094868935342,-0.017755890098581213,0.007346403205047012 +-0.0007458704516273,-0.001982076369748365,0.0012362059181210648 +-0.0019926250153884,-0.003373505311388497,0.0013808802960000974 +-0.0067094522111387,-0.005789165245259511,-0.0009202869658791885 +-0.0007713230369176,0.0012402266579821346,-0.0020115496948997346 +-0.0009264786171953,0.0010793023104413834,-0.002005780927636683 +-0.0048682939239769,-0.010842246768661199,0.0059739528446842985 +-0.0015514429803847,-0.005987417572614714,0.004435974592230015 +-0.0082797555491993,-0.006796396801820442,-0.0014833587473788582 +-0.0067435512728419,0.00680790397400651,-0.01355145524684841 +-0.0002679427721747,0.0006763820975785838,-0.0009443248697532838 +-0.0117102395891512,0.0006489142032870647,-0.012359153792438266 +-0.0038564225187403,-0.0035722131493459413,-0.00028420936939435853 +-0.0043643611560866,-0.002491745084681229,-0.0018726160714053709 +-0.00690334164553,0.00027024498753304947,-0.007173586633063049 +-0.0032694837296735,-0.003198392657812337,-7.109107186116298e-05 +-0.0065802327277111,-0.006997526861566011,0.000417294133854911 +-0.0057609873946602,-0.008005191210867332,0.0022442038162071324 +-0.0978259601841187,-0.10476466176020587,0.0069387015760871695 +-0.0012815473411189,0.005795482786457884,-0.007077030127576784 +-0.2116989856844677,-0.1421513721299082,-0.06954761355455952 +-0.0005873182212224,0.003556659925605549,-0.004143978146827949 +-0.0102808405040129,-0.013683666669211234,0.0034028261651983336 +-0.0274808302842225,-0.023811924704933395,-0.0036689055792891064 +-0.0055843874647924,-0.004446019039931307,-0.0011383684248610932 +-0.0936698604467179,-0.08476905261398741,-0.008900807832730492 +-0.0042383507360532,-0.0028395908107003084,-0.0013987599253528918 +-0.0064139601803737,-0.002776365124053777,-0.003637595056319923 +-0.0500503637977256,-0.04468561411923253,-0.005364749678493069 +-0.0032551040880256,-0.007385900526260484,0.004130796438234884 +-0.0006720203919551,-0.0017365014801694594,0.0010644810882143594 +-0.0140013434972624,-0.018657807128895445,0.004656463631633044 +-0.0136746738089856,-0.01026732343038411,-0.0034073503786014904 +-0.0029028424886419,-0.006199045083251903,0.003296202594610003 +-0.0002977300790917,-0.003466576745293704,0.003168846666202004 +-0.0004013312796635,0.006938137232452899,-0.0073394685121164 +-0.001387036808518,-0.002671626354811536,0.0012845895462935358 +-0.0032416418934662,-2.721371817649039e-05,-0.0032144281752897095 +-0.0012865758997511,-0.004382274888399403,0.003095698988648303 +-0.000781489181828,0.014354677949252189,-0.015136167131080189 +-0.0282740230807759,-0.024446790443019183,-0.0038272326377567153 +-0.0014462597918054,-0.0025761975119785365,0.0011299377201731365 +-0.0009190994374579,-0.0015752616401020422,0.0006561622026441422 +-0.0024183380949151,0.001816942808085446,-0.004235280903000546 +-0.0080349039892428,-0.008306703776443457,0.00027179978720065674 +-0.0317498587484236,-0.026193372689921272,-0.005556486058502327 +-0.0028680506091303,-0.006249707444856041,0.003381656835725741 +-0.0327551701410487,-0.032500451863128575,-0.0002547182779201254 +-0.0002898009271785,-0.004854942238588591,0.00456514131141009 +-0.0049641733799332,-0.008914425892218376,0.003950252512285176 +-0.0005541404200699,0.0025102185642642497,-0.00306435898433415 diff --git a/FESurrogateModelTutorial/reports/predictions/gradient_boosting_predictions.csv b/FESurrogateModelTutorial/reports/predictions/gradient_boosting_predictions.csv new file mode 100644 index 0000000..a9e3487 --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/gradient_boosting_predictions.csv @@ -0,0 +1,61 @@ +y_true,y_pred,residual +-0.0098010662183848,-0.011534131293699368,0.0017330650753145677 +-0.0038113194658788,-0.004566781630798601,0.0007554621649198006 +-0.0027310799449712,-0.002569043760932535,-0.0001620361840386649 +-0.0100024342038305,-0.008113001885148158,-0.0018894323186823426 +-0.0499253522341076,-0.05179401032427151,0.0018686580901639063 +-0.001425202073237,-0.0027429307062308422,0.0013177286329938422 +-0.0082152702385898,-0.005244392438185645,-0.002970877800404154 +-0.030294940628359,-0.026941379068354337,-0.003353561560004662 +-0.0003394669209404,0.0011920263930887208,-0.0015314933140291207 +-0.0104094868935342,-0.02009314147825499,0.009683654584720789 +-0.0007458704516273,-0.0006783343667398965,-6.753608488740351e-05 +-0.0019926250153884,-0.0035238050937769078,0.001531180078388508 +-0.0067094522111387,0.0045098371679258165,-0.011219289379064516 +-0.0007713230369176,0.0009525966890204713,-0.0017239197259380713 +-0.0009264786171953,-0.0010291487366641794,0.00010267011946887947 +-0.0048682939239769,-0.008554456850656682,0.003686162926679782 +-0.0015514429803847,-0.003701977800637113,0.002150534820252413 +-0.0082797555491993,-0.010581044499564738,0.0023012889503654377 +-0.0067435512728419,-0.03214833568118499,0.02540478440834309 +-0.0002679427721747,0.0012861204508787876,-0.0015540632230534876 +-0.0117102395891512,-0.011566111843574601,-0.00014412774557659956 +-0.0038564225187403,-0.007016644417818661,0.0031602218990783608 +-0.0043643611560866,-0.0037020847290705405,-0.0006622764270160595 +-0.00690334164553,-0.006957646097004912,5.430445147491282e-05 +-0.0032694837296735,-0.008033853112083723,0.0047643693824102225 +-0.0065802327277111,-0.0029191448483979277,-0.003661087879313172 +-0.0057609873946602,-0.005458264133573717,-0.0003027232610864827 +-0.0978259601841187,-0.07722849901007466,-0.02059746117404404 +-0.0012815473411189,-0.0006360341549186051,-0.0006455131862002948 +-0.2116989856844677,-0.08524519557269143,-0.12645379011177627 +-0.0005873182212224,0.00015120038122322824,-0.0007385186024456282 +-0.0102808405040129,-0.013413515680865662,0.0031326751768527613 +-0.0274808302842225,-0.021333224751441966,-0.006147605532780535 +-0.0055843874647924,-0.004638268193139127,-0.0009461192716532733 +-0.0936698604467179,-0.10583589880087134,0.012166038354153433 +-0.0042383507360532,-0.006994619539973823,0.002756268803920623 +-0.0064139601803737,-0.00721593555420163,0.0008019753738279295 +-0.0500503637977256,-0.029980393421298147,-0.020069970376427455 +-0.0032551040880256,-0.004159641884839451,0.0009045377968138514 +-0.0006720203919551,0.000695861307391559,-0.001367881699346659 +-0.0140013434972624,-0.012330661500693965,-0.0016706819965684357 +-0.0136746738089856,-0.01605569638030974,0.002381022571324139 +-0.0029028424886419,-0.0023341567334053885,-0.0005686857552365115 +-0.0002977300790917,0.0018160654458535613,-0.002113795524945261 +-0.0004013312796635,0.000462792310109772,-0.000864123589773272 +-0.001387036808518,-0.0002553521012893653,-0.0011316847072286348 +-0.0032416418934662,-0.0034662863630390488,0.0002246444695728489 +-0.0012865758997511,-0.001419939312063704,0.00013336341231260382 +-0.000781489181828,0.0004714048460805055,-0.0012528940279085054 +-0.0282740230807759,-0.01656459879962216,-0.011709424281153739 +-0.0014462597918054,-0.001017996892076924,-0.00042826289972847597 +-0.0009190994374579,-0.0030369363287297622,0.0021178368912718624 +-0.0024183380949151,-0.0027257756235102245,0.0003074375285951245 +-0.0080349039892428,-0.013922103226463197,0.005887199237220397 +-0.0317498587484236,-0.04130074037606117,0.009550881627637571 +-0.0028680506091303,-0.004243174200819291,0.0013751235916889906 +-0.0327551701410487,-0.023406512064579634,-0.009348658076469066 +-0.0002898009271785,-0.0003650281642084131,7.522723702991309e-05 +-0.0049641733799332,-0.005214160126186096,0.00024998674625289607 +-0.0005541404200699,0.0014239990341411483,-0.0019781394542110484 diff --git a/FESurrogateModelTutorial/reports/predictions/mlp_predictions.csv b/FESurrogateModelTutorial/reports/predictions/mlp_predictions.csv new file mode 100644 index 0000000..c9fc2b2 --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/mlp_predictions.csv @@ -0,0 +1,61 @@ +y_true,y_pred,residual +-0.0098010662183848,-0.012802577507486139,0.0030015112891013385 +-0.0038113194658788,-0.0046515848170360155,0.0008402653511572155 +-0.0027310799449712,-0.010083004760998512,0.007351924816027313 +-0.0100024342038305,-0.020845283706657287,0.010842849502826787 +-0.0499253522341076,-0.040221134519255825,-0.009704217714851776 +-0.001425202073237,-0.009762081791064406,0.008336879717827405 +-0.0082152702385898,-0.016983972468944918,0.008768702230355118 +-0.030294940628359,-0.038763408487603644,0.008468467859244645 +-0.0003394669209404,0.0035130197140065145,-0.0038524866349469145 +-0.0104094868935342,-0.022486601392994278,0.012077114499460077 +-0.0007458704516273,0.008186185488004654,-0.008932055939631955 +-0.0019926250153884,-0.0024680978973060427,0.00047547288191764287 +-0.0067094522111387,-0.00822713371747703,0.0015176815063383305 +-0.0007713230369176,0.00270054572699867,-0.00347186876391627 +-0.0009264786171953,0.010416977768376428,-0.011343456385571727 +-0.0048682939239769,-0.011506092562164864,0.006637798638187964 +-0.0015514429803847,-0.009492646984972703,0.007941204004588002 +-0.0082797555491993,-0.021706863748004304,0.013427108198805004 +-0.0067435512728419,-0.024564514477627017,0.017820963204785118 +-0.0002679427721747,0.003440036938729933,-0.003707979710904633 +-0.0117102395891512,-0.01948773259389725,0.007777493004746051 +-0.0038564225187403,-0.009001834137222772,0.005145411618482472 +-0.0043643611560866,-0.005807071720011332,0.0014427105639247323 +-0.00690334164553,-0.012051489346343392,0.005148147700813392 +-0.0032694837296735,-0.014340233862364537,0.011070750132691036 +-0.0065802327277111,-0.023390501917222473,0.016810269189511375 +-0.0057609873946602,-0.009722949790975566,0.003961962396315366 +-0.0978259601841187,-0.04217063129833486,-0.05565532888578384 +-0.0012815473411189,0.0006427388868981282,-0.001924286228017028 +-0.2116989856844677,-0.03964152265257154,-0.17205746303189617 +-0.0005873182212224,0.013045438142832844,-0.013632756364055244 +-0.0102808405040129,-0.014460966326431212,0.004180125822418312 +-0.0274808302842225,-0.020779704750440366,-0.006701125533782135 +-0.0055843874647924,-0.011569739698444227,0.005985352233651826 +-0.0936698604467179,-0.04673036973556968,-0.046939490711148224 +-0.0042383507360532,-0.015102657835677205,0.010864307099624004 +-0.0064139601803737,-0.021855516256323176,0.015441556075949476 +-0.0500503637977256,-0.04096821507960851,-0.009082148718117092 +-0.0032551040880256,-0.009473373379514092,0.006218269291488492 +-0.0006720203919551,0.008151921529756209,-0.008823941921711308 +-0.0140013434972624,-0.027026365760490362,0.013025022263227961 +-0.0136746738089856,-0.018268174014002526,0.004593500205016926 +-0.0029028424886419,-0.005384453894577489,0.0024816114059355893 +-0.0002977300790917,0.01502494424731751,-0.01532267432640921 +-0.0004013312796635,0.008176262367795053,-0.008577593647458553 +-0.001387036808518,-0.0039136142664811455,0.002526577457963145 +-0.0032416418934662,-0.0005496866955882595,-0.0026919551978779404 +-0.0012865758997511,-0.0015485890387889768,0.00026201313903787666 +-0.000781489181828,0.00021154768682392702,-0.000993036868651927 +-0.0282740230807759,-0.0361710784496896,0.007897055368913702 +-0.0014462597918054,0.0010447393076983297,-0.0024909990995037297 +-0.0009190994374579,0.0005076838393030369,-0.001426783276760937 +-0.0024183380949151,-0.005814615168676113,0.0033962770737610134 +-0.0080349039892428,-0.017259202204240334,0.009224298214997534 +-0.0317498587484236,-0.01979978011856444,-0.01195007862985916 +-0.0028680506091303,-0.0025464659000482004,-0.00032158470908209965 +-0.0327551701410487,-0.02888314308530364,-0.0038720270557450615 +-0.0002898009271785,0.004474162373067482,-0.004763963300245982 +-0.0049641733799332,-0.009564500114358745,0.004600326734425545 +-0.0005541404200699,-0.008223453061769786,0.007669312641699886 diff --git a/FESurrogateModelTutorial/reports/predictions/random_forest_predictions.csv b/FESurrogateModelTutorial/reports/predictions/random_forest_predictions.csv new file mode 100644 index 0000000..5fb6cfb --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/random_forest_predictions.csv @@ -0,0 +1,61 @@ +y_true,y_pred,residual +-0.0098010662183848,-0.013052973102582651,0.003251906884197851 +-0.0038113194658788,-0.0030832217758694816,-0.0007280976900093184 +-0.0027310799449712,-0.003443617119201804,0.0007125371742306044 +-0.0100024342038305,-0.006389981087977729,-0.0036124531158527717 +-0.0499253522341076,-0.015352738964269329,-0.03457261326983827 +-0.001425202073237,-0.018459481777023584,0.017034279703786584 +-0.0082152702385898,-0.004456616612166581,-0.003758653626423218 +-0.030294940628359,-0.0233835822329406,-0.006911358395418399 +-0.0003394669209404,-0.001335100166637542,0.000995633245697142 +-0.0104094868935342,-0.029524370191124354,0.019114883297590153 +-0.0007458704516273,-0.0010697310437712045,0.00032386059214390447 +-0.0019926250153884,-0.0020536291218114233,6.1004106423023525e-05 +-0.0067094522111387,-0.014557394629973457,0.007847942418834757 +-0.0007713230369176,-0.0012499540750173254,0.0004786310380997254 +-0.0009264786171953,-0.0012898747386864487,0.0003633961214911487 +-0.0048682939239769,-0.010281716791039721,0.005413422867062821 +-0.0015514429803847,-0.006064928713181127,0.004513485732796427 +-0.0082797555491993,-0.01020851915611972,0.0019287636069204202 +-0.0067435512728419,-0.05887513385526359,0.05213158258242169 +-0.0002679427721747,-0.0014368826831020398,0.0011689399109273398 +-0.0117102395891512,-0.00897904191064286,-0.002731197678508341 +-0.0038564225187403,-0.007064039332542149,0.0032076168138018488 +-0.0043643611560866,-0.0038693413391170145,-0.0004950198169695855 +-0.00690334164553,-0.0038950530678037608,-0.003008288577726239 +-0.0032694837296735,-0.006531726228139853,0.003262242498466353 +-0.0065802327277111,-0.01746332239669454,0.010883089668983442 +-0.0057609873946602,-0.0042079257092469515,-0.0015530616854132484 +-0.0978259601841187,-0.06427351865985945,-0.03355244152425925 +-0.0012815473411189,-0.00131837298277374,3.6825641654840034e-05 +-0.2116989856844677,-0.07299885186088179,-0.13870013382358592 +-0.0005873182212224,-0.0008764204060906751,0.00028910218486827516 +-0.0102808405040129,-0.025656683515613517,0.015375843011600616 +-0.0274808302842225,-0.0233500781934777,-0.004130752090744802 +-0.0055843874647924,-0.00360254423169254,-0.0019818432330998602 +-0.0936698604467179,-0.06835084136803436,-0.025319019078683544 +-0.0042383507360532,-0.008322436707289365,0.004084085971236165 +-0.0064139601803737,-0.007978798272136524,0.0015648380917628237 +-0.0500503637977256,-0.029134230073410976,-0.020916133724314626 +-0.0032551040880256,-0.0038575984824836713,0.0006024943944580715 +-0.0006720203919551,-0.0008321139785327232,0.0001600935865776232 +-0.0140013434972624,-0.009220030597946776,-0.004781312899315625 +-0.0136746738089856,-0.012918969755213044,-0.000755704053772557 +-0.0029028424886419,-0.007061520705634288,0.004158678216992388 +-0.0002977300790917,-0.0008244792974268024,0.0005267492183351024 +-0.0004013312796635,-0.0006335738388530976,0.00023224255918959758 +-0.001387036808518,-0.0022687878733974104,0.0008817510648794103 +-0.0032416418934662,-0.0031708494223524506,-7.079247111374922e-05 +-0.0012865758997511,-0.0020774063656214905,0.0007908304658703904 +-0.000781489181828,-0.004022832924982734,0.003241343743154734 +-0.0282740230807759,-0.023201264759851178,-0.005072758320924721 +-0.0014462597918054,-0.0021273945521054927,0.0006811347603000928 +-0.0009190994374579,-0.0011891624177935518,0.0002700629803356518 +-0.0024183380949151,-0.002935912586886879,0.0005175744919717791 +-0.0080349039892428,-0.012493932904864844,0.004459028915622044 +-0.0317498587484236,-0.02917403551090198,-0.0025758232375216183 +-0.0028680506091303,-0.002477127846164984,-0.00039092276296531607 +-0.0327551701410487,-0.020574378594002435,-0.012180791547046266 +-0.0002898009271785,-0.0030515507112431066,0.0027617497840646066 +-0.0049641733799332,-0.005639149529924011,0.0006749761499908113 +-0.0005541404200699,-0.0033415931535627887,0.0027874527334928885 diff --git a/FESurrogateModelTutorial/reports/predictions/rsm_predictions.csv b/FESurrogateModelTutorial/reports/predictions/rsm_predictions.csv new file mode 100644 index 0000000..404e844 --- /dev/null +++ b/FESurrogateModelTutorial/reports/predictions/rsm_predictions.csv @@ -0,0 +1,61 @@ +y_true,y_pred,residual +-0.0098010662183848,-0.008063708243651395,-0.0017373579747334052 +-0.0038113194658788,-0.0036842415434730005,-0.0001270779224057995 +-0.0027310799449712,0.0015855198478710482,-0.0043165997928422475 +-0.0100024342038305,-0.020768772136692537,0.010766337932862037 +-0.0499253522341076,-0.08634072145899263,0.03641536922488503 +-0.001425202073237,0.009057330651376155,-0.010482532724613155 +-0.0082152702385898,-0.01623359805579585,0.00801832781720605 +-0.030294940628359,-0.05494634739863705,0.024651406770278047 +-0.0003394669209404,0.0003057217392075026,-0.0006451886601479026 +-0.0104094868935342,-0.025186470928988097,0.014776984035453897 +-0.0007458704516273,0.005123479401716775,-0.0058693498533440745 +-0.0019926250153884,0.006483277853635958,-0.008475902869024358 +-0.0067094522111387,-0.004951368629097145,-0.0017580835820415544 +-0.0007713230369176,0.002431470422207911,-0.003202793459125511 +-0.0009264786171953,0.002098379150773981,-0.0030248577679692808 +-0.0048682939239769,0.0023269502345097445,-0.007195244158486645 +-0.0015514429803847,0.0054304376400158855,-0.006981880620400585 +-0.0082797555491993,-0.011235438686456989,0.0029556831372576885 +-0.0067435512728419,-0.01513640141753062,0.00839285014468872 +-0.0002679427721747,0.005023227034898421,-0.005291169807073121 +-0.0117102395891512,-0.021353175562886392,0.009642935973735191 +-0.0038564225187403,0.0024523028133941953,-0.006308725332134495 +-0.0043643611560866,0.0018118523029535282,-0.006176213459040129 +-0.00690334164553,-0.011090572616019383,0.004187230970489383 +-0.0032694837296735,-0.0010327942129321746,-0.0022366895167413255 +-0.0065802327277111,-0.010449659009701765,0.0038694262819906657 +-0.0057609873946602,-0.004202124085317911,-0.0015588633093422885 +-0.0978259601841187,-0.07849009051035474,-0.019335869673763953 +-0.0012815473411189,0.0045897836777313485,-0.005871331018850248 +-0.2116989856844677,-0.11236685970490985,-0.09933212597955786 +-0.0005873182212224,-0.0038346423165307834,0.0032473240953083834 +-0.0102808405040129,-0.011514380677840113,0.001233540173827213 +-0.0274808302842225,-0.03997989882186454,0.01249906853764204 +-0.0055843874647924,-0.007827865894016085,0.0022434784292236845 +-0.0936698604467179,-0.07675546207236617,-0.01691439837435174 +-0.0042383507360532,0.0005673146824279073,-0.004805665418481108 +-0.0064139601803737,-0.011035615882888063,0.004621655702514363 +-0.0500503637977256,-0.07016141645387532,0.020111052656149715 +-0.0032551040880256,0.00548188417318066,-0.008736988261206259 +-0.0006720203919551,0.0009801902763272793,-0.0016522106682823794 +-0.0140013434972624,-0.025223749239703955,0.011222405742441554 +-0.0136746738089856,-0.033649129185114264,0.019974455376128665 +-0.0029028424886419,0.0016175132552921486,-0.004520355743934048 +-0.0002977300790917,-0.012343066428881081,0.012045336349789382 +-0.0004013312796635,-0.0019906728761898054,0.0015893415965263053 +-0.001387036808518,0.014394621344358304,-0.015781658152876302 +-0.0032416418934662,-0.0017551803377404158,-0.001486461555725784 +-0.0012865758997511,0.011086934940341958,-0.012373510840093058 +-0.000781489181828,0.009985914013305923,-0.010767403195133923 +-0.0282740230807759,-0.03956474679869234,0.01129072371791644 +-0.0014462597918054,0.0017128674505823053,-0.0031591272423877052 +-0.0009190994374579,0.008880594747871942,-0.009799694185329841 +-0.0024183380949151,0.006026571466815343,-0.008444909561730444 +-0.0080349039892428,-0.012179467818523977,0.004144563829281177 +-0.0317498587484236,-0.03552887414112901,0.0037790153927054135 +-0.0028680506091303,0.0019507691371565064,-0.004818819746286807 +-0.0327551701410487,-0.06483928482330545,0.03208411468225675 +-0.0002898009271785,0.0050500589361993,-0.0053398598633778 +-0.0049641733799332,-0.001537216194153125,-0.003426957185780075 +-0.0005541404200699,0.015848580403955245,-0.016402720824025146 diff --git a/FESurrogateModelTutorial/reports/results/.gitkeep b/FESurrogateModelTutorial/reports/results/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/reports/results/gpr_metrics.json b/FESurrogateModelTutorial/reports/results/gpr_metrics.json new file mode 100644 index 0000000..896d16b --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/gpr_metrics.json @@ -0,0 +1,9 @@ +{ + "model_name": "gpr", + "target_column": "tip_uy_m", + "rmse": 0.0102275703149458, + "mae": 0.004887524852501009, + "r2": 0.8984149766904297, + "fit_time_s": 0.15534250007476658, + "predict_time_s": 0.0016494999872520566 +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/reports/results/gradient_boosting_metrics.json b/FESurrogateModelTutorial/reports/results/gradient_boosting_metrics.json new file mode 100644 index 0000000..842f30d --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/gradient_boosting_metrics.json @@ -0,0 +1,9 @@ +{ + "model_name": "gradient_boosting", + "target_column": "tip_uy_m", + "rmse": 0.017503204254034725, + "mae": 0.0056308989929006875, + "r2": 0.7024774700809826, + "fit_time_s": 0.4196564999874681, + "predict_time_s": 0.0021611000411212444 +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/reports/results/mlp_metrics.json b/FESurrogateModelTutorial/reports/results/mlp_metrics.json new file mode 100644 index 0000000..8c0d137 --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/mlp_metrics.json @@ -0,0 +1,9 @@ +{ + "model_name": "mlp", + "target_column": "tip_uy_m", + "rmse": 0.02540606802811113, + "mae": 0.011091159933185777, + "r2": 0.3731557450209999, + "fit_time_s": 0.114232899970375, + "predict_time_s": 0.0020921999821439385 +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/reports/results/model_comparison.csv b/FESurrogateModelTutorial/reports/results/model_comparison.csv new file mode 100644 index 0000000..381ad6e --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/model_comparison.csv @@ -0,0 +1,6 @@ +model_name,target_column,rmse,mae,r2,fit_time_s,predict_time_s +gpr,tip_uy_m,0.0102275703149458,0.004887524852501009,0.8984149766904297,0.15534250007476658,0.0016494999872520566 +rsm,tip_uy_m,0.01692421409745107,0.00986868711451997,0.7218354651088679,0.008010599995031953,0.0015782000264152884 +gradient_boosting,tip_uy_m,0.017503204254034725,0.0056308989929006875,0.7024774700809826,0.4196564999874681,0.0021611000411212444 +random_forest,tip_uy_m,0.021182094831369028,0.008076487480895672,0.5642648925820651,0.6153512999881059,0.08771130000241101 +mlp,tip_uy_m,0.02540606802811113,0.011091159933185777,0.3731557450209999,0.114232899970375,0.0020921999821439385 diff --git a/FESurrogateModelTutorial/reports/results/random_forest_metrics.json b/FESurrogateModelTutorial/reports/results/random_forest_metrics.json new file mode 100644 index 0000000..353edd9 --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/random_forest_metrics.json @@ -0,0 +1,9 @@ +{ + "model_name": "random_forest", + "target_column": "tip_uy_m", + "rmse": 0.021182094831369028, + "mae": 0.008076487480895672, + "r2": 0.5642648925820651, + "fit_time_s": 0.6153512999881059, + "predict_time_s": 0.08771130000241101 +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/reports/results/rsm_metrics.json b/FESurrogateModelTutorial/reports/results/rsm_metrics.json new file mode 100644 index 0000000..fd8be90 --- /dev/null +++ b/FESurrogateModelTutorial/reports/results/rsm_metrics.json @@ -0,0 +1,9 @@ +{ + "model_name": "rsm", + "target_column": "tip_uy_m", + "rmse": 0.01692421409745107, + "mae": 0.00986868711451997, + "r2": 0.7218354651088679, + "fit_time_s": 0.008010599995031953, + "predict_time_s": 0.0015782000264152884 +} \ No newline at end of file diff --git a/FESurrogateModelTutorial/scripts/execute.py b/FESurrogateModelTutorial/scripts/execute.py new file mode 100644 index 0000000..8016252 --- /dev/null +++ b/FESurrogateModelTutorial/scripts/execute.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +""" +Harness Step Executor — phase 내 step을 순차 실행하고 자가 교정한다. + +Usage: + python scripts/execute.py [--push] +""" + +import argparse +import contextlib +import json +import os +import subprocess +import sys +import threading +import time +import types +from datetime import datetime, timezone, timedelta +from pathlib import Path +from typing import Optional + +ROOT = Path(__file__).resolve().parent.parent + + +@contextlib.contextmanager +def progress_indicator(label: str): + """터미널 진행 표시기. with 문으로 사용하며 .elapsed 로 경과 시간을 읽는다.""" + frames = "◐◓◑◒" + stop = threading.Event() + t0 = time.monotonic() + + def _animate(): + idx = 0 + while not stop.wait(0.12): + sec = int(time.monotonic() - t0) + sys.stderr.write(f"\r{frames[idx % len(frames)]} {label} [{sec}s]") + sys.stderr.flush() + idx += 1 + sys.stderr.write("\r" + " " * (len(label) + 20) + "\r") + sys.stderr.flush() + + th = threading.Thread(target=_animate, daemon=True) + th.start() + info = types.SimpleNamespace(elapsed=0.0) + try: + yield info + finally: + stop.set() + th.join() + info.elapsed = time.monotonic() - t0 + + +class StepExecutor: + """Phase 디렉토리 안의 step들을 순차 실행하는 하네스.""" + + MAX_RETRIES = 3 + FEAT_MSG = "feat({phase}): step {num} — {name}" + CHORE_MSG = "chore({phase}): step {num} output" + TZ = timezone(timedelta(hours=9)) + + def __init__(self, phase_dir_name: str, *, auto_push: bool = False): + self._root = str(ROOT) + self._phases_dir = ROOT / "phases" + self._phase_dir = self._phases_dir / phase_dir_name + self._phase_dir_name = phase_dir_name + self._top_index_file = self._phases_dir / "index.json" + self._auto_push = auto_push + + if not self._phase_dir.is_dir(): + print(f"ERROR: {self._phase_dir} not found") + sys.exit(1) + + self._index_file = self._phase_dir / "index.json" + if not self._index_file.exists(): + print(f"ERROR: {self._index_file} not found") + sys.exit(1) + + idx = self._read_json(self._index_file) + self._project = idx.get("project", "project") + self._phase_name = idx.get("phase", phase_dir_name) + self._total = len(idx["steps"]) + + def run(self): + self._print_header() + self._check_blockers() + self._checkout_branch() + guardrails = self._load_guardrails() + self._ensure_created_at() + self._execute_all_steps(guardrails) + self._finalize() + + # --- timestamps --- + + def _stamp(self) -> str: + return datetime.now(self.TZ).strftime("%Y-%m-%dT%H:%M:%S%z") + + # --- JSON I/O --- + + @staticmethod + def _read_json(p: Path) -> dict: + return json.loads(p.read_text(encoding="utf-8")) + + @staticmethod + def _write_json(p: Path, data: dict): + p.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") + + # --- git --- + + def _run_git(self, *args) -> subprocess.CompletedProcess: + cmd = ["git"] + list(args) + return subprocess.run(cmd, cwd=self._root, capture_output=True, text=True) + + def _checkout_branch(self): + branch = f"feat-{self._phase_name}" + + r = self._run_git("rev-parse", "--abbrev-ref", "HEAD") + if r.returncode != 0: + print(f" ERROR: git을 사용할 수 없거나 git repo가 아닙니다.") + print(f" {r.stderr.strip()}") + sys.exit(1) + + if r.stdout.strip() == branch: + return + + r = self._run_git("rev-parse", "--verify", branch) + r = self._run_git("checkout", branch) if r.returncode == 0 else self._run_git("checkout", "-b", branch) + + if r.returncode != 0: + print(f" ERROR: 브랜치 '{branch}' checkout 실패.") + print(f" {r.stderr.strip()}") + print(f" Hint: 변경사항을 stash하거나 commit한 후 다시 시도하세요.") + sys.exit(1) + + print(f" Branch: {branch}") + + def _commit_step(self, step_num: int, step_name: str): + output_rel = f"phases/{self._phase_dir_name}/step{step_num}-output.json" + index_rel = f"phases/{self._phase_dir_name}/index.json" + + self._run_git("add", "-A") + self._run_git("reset", "HEAD", "--", output_rel) + self._run_git("reset", "HEAD", "--", index_rel) + + if self._run_git("diff", "--cached", "--quiet").returncode != 0: + msg = self.FEAT_MSG.format(phase=self._phase_name, num=step_num, name=step_name) + r = self._run_git("commit", "-m", msg) + if r.returncode == 0: + print(f" Commit: {msg}") + else: + print(f" WARN: 코드 커밋 실패: {r.stderr.strip()}") + + self._run_git("add", "-A") + if self._run_git("diff", "--cached", "--quiet").returncode != 0: + msg = self.CHORE_MSG.format(phase=self._phase_name, num=step_num) + r = self._run_git("commit", "-m", msg) + if r.returncode != 0: + print(f" WARN: housekeeping 커밋 실패: {r.stderr.strip()}") + + # --- top-level index --- + + def _update_top_index(self, status: str): + if not self._top_index_file.exists(): + return + top = self._read_json(self._top_index_file) + ts = self._stamp() + for phase in top.get("phases", []): + if phase.get("dir") == self._phase_dir_name: + phase["status"] = status + ts_key = {"completed": "completed_at", "error": "failed_at", "blocked": "blocked_at"}.get(status) + if ts_key: + phase[ts_key] = ts + break + self._write_json(self._top_index_file, top) + + # --- guardrails & context --- + + def _load_guardrails(self) -> str: + sections = [] + agents_md = ROOT / "AGENTS.md" + if agents_md.exists(): + sections.append(f"## 프로젝트 규칙 (AGENTS.md)\n\n{agents_md.read_text(encoding='utf-8')}") + docs_dir = ROOT / "docs" + if docs_dir.is_dir(): + for doc in sorted(docs_dir.glob("*.md")): + sections.append(f"## {doc.stem}\n\n{doc.read_text(encoding='utf-8')}") + return "\n\n---\n\n".join(sections) if sections else "" + + @staticmethod + def _build_step_context(index: dict) -> str: + lines = [ + f"- Step {s['step']} ({s['name']}): {s['summary']}" + for s in index["steps"] + if s["status"] == "completed" and s.get("summary") + ] + if not lines: + return "" + return "## 이전 Step 산출물\n\n" + "\n".join(lines) + "\n\n" + + def _build_preamble(self, guardrails: str, step_context: str, + prev_error: Optional[str] = None) -> str: + commit_example = self.FEAT_MSG.format( + phase=self._phase_name, num="N", name="" + ) + retry_section = "" + if prev_error: + retry_section = ( + f"\n## ⚠ 이전 시도 실패 — 아래 에러를 반드시 참고하여 수정하라\n\n" + f"{prev_error}\n\n---\n\n" + ) + return ( + f"당신은 {self._project} 프로젝트의 개발자입니다. 아래 step을 수행하세요.\n\n" + f"{guardrails}\n\n---\n\n" + f"{step_context}{retry_section}" + f"## 작업 규칙\n\n" + f"1. 이전 step에서 작성된 코드를 확인하고 일관성을 유지하라.\n" + f"2. 이 step에 명시된 작업만 수행하라. 추가 기능이나 파일을 만들지 마라.\n" + f"3. 기존 테스트를 깨뜨리지 마라.\n" + f"4. AC(Acceptance Criteria) 검증을 직접 실행하라.\n" + f"5. /phases/{self._phase_dir_name}/index.json의 해당 step status를 업데이트하라:\n" + f" - AC 통과 → \"completed\" + \"summary\" 필드에 이 step의 산출물을 한 줄로 요약\n" + f" - {self.MAX_RETRIES}회 수정 시도 후에도 실패 → \"error\" + \"error_message\" 기록\n" + f" - 사용자 개입이 필요한 경우 (API 키, 인증, 수동 설정 등) → \"blocked\" + \"blocked_reason\" 기록 후 즉시 중단\n" + f"6. 모든 변경사항을 커밋하라:\n" + f" {commit_example}\n\n---\n\n" + ) + + # --- Codex 호출 --- + + def _invoke_codex(self, step: dict, preamble: str) -> dict: + step_num, step_name = step["step"], step["name"] + step_file = self._phase_dir / f"step{step_num}.md" + + if not step_file.exists(): + print(f" ERROR: {step_file} not found") + sys.exit(1) + + prompt = preamble + step_file.read_text(encoding="utf-8") + result = subprocess.run( + ["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", prompt], + cwd=self._root, capture_output=True, text=True, timeout=1800, + ) + + if result.returncode != 0: + print(f"\n WARN: Codex가 비정상 종료됨 (code {result.returncode})") + if result.stderr: + print(f" stderr: {result.stderr[:500]}") + + output = { + "step": step_num, "name": step_name, + "exitCode": result.returncode, + "stdout": result.stdout, "stderr": result.stderr, + } + out_path = self._phase_dir / f"step{step_num}-output.json" + with open(out_path, "w", encoding="utf-8") as f: + json.dump(output, f, indent=2, ensure_ascii=False) + + return output + + # --- 헤더 & 검증 --- + + def _print_header(self): + print(f"\n{'='*60}") + print(f" Harness Step Executor") + print(f" Phase: {self._phase_name} | Steps: {self._total}") + if self._auto_push: + print(f" Auto-push: enabled") + print(f"{'='*60}") + + def _check_blockers(self): + index = self._read_json(self._index_file) + for s in reversed(index["steps"]): + if s["status"] == "error": + print(f"\n ✗ Step {s['step']} ({s['name']}) failed.") + print(f" Error: {s.get('error_message', 'unknown')}") + print(f" Fix and reset status to 'pending' to retry.") + sys.exit(1) + if s["status"] == "blocked": + print(f"\n ⏸ Step {s['step']} ({s['name']}) blocked.") + print(f" Reason: {s.get('blocked_reason', 'unknown')}") + print(f" Resolve and reset status to 'pending' to retry.") + sys.exit(2) + if s["status"] != "pending": + break + + def _ensure_created_at(self): + index = self._read_json(self._index_file) + if "created_at" not in index: + index["created_at"] = self._stamp() + self._write_json(self._index_file, index) + + # --- 실행 루프 --- + + def _execute_single_step(self, step: dict, guardrails: str) -> bool: + """단일 step 실행 (재시도 포함). 완료되면 True, 실패/차단이면 False.""" + step_num, step_name = step["step"], step["name"] + done = sum(1 for s in self._read_json(self._index_file)["steps"] if s["status"] == "completed") + prev_error = None + + for attempt in range(1, self.MAX_RETRIES + 1): + index = self._read_json(self._index_file) + step_context = self._build_step_context(index) + preamble = self._build_preamble(guardrails, step_context, prev_error) + + tag = f"Step {step_num}/{self._total - 1} ({done} done): {step_name}" + if attempt > 1: + tag += f" [retry {attempt}/{self.MAX_RETRIES}]" + + with progress_indicator(tag) as pi: + self._invoke_codex(step, preamble) + elapsed = int(pi.elapsed) + + index = self._read_json(self._index_file) + status = next((s.get("status", "pending") for s in index["steps"] if s["step"] == step_num), "pending") + ts = self._stamp() + + if status == "completed": + for s in index["steps"]: + if s["step"] == step_num: + s["completed_at"] = ts + self._write_json(self._index_file, index) + self._commit_step(step_num, step_name) + print(f" ✓ Step {step_num}: {step_name} [{elapsed}s]") + return True + + if status == "blocked": + for s in index["steps"]: + if s["step"] == step_num: + s["blocked_at"] = ts + self._write_json(self._index_file, index) + reason = next((s.get("blocked_reason", "") for s in index["steps"] if s["step"] == step_num), "") + print(f" ⏸ Step {step_num}: {step_name} blocked [{elapsed}s]") + print(f" Reason: {reason}") + self._update_top_index("blocked") + sys.exit(2) + + err_msg = next( + (s.get("error_message", "Step did not update status") for s in index["steps"] if s["step"] == step_num), + "Step did not update status", + ) + + if attempt < self.MAX_RETRIES: + for s in index["steps"]: + if s["step"] == step_num: + s["status"] = "pending" + s.pop("error_message", None) + self._write_json(self._index_file, index) + prev_error = err_msg + print(f" ↻ Step {step_num}: retry {attempt}/{self.MAX_RETRIES} — {err_msg}") + else: + for s in index["steps"]: + if s["step"] == step_num: + s["status"] = "error" + s["error_message"] = f"[{self.MAX_RETRIES}회 시도 후 실패] {err_msg}" + s["failed_at"] = ts + self._write_json(self._index_file, index) + self._commit_step(step_num, step_name) + print(f" ✗ Step {step_num}: {step_name} failed after {self.MAX_RETRIES} attempts [{elapsed}s]") + print(f" Error: {err_msg}") + self._update_top_index("error") + sys.exit(1) + + return False # unreachable + + def _execute_all_steps(self, guardrails: str): + while True: + index = self._read_json(self._index_file) + pending = next((s for s in index["steps"] if s["status"] == "pending"), None) + if pending is None: + print("\n All steps completed!") + return + + step_num = pending["step"] + for s in index["steps"]: + if s["step"] == step_num and "started_at" not in s: + s["started_at"] = self._stamp() + self._write_json(self._index_file, index) + break + + self._execute_single_step(pending, guardrails) + + def _finalize(self): + index = self._read_json(self._index_file) + index["completed_at"] = self._stamp() + self._write_json(self._index_file, index) + self._update_top_index("completed") + + self._run_git("add", "-A") + if self._run_git("diff", "--cached", "--quiet").returncode != 0: + msg = f"chore({self._phase_name}): mark phase completed" + r = self._run_git("commit", "-m", msg) + if r.returncode == 0: + print(f" ✓ {msg}") + + if self._auto_push: + branch = f"feat-{self._phase_name}" + r = self._run_git("push", "-u", "origin", branch) + if r.returncode != 0: + print(f"\n ERROR: git push 실패: {r.stderr.strip()}") + sys.exit(1) + print(f" ✓ Pushed to origin/{branch}") + + print(f"\n{'='*60}") + print(f" Phase '{self._phase_name}' completed!") + print(f"{'='*60}") + + +def main(): + parser = argparse.ArgumentParser(description="Harness Step Executor") + parser.add_argument("phase_dir", help="Phase directory name (e.g. 0-mvp)") + parser.add_argument("--push", action="store_true", help="Push branch after completion") + args = parser.parse_args() + + StepExecutor(args.phase_dir, auto_push=args.push).run() + + +if __name__ == "__main__": + main() diff --git a/FESurrogateModelTutorial/src/femsurrogate/__init__.py b/FESurrogateModelTutorial/src/femsurrogate/__init__.py new file mode 100644 index 0000000..d665e45 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/__init__.py @@ -0,0 +1,3 @@ +"""FEM surrogate tutorial package.""" + +__version__ = "0.1.0" diff --git a/FESurrogateModelTutorial/src/femsurrogate/data/__init__.py b/FESurrogateModelTutorial/src/femsurrogate/data/__init__.py new file mode 100644 index 0000000..09a6459 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/data/__init__.py @@ -0,0 +1 @@ +"""Dataset generation helpers for FEM surrogate tutorials.""" diff --git a/FESurrogateModelTutorial/src/femsurrogate/data/bounds.py b/FESurrogateModelTutorial/src/femsurrogate/data/bounds.py new file mode 100644 index 0000000..e019d06 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/data/bounds.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ParameterBound: + lower: float + upper: float + + +DEFAULT_PARAMETER_BOUNDS = { + "L_m": ParameterBound(lower=1.0, upper=3.0), + "b_m": ParameterBound(lower=0.02, upper=0.08), + "h_m": ParameterBound(lower=0.04, upper=0.16), + "E_pa": ParameterBound(lower=100e9, upper=220e9), + "P_n": ParameterBound(lower=100.0, upper=2000.0), +} diff --git a/FESurrogateModelTutorial/src/femsurrogate/data/dataset.py b/FESurrogateModelTutorial/src/femsurrogate/data/dataset.py new file mode 100644 index 0000000..01fd17d --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/data/dataset.py @@ -0,0 +1,106 @@ +from collections.abc import Mapping +from dataclasses import asdict + +import pandas as pd + +from femsurrogate.data.schema import ( + DENSITY_KG_M3, + DERIVED_COLUMNS, + PARAMETER_COLUMNS, + TARGET_COLUMNS, + AnalysisResult, + BeamParameters, +) +from femsurrogate.fea.model import Beam, BeamModel, NodalLoad, Node +from femsurrogate.fea.solver import solve_linear_static + +N_CANTILEVER_NODES = 6 + + +def _section_area(params: BeamParameters) -> float: + return params.b_m * params.h_m + + +def _section_izz(params: BeamParameters) -> float: + return params.b_m * params.h_m**3 / 12.0 + + +def _coerce_parameters(params: BeamParameters | Mapping[str, float]) -> BeamParameters: + if isinstance(params, BeamParameters): + return params + return BeamParameters(**{column: float(params[column]) for column in PARAMETER_COLUMNS}) + + +def _make_cantilever_model(params: BeamParameters) -> BeamModel: + area = _section_area(params) + izz = _section_izz(params) + iyy = params.h_m * params.b_m**3 / 12.0 + nodes = { + node_id: Node( + id=node_id, + x=params.L_m * (node_id - 1) / (N_CANTILEVER_NODES - 1), + y=0.0, + ) + for node_id in range(1, N_CANTILEVER_NODES + 1) + } + beams = tuple( + Beam(id=beam_id, node_i=beam_id, node_j=beam_id + 1) + for beam_id in range(1, N_CANTILEVER_NODES) + ) + + return BeamModel( + metadata={ + "Area": area, + "J": iyy + izz, + "Iyy": iyy, + "Izz": izz, + "ElasticModulus": params.E_pa, + "Poisson'sRatio": 0.3, + }, + nodes=nodes, + beams=beams, + fixed_nodes=(1,), + nodal_loads={ + N_CANTILEVER_NODES: NodalLoad( + node_id=N_CANTILEVER_NODES, + fx=0.0, + fy=-params.P_n, + mz=0.0, + ) + }, + ) + + +def run_beam2d_case(params: BeamParameters | Mapping[str, float]) -> AnalysisResult: + beam_params = _coerce_parameters(params) + area = _section_area(beam_params) + izz = _section_izz(beam_params) + model = _make_cantilever_model(beam_params) + solution = solve_linear_static(model) + tip_uy = solution[N_CANTILEVER_NODES].uy + max_moment = abs(beam_params.P_n) * beam_params.L_m + max_abs_bending_stress = max_moment * (beam_params.h_m / 2.0) / izz + mass = DENSITY_KG_M3 * area * beam_params.L_m + compliance = -beam_params.P_n * tip_uy + + return AnalysisResult( + tip_uy_m=tip_uy, + max_abs_bending_stress_pa=max_abs_bending_stress, + mass_kg=mass, + compliance_j=compliance, + ) + + +def build_dataset(samples: pd.DataFrame) -> pd.DataFrame: + rows: list[dict[str, float]] = [] + + for sample in samples.to_dict(orient="records"): + params = _coerce_parameters(sample) + result = run_beam2d_case(params) + row = dict(sample) + row["A_m2"] = _section_area(params) + row["I_m4"] = _section_izz(params) + row.update(asdict(result)) + rows.append(row) + + return pd.DataFrame(rows, columns=[*samples.columns, *DERIVED_COLUMNS, *TARGET_COLUMNS]) diff --git a/FESurrogateModelTutorial/src/femsurrogate/data/sampling.py b/FESurrogateModelTutorial/src/femsurrogate/data/sampling.py new file mode 100644 index 0000000..d48a01a --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/data/sampling.py @@ -0,0 +1,18 @@ +import pandas as pd +from scipy.stats import qmc + +from femsurrogate.data.bounds import ParameterBound + + +def generate_lhs_samples(bounds: dict[str, ParameterBound], n: int, seed: int) -> pd.DataFrame: + if n <= 0: + raise ValueError("n must be positive") + + columns = list(bounds) + sampler = qmc.LatinHypercube(d=len(columns), seed=seed) + unit_samples = sampler.random(n) + lower = [bounds[column].lower for column in columns] + upper = [bounds[column].upper for column in columns] + samples = qmc.scale(unit_samples, lower, upper) + + return pd.DataFrame(samples, columns=columns) diff --git a/FESurrogateModelTutorial/src/femsurrogate/data/schema.py b/FESurrogateModelTutorial/src/femsurrogate/data/schema.py new file mode 100644 index 0000000..d016b2b --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/data/schema.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + +DEFAULT_RANDOM_SEED = 20260521 +DENSITY_KG_M3 = 7850.0 +PARAMETER_COLUMNS = ("L_m", "b_m", "h_m", "E_pa", "P_n") +DERIVED_COLUMNS = ("A_m2", "I_m4") +TARGET_COLUMNS = ("tip_uy_m", "max_abs_bending_stress_pa", "mass_kg", "compliance_j") + + +@dataclass(frozen=True) +class BeamParameters: + L_m: float + b_m: float + h_m: float + E_pa: float + P_n: float + + +@dataclass(frozen=True) +class AnalysisResult: + tip_uy_m: float + max_abs_bending_stress_pa: float + mass_kg: float + compliance_j: float diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/__init__.py b/FESurrogateModelTutorial/src/femsurrogate/fea/__init__.py new file mode 100644 index 0000000..bafcc0f --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/__init__.py @@ -0,0 +1 @@ +"""Finite element helpers for 2D beam/frame examples.""" diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/assembly.py b/FESurrogateModelTutorial/src/femsurrogate/fea/assembly.py new file mode 100644 index 0000000..17754ec --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/assembly.py @@ -0,0 +1,60 @@ +import math + +import numpy as np +from scipy.sparse import csr_matrix, lil_matrix + +from femsurrogate.fea.element import local_frame_stiffness, transformation_matrix +from femsurrogate.fea.model import BeamModel + + +def node_ids(model: BeamModel) -> tuple[int, ...]: + return tuple(sorted(model.nodes)) + + +def dof_index(model: BeamModel, node_id: int, local_dof: int) -> int: + node_position = node_ids(model).index(node_id) + return node_position * 3 + local_dof + + +def node_dofs(model: BeamModel, node_id: int) -> tuple[int, int, int]: + base = dof_index(model, node_id, 0) + return (base, base + 1, base + 2) + + +def constrained_dofs(model: BeamModel) -> tuple[int, ...]: + return tuple(dof for node_id in sorted(model.fixed_nodes) for dof in node_dofs(model, node_id)) + + +def assemble_global_stiffness(model: BeamModel) -> csr_matrix: + size = 3 * len(model.nodes) + stiffness = lil_matrix((size, size), dtype=float) + E = model.metadata["ElasticModulus"] + A = model.metadata["Area"] + Izz = model.metadata["Izz"] + + for beam in model.beams: + node_i = model.nodes[beam.node_i] + node_j = model.nodes[beam.node_j] + length = math.hypot(node_j.x - node_i.x, node_j.y - node_i.y) + local_stiffness = local_frame_stiffness(E=E, A=A, Izz=Izz, L=length) + transform = transformation_matrix(node_i.x, node_i.y, node_j.x, node_j.y) + element_stiffness = transform.T @ local_stiffness @ transform + element_dofs = (*node_dofs(model, beam.node_i), *node_dofs(model, beam.node_j)) + + for row_local, row_global in enumerate(element_dofs): + for col_local, col_global in enumerate(element_dofs): + stiffness[row_global, col_global] += element_stiffness[row_local, col_local] + + return stiffness.tocsr() + + +def assemble_load_vector(model: BeamModel) -> np.ndarray: + loads = np.zeros(3 * len(model.nodes), dtype=float) + + for node_id, load in model.nodal_loads.items(): + ux_dof, uy_dof, rz_dof = node_dofs(model, node_id) + loads[ux_dof] += load.fx + loads[uy_dof] += load.fy + loads[rz_dof] += load.mz + + return loads diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/benchmark.py b/FESurrogateModelTutorial/src/femsurrogate/fea/benchmark.py new file mode 100644 index 0000000..9a1200d --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/benchmark.py @@ -0,0 +1,2 @@ +def cantilever_tip_displacement(*, force_y: float, length: float, E: float, Izz: float) -> float: + return force_y * length**3 / (3.0 * E * Izz) diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/element.py b/FESurrogateModelTutorial/src/femsurrogate/fea/element.py new file mode 100644 index 0000000..73af108 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/element.py @@ -0,0 +1,46 @@ +import math + +import numpy as np + + +def local_frame_stiffness(*, E: float, A: float, Izz: float, L: float) -> np.ndarray: + """Return local frame stiffness with Rz positive clockwise.""" + axial = E * A / L + b12 = 12.0 * E * Izz / L**3 + b6 = 6.0 * E * Izz / L**2 + b4 = 4.0 * E * Izz / L + b2 = 2.0 * E * Izz / L + + return np.array( + [ + [axial, 0.0, 0.0, -axial, 0.0, 0.0], + [0.0, b12, -b6, 0.0, -b12, -b6], + [0.0, -b6, b4, 0.0, b6, b2], + [-axial, 0.0, 0.0, axial, 0.0, 0.0], + [0.0, -b12, b6, 0.0, b12, b6], + [0.0, -b6, b2, 0.0, b6, b4], + ], + dtype=float, + ) + + +def transformation_matrix(x1: float, y1: float, x2: float, y2: float) -> np.ndarray: + dx = x2 - x1 + dy = y2 - y1 + length = math.hypot(dx, dy) + c = dx / length + s = dy / length + + block = np.array( + [ + [c, s, 0.0], + [-s, c, 0.0], + [0.0, 0.0, 1.0], + ], + dtype=float, + ) + + matrix = np.zeros((6, 6), dtype=float) + matrix[:3, :3] = block + matrix[3:, 3:] = block + return matrix diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/io.py b/FESurrogateModelTutorial/src/femsurrogate/fea/io.py new file mode 100644 index 0000000..a773b97 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/io.py @@ -0,0 +1,70 @@ +import re +from pathlib import Path + +from femsurrogate.fea.model import Beam, BeamModel, Displacement, NodalLoad, Node + + +def _tokens(line: str) -> list[str]: + return [token for token in re.split(r"[,\s]+", line.strip()) if token] + + +def read_beam_example(path: str | Path) -> BeamModel: + metadata: dict[str, float] = {} + nodes: dict[int, Node] = {} + beams: list[Beam] = [] + fixed_nodes: list[int] = [] + nodal_loads: dict[int, NodalLoad] = {} + + for raw_line in Path(path).read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + + parts = _tokens(line) + keyword = parts[0] + + if keyword == "Node": + node_id = int(parts[1]) + nodes[node_id] = Node(id=node_id, x=float(parts[2]), y=float(parts[3])) + elif keyword == "Beam": + beams.append(Beam(id=int(parts[1]), node_i=int(parts[2]), node_j=int(parts[3]))) + elif keyword == "Fix": + fixed_nodes.append(int(parts[1])) + elif keyword == "NodeLoad": + node_id = int(parts[1]) + nodal_loads[node_id] = NodalLoad( + node_id=node_id, + fx=float(parts[2]), + fy=float(parts[3]), + mz=float(parts[4]), + ) + else: + metadata[keyword] = float(parts[1]) + + return BeamModel( + metadata=metadata, + nodes=nodes, + beams=tuple(beams), + fixed_nodes=tuple(fixed_nodes), + nodal_loads=nodal_loads, + ) + + +def read_expected_displacements(path: str | Path) -> dict[int, Displacement]: + displacements: dict[int, Displacement] = {} + + for raw_line in Path(path).read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + + parts = _tokens(line) + node_id = int(parts[0]) + displacements[node_id] = Displacement( + node_id=node_id, + ux=float(parts[1]), + uy=float(parts[2]), + rz=float(parts[3]), + ) + + return displacements diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/model.py b/FESurrogateModelTutorial/src/femsurrogate/fea/model.py new file mode 100644 index 0000000..b793cdd --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/model.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Node: + id: int + x: float + y: float + + +@dataclass(frozen=True) +class Beam: + id: int + node_i: int + node_j: int + + +@dataclass(frozen=True) +class NodalLoad: + node_id: int + fx: float + fy: float + mz: float + + +@dataclass(frozen=True) +class Displacement: + node_id: int + ux: float + uy: float + rz: float + + +@dataclass(frozen=True) +class BeamModel: + metadata: dict[str, float] + nodes: dict[int, Node] + beams: tuple[Beam, ...] + fixed_nodes: tuple[int, ...] + nodal_loads: dict[int, NodalLoad] diff --git a/FESurrogateModelTutorial/src/femsurrogate/fea/solver.py b/FESurrogateModelTutorial/src/femsurrogate/fea/solver.py new file mode 100644 index 0000000..64ec2d7 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/fea/solver.py @@ -0,0 +1,33 @@ +import numpy as np +from scipy.sparse.linalg import spsolve + +from femsurrogate.fea.assembly import ( + assemble_global_stiffness, + assemble_load_vector, + constrained_dofs, + node_ids, +) +from femsurrogate.fea.model import BeamModel, Displacement + + +def solve_linear_static(model: BeamModel) -> dict[int, Displacement]: + stiffness = assemble_global_stiffness(model) + loads = assemble_load_vector(model) + fixed = np.array(constrained_dofs(model), dtype=int) + all_dofs = np.arange(loads.size) + free = np.setdiff1d(all_dofs, fixed, assume_unique=True) + + displacements = np.zeros_like(loads) + displacements[free] = spsolve(stiffness[free][:, free], loads[free]) + + result: dict[int, Displacement] = {} + for position, node_id in enumerate(node_ids(model)): + base = position * 3 + result[node_id] = Displacement( + node_id=node_id, + ux=float(displacements[base]), + uy=float(displacements[base + 1]), + rz=float(displacements[base + 2]), + ) + + return result diff --git a/FESurrogateModelTutorial/src/femsurrogate/plotting/__init__.py b/FESurrogateModelTutorial/src/femsurrogate/plotting/__init__.py new file mode 100644 index 0000000..d27e13e --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/plotting/__init__.py @@ -0,0 +1 @@ +"""Plotting helpers for diagnostics and model comparison.""" diff --git a/FESurrogateModelTutorial/src/femsurrogate/plotting/comparison.py b/FESurrogateModelTutorial/src/femsurrogate/plotting/comparison.py new file mode 100644 index 0000000..49cfa3f --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/plotting/comparison.py @@ -0,0 +1,27 @@ +import pandas as pd +from matplotlib import pyplot as plt +from matplotlib.figure import Figure + +from femsurrogate.surrogates.common import MetricsReport, metrics_to_dict + + +def metrics_table(reports: list[MetricsReport], *, sort_by: str = "rmse") -> pd.DataFrame: + table = pd.DataFrame([metrics_to_dict(report) for report in reports]) + ascending = sort_by != "r2" + return table.sort_values(sort_by, ascending=ascending).reset_index(drop=True) + + +def plot_metric_comparison( + table: pd.DataFrame, + *, + metric: str, + title: str = "Metric comparison", +) -> Figure: + fig, ax = plt.subplots() + ax.bar(table["model_name"], table[metric]) + ax.set_xlabel("Model") + ax.set_ylabel(metric) + ax.set_title(title) + ax.grid(True, axis="y", alpha=0.3) + fig.tight_layout() + return fig diff --git a/FESurrogateModelTutorial/src/femsurrogate/plotting/diagnostics.py b/FESurrogateModelTutorial/src/femsurrogate/plotting/diagnostics.py new file mode 100644 index 0000000..7cbe341 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/plotting/diagnostics.py @@ -0,0 +1,30 @@ +import pandas as pd +from matplotlib import pyplot as plt +from matplotlib.figure import Figure + + +def plot_parity(predictions: pd.DataFrame, *, title: str = "Parity plot") -> Figure: + y_true = predictions["y_true"] + y_pred = predictions["y_pred"] + axis_min = min(y_true.min(), y_pred.min()) + axis_max = max(y_true.max(), y_pred.max()) + + fig, ax = plt.subplots() + ax.scatter(y_true, y_pred, s=24, alpha=0.8) + ax.plot([axis_min, axis_max], [axis_min, axis_max], color="black", linestyle="--") + ax.set_xlabel("True response") + ax.set_ylabel("Predicted response") + ax.set_title(title) + ax.grid(True, alpha=0.3) + return fig + + +def plot_residuals(predictions: pd.DataFrame, *, title: str = "Residual plot") -> Figure: + fig, ax = plt.subplots() + ax.scatter(predictions["y_pred"], predictions["residual"], s=24, alpha=0.8) + ax.axhline(0.0, color="black", linestyle="--") + ax.set_xlabel("Predicted response") + ax.set_ylabel("Residual") + ax.set_title(title) + ax.grid(True, alpha=0.3) + return fig diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/__init__.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/__init__.py new file mode 100644 index 0000000..798409d --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/__init__.py @@ -0,0 +1 @@ +"""Surrogate model helpers.""" diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/boosting.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/boosting.py new file mode 100644 index 0000000..27ef2dd --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/boosting.py @@ -0,0 +1,21 @@ +from sklearn.ensemble import GradientBoostingRegressor + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED + + +def make_gradient_boosting( + *, + random_state: int = DEFAULT_RANDOM_SEED, + learning_rate: float = 0.05, + n_estimators: int = 300, + max_depth: int = 3, + subsample: float = 0.9, +) -> GradientBoostingRegressor: + return GradientBoostingRegressor( + loss="squared_error", + learning_rate=learning_rate, + n_estimators=n_estimators, + max_depth=max_depth, + subsample=subsample, + random_state=random_state, + ) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/common.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/common.py new file mode 100644 index 0000000..5140575 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/common.py @@ -0,0 +1,96 @@ +from dataclasses import asdict, dataclass +from time import perf_counter +from typing import Any + +import numpy as np +import pandas as pd +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score +from sklearn.model_selection import train_test_split + + +@dataclass(frozen=True) +class SplitData: + X_train: pd.DataFrame + X_test: pd.DataFrame + y_train: pd.Series + y_test: pd.Series + + +@dataclass(frozen=True) +class MetricsReport: + model_name: str + target_column: str + rmse: float + mae: float + r2: float + fit_time_s: float + predict_time_s: float + + +@dataclass(frozen=True) +class EvaluationResult: + metrics: MetricsReport + predictions: pd.DataFrame + + +def split_dataset( + dataset: pd.DataFrame, + *, + feature_columns: list[str], + target_column: str, + test_size: float, + random_state: int, +) -> SplitData: + X = dataset[feature_columns] + y = dataset[target_column] + X_train, X_test, y_train, y_test = train_test_split( + X, + y, + test_size=test_size, + random_state=random_state, + ) + return SplitData(X_train=X_train, X_test=X_test, y_train=y_train, y_test=y_test) + + +def evaluate_model( + model: Any, + X_train: pd.DataFrame, + X_test: pd.DataFrame, + y_train: pd.Series, + y_test: pd.Series, + *, + model_name: str, + target_column: str, +) -> EvaluationResult: + fit_start = perf_counter() + model.fit(X_train, y_train) + fit_time_s = perf_counter() - fit_start + + predict_start = perf_counter() + y_pred = np.asarray(model.predict(X_test), dtype=float) + predict_time_s = perf_counter() - predict_start + y_true = np.asarray(y_test, dtype=float) + + metrics = MetricsReport( + model_name=model_name, + target_column=target_column, + rmse=float(np.sqrt(mean_squared_error(y_true, y_pred))), + mae=float(mean_absolute_error(y_true, y_pred)), + r2=float(r2_score(y_true, y_pred)), + fit_time_s=fit_time_s, + predict_time_s=predict_time_s, + ) + predictions = pd.DataFrame( + { + "y_true": y_true, + "y_pred": y_pred, + "residual": y_true - y_pred, + }, + index=y_test.index, + ) + + return EvaluationResult(metrics=metrics, predictions=predictions) + + +def metrics_to_dict(metrics: MetricsReport) -> dict[str, str | float]: + return asdict(metrics) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/gpr.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/gpr.py new file mode 100644 index 0000000..2db602a --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/gpr.py @@ -0,0 +1,34 @@ +from sklearn.gaussian_process import GaussianProcessRegressor +from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED + + +def make_gpr( + *, + random_state: int = DEFAULT_RANDOM_SEED, + alpha: float = 1e-10, + n_restarts_optimizer: int = 0, +) -> Pipeline: + kernel = ( + ConstantKernel(1.0, constant_value_bounds=(1e-3, 1e3)) + * RBF(length_scale=1.0, length_scale_bounds=(1e-3, 1e3)) + + WhiteKernel(noise_level=1e-10, noise_level_bounds=(1e-12, 1e-3)) + ) + return Pipeline( + steps=[ + ("scaler", StandardScaler()), + ( + "regressor", + GaussianProcessRegressor( + kernel=kernel, + alpha=alpha, + normalize_y=True, + n_restarts_optimizer=n_restarts_optimizer, + random_state=random_state, + ), + ), + ] + ) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/mlp.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/mlp.py new file mode 100644 index 0000000..8e247d9 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/mlp.py @@ -0,0 +1,39 @@ +from sklearn.compose import TransformedTargetRegressor +from sklearn.neural_network import MLPRegressor +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED + + +def make_mlp( + *, + random_state: int = DEFAULT_RANDOM_SEED, + hidden_layer_sizes: tuple[int, ...] = (64, 32), + alpha: float = 1e-4, + learning_rate_init: float = 1e-3, + early_stopping: bool = True, + max_iter: int = 2000, +) -> Pipeline: + regressor = MLPRegressor( + hidden_layer_sizes=hidden_layer_sizes, + activation="relu", + solver="adam", + alpha=alpha, + learning_rate_init=learning_rate_init, + early_stopping=early_stopping, + max_iter=max_iter, + random_state=random_state, + ) + return Pipeline( + steps=[ + ("scaler", StandardScaler()), + ( + "regressor", + TransformedTargetRegressor( + regressor=regressor, + transformer=StandardScaler(), + ), + ), + ] + ) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/random_forest.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/random_forest.py new file mode 100644 index 0000000..08ff32e --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/random_forest.py @@ -0,0 +1,20 @@ +from sklearn.ensemble import RandomForestRegressor + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED + + +def make_random_forest( + *, + random_state: int = DEFAULT_RANDOM_SEED, + n_estimators: int = 300, + max_depth: int | None = None, + min_samples_leaf: int = 2, + n_jobs: int = -1, +) -> RandomForestRegressor: + return RandomForestRegressor( + n_estimators=n_estimators, + max_depth=max_depth, + min_samples_leaf=min_samples_leaf, + random_state=random_state, + n_jobs=n_jobs, + ) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/registry.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/registry.py new file mode 100644 index 0000000..b5fad14 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/registry.py @@ -0,0 +1,28 @@ +from typing import Any + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED +from femsurrogate.surrogates.boosting import make_gradient_boosting +from femsurrogate.surrogates.gpr import make_gpr +from femsurrogate.surrogates.mlp import make_mlp +from femsurrogate.surrogates.random_forest import make_random_forest +from femsurrogate.surrogates.rsm import make_rsm + +MODEL_NAMES = ("rsm", "gpr", "random_forest", "gradient_boosting", "mlp") + +_BUILDERS = { + "rsm": make_rsm, + "gpr": make_gpr, + "random_forest": make_random_forest, + "gradient_boosting": make_gradient_boosting, + "mlp": make_mlp, +} + + +def make_model(model_name: str, random_state: int = DEFAULT_RANDOM_SEED, **kwargs: Any): + try: + builder = _BUILDERS[model_name] + except KeyError as exc: + valid = ", ".join(MODEL_NAMES) + raise ValueError(f"Unknown model_name {model_name!r}. Expected one of: {valid}") from exc + + return builder(random_state=random_state, **kwargs) diff --git a/FESurrogateModelTutorial/src/femsurrogate/surrogates/rsm.py b/FESurrogateModelTutorial/src/femsurrogate/surrogates/rsm.py new file mode 100644 index 0000000..fd07f77 --- /dev/null +++ b/FESurrogateModelTutorial/src/femsurrogate/surrogates/rsm.py @@ -0,0 +1,20 @@ +from sklearn.linear_model import Ridge +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import PolynomialFeatures, StandardScaler + +from femsurrogate.data.schema import DEFAULT_RANDOM_SEED + + +def make_rsm( + *, + random_state: int = DEFAULT_RANDOM_SEED, + degree: int = 2, + alpha: float = 1.0, +) -> Pipeline: + return Pipeline( + steps=[ + ("scaler", StandardScaler()), + ("polynomial", PolynomialFeatures(degree=degree, include_bias=False)), + ("regressor", Ridge(alpha=alpha)), + ] + ) diff --git a/FESurrogateModelTutorial/tests/.gitkeep b/FESurrogateModelTutorial/tests/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/FESurrogateModelTutorial/tests/.gitkeep @@ -0,0 +1 @@ + diff --git a/FESurrogateModelTutorial/tests/test_beam_solver.py b/FESurrogateModelTutorial/tests/test_beam_solver.py new file mode 100644 index 0000000..debf60b --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_beam_solver.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import numpy as np + +from femsurrogate.fea.assembly import assemble_global_stiffness, constrained_dofs +from femsurrogate.fea.io import read_beam_example +from femsurrogate.fea.solver import solve_linear_static + +ROOT = Path(__file__).resolve().parents[1] + + +def test_assemble_global_stiffness_for_cantilever_fixture_has_expected_shape(): + model = read_beam_example(ROOT / "BeamExamples" / "CantileverBeam.txt") + stiffness = assemble_global_stiffness(model) + + assert stiffness.shape == (18, 18) + + +def test_fixed_node_dofs_are_constrained(): + model = read_beam_example(ROOT / "BeamExamples" / "CantileverBeam.txt") + + assert constrained_dofs(model) == (0, 1, 2) + + +def test_solve_linear_static_returns_finite_displacements_for_all_nodes(): + model = read_beam_example(ROOT / "BeamExamples" / "CantileverBeam.txt") + displacements = solve_linear_static(model) + + assert set(displacements) == set(model.nodes) + values = np.array([[d.ux, d.uy, d.rz] for d in displacements.values()]) + assert np.isfinite(values).all() diff --git a/FESurrogateModelTutorial/tests/test_beamexamples_io.py b/FESurrogateModelTutorial/tests/test_beamexamples_io.py new file mode 100644 index 0000000..5d3ce96 --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_beamexamples_io.py @@ -0,0 +1,47 @@ +from pathlib import Path + +import pytest + +from femsurrogate.fea.io import read_beam_example, read_expected_displacements + +ROOT = Path(__file__).resolve().parents[1] + + +def test_read_beam_example_parses_cantilever_fixture(): + model = read_beam_example(ROOT / "BeamExamples" / "CantileverBeam.txt") + + assert model.metadata["Area"] == pytest.approx(1.0) + assert model.metadata["Izz"] == pytest.approx(0.0833333) + assert model.metadata["ElasticModulus"] == pytest.approx(2.05e11) + assert model.metadata["Poisson'sRatio"] == pytest.approx(0.3) + + assert len(model.nodes) == 6 + assert model.nodes[1].x == pytest.approx(0.0) + assert model.nodes[6].x == pytest.approx(5.0) + assert model.nodes[6].y == pytest.approx(0.0) + + assert len(model.beams) == 5 + assert model.beams[0].id == 1 + assert model.beams[0].node_i == 1 + assert model.beams[0].node_j == 2 + assert model.beams[-1].node_i == 5 + assert model.beams[-1].node_j == 6 + + assert model.fixed_nodes == (1,) + assert model.nodal_loads[6].fx == pytest.approx(0.0) + assert model.nodal_loads[6].fy == pytest.approx(-100000.0) + assert model.nodal_loads[6].mz == pytest.approx(0.0) + + +def test_read_expected_displacements_parses_reference_values(): + displacements = read_expected_displacements( + ROOT / "BeamExamples" / "CantileverBeam_Displacements.txt" + ) + + assert len(displacements) == 6 + assert displacements[1].ux == pytest.approx(0.0) + assert displacements[1].uy == pytest.approx(0.0) + assert displacements[1].rz == pytest.approx(0.0) + assert displacements[6].ux == pytest.approx(0.0) + assert displacements[6].uy == pytest.approx(-0.000244) + assert displacements[6].rz == pytest.approx(0.000073) diff --git a/FESurrogateModelTutorial/tests/test_cantilever_fixture_regression.py b/FESurrogateModelTutorial/tests/test_cantilever_fixture_regression.py new file mode 100644 index 0000000..e83761f --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_cantilever_fixture_regression.py @@ -0,0 +1,42 @@ +from pathlib import Path + +import numpy as np + +from femsurrogate.fea.benchmark import cantilever_tip_displacement +from femsurrogate.fea.io import read_beam_example, read_expected_displacements +from femsurrogate.fea.solver import solve_linear_static + +ROOT = Path(__file__).resolve().parents[1] +INPUT_PATH = ROOT / "BeamExamples" / "CantileverBeam.txt" +EXPECTED_PATH = ROOT / "BeamExamples" / "CantileverBeam_Displacements.txt" + + +def test_solver_matches_cantilever_fixture_displacements(): + model = read_beam_example(INPUT_PATH) + actual = solve_linear_static(model) + expected = read_expected_displacements(EXPECTED_PATH) + + assert set(actual) == set(expected) + for node_id, expected_displacement in expected.items(): + actual_displacement = actual[node_id] + np.testing.assert_allclose( + [actual_displacement.ux, actual_displacement.uy, actual_displacement.rz], + [expected_displacement.ux, expected_displacement.uy, expected_displacement.rz], + atol=5e-7, + rtol=1e-6, + ) + + +def test_tip_displacement_matches_analytical_cantilever_magnitude(): + model = read_beam_example(INPUT_PATH) + actual_tip_uy = solve_linear_static(model)[6].uy + node_x = [node.x for node in model.nodes.values()] + length = max(node_x) - min(node_x) + analytical_tip_uy = cantilever_tip_displacement( + force_y=model.nodal_loads[6].fy, + length=length, + E=model.metadata["ElasticModulus"], + Izz=model.metadata["Izz"], + ) + + np.testing.assert_allclose(abs(actual_tip_uy), abs(analytical_tip_uy), rtol=1e-9, atol=0.0) diff --git a/FESurrogateModelTutorial/tests/test_dataset_builder.py b/FESurrogateModelTutorial/tests/test_dataset_builder.py new file mode 100644 index 0000000..1e0642e --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_dataset_builder.py @@ -0,0 +1,27 @@ +import pandas as pd + +from femsurrogate.data.bounds import DEFAULT_PARAMETER_BOUNDS +from femsurrogate.data.dataset import build_dataset, run_beam2d_case +from femsurrogate.data.sampling import generate_lhs_samples +from femsurrogate.data.schema import TARGET_COLUMNS, BeamParameters + + +def test_run_beam2d_case_returns_physical_responses(): + result = run_beam2d_case( + BeamParameters(L_m=2.0, b_m=0.05, h_m=0.1, E_pa=200e9, P_n=1000.0) + ) + + assert result.tip_uy_m < 0.0 + assert result.max_abs_bending_stress_pa > 0.0 + assert result.mass_kg > 0.0 + assert result.compliance_j > 0.0 + + +def test_build_dataset_preserves_samples_and_adds_schema_columns(): + samples = generate_lhs_samples(DEFAULT_PARAMETER_BOUNDS, n=4, seed=20260521) + dataset = build_dataset(samples) + + assert len(dataset) == len(samples) + pd.testing.assert_frame_equal(dataset[list(samples.columns)], samples) + for column in ["A_m2", "I_m4", *TARGET_COLUMNS]: + assert column in dataset.columns diff --git a/FESurrogateModelTutorial/tests/test_frame_element.py b/FESurrogateModelTutorial/tests/test_frame_element.py new file mode 100644 index 0000000..366712d --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_frame_element.py @@ -0,0 +1,42 @@ +import numpy as np +import pytest + +from femsurrogate.fea.element import local_frame_stiffness, transformation_matrix + + +def test_local_frame_stiffness_has_expected_shape_and_symmetry(): + stiffness = local_frame_stiffness(E=200.0, A=3.0, Izz=5.0, L=7.0) + + assert stiffness.shape == (6, 6) + np.testing.assert_allclose(stiffness, stiffness.T) + + +def test_local_frame_stiffness_contains_clockwise_rotation_terms(): + E = 210.0 + A = 2.0 + Izz = 4.0 + L = 3.0 + + stiffness = local_frame_stiffness(E=E, A=A, Izz=Izz, L=L) + + assert stiffness[0, 0] == pytest.approx(E * A / L) + assert stiffness[0, 3] == pytest.approx(-E * A / L) + assert stiffness[1, 1] == pytest.approx(12 * E * Izz / L**3) + assert stiffness[1, 2] == pytest.approx(-6 * E * Izz / L**2) + assert stiffness[2, 2] == pytest.approx(4 * E * Izz / L) + assert stiffness[2, 5] == pytest.approx(2 * E * Izz / L) + assert stiffness[4, 4] == pytest.approx(12 * E * Izz / L**3) + assert stiffness[4, 5] == pytest.approx(6 * E * Izz / L**2) + assert stiffness[5, 5] == pytest.approx(4 * E * Izz / L) + + +def test_transformation_matrix_is_identity_for_horizontal_element(): + matrix = transformation_matrix(0.0, 0.0, 2.0, 0.0) + + np.testing.assert_allclose(matrix, np.eye(6)) + + +def test_transformation_matrix_is_orthogonal_for_inclined_element(): + matrix = transformation_matrix(0.0, 0.0, 1.0, 1.0) + + np.testing.assert_allclose(matrix.T @ matrix, np.eye(6), atol=1e-12) diff --git a/FESurrogateModelTutorial/tests/test_plotting_and_results.py b/FESurrogateModelTutorial/tests/test_plotting_and_results.py new file mode 100644 index 0000000..77e658a --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_plotting_and_results.py @@ -0,0 +1,41 @@ +import matplotlib.pyplot as plt +import pandas as pd +from matplotlib.figure import Figure + +from femsurrogate.plotting.comparison import metrics_table, plot_metric_comparison +from femsurrogate.plotting.diagnostics import plot_parity, plot_residuals +from femsurrogate.surrogates.common import MetricsReport + + +def _predictions() -> pd.DataFrame: + return pd.DataFrame( + { + "y_true": [-1.0, -2.0, -3.0], + "y_pred": [-1.1, -1.9, -3.2], + "residual": [0.1, -0.1, 0.2], + } + ) + + +def test_diagnostic_plots_return_figures(): + parity = plot_parity(_predictions(), title="Parity") + residuals = plot_residuals(_predictions(), title="Residuals") + + assert isinstance(parity, Figure) + assert isinstance(residuals, Figure) + plt.close(parity) + plt.close(residuals) + + +def test_metrics_table_and_comparison_plot(): + reports = [ + MetricsReport("rsm", "tip_uy_m", 0.2, 0.1, 0.9, 0.01, 0.001), + MetricsReport("gpr", "tip_uy_m", 0.1, 0.05, 0.95, 0.2, 0.01), + ] + + table = metrics_table(reports) + figure = plot_metric_comparison(table, metric="rmse", title="RMSE") + + assert list(table["model_name"]) == ["gpr", "rsm"] + assert isinstance(figure, Figure) + plt.close(figure) diff --git a/FESurrogateModelTutorial/tests/test_project_structure.py b/FESurrogateModelTutorial/tests/test_project_structure.py new file mode 100644 index 0000000..3a5372c --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_project_structure.py @@ -0,0 +1,18 @@ +import importlib + +import femsurrogate + + +def test_package_exposes_version_string(): + assert isinstance(femsurrogate.__version__, str) + assert femsurrogate.__version__ + + +def test_expected_subpackages_are_importable(): + for module_name in [ + "femsurrogate.fea", + "femsurrogate.data", + "femsurrogate.surrogates", + "femsurrogate.plotting", + ]: + assert importlib.import_module(module_name).__name__ == module_name diff --git a/FESurrogateModelTutorial/tests/test_sampling.py b/FESurrogateModelTutorial/tests/test_sampling.py new file mode 100644 index 0000000..bf3d880 --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_sampling.py @@ -0,0 +1,19 @@ +import pandas as pd + +from femsurrogate.data.bounds import DEFAULT_PARAMETER_BOUNDS +from femsurrogate.data.sampling import generate_lhs_samples + + +def test_lhs_samples_are_reproducible_for_fixed_seed(): + first = generate_lhs_samples(DEFAULT_PARAMETER_BOUNDS, n=8, seed=20260521) + second = generate_lhs_samples(DEFAULT_PARAMETER_BOUNDS, n=8, seed=20260521) + + pd.testing.assert_frame_equal(first, second) + + +def test_lhs_samples_have_expected_columns_and_bounds(): + samples = generate_lhs_samples(DEFAULT_PARAMETER_BOUNDS, n=16, seed=20260521) + + assert list(samples.columns) == list(DEFAULT_PARAMETER_BOUNDS) + for column, bounds in DEFAULT_PARAMETER_BOUNDS.items(): + assert samples[column].between(bounds.lower, bounds.upper).all() diff --git a/FESurrogateModelTutorial/tests/test_surrogate_common.py b/FESurrogateModelTutorial/tests/test_surrogate_common.py new file mode 100644 index 0000000..f6e7d80 --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_surrogate_common.py @@ -0,0 +1,67 @@ +import pandas as pd +from sklearn.linear_model import LinearRegression + +from femsurrogate.surrogates.common import evaluate_model, split_dataset + + +def _toy_dataset() -> pd.DataFrame: + return pd.DataFrame( + { + "x1": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], + "x2": [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5], + "target": [1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0, 11.5], + } + ) + + +def test_split_dataset_is_reproducible(): + dataset = _toy_dataset() + first = split_dataset( + dataset, + feature_columns=["x1", "x2"], + target_column="target", + test_size=0.25, + random_state=20260521, + ) + second = split_dataset( + dataset, + feature_columns=["x1", "x2"], + target_column="target", + test_size=0.25, + random_state=20260521, + ) + + pd.testing.assert_frame_equal(first.X_train, second.X_train) + pd.testing.assert_frame_equal(first.X_test, second.X_test) + pd.testing.assert_series_equal(first.y_train, second.y_train) + pd.testing.assert_series_equal(first.y_test, second.y_test) + + +def test_evaluate_model_returns_metrics_and_predictions(): + dataset = _toy_dataset() + split = split_dataset( + dataset, + feature_columns=["x1", "x2"], + target_column="target", + test_size=0.25, + random_state=20260521, + ) + result = evaluate_model( + LinearRegression(), + split.X_train, + split.X_test, + split.y_train, + split.y_test, + model_name="linear", + target_column="target", + ) + + assert result.metrics.model_name == "linear" + assert result.metrics.target_column == "target" + assert result.metrics.rmse >= 0.0 + assert result.metrics.mae >= 0.0 + assert result.metrics.r2 <= 1.0 + assert result.metrics.fit_time_s >= 0.0 + assert result.metrics.predict_time_s >= 0.0 + assert list(result.predictions.columns) == ["y_true", "y_pred", "residual"] + assert len(result.predictions) == len(split.y_test) diff --git a/FESurrogateModelTutorial/tests/test_surrogate_models.py b/FESurrogateModelTutorial/tests/test_surrogate_models.py new file mode 100644 index 0000000..3cf2c81 --- /dev/null +++ b/FESurrogateModelTutorial/tests/test_surrogate_models.py @@ -0,0 +1,47 @@ +import warnings + +import numpy as np +import pandas as pd +from sklearn.exceptions import ConvergenceWarning + +from femsurrogate.surrogates.registry import MODEL_NAMES, make_model + + +def _toy_regression_data(): + X = pd.DataFrame( + { + "L_m": np.linspace(1.0, 3.0, 12), + "b_m": np.linspace(0.02, 0.08, 12), + "h_m": np.linspace(0.04, 0.16, 12), + "E_pa": np.linspace(100e9, 220e9, 12), + "P_n": np.linspace(100.0, 2000.0, 12), + } + ) + y = -X["P_n"] * X["L_m"] ** 3 / (3.0 * X["E_pa"] * X["b_m"] * X["h_m"] ** 3) + return X, y + + +def test_registry_exposes_expected_surrogate_names(): + assert MODEL_NAMES == ("rsm", "gpr", "random_forest", "gradient_boosting", "mlp") + + +def test_make_model_builds_estimators_that_fit_and_predict(): + X, y = _toy_regression_data() + fast_overrides = { + "random_forest": {"n_estimators": 5, "n_jobs": 1}, + "gradient_boosting": {"n_estimators": 5}, + "mlp": {"hidden_layer_sizes": (4,), "max_iter": 25, "early_stopping": False}, + } + + for model_name in MODEL_NAMES: + model = make_model( + model_name, + random_state=20260521, + **fast_overrides.get(model_name, {}), + ) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=ConvergenceWarning) + model.fit(X, y) + predictions = model.predict(X.iloc[:3]) + + assert predictions.shape == (3,) diff --git a/FESurrogateModelTutorial/uv.lock b/FESurrogateModelTutorial/uv.lock new file mode 100644 index 0000000..84bfbd8 --- /dev/null +++ b/FESurrogateModelTutorial/uv.lock @@ -0,0 +1,2146 @@ +version = 1 +revision = 3 +requires-python = ">=3.12, <3.15" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "async-lru" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "femsurrogate-tutorial" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "joblib" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipykernel" }, + { name = "jupyterlab" }, + { name = "nbconvert" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "joblib" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel" }, + { name = "jupyterlab" }, + { name = "nbconvert" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, +] + +[[package]] +name = "ipython" +version = "9.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/86/3060e8029b7cc505cce9a0137431dda81d0a3fde93a8f0f50ee0bf37a795/ipython-9.13.0-py3-none-any.whl", hash = "sha256:57f9d4639e20818d328d287c7b549af3d05f12486ea8f2e7f73e52a36ec4d201", size = 627274, upload-time = "2026-04-24T12:24:53.038Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "json5" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/f8/475c4241b2b75af0deaae453ed003c6c851766dbc44d332d8baf245dc931/jupyter_events-0.12.1.tar.gz", hash = "sha256:faff25f77218335752f35f23c5fe6e4a392a7bd99a5939ccb9b8fbf594636cf3", size = 62854, upload-time = "2026-04-20T23:17:50.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/6c/6fcde0c8f616ed360ffd3587f7db9e225a7e62b583a04494d2f069cf64ea/jupyter_events-0.12.1-py3-none-any.whl", hash = "sha256:c366585253f537a627da52fa7ca7410c5b5301fe893f511e7b077c2d93ec8bcf", size = 19512, upload-time = "2026-04-20T23:17:48.927Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/ff/1e4a61f5170a9a1d978f3ac3872449de6c01fc71eaf89657824c878b1549/jupyter_lsp-2.3.1.tar.gz", hash = "sha256:fdf8a4aa7d85813976d6e29e95e6a2c8f752701f926f2715305249a3829805a6", size = 55677, upload-time = "2026-04-02T08:10:06.749Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/e8/9d61dcbd1dce8ef418f06befd4ac084b4720429c26b0b1222bc218685eff/jupyter_lsp-2.3.1-py3-none-any.whl", hash = "sha256:71b954d834e85ff3096400554f2eefaf7fe37053036f9a782b0f7c5e42dadb81", size = 77513, upload-time = "2026-04-02T08:10:01.753Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/15/1eacb0fcb79ef86e8a0a79a708e6ad7435f6f223097dd29a4ce861fabc44/jupyter_server-2.18.2.tar.gz", hash = "sha256:06b4f40d8a7a00bb39d5216859c81374a0e7cfefe6d8a5a7facc5a5c37c679a7", size = 753177, upload-time = "2026-05-06T07:04:36.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/50/ecf4f70d65bdb7519b28a33d1b2fee8a4b4ba1ae1a92f15d97e877c5de21/jupyter_server-2.18.2-py3-none-any.whl", hash = "sha256:fa5e46539ded65791838035a2b6001f13e54d5f64b8b3752eb1e91fdd641a5b8", size = 391907, upload-time = "2026-05-06T07:04:34.014Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, +] + +[[package]] +name = "jupyterlab" +version = "4.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/22/8440ec827762146e7cdecf04335bd348795899d29dc6ae82238707353a2c/jupyterlab-4.5.7.tar.gz", hash = "sha256:55a9822c4754da305f41e113452c68383e214dcf96de760146af89ce5d5117b0", size = 23992763, upload-time = "2026-04-29T16:43:51.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/aa/537b8f7d80e799af19af35fb3ddfc970b951088a13c57dd9387dcfbb7f61/jupyterlab-4.5.7-py3-none-any.whl", hash = "sha256:fba4cb0e2c44a52859669d8c98b45de029d5e515f8407bf8534d2a8fc5f0964d", size = 12450123, upload-time = "2026-04-29T16:43:46.639Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mistune" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, + { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, + { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, + { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, + { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, + { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, + { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, + { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, + { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "send2trash" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/22/40f55b26baeab80c2d7b3f1db0682f8954e4617fee7d90ce634022ef05c6/traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", size = 163197, upload-time = "2026-05-06T08:05:58.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +]