diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ddb9898..1692ff48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,3 +1,4 @@ +--- name: Python tests on: @@ -12,7 +13,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/engibench/constraint.py b/engibench/constraint.py index e4a7992a..56825ff2 100644 --- a/engibench/constraint.py +++ b/engibench/constraint.py @@ -1,7 +1,5 @@ """Constraints for parameters of Problem classes.""" -from __future__ import annotations - from collections.abc import Callable, Iterable import dataclasses from dataclasses import dataclass @@ -48,15 +46,15 @@ class Constraint: criticality: Criticality = Criticality.Error """Criticality of a violation of the constraint.""" - def category(self, category: Category) -> Constraint: + def category(self, category: Category) -> "Constraint": """Return a copy of the constraint which has the specified category added.""" return Constraint(check=self.check, criticality=self.criticality, categories=self.categories | category) - def warning(self) -> Constraint: + def warning(self) -> "Constraint": """Return a copy of the constraint with the criticality level set to "warning".""" return Constraint(check=self.check, criticality=Criticality.Warning, categories=self.categories) - def check_dict(self, parameter_args: dict[str, Any]) -> Violation | None: + def check_dict(self, parameter_args: dict[str, Any]) -> "Violation | None": """Check for a violation of the given constraint for the given parameters.""" # We first inspect the arguments of check callback: sig = inspect.signature(self.check) @@ -82,7 +80,7 @@ def check_dict(self, parameter_args: dict[str, Any]) -> Violation | None: return Violation(self, str(e)) return None - def check_value(self, value: Any) -> Violation | None: + def check_value(self, value: Any) -> "Violation | None": """Check for a violation for the given single positional value.""" try: self.check(value) @@ -165,13 +163,13 @@ def __init__(self, violations: list[Violation], n_constraints: int) -> None: self.violations = violations self.n_constraints = n_constraints - def by_category(self, category: Category) -> Violations: + def by_category(self, category: Category) -> "Violations": """Filter the violations by the category of the constraint causing the violation.""" return Violations( [violation for violation in self.violations if category in violation.constraint.categories], self.n_constraints ) - def by_criticality(self, criticality: Criticality) -> Violations: + def by_criticality(self, criticality: Criticality) -> "Violations": """Filter the violations by criticality.""" return Violations( [violation for violation in self.violations if violation.constraint.criticality == criticality], diff --git a/engibench/core.py b/engibench/core.py index 41b682f4..2021801d 100644 --- a/engibench/core.py +++ b/engibench/core.py @@ -1,24 +1,19 @@ """Core API for Problem and other base classes.""" -from __future__ import annotations - +from collections.abc import Sequence import dataclasses from enum import auto from enum import Enum -from typing import Any, Generic, TYPE_CHECKING, TypeVar +from typing import Any, Generic, TypeVar from datasets import Dataset from datasets import load_dataset +from gymnasium import spaces import numpy as np import numpy.typing as npt from engibench import constraint -if TYPE_CHECKING: - from collections.abc import Sequence - - from gymnasium import spaces - DesignType = TypeVar("DesignType") diff --git a/engibench/problems/airfoil/v0.py b/engibench/problems/airfoil/v0.py index bb0fef13..a9f2d410 100644 --- a/engibench/problems/airfoil/v0.py +++ b/engibench/problems/airfoil/v0.py @@ -15,8 +15,6 @@ +-+-+-+-+-+-+-+-+-+ """ -from __future__ import annotations - from dataclasses import dataclass from dataclasses import field import os diff --git a/engibench/problems/beams2d/backend.py b/engibench/problems/beams2d/backend.py index a6393d2b..4f082797 100644 --- a/engibench/problems/beams2d/backend.py +++ b/engibench/problems/beams2d/backend.py @@ -6,8 +6,6 @@ This code has been adapted from the Python implementation by Niels Aage and Villads Egede Johansen: https://github.com/arjendeetman/TopOpt-MMA-Python """ -from __future__ import annotations - import dataclasses from typing import Any, overload diff --git a/engibench/problems/beams2d/v0.py b/engibench/problems/beams2d/v0.py index 177bc5f1..4683e0bf 100644 --- a/engibench/problems/beams2d/v0.py +++ b/engibench/problems/beams2d/v0.py @@ -3,8 +3,6 @@ """Beams 2D problem.""" -from __future__ import annotations - from copy import deepcopy from dataclasses import dataclass from dataclasses import field diff --git a/engibench/problems/heatconduction2d/v0.py b/engibench/problems/heatconduction2d/v0.py index d63875af..4969797c 100644 --- a/engibench/problems/heatconduction2d/v0.py +++ b/engibench/problems/heatconduction2d/v0.py @@ -4,8 +4,6 @@ The problem is solved using the dolfin-adjoint software within a Docker container. """ -from __future__ import annotations - from dataclasses import dataclass import os import subprocess diff --git a/engibench/problems/heatconduction3d/v0.py b/engibench/problems/heatconduction3d/v0.py index 23ffabfc..5aa6386f 100644 --- a/engibench/problems/heatconduction3d/v0.py +++ b/engibench/problems/heatconduction3d/v0.py @@ -4,8 +4,6 @@ The problem is solved using the dolfin-adjoint software within a Docker container. """ -from __future__ import annotations - from dataclasses import dataclass import os import subprocess @@ -292,7 +290,7 @@ def render(self, design: npt.NDArray, *, open_window: bool = False) -> Any: ] # Side edges for edge in cube_edges: - ax.plot(*zip(*cube_vertices[list(edge)]), color="red", linewidth=2) + ax.plot(*zip(*cube_vertices[list(edge)], strict=True), color="red", linewidth=2) if open_window: plt.show() diff --git a/engibench/problems/photonics2d/dataset_slurm_test.py b/engibench/problems/photonics2d/dataset_slurm_test.py index 375ecc9b..73974820 100644 --- a/engibench/problems/photonics2d/dataset_slurm_test.py +++ b/engibench/problems/photonics2d/dataset_slurm_test.py @@ -4,12 +4,13 @@ """ from argparse import ArgumentParser +from collections.abc import Callable from itertools import product import os import pickle import shutil import time -from typing import Any, Callable +from typing import Any import numpy as np diff --git a/engibench/problems/photonics2d/v0.py b/engibench/problems/photonics2d/v0.py index b57d2c06..fdd1573d 100644 --- a/engibench/problems/photonics2d/v0.py +++ b/engibench/problems/photonics2d/v0.py @@ -6,17 +6,16 @@ Author: Mark Fuge @markfuge """ -from __future__ import annotations - from dataclasses import dataclass # Need os import for makedirs for saving plots import os import pprint -from typing import Annotated, Any, ClassVar, TYPE_CHECKING +from typing import Annotated, Any, ClassVar # Importing autograd since the ceviche library uses it for automatic differentiation of the FDFD solver import autograd.numpy as npa +from autograd.numpy.numpy_boxes import ArrayBox # Import ArrayBox type for checking import ceviche @@ -47,9 +46,6 @@ from engibench.problems.photonics2d.backend import poly_ramp from engibench.problems.photonics2d.backend import wavelength_to_frequency -if TYPE_CHECKING: - from autograd.numpy.numpy_boxes import ArrayBox - class Photonics2D(Problem[npt.NDArray]): r"""Photonic Inverse Design 2D Problem (Wavelength Demultiplexer). diff --git a/engibench/problems/power_electronics/utils/config.py b/engibench/problems/power_electronics/utils/config.py index 4e9b2b4c..4b6142fe 100644 --- a/engibench/problems/power_electronics/utils/config.py +++ b/engibench/problems/power_electronics/utils/config.py @@ -1,8 +1,6 @@ """Set up the configuration for the Power Electronics problem.""" # ruff: noqa: N806, N815 # Upper case -from __future__ import annotations - from dataclasses import dataclass from dataclasses import field import os diff --git a/engibench/problems/power_electronics/utils/netlist_handler.py b/engibench/problems/power_electronics/utils/netlist_handler.py index a2e3d9a8..093f5241 100644 --- a/engibench/problems/power_electronics/utils/netlist_handler.py +++ b/engibench/problems/power_electronics/utils/netlist_handler.py @@ -2,18 +2,12 @@ # ruff: noqa: N806 # Upper case variables -from __future__ import annotations - -from typing import TYPE_CHECKING - import networkx as nx +from engibench.problems.power_electronics.utils.config import Config from engibench.problems.power_electronics.utils.constants import COLOR_DICT from engibench.problems.power_electronics.utils.constants import COMPONENTS -if TYPE_CHECKING: - from engibench.problems.power_electronics.utils.config import Config - def parse_topology(config: Config) -> tuple[Config, str, dict[str, list[int]], nx.Graph]: """Parse the topology from config.original_netlist_path. It does NOT change config. diff --git a/engibench/problems/power_electronics/utils/ngspice.py b/engibench/problems/power_electronics/utils/ngspice.py index d414c91a..a801001f 100644 --- a/engibench/problems/power_electronics/utils/ngspice.py +++ b/engibench/problems/power_electronics/utils/ngspice.py @@ -1,7 +1,5 @@ """NgSpice wrapper for cross-platform support.""" -from __future__ import annotations - import os import platform import re diff --git a/engibench/problems/power_electronics/v0.py b/engibench/problems/power_electronics/v0.py index b95ea28f..bef296d4 100644 --- a/engibench/problems/power_electronics/v0.py +++ b/engibench/problems/power_electronics/v0.py @@ -3,8 +3,6 @@ """Power Electronics problem.""" -from __future__ import annotations - import os from typing import Any, NoReturn diff --git a/engibench/problems/thermoelastic2d/model/fea_model.py b/engibench/problems/thermoelastic2d/model/fea_model.py index 5c3536fe..677719ad 100644 --- a/engibench/problems/thermoelastic2d/model/fea_model.py +++ b/engibench/problems/thermoelastic2d/model/fea_model.py @@ -1,7 +1,5 @@ """This module contains the Python implementation of the thermoelastic 2D problem.""" -from __future__ import annotations - from math import ceil from math import hypot import time diff --git a/engibench/problems/thermoelastic2d/model/mma_subroutine.py b/engibench/problems/thermoelastic2d/model/mma_subroutine.py index a3e8b50e..bd35be96 100644 --- a/engibench/problems/thermoelastic2d/model/mma_subroutine.py +++ b/engibench/problems/thermoelastic2d/model/mma_subroutine.py @@ -1,15 +1,10 @@ """This module contains the MMA subroutine used in the thermoelastic2d problem.""" -from __future__ import annotations - from dataclasses import dataclass -from typing import TYPE_CHECKING from mmapy import mmasub as external_mmasub import numpy as np - -if TYPE_CHECKING: - from numpy.typing import NDArray +from numpy.typing import NDArray RESIDUAL_MAX_VAL = 0.9 ITERATION_MAX = 500 diff --git a/engibench/problems/thermoelastic2d/utils.py b/engibench/problems/thermoelastic2d/utils.py index 982f64a4..3ec2728f 100644 --- a/engibench/problems/thermoelastic2d/utils.py +++ b/engibench/problems/thermoelastic2d/utils.py @@ -1,17 +1,11 @@ """Utility functions for the thermoelastic2d problem.""" -from __future__ import annotations - -from typing import TYPE_CHECKING - from matplotlib import colors +from matplotlib.figure import Figure import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt -if TYPE_CHECKING: - from matplotlib.figure import Figure - def get_res_bounds(x_res: int, y_res: int) -> tuple[npt.NDArray, npt.NDArray, npt.NDArray, npt.NDArray]: """Generates the indices corresponding to the left, top, right, and bottom elements in the domain. diff --git a/engibench/problems/thermoelastic2d/v0.py b/engibench/problems/thermoelastic2d/v0.py index 7d09a5fe..2ae6c518 100644 --- a/engibench/problems/thermoelastic2d/v0.py +++ b/engibench/problems/thermoelastic2d/v0.py @@ -1,7 +1,5 @@ """Thermo Elastic 2D Problem.""" -from __future__ import annotations - from dataclasses import dataclass from dataclasses import field from typing import Annotated, Any, ClassVar diff --git a/engibench/utils/container.py b/engibench/utils/container.py index fa711c62..c3f2cfb0 100644 --- a/engibench/utils/container.py +++ b/engibench/utils/container.py @@ -1,13 +1,8 @@ """Abstraction over container runtimes.""" -from __future__ import annotations - +from collections.abc import Sequence import os import subprocess -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence def pull(image: str) -> None: @@ -51,26 +46,6 @@ def run( raise RuntimeError(msg) from e -def runtime() -> type[ContainerRuntime] | None: - """Determine the container runtime to use according to the environment variable `CONTAINER_RUNTIME`. - - If not set, check for availability. - - Returns: - Class object of the first available container runtime or the container runtime selected by the - `CONTAINER_RUNTIME` environment variable if set. - """ - runtimes_by_name = {rt.name: rt for rt in RUNTIMES} - rt_name = os.environ.get("CONTAINER_RUNTIME") - rt = runtimes_by_name.get(rt_name) if rt_name is not None else None - if rt is not None: - return rt - for rt in RUNTIMES: - if rt.is_available(): - return rt - return None - - class ContainerRuntime: """Abstraction over container runtimes.""" @@ -125,6 +100,26 @@ def run( raise NotImplementedError("Must be implemented by a subclass") +def runtime() -> type[ContainerRuntime] | None: + """Determine the container runtime to use according to the environment variable `CONTAINER_RUNTIME`. + + If not set, check for availability. + + Returns: + Class object of the first available container runtime or the container runtime selected by the + `CONTAINER_RUNTIME` environment variable if set. + """ + runtimes_by_name = {rt.name: rt for rt in RUNTIMES} + rt_name = os.environ.get("CONTAINER_RUNTIME") + rt = runtimes_by_name.get(rt_name) if rt_name is not None else None + if rt is not None: + return rt + for rt in RUNTIMES: + if rt.is_available(): + return rt + return None + + class Docker(ContainerRuntime): """Docker 🐋 runtime.""" diff --git a/engibench/utils/slurm.py b/engibench/utils/slurm.py index 72a0831a..a485e7af 100644 --- a/engibench/utils/slurm.py +++ b/engibench/utils/slurm.py @@ -1,8 +1,7 @@ """Slurm executor for parameter space discovery.""" -from __future__ import annotations - from argparse import ArgumentParser +from collections.abc import Callable, Iterable, Sequence from dataclasses import asdict from dataclasses import dataclass from dataclasses import field @@ -14,18 +13,13 @@ import subprocess import sys import tempfile -from typing import Any, Generic, TYPE_CHECKING, TypeVar +from typing import Any, Generic, TypeVar from numpy import typing as npt from engibench.core import OptiStep from engibench.core import Problem -if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Sequence - - import numpy.typing as npt - @dataclass class Args: @@ -73,7 +67,7 @@ def serialize(self) -> dict[str, Any]: } @classmethod - def deserialize(cls, serialized_job: dict[str, Any]) -> Job: + def deserialize(cls, serialized_job: dict[str, Any]) -> "Job": """Deserialize a job object from an other python process.""" design_factory = serialized_job["design_factory"] return cls( diff --git a/pyproject.toml b/pyproject.toml index 15e2be52..de741d50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,13 @@ name = "engibench" dynamic = ["version"] description="A suite of benchmarks for automated engineering design." readme = "README.md" -requires-python = ">= 3.9" +requires-python = ">= 3.10" authors = [{ name = "Florian Felten", email = "ffelten@mavt.ethz.ch" }] license = { text = "GPL-3.0" } keywords = ["Mechanical Engineering", "Machine Learning", "AI", "Optimization"] classifiers = [ "Development Status :: 4 - Beta", # change to `5 - Production/Stable` when ready "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -113,9 +112,7 @@ exclude = [ # Same as Black. line-length = 124 indent-width = 4 - -# Assume Python 3.9 -target-version = "py39" +target-version = "py310" ######################################## LINTING ######################################## @@ -228,7 +225,7 @@ exclude = ["**/node_modules", "**/__pycache__", "**/templates/**", "**/study*", strict = [] typeCheckingMode = "basic" -pythonVersion = "3.9" +pythonVersion = "3.10" pythonPlatform = "All" typeshedPath = "typeshed" enableTypeIgnoreComments = true