From 27055ae4248f0594a71e63950c7336462e7decec Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 30 Sep 2026 13:47:00 +0200 Subject: [PATCH] fix(heatconduction)!: name the volume target volfrac HeatConduction2D and 3D called their volume target `volume`. Every other volume-constrained problem calls it `volfrac`, so rename it here too. - Rename the condition, the constraint argument, the config keys and the initialize_design keyword from volume to volfrac. - Point at heat_conduction_2d_v1 and heat_conduction_3d_v1. These are clerical copies of v0 with the column renamed. Nothing was recomputed. The container templates read their inputs by position, so they need no change. BREAKING CHANGE: callers pass volfrac instead of volume, in the constructor config, in simulate/optimize configs, and to initialize_design. Refs #256 Co-Authored-By: Claude Opus 5.5 --- docs/problems/heatconduction2d.md | 2 +- docs/problems/heatconduction3d.md | 2 +- engibench/problems/heatconduction2d/v0.py | 40 +++++++++++------------ engibench/problems/heatconduction3d/v0.py | 40 +++++++++++------------ 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/problems/heatconduction2d.md b/docs/problems/heatconduction2d.md index bd5064ee..a86153c3 100644 --- a/docs/problems/heatconduction2d.md +++ b/docs/problems/heatconduction2d.md @@ -25,7 +25,7 @@ The simulator is a docker container with the dolfin-adjoint software that comput We convert use intermediary files to convert from and to the simulator that is run from a Docker image. ## Dataset -The dataset has been generated the dolfin-adjoint software. It is hosted on the [Hugging Face Datasets Hub](https://huggingface.co/datasets/IDEALLab/heat_conduction_2d_v0). +The dataset has been generated the dolfin-adjoint software. It is hosted on the [Hugging Face Datasets Hub](https://huggingface.co/datasets/IDEALLab/heat_conduction_2d_v1). ### v0 diff --git a/docs/problems/heatconduction3d.md b/docs/problems/heatconduction3d.md index a3f62e8e..4bcaf40c 100644 --- a/docs/problems/heatconduction3d.md +++ b/docs/problems/heatconduction3d.md @@ -31,7 +31,7 @@ The simulator is a docker container with the dolfin-adjoint software that comput We convert use intermediary files to convert from and to the simulator that is run from a Docker image. ## Dataset -The dataset has been generated the dolfin-adjoint software. It is hosted on the [Hugging Face Datasets Hub](https://huggingface.co/datasets/IDEALLab/heat_conduction_3d_v0). +The dataset has been generated the dolfin-adjoint software. It is hosted on the [Hugging Face Datasets Hub](https://huggingface.co/datasets/IDEALLab/heat_conduction_3d_v1). ### v0 diff --git a/engibench/problems/heatconduction2d/v0.py b/engibench/problems/heatconduction2d/v0.py index 3538e0af..b02cf937 100644 --- a/engibench/problems/heatconduction2d/v0.py +++ b/engibench/problems/heatconduction2d/v0.py @@ -29,12 +29,12 @@ @constraint(categories=THEORY, criticality=Criticality.Warning) -def volume_fraction_bound(design: npt.NDArray, volume: float) -> None: +def volume_fraction_bound(design: npt.NDArray, volfrac: float) -> None: """Constraint for volume fraction of the design.""" actual_volfrac = design.mean() tolerance = 0.01 - assert abs(actual_volfrac - volume) <= tolerance, ( - f"Volume fraction of the design {actual_volfrac:.4f} does not match target {volume:.4f} specified in the conditions. While the optimizer might fix it, this is likely to affect objective values as the initial design is not feasible given the constraints." + assert abs(actual_volfrac - volfrac) <= tolerance, ( + f"Volume fraction of the design {actual_volfrac:.4f} does not match target {volfrac:.4f} specified in the conditions. While the optimizer might fix it, this is likely to affect objective values as the initial design is not feasible given the constraints." ) @@ -56,7 +56,7 @@ class HeatConduction2D(Problem[npt.NDArray]): class Conditions: """Conditions.""" - volume: Annotated[ + volfrac: Annotated[ float, bounded(lower=0.0, upper=1.0).category(THEORY), bounded(lower=0.3, upper=0.6).warning().category(IMPL), @@ -80,7 +80,7 @@ class Config(Conditions): design_constraints = (volume_fraction_bound,) design_space = spaces.Box(low=0.0, high=1.0, shape=(101, 101), dtype=np.float64) - dataset_id = "IDEALLab/heat_conduction_2d_v0" + dataset_id = "IDEALLab/heat_conduction_2d_v1" container_id = "quay.io/dolfinadjoint/pyadjoint:master" def __init__(self, seed: int = 0, **kwargs: Any) -> None: @@ -93,7 +93,7 @@ def __init__(self, seed: int = 0, **kwargs: Any) -> None: super().__init__(seed=seed) self.config = self.Config(**kwargs) resolution = self.config.resolution - self.conditions = self.Conditions(self.config.volume, self.config.length) + self.conditions = self.Conditions(self.config.volfrac, self.config.length) self.design_space = spaces.Box(low=0.0, high=1.0, shape=(resolution, resolution), dtype=np.float64) def simulate_verbose(self, design: npt.NDArray | None = None, config: dict[str, Any] | None = None) -> SimulationResult: @@ -101,23 +101,23 @@ def simulate_verbose(self, design: npt.NDArray | None = None, config: dict[str, Args: design (Optional[np.ndarray]): The design to simulate. - config (dict): A dictionary with configuration (e.g., volume (float): Volume constraint,length (float): Length constraint,resolution (int): Resolution of the design space) for the simulation. + config (dict): A dictionary with configuration (e.g., volfrac (float): Target volume fraction,length (float): Length constraint,resolution (int): Resolution of the design space) for the simulation. Returns: A `SimulationResult` instance containing the thermal compliance of the design. """ config = config or {} - volume = config.get("volume", self.config.volume) + volfrac = config.get("volfrac", self.config.volfrac) length = config.get("length", self.config.length) resolution = config.get("resolution", self.config.resolution) if design is None: - design = self.initialize_design(volume, resolution) + design = self.initialize_design(volfrac, resolution) perf = load_float( run_container_script( self.container_id, Path(__file__).parent / "templates" / "simulate_heat_conduction_2d.py", - args=(resolution - 1, volume, length), + args=(resolution - 1, volfrac, length), stdin=np_array_to_bytes(design), output_path="RES_SIM/Performance.txt", ) @@ -132,26 +132,26 @@ def optimize( Args: starting_point (npt.NDArray | None): The initial design for optimization. - config (dict): A dictionary with configuration (e.g., volume (float): Volume constraint,length (float): Length constraint,resolution (int): Resolution of the design space) for the simulation. + config (dict): A dictionary with configuration (e.g., volfrac (float): Target volume fraction,length (float): Length constraint,resolution (int): Resolution of the design space) for the simulation. Returns: Tuple[OptimalDesign, list[OptiStep]]: The optimized design and the optimization history. """ config = config or {} - volume = config.get("volume", self.config.volume) + volfrac = config.get("volfrac", self.config.volfrac) length = config.get("length", self.config.length) max_iter = config.get("max_iter", self.config.max_iter) resolution = config.get("resolution", self.config.resolution) if starting_point is None: - starting_point = self.initialize_design(volume, resolution) + starting_point = self.initialize_design(volfrac, resolution) output = np.load( run_container_script( self.container_id, Path(__file__).parent / "templates" / "optimize_heat_conduction_2d.py", - args=(resolution - 1, volume, length, max_iter), + args=(resolution - 1, volfrac, length, max_iter), stdin=np_array_to_bytes(starting_point), - output_path=f"RES_OPT/OUTPUT={volume}_w={length}.npz", + output_path=f"RES_OPT/OUTPUT={volfrac}_w={length}.npz", ) ) @@ -164,17 +164,17 @@ def reset(self, seed: int | None = None, **kwargs) -> None: """Reset the problem to a given seed.""" super().reset(seed, **kwargs) - def initialize_design(self, volume: float | None = None, resolution: int | None = None) -> npt.NDArray: + def initialize_design(self, volfrac: float | None = None, resolution: int | None = None) -> npt.NDArray: """Initialize the design based on SIMP method. Args: - volume (Optional[float]): Volume constraint. + volfrac (Optional[float]): Target volume fraction. resolution (Optional[int]): Resolution of the design space. Returns: HeatConduction2D: The initialized design. """ - volume = volume if volume is not None else self.config.volume + volfrac = volfrac if volfrac is not None else self.config.volfrac resolution = resolution if resolution is not None else self.config.resolution # Run the Docker command @@ -182,8 +182,8 @@ def initialize_design(self, volume: float | None = None, resolution: int | None run_container_script( self.container_id, Path(__file__).parent / "templates" / "initialize_design_2d.py", - args=(resolution - 1, volume), - output_path=f"initialize_design/initial_v={volume}_resol={resolution}.npy", + args=(resolution - 1, volfrac), + output_path=f"initialize_design/initial_v={volfrac}_resol={resolution}.npy", ) ) diff --git a/engibench/problems/heatconduction3d/v0.py b/engibench/problems/heatconduction3d/v0.py index b8543486..43e5f07d 100644 --- a/engibench/problems/heatconduction3d/v0.py +++ b/engibench/problems/heatconduction3d/v0.py @@ -29,12 +29,12 @@ @constraint(categories=THEORY, criticality=Criticality.Warning) -def volume_fraction_bound(design: npt.NDArray, volume: float) -> None: +def volume_fraction_bound(design: npt.NDArray, volfrac: float) -> None: """Constraint for volume fraction of the design.""" actual_volfrac = design.mean() tolerance = 0.01 - assert abs(actual_volfrac - volume) <= tolerance, ( - f"Volume fraction of the design {actual_volfrac:.4f} does not match target {volume:.4f} specified in the conditions. While the optimizer might fix it, this is likely to affect objective values as the initial design is not feasible given the constraints." + assert abs(actual_volfrac - volfrac) <= tolerance, ( + f"Volume fraction of the design {actual_volfrac:.4f} does not match target {volfrac:.4f} specified in the conditions. While the optimizer might fix it, this is likely to affect objective values as the initial design is not feasible given the constraints." ) @@ -52,7 +52,7 @@ class HeatConduction3D(Problem[npt.NDArray]): class Conditions: """Structured representation of the conditions.""" - volume: Annotated[ + volfrac: Annotated[ float, bounded(lower=0.0, upper=1.0).category(THEORY), bounded(lower=0.3, upper=0.6).warning().category(IMPL), @@ -76,7 +76,7 @@ class Config(Conditions): design_constraints = (volume_fraction_bound,) design_space = spaces.Box(low=0.0, high=1.0, shape=(51, 51, 51), dtype=np.float64) - dataset_id = "IDEALLab/heat_conduction_3d_v0" + dataset_id = "IDEALLab/heat_conduction_3d_v1" container_id = "quay.io/dolfinadjoint/pyadjoint:master" def __init__(self, seed: int = 0, **kwargs) -> None: @@ -89,7 +89,7 @@ def __init__(self, seed: int = 0, **kwargs) -> None: super().__init__(seed=seed) self.config = self.Config(**kwargs) resolution = self.config.resolution - self.conditions = self.Conditions(self.config.volume, self.config.area) + self.conditions = self.Conditions(self.config.volfrac, self.config.area) self.design_space = spaces.Box(low=0.0, high=1.0, shape=(resolution, resolution, resolution), dtype=np.float64) def simulate_verbose(self, design: npt.NDArray | None = None, config: dict[str, Any] | None = None) -> SimulationResult: @@ -97,23 +97,23 @@ def simulate_verbose(self, design: npt.NDArray | None = None, config: dict[str, Args: design (Optional[np.ndarray]): The design to simulate. - config (dict): A dictionary with configuration (e.g., volume (float): Volume constraint,area (float): Area constraint,resolution (int): Resolution of the design space) for the simulation. + config (dict): A dictionary with configuration (e.g., volfrac (float): Target volume fraction,area (float): Area constraint,resolution (int): Resolution of the design space) for the simulation. Returns: A `SimulationResult` instance containing the thermal compliance of the design. """ config = config or {} - volume = config.get("volume", self.config.volume) + volfrac = config.get("volfrac", self.config.volfrac) area = config.get("area", self.config.area) resolution = config.get("resolution", self.config.resolution) if design is None: - design = self.initialize_design(volume, resolution) + design = self.initialize_design(volfrac, resolution) perf = load_float( run_container_script( self.container_id, Path(__file__).parent / "templates" / "simulate_heat_conduction_3d.py", - args=(resolution - 1, volume, area), + args=(resolution - 1, volfrac, area), stdin=cli.np_array_to_bytes(design), output_path="RES_SIM/Performance.txt", ) @@ -128,26 +128,26 @@ def optimize( Args: starting_point (npt.NDArray | None): The initial design for optimization. - config (dict): A dictionary with configuration (e.g., volume (float): Volume constraint,Area (float): Area constraint,resolution (int): Resolution of the design space) for the simulation. + config (dict): A dictionary with configuration (e.g., volfrac (float): Target volume fraction,Area (float): Area constraint,resolution (int): Resolution of the design space) for the simulation. Returns: Tuple[OptimalDesign, list[OptiStep]]: The optimized design and the optimization history. """ config = config or {} - volume = config.get("volume", self.config.volume) + volfrac = config.get("volfrac", self.config.volfrac) area = config.get("area", self.config.area) resolution = config.get("resolution", self.config.resolution) max_iter = config.get("max_iter", self.config.max_iter) if starting_point is None: - starting_point = self.initialize_design(volume, resolution) + starting_point = self.initialize_design(volfrac, resolution) output = np.load( run_container_script( self.container_id, Path(__file__).parent / "templates" / "optimize_heat_conduction_3d.py", - args=(resolution - 1, volume, area, max_iter), + args=(resolution - 1, volfrac, area, max_iter), stdin=cli.np_array_to_bytes(starting_point), - output_path=f"RES_OPT/OUTPUT={volume}_w={area}.npz", + output_path=f"RES_OPT/OUTPUT={volfrac}_w={area}.npz", ) ) @@ -160,25 +160,25 @@ def reset(self, seed: int | None = None, **kwargs) -> None: """Reset the problem to a given seed.""" super().reset(seed, **kwargs) - def initialize_design(self, volume: float | None = None, resolution: int | None = None) -> npt.NDArray: + def initialize_design(self, volfrac: float | None = None, resolution: int | None = None) -> npt.NDArray: """Initialize the design based on SIMP method. Args: - volume (Optional[float]): Volume constraint. + volfrac (Optional[float]): Target volume fraction. resolution (Optional[int]): Resolution of the design space. Returns: HeatConduction3D: The initialized design. """ - volume = volume if volume is not None else self.config.volume + volfrac = volfrac if volfrac is not None else self.config.volfrac resolution = resolution if resolution is not None else self.config.resolution return np.load( run_container_script( self.container_id, Path(__file__).parent / "templates" / "initialize_design_3d.py", - args=(resolution - 1, volume), - output_path=f"initialize_design/initial_v={volume}_resol={resolution}.npy", + args=(resolution - 1, volfrac), + output_path=f"initialize_design/initial_v={volfrac}_resol={resolution}.npy", ) )