From fa8aca27c035136d5441889cd004dce073de5334 Mon Sep 17 00:00:00 2001
From: Dan Martin <93075337+Mingqwqqaq@users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:57:45 +0800
Subject: [PATCH] feat(StructuralOptimization): add composite laminate stacking
benchmark
---
TASK_DETAILS.md | 6 +-
TASK_DETAILS_zh-CN.md | 6 +-
.../CompositeLaminateStacking/README.md | 53 ++++
.../CompositeLaminateStacking/README_zh-CN.md | 52 ++++
.../CompositeLaminateStacking/Task.md | 127 ++++++++
.../CompositeLaminateStacking/Task_zh-CN.md | 87 ++++++
.../baseline/result_log.txt | 32 ++
.../baseline/solution.py | 10 +
.../frontier_eval/agent_files.txt | 7 +
.../frontier_eval/artifact_files.txt | 2 +
.../frontier_eval/candidate_destination.txt | 1 +
.../frontier_eval/constraints.txt | 12 +
.../frontier_eval/copy_files.txt | 12 +
.../frontier_eval/eval_command.txt | 1 +
.../frontier_eval/initial_program.txt | 1 +
.../frontier_eval/readonly_files.txt | 13 +
.../references/config.json | 120 ++++++++
.../references/design_notes.md | 78 +++++
.../CompositeLaminateStacking/scripts/init.py | 14 +
.../verification/design_runtime.py | 276 ++++++++++++++++++
.../verification/design_worker.py | 98 +++++++
.../verification/evaluator.py | 221 ++++++++++++++
.../verification/mechanics.py | 266 +++++++++++++++++
.../verification/requirements.txt | 1 +
.../verification/test_evaluator.py | 103 +++++++
.../verification/test_mechanics.py | 71 +++++
benchmarks/StructuralOptimization/README.md | 19 +-
.../StructuralOptimization/README_zh-CN.md | 19 +-
.../task/composite_laminate_stacking.yaml | 6 +
29 files changed, 1694 insertions(+), 20 deletions(-)
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/README.md
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/README_zh-CN.md
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/Task.md
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/Task_zh-CN.md
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/result_log.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/solution.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/agent_files.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/artifact_files.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/candidate_destination.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/constraints.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/copy_files.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/eval_command.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/initial_program.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/readonly_files.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/references/config.json
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/references/design_notes.md
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/scripts/init.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_runtime.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_worker.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/evaluator.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/mechanics.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/requirements.txt
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_evaluator.py
create mode 100644 benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_mechanics.py
create mode 100644 frontier_eval/conf/task/composite_laminate_stacking.yaml
diff --git a/TASK_DETAILS.md b/TASK_DETAILS.md
index 7475c4cc..7a9344a2 100644
--- a/TASK_DETAILS.md
+++ b/TASK_DETAILS.md
@@ -286,7 +286,7 @@ We welcome new engineering problem ideas — even without complete verification
JSSP YN family (Yamada and Nakano 1992) |
- | StructuralOptimization |
+ StructuralOptimization |
ISCSO2015 |
Minimize weight of 45-bar 2D truss under stress / displacement constraints |
@@ -302,6 +302,10 @@ We welcome new engineering problem ideas — even without complete verification
PyMOTOSIMPCompliance |
pyMOTO-based 2D beam topology optimization (SIMP + OC/MMA) under a volume-fraction constraint |
+
+ CompositeLaminateStacking |
+ Optimize balanced, symmetric 48-ply composite laminates for buckling and maximum-strain failure across ten plate cases |
+
| Robotics |
DynamicObstacleAvoidanceNavigation |
diff --git a/TASK_DETAILS_zh-CN.md b/TASK_DETAILS_zh-CN.md
index e2a070a2..ef1be0c3 100644
--- a/TASK_DETAILS_zh-CN.md
+++ b/TASK_DETAILS_zh-CN.md
@@ -286,7 +286,7 @@ Frontier-Eng 目前已覆盖以下领域的任务。每个任务均配有可运
JSSP YN 家族(Yamada & Nakano 1992) |
- | StructuralOptimization |
+ StructuralOptimization |
ISCSO2015 |
在应力 / 位移约束下最小化 45 杆 2D 桁架重量 |
@@ -302,6 +302,10 @@ Frontier-Eng 目前已覆盖以下领域的任务。每个任务均配有可运
PyMOTOSIMPCompliance |
基于 pyMOTO 的 2D 梁拓扑优化(SIMP + OC/MMA),体积分数约束 |
+
+ CompositeLaminateStacking |
+ 在十个板工况下优化平衡、对称的 48 层复合材料铺层,同时提高屈曲和最大应变失效性能 |
+
| Robotics |
DynamicObstacleAvoidanceNavigation |
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/README.md b/benchmarks/StructuralOptimization/CompositeLaminateStacking/README.md
new file mode 100644
index 00000000..2a9d8374
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/README.md
@@ -0,0 +1,53 @@
+# CompositeLaminateStacking
+
+Optimize balanced, symmetric 48-ply composite laminates across ten simply supported
+plate cases derived from an MIT-licensed published benchmark.
+
+## What the agent edits
+
+Only [`scripts/init.py`](scripts/init.py) is editable. Implement:
+
+```python
+def design_laminates(cases: list[dict]) -> dict[str, list[int]]:
+ ...
+```
+
+Return one list of 12 integer base angles in `[0, 90]` for every `case_id`. The evaluator
+expands the list into a balanced, symmetric 48-ply stack. See [`Task.md`](Task.md) for the
+complete contract and [`references/design_notes.md`](references/design_notes.md) for the
+mechanics and source cross-check.
+
+## Requirements
+
+- Python 3.10+
+- NumPy
+- no GPU, Docker, external data, or proprietary solver
+
+Install task-local requirements if needed:
+
+```bash
+python -m pip install -r verification/requirements.txt
+```
+
+## Direct evaluation
+
+From this task directory:
+
+```bash
+python verification/evaluator.py scripts/init.py
+python -m unittest discover -s verification -p "test_*.py" -v
+```
+
+## Unified evaluation
+
+From the repository root:
+
+```bash
+python -m frontier_eval \
+ task=unified \
+ task.benchmark=StructuralOptimization/CompositeLaminateStacking \
+ algorithm=openevolve \
+ algorithm.iterations=0
+```
+
+The baseline is feasible in all ten cases and scores 50. Higher is better.
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/README_zh-CN.md b/benchmarks/StructuralOptimization/CompositeLaminateStacking/README_zh-CN.md
new file mode 100644
index 00000000..8fea7f7d
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/README_zh-CN.md
@@ -0,0 +1,52 @@
+# CompositeLaminateStacking
+
+在十个简支板工况上优化平衡、对称的 48 层复合材料铺层。任务参数来自一个采用
+MIT 许可证发布的真实研究 benchmark。
+
+## Agent 修改内容
+
+仅允许修改 [`scripts/init.py`](scripts/init.py),并实现:
+
+```python
+def design_laminates(cases: list[dict]) -> dict[str, list[int]]:
+ ...
+```
+
+必须为每个 `case_id` 返回一个包含 12 个 `[0, 90]` 区间整数角度的列表。评测器会
+把它展开为平衡、对称的 48 层铺层。完整接口见 [`Task_zh-CN.md`](Task_zh-CN.md),
+力学模型与来源交叉验证见 [`references/design_notes.md`](references/design_notes.md)。
+
+## 环境要求
+
+- Python 3.10+
+- NumPy
+- 不需要 GPU、Docker、外部数据或商业求解器
+
+如有需要,可安装任务依赖:
+
+```bash
+python -m pip install -r verification/requirements.txt
+```
+
+## 直接评测
+
+在当前任务目录运行:
+
+```bash
+python verification/evaluator.py scripts/init.py
+python -m unittest discover -s verification -p "test_*.py" -v
+```
+
+## Unified 评测
+
+在仓库根目录运行:
+
+```bash
+python -m frontier_eval \
+ task=unified \
+ task.benchmark=StructuralOptimization/CompositeLaminateStacking \
+ algorithm=openevolve \
+ algorithm.iterations=0
+```
+
+baseline 在全部十个工况中均可行,得分为 50;分数越高越好。
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task.md b/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task.md
new file mode 100644
index 00000000..af55edc0
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task.md
@@ -0,0 +1,127 @@
+# Task: Composite Laminate Stacking Optimization
+
+## 1. Engineering problem
+
+Design the stacking sequence of a 48-ply orthotropic composite plate. A good laminate
+must resist both material failure and plate buckling under several geometries and load
+ratios. This is a discrete, coupled structural-design problem: moving stiff plies toward
+the surfaces can help buckling, while changing fiber directions alters in-plane strength
+and can hurt another load case.
+
+The task is derived from the MIT-licensed Zenodo release *Beyond Double-Double Theory:
+n-Directional Stacking Sequence Optimisation in Composite Laminates*
+([doi:10.5281/zenodo.15864525](https://doi.org/10.5281/zenodo.15864525)). It preserves
+the released 48-ply construction, orthotropic material, ply thickness, five aspect ratios,
+uniaxial/biaxial compression, strain allowables, and Haftka reference layup. The evaluator
+is an independent NumPy implementation documented in `references/design_notes.md`.
+
+## 2. Candidate API
+
+Implement in `scripts/init.py`:
+
+```python
+def design_laminates(cases: list[dict]) -> dict[str, list[int]]:
+ ...
+```
+
+The function receives all public cases in one call. Each case contains only:
+
+```python
+{
+ "case_id": str,
+ "aspect_ratio": float,
+ "a_mm": float,
+ "b_mm": float,
+ "Nx_N_per_mm": float,
+ "Ny_N_per_mm": float,
+}
+```
+
+Compression is negative. `Ny_N_per_mm == 0` denotes uniaxial loading; a negative value
+denotes biaxial compression.
+
+Return a dictionary with exactly the supplied `case_id` values. Each value must be a JSON
+list containing exactly 12 finite integer angles in the inclusive interval `[0, 90]`.
+
+## 3. Layup construction
+
+For candidate angles `theta_1 ... theta_12`, the verifier constructs the first half as:
+
+```text
+[theta_1, -theta_1, theta_2, -theta_2, ..., theta_12, -theta_12]
+```
+
+and appends its reverse. The result has 48 plies and is balanced and symmetric by
+construction. The candidate therefore optimizes both angle selection and through-thickness
+ordering without needing to implement manufacturing-constraint repair.
+
+## 4. Evaluation cases
+
+Ten deterministic cases combine aspect ratios `0.5, 1, 2, 3, 4` with:
+
+- uniaxial compression: `Nx = -0.175126835 N/mm`, `Ny = 0`;
+- biaxial compression: `Nx = -0.175126835 N/mm`, `Ny = -0.087563418 N/mm`.
+
+The plate widths are fixed at `127 mm`; lengths vary with aspect ratio. Material and
+allowable values are committed in `references/config.json`.
+
+## 5. Physics
+
+The verifier applies classical lamination theory to compute `A`, `B`, and `D`. It then
+computes:
+
+1. a maximum-strain failure load factor from the most critical longitudinal, transverse,
+ or shear ply strain under the released 1.5 design-load factor;
+2. a simply supported plate buckling load factor from a double-sine Ritz basis, numerical
+ quadrature, and a symmetric generalized eigenproblem.
+
+The governing reserve factor is:
+
+```text
+reserve = min(failure_load_factor, buckling_load_factor)
+```
+
+## 6. Score
+
+Each case is normalized against the released Haftka reference stacking sequence:
+
+```text
+case_score = clip(50 + 50*tanh(log(reserve/anchor_reserve)/0.5), 0, 100)
+```
+
+The final diagnostic score is:
+
+```text
+0.75 * mean(case_scores) + 0.25 * percentile(case_scores, 20)
+```
+
+A valid candidate receives that value as `combined_score`. Missing cases, extra case ids,
+bad types, non-integer/out-of-range angles, timeouts, import failures, or invalid mechanics
+make the candidate invalid and set `combined_score` to zero. Per-case diagnostic feedback
+remains available.
+
+## 7. Runtime and integrity contract
+
+- Keep `design_laminates` deterministic and self-contained.
+- Candidate import and execution occur in a separate Python process with a bounded runtime.
+- The process receives cases and returns designs through a JSON-lines protocol.
+- Candidate stdout is discarded so it cannot corrupt the evaluator protocol.
+- This is process isolation, not an operating-system security sandbox.
+- Do not read or modify evaluator, reference, output, or environment-secret files.
+- Edit only the region between the `EVOLVE-BLOCK` markers in `scripts/init.py`.
+
+## 8. Commands
+
+Direct evaluation, from this directory:
+
+```bash
+python verification/evaluator.py scripts/init.py
+```
+
+Unified evaluation, from the repository root:
+
+```bash
+python -m frontier_eval task=unified \
+ task.benchmark=StructuralOptimization/CompositeLaminateStacking \
+ algorithm=openevolve algorithm.iterations=0
+```
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task_zh-CN.md b/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task_zh-CN.md
new file mode 100644
index 00000000..90074496
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/Task_zh-CN.md
@@ -0,0 +1,87 @@
+# 任务:复合材料层合板铺层优化
+
+## 1. 工程问题
+
+设计一个 48 层正交各向异性复合材料板的铺层顺序。铺层需要在多种几何尺寸和载荷
+比例下同时抵抗材料失效和板屈曲。这是一个耦合的离散结构设计问题:把高刚度方向
+的铺层放到外侧可能提高屈曲性能,但改变纤维方向也会改变面内强度,并可能损害另一
+载荷工况。
+
+本任务源自采用 MIT 许可证发布的 Zenodo 数据集 *Beyond Double-Double Theory:
+n-Directional Stacking Sequence Optimisation in Composite Laminates*
+([doi:10.5281/zenodo.15864525](https://doi.org/10.5281/zenodo.15864525))。任务保留了
+原发布代码中的 48 层构造、正交各向异性材料、单层厚度、五种长宽比、单轴/双轴压缩、
+应变许用值和 Haftka 参考铺层。评测器采用独立的 NumPy 实现,详见
+`references/design_notes.md`。
+
+## 2. 候选程序接口
+
+在 `scripts/init.py` 中实现:
+
+```python
+def design_laminates(cases: list[dict]) -> dict[str, list[int]]:
+ ...
+```
+
+函数一次收到所有公开工况。每个工况仅包含 `case_id`、长宽比、板长宽和 `Nx/Ny`
+膜内载荷。负值表示压缩;`Ny_N_per_mm == 0` 表示单轴载荷。
+
+返回字典必须恰好包含输入给出的全部 `case_id`。每个值必须是可 JSON 序列化的列表,
+包含恰好 12 个 `[0, 90]` 闭区间内的有限整数角度。
+
+## 3. 铺层构造
+
+对于候选角度 `theta_1 ... theta_12`,评测器先构造:
+
+```text
+[theta_1, -theta_1, theta_2, -theta_2, ..., theta_12, -theta_12]
+```
+
+再追加其逆序,得到平衡、对称的 48 层铺层。候选程序需要优化角度选择和厚度方向顺序,
+但不需要自行修复制造约束。
+
+## 4. 评测工况与物理模型
+
+十个确定性工况由长宽比 `0.5, 1, 2, 3, 4` 与单轴、双轴压缩组合而成。板宽固定为
+`127 mm`,长度随长宽比变化。材料、载荷和许用值记录在 `references/config.json`。
+
+评测器使用经典层合板理论计算 `A`、`B`、`D`,然后求得:
+
+1. 由最危险的纵向、横向或剪切层内应变确定的最大应变失效载荷因子;
+2. 通过双正弦 Ritz 基、数值积分和对称广义特征值问题得到的简支板屈曲载荷因子。
+
+控制储备因子为:
+
+```text
+reserve = min(failure_load_factor, buckling_load_factor)
+```
+
+## 5. 评分
+
+每个工况相对于已发布的 Haftka 参考铺层归一化:
+
+```text
+case_score = clip(50 + 50*tanh(log(reserve/anchor_reserve)/0.5), 0, 100)
+```
+
+最终诊断分数为 `75%` 平均工况分加 `25%` 的第 20 百分位工况分。缺少工况、多余 ID、
+错误类型、非整数或越界角度、超时、导入失败或非法力学结果都会令 `combined_score=0`;
+逐工况诊断反馈仍会保留。
+
+## 6. 运行与完整性约束
+
+- `design_laminates` 必须确定性、可独立运行。
+- 候选程序在受时间限制的独立 Python 进程中导入和执行。
+- 工况和设计通过 JSON-lines 协议交换;候选 stdout 会被丢弃。
+- 这是进程隔离,并非操作系统安全沙箱。
+- 不得读取或修改 evaluator、参考参数、输出或环境机密文件。
+- 仅修改 `scripts/init.py` 内 `EVOLVE-BLOCK` 标记之间的区域。
+
+## 7. 运行命令
+
+```bash
+python verification/evaluator.py scripts/init.py
+python -m frontier_eval task=unified \
+ task.benchmark=StructuralOptimization/CompositeLaminateStacking \
+ algorithm=openevolve algorithm.iterations=0
+```
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/result_log.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/result_log.txt
new file mode 100644
index 00000000..00e9943a
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/result_log.txt
@@ -0,0 +1,32 @@
+CompositeLaminateStacking baseline verification
+================================================
+
+Reference dataset:
+ Dataset for Beyond Double-Double Theory: n-Directional Stacking Sequence
+ Optimisation in Composite Laminates
+ DOI: 10.5281/zenodo.15864525
+ License: MIT
+
+Verification date: 2026-08-13
+
+Direct evaluator:
+ python verification/evaluator.py scripts/init.py
+ feasible_cases: 10 / 10
+ diagnostic_score: 50.0
+ combined_score: 50.0
+
+Unit tests:
+ python -m unittest discover -s verification -p "test_*.py" -v
+ result: 13 tests passed
+
+Unified evaluator:
+ python -m frontier_eval task=unified \
+ task.benchmark=StructuralOptimization/CompositeLaminateStacking \
+ algorithm=openevolve algorithm.iterations=0
+ benchmark_returncode: 0
+ valid: 1
+ feasible_cases: 10
+ combined_score: 50.0
+
+The unified run was also exercised on Windows with platform-specific shell and
+Python-path overrides; the benchmark output and score matched the direct run.
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/solution.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/solution.py
new file mode 100644
index 00000000..83ea527f
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/baseline/solution.py
@@ -0,0 +1,10 @@
+"""Recorded baseline; intentionally identical to scripts/init.py."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def design_laminates(cases: list[dict[str, Any]]) -> dict[str, list[int]]:
+ haftka_reference = [90, 45, 45, 90, 45, 90, 45, 45, 45, 45, 45, 45]
+ return {str(case["case_id"]): list(haftka_reference) for case in cases}
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/agent_files.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/agent_files.txt
new file mode 100644
index 00000000..e8a78865
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/agent_files.txt
@@ -0,0 +1,7 @@
+README.md
+README_zh-CN.md
+Task.md
+Task_zh-CN.md
+scripts/init.py
+references/design_notes.md
+frontier_eval/constraints.txt
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/artifact_files.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/artifact_files.txt
new file mode 100644
index 00000000..76dc893a
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/artifact_files.txt
@@ -0,0 +1,2 @@
+metrics.json
+artifacts.json
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/candidate_destination.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/candidate_destination.txt
new file mode 100644
index 00000000..b9411b3d
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/candidate_destination.txt
@@ -0,0 +1 @@
+scripts/init.py
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/constraints.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/constraints.txt
new file mode 100644
index 00000000..7e9a961b
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/constraints.txt
@@ -0,0 +1,12 @@
+Composite laminate stacking constraints:
+1) Edit only `scripts/init.py` inside the EVOLVE-BLOCK markers.
+2) Keep `design_laminates(cases) -> dict` working.
+3) Return exactly one design for every supplied case_id and no unknown case ids.
+4) Each design is a JSON list of exactly 12 finite integer angles in [0, 90] degrees.
+5) The evaluator expands every base angle theta to [theta, -theta], then mirrors the
+ 24-ply half-stack to form a balanced, symmetric 48-ply laminate.
+6) Maximize the governing reserve factor, min(buckling load factor, maximum-strain
+ failure load factor), across all uniaxial and biaxial aspect-ratio cases.
+7) Keep the design function deterministic, self-contained, and JSON serializable.
+8) Do not read, write, or modify evaluator, reference, result, or environment-secret files.
+ Candidate code runs in a separate process with a bounded total runtime.
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/copy_files.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/copy_files.txt
new file mode 100644
index 00000000..a167c883
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/copy_files.txt
@@ -0,0 +1,12 @@
+README.md
+README_zh-CN.md
+Task.md
+Task_zh-CN.md
+scripts/init.py
+verification/evaluator.py
+verification/mechanics.py
+verification/design_runtime.py
+verification/design_worker.py
+verification/requirements.txt
+references/config.json
+references/design_notes.md
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/eval_command.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/eval_command.txt
new file mode 100644
index 00000000..527d23b5
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/eval_command.txt
@@ -0,0 +1 @@
+{python} verification/evaluator.py {candidate} --metrics-out metrics.json --artifacts-out artifacts.json
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/initial_program.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/initial_program.txt
new file mode 100644
index 00000000..b9411b3d
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/initial_program.txt
@@ -0,0 +1 @@
+scripts/init.py
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/readonly_files.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/readonly_files.txt
new file mode 100644
index 00000000..7f0018cd
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/frontier_eval/readonly_files.txt
@@ -0,0 +1,13 @@
+README.md
+README_zh-CN.md
+Task.md
+Task_zh-CN.md
+verification/evaluator.py
+verification/mechanics.py
+verification/design_runtime.py
+verification/design_worker.py
+verification/test_evaluator.py
+verification/test_mechanics.py
+verification/requirements.txt
+references/config.json
+references/design_notes.md
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/config.json b/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/config.json
new file mode 100644
index 00000000..56489bb5
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/config.json
@@ -0,0 +1,120 @@
+{
+ "source": {
+ "title": "Dataset for Beyond Double-Double Theory: n-Directional Stacking Sequence Optimisation in Composite Laminates",
+ "authors": [
+ "Jose Humberto S. Almeida Jr.",
+ "Emilia Balonek",
+ "Saullo G. P. Castro"
+ ],
+ "doi": "10.5281/zenodo.15864525",
+ "concept_doi": "10.5281/zenodo.15864524",
+ "license": "MIT"
+ },
+ "material": {
+ "E11_MPa": 127550.0,
+ "E22_MPa": 13030.0,
+ "nu12": 0.3,
+ "G12_MPa": 6410.0
+ },
+ "laminate": {
+ "num_plies": 48,
+ "design_angles": 12,
+ "ply_thickness_mm": 0.127,
+ "angle_min_deg": 0,
+ "angle_max_deg": 90
+ },
+ "failure": {
+ "epsilon_1_allowable": 0.008,
+ "epsilon_2_allowable": 0.029,
+ "gamma_12_allowable": 0.015,
+ "design_load_factor": 1.5
+ },
+ "buckling": {
+ "modes_x": 5,
+ "modes_y": 5,
+ "quadrature_order": 12
+ },
+ "anchor_base_angles_deg": [90, 45, 45, 90, 45, 90, 45, 45, 45, 45, 45, 45],
+ "cases": [
+ {
+ "case_id": "uniaxial_ar_0_5",
+ "aspect_ratio": 0.5,
+ "a_mm": 63.5,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": 0.0
+ },
+ {
+ "case_id": "uniaxial_ar_1_0",
+ "aspect_ratio": 1.0,
+ "a_mm": 127.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": 0.0
+ },
+ {
+ "case_id": "uniaxial_ar_2_0",
+ "aspect_ratio": 2.0,
+ "a_mm": 254.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": 0.0
+ },
+ {
+ "case_id": "uniaxial_ar_3_0",
+ "aspect_ratio": 3.0,
+ "a_mm": 381.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": 0.0
+ },
+ {
+ "case_id": "uniaxial_ar_4_0",
+ "aspect_ratio": 4.0,
+ "a_mm": 508.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": 0.0
+ },
+ {
+ "case_id": "biaxial_ar_0_5",
+ "aspect_ratio": 0.5,
+ "a_mm": 63.5,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": -0.0875634176232382
+ },
+ {
+ "case_id": "biaxial_ar_1_0",
+ "aspect_ratio": 1.0,
+ "a_mm": 127.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": -0.0875634176232382
+ },
+ {
+ "case_id": "biaxial_ar_2_0",
+ "aspect_ratio": 2.0,
+ "a_mm": 254.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": -0.0875634176232382
+ },
+ {
+ "case_id": "biaxial_ar_3_0",
+ "aspect_ratio": 3.0,
+ "a_mm": 381.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": -0.0875634176232382
+ },
+ {
+ "case_id": "biaxial_ar_4_0",
+ "aspect_ratio": 4.0,
+ "a_mm": 508.0,
+ "b_mm": 127.0,
+ "Nx_N_per_mm": -0.1751268352464764,
+ "Ny_N_per_mm": -0.0875634176232382
+ }
+ ]
+}
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/design_notes.md b/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/design_notes.md
new file mode 100644
index 00000000..be044677
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/references/design_notes.md
@@ -0,0 +1,78 @@
+# Design and verification notes
+
+## Provenance
+
+This benchmark is an independent, NumPy-only adaptation of:
+
+- J. H. S. Almeida Jr., E. Balonek, and S. G. P. Castro,
+ *Dataset for Beyond Double-Double Theory: n-Directional Stacking Sequence
+ Optimisation in Composite Laminates*, Zenodo,
+ [doi:10.5281/zenodo.15864525](https://doi.org/10.5281/zenodo.15864525), MIT License.
+- R. T. Haftka, *Optimization of Composite Structures*, 1993, including the
+ 48-ply square-plate reference case used by the released code.
+
+The repository does not vendor the upstream optimizer or its dependencies. The material,
+ply, geometry, loading, allowable-strain, and reference-layup values in `config.json` are
+transcribed from the MIT-licensed release. The evaluator independently implements the
+mechanics described below.
+
+## Design encoding
+
+The candidate returns 12 integer angles `theta_i` in `[0, 90]`. The verifier constructs
+the first half of the laminate as:
+
+```text
+[theta_1, -theta_1, theta_2, -theta_2, ..., theta_12, -theta_12]
+```
+
+and mirrors that sequence to obtain 48 plies. Thus every accepted design is balanced and
+symmetric by construction. Angles `90` and `-90` describe the same material direction.
+
+## Classical lamination theory
+
+The lamina reduced-stiffness matrix uses the released orthotropic properties
+`E11`, `E22`, `nu12`, and `G12`. Each ply stiffness is transformed into global axes and
+integrated through the thickness to form the laminate `A`, `B`, and `D` matrices. The
+symmetry construction makes `B` zero to numerical precision.
+
+## Maximum-strain failure factor
+
+For each nominal membrane load, the evaluator solves:
+
+```text
+epsilon_0 = A^-1 N
+```
+
+after applying the released 1.5 design-load factor. Global strains are transformed into
+every ply's material axes. The failure load factor is the smallest ratio between the
+released allowable longitudinal, transverse, and shear strains and the corresponding
+absolute ply strain.
+
+## Buckling factor
+
+The verifier approximates a simply supported plate with a double-sine Ritz basis. It
+integrates bending and geometric stiffness with Gauss-Legendre quadrature, preserving
+`D16` and `D26` coupling, and solves the symmetric generalized eigenproblem. Five modes
+per direction are used in the committed configuration.
+
+For the released square biaxial anchor, the upstream code reports a failure factor of
+`10394.81` and a BFSC finite-element buckling factor of `9998.19`. The independent
+implementation returns approximately `10396.48` and `10430.16`, respectively: failure
+agrees within 0.1%, and the lower-order Ritz buckling approximation agrees within 5%.
+These bounds are enforced by regression tests.
+
+## Ranking score
+
+For each case, engineering performance is the governing reserve factor:
+
+```text
+reserve = min(failure_load_factor, buckling_load_factor)
+```
+
+The case score is centered at 50 for the released Haftka reference layup and varies
+smoothly with the logarithm of the candidate-to-anchor reserve ratio. The final score is
+75% mean case score plus 25% twentieth-percentile case score. Invalid or incomplete
+candidate output receives `combined_score = 0`, while per-case errors remain visible.
+
+The anchor only normalizes task difficulty. It is not an optimum: a simple load-aware
+design exceeds 60 points in the regression suite, leaving measurable optimization room.
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/scripts/init.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/scripts/init.py
new file mode 100644
index 00000000..1b9a8bdc
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/scripts/init.py
@@ -0,0 +1,14 @@
+"""Feasible starting design for CompositeLaminateStacking."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def design_laminates(cases: list[dict[str, Any]]) -> dict[str, list[int]]:
+ """Return twelve non-negative integer base angles for every requested case."""
+
+ # EVOLVE-BLOCK-START
+ haftka_reference = [90, 45, 45, 90, 45, 90, 45, 45, 45, 45, 45, 45]
+ return {str(case["case_id"]): list(haftka_reference) for case in cases}
+ # EVOLVE-BLOCK-END
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_runtime.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_runtime.py
new file mode 100644
index 00000000..e347ef62
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_runtime.py
@@ -0,0 +1,276 @@
+"""Bounded parent-side runtime for laminate candidate modules."""
+
+from __future__ import annotations
+
+import json
+import os
+import queue
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+import threading
+import time
+from collections import deque
+from pathlib import Path
+from typing import Any
+
+
+MAX_RESPONSE_BYTES = 64 * 1024
+_EOF = object()
+
+
+class DesignRuntimeError(RuntimeError):
+ pass
+
+
+class DesignTimeoutError(DesignRuntimeError):
+ pass
+
+
+class DesignProtocolError(DesignRuntimeError):
+ pass
+
+
+class DesignCandidateError(DesignRuntimeError):
+ pass
+
+
+def _clean_environment() -> dict[str, str]:
+ allowed = {
+ "COMSPEC",
+ "LANG",
+ "LC_ALL",
+ "NUMBER_OF_PROCESSORS",
+ "PATH",
+ "PATHEXT",
+ "SYSTEMDRIVE",
+ "SYSTEMROOT",
+ "TEMP",
+ "TMP",
+ "WINDIR",
+ }
+ env = {key: value for key, value in os.environ.items() if key.upper() in allowed}
+ env["PYTHONUTF8"] = "1"
+ env["PYTHONIOENCODING"] = "utf-8"
+ return env
+
+
+class DesignRuntime:
+ def __init__(
+ self,
+ candidate_path: str | Path,
+ *,
+ startup_timeout_s: float = 3.0,
+ call_timeout_s: float = 8.0,
+ total_timeout_s: float = 12.0,
+ ) -> None:
+ if min(startup_timeout_s, call_timeout_s, total_timeout_s) <= 0:
+ raise ValueError("design timeouts must be positive")
+ source = Path(candidate_path).expanduser().resolve()
+ if not source.is_file():
+ raise FileNotFoundError(source)
+ self.call_timeout_s = float(call_timeout_s)
+ self._deadline = time.monotonic() + float(total_timeout_s)
+ self._next_id = 1
+ self._closed = False
+ self._responses: queue.Queue[Any] = queue.Queue()
+ self._stderr_chunks: deque[bytes] = deque()
+ self._stderr_size = 0
+ self._tempdir = Path(tempfile.mkdtemp(prefix="laminate_design_"))
+ self._process: subprocess.Popen[bytes] | None = None
+ try:
+ copied_candidate = self._tempdir / "candidate.py"
+ shutil.copy2(source, copied_candidate)
+ worker = Path(__file__).with_name("design_worker.py").resolve()
+ popen_kwargs: dict[str, Any] = {
+ "cwd": str(self._tempdir),
+ "env": _clean_environment(),
+ "stdin": subprocess.PIPE,
+ "stdout": subprocess.PIPE,
+ "stderr": subprocess.PIPE,
+ "bufsize": 0,
+ }
+ if os.name == "nt":
+ popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
+ else:
+ popen_kwargs["start_new_session"] = True
+ self._process = subprocess.Popen(
+ [sys.executable, "-I", "-u", str(worker), str(copied_candidate)],
+ **popen_kwargs,
+ )
+ assert self._process.stdout is not None
+ assert self._process.stderr is not None
+ threading.Thread(target=self._read_stdout, args=(self._process.stdout,), daemon=True).start()
+ threading.Thread(target=self._drain_stderr, args=(self._process.stderr,), daemon=True).start()
+ ready = self._receive(startup_timeout_s, "candidate import")
+ if not isinstance(ready, dict) or not ready.get("ok") or not ready.get("ready"):
+ self._raise_response_error(ready, "candidate import")
+ except BaseException:
+ self.close(force=True)
+ raise
+
+ @property
+ def stderr_tail(self) -> str:
+ return b"".join(self._stderr_chunks).decode("utf-8", errors="replace")
+
+ def _read_stdout(self, stream: Any) -> None:
+ try:
+ while True:
+ line = stream.readline(MAX_RESPONSE_BYTES + 1)
+ if not line:
+ break
+ self._responses.put(line)
+ if len(line) > MAX_RESPONSE_BYTES or not line.endswith(b"\n"):
+ break
+ finally:
+ self._responses.put(_EOF)
+
+ def _drain_stderr(self, stream: Any) -> None:
+ while True:
+ chunk = stream.read(4096)
+ if not chunk:
+ return
+ self._stderr_chunks.append(chunk)
+ self._stderr_size += len(chunk)
+ while self._stderr_size > 16 * 1024 and self._stderr_chunks:
+ self._stderr_size -= len(self._stderr_chunks.popleft())
+
+ def _remaining(self) -> float:
+ return self._deadline - time.monotonic()
+
+ def _receive(self, timeout_s: float, operation: str) -> dict[str, Any]:
+ timeout = min(float(timeout_s), self._remaining())
+ if timeout <= 0:
+ self.close(force=True)
+ raise DesignTimeoutError("candidate total runtime budget exceeded")
+ try:
+ item = self._responses.get(timeout=timeout)
+ except queue.Empty as exc:
+ self.close(force=True)
+ raise DesignTimeoutError(f"{operation} timed out after {timeout:.3f}s") from exc
+ if item is _EOF:
+ code = None if self._process is None else self._process.poll()
+ detail = self.stderr_tail[-1000:]
+ raise DesignCandidateError(
+ f"candidate worker exited unexpectedly (code={code})"
+ + (f": {detail}" if detail else "")
+ )
+ if not isinstance(item, bytes) or len(item) > MAX_RESPONSE_BYTES or not item.endswith(b"\n"):
+ self.close(force=True)
+ raise DesignProtocolError("candidate response exceeds 64 KiB or lacks newline")
+ try:
+ response = json.loads(item.decode("utf-8"))
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
+ self.close(force=True)
+ raise DesignProtocolError("candidate returned invalid UTF-8 JSON") from exc
+ if not isinstance(response, dict):
+ raise DesignProtocolError("candidate response must be a JSON object")
+ return response
+
+ @staticmethod
+ def _raise_response_error(response: Any, operation: str) -> None:
+ if isinstance(response, dict):
+ error = response.get("error")
+ if isinstance(error, dict):
+ raise DesignCandidateError(
+ f"{operation} failed: {error.get('type', 'CandidateError')}: "
+ f"{error.get('message', '')}"
+ )
+ raise DesignProtocolError(f"malformed response during {operation}")
+
+ def _rpc(self, operation: str, **payload: Any) -> dict[str, Any]:
+ if self._closed or self._process is None or self._process.poll() is not None:
+ raise DesignCandidateError("candidate worker is not running")
+ request_id = self._next_id
+ self._next_id += 1
+ encoded = (
+ json.dumps(
+ {"id": request_id, "op": operation, **payload},
+ ensure_ascii=True,
+ allow_nan=False,
+ separators=(",", ":"),
+ )
+ + "\n"
+ ).encode("utf-8")
+ try:
+ assert self._process.stdin is not None
+ self._process.stdin.write(encoded)
+ self._process.stdin.flush()
+ except (BrokenPipeError, OSError) as exc:
+ raise DesignCandidateError("candidate worker closed its input") from exc
+ response = self._receive(self.call_timeout_s, operation)
+ if response.get("id") != request_id:
+ self.close(force=True)
+ raise DesignProtocolError("candidate response id does not match request")
+ if not response.get("ok"):
+ self._raise_response_error(response, operation)
+ return response
+
+ def design_laminates(self, cases: list[dict[str, Any]]) -> dict[str, Any]:
+ response = self._rpc("design", cases=cases)
+ designs = response.get("designs")
+ if not isinstance(designs, dict):
+ raise DesignProtocolError("design_laminates must return a JSON object")
+ return designs
+
+ def close(self, *, force: bool = False) -> None:
+ if self._closed:
+ return
+ process = self._process
+ if process is not None and process.poll() is None and not force:
+ try:
+ self._rpc("shutdown")
+ process.wait(timeout=0.25)
+ except Exception:
+ force = True
+ if process is not None and process.poll() is None:
+ self._kill_process_tree(process)
+ self._closed = True
+ if process is not None:
+ for stream in (process.stdin, process.stdout, process.stderr):
+ try:
+ if stream is not None:
+ stream.close()
+ except OSError:
+ pass
+ shutil.rmtree(self._tempdir, ignore_errors=True)
+
+ @staticmethod
+ def _kill_process_tree(process: subprocess.Popen[bytes]) -> None:
+ if process.poll() is not None:
+ return
+ if os.name == "nt":
+ try:
+ subprocess.run(
+ ["taskkill", "/PID", str(process.pid), "/T", "/F"],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ timeout=2.0,
+ check=False,
+ )
+ except Exception:
+ process.kill()
+ else:
+ try:
+ os.killpg(process.pid, signal.SIGKILL)
+ except (ProcessLookupError, PermissionError):
+ process.kill()
+ try:
+ process.wait(timeout=2.0)
+ except subprocess.TimeoutExpired:
+ process.kill()
+ process.wait(timeout=2.0)
+
+ def __enter__(self) -> "DesignRuntime":
+ return self
+
+ def __exit__(self, exc_type: Any, exc: Any, traceback: Any) -> None:
+ self.close()
+
+ def __del__(self) -> None:
+ try:
+ self.close(force=True)
+ except Exception:
+ pass
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_worker.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_worker.py
new file mode 100644
index 00000000..e399571b
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/design_worker.py
@@ -0,0 +1,98 @@
+"""JSON-lines worker for an untrusted laminate design module.
+
+This gives process and protocol isolation. It is not an operating-system sandbox.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import json
+import os
+import sys
+from pathlib import Path
+from typing import Any
+
+
+_json_loads = json.loads
+_json_dumps = json.dumps
+
+
+def _protocol_streams() -> tuple[Any, Any]:
+ protocol_in = os.fdopen(os.dup(0), "r", encoding="utf-8", newline="\n")
+ protocol_out = os.fdopen(os.dup(1), "w", encoding="utf-8", newline="\n", buffering=1)
+ devnull = os.open(os.devnull, os.O_WRONLY)
+ try:
+ os.dup2(devnull, 1)
+ finally:
+ os.close(devnull)
+ return protocol_in, protocol_out
+
+
+def _load_candidate(path: Path) -> Any:
+ spec = importlib.util.spec_from_file_location("laminate_candidate", path)
+ if spec is None or spec.loader is None:
+ raise ImportError(f"cannot load candidate from {path}")
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ if not callable(getattr(module, "design_laminates", None)):
+ raise AttributeError("candidate must define callable design_laminates(cases)")
+ return module
+
+
+def _error(request_id: Any, exc: BaseException) -> dict[str, Any]:
+ try:
+ message = str(exc)[:500]
+ except Exception:
+ message = "failed to format candidate exception"
+ return {
+ "id": request_id,
+ "ok": False,
+ "error": {"type": type(exc).__name__, "message": message},
+ }
+
+
+def _send(stream: Any, payload: dict[str, Any]) -> None:
+ stream.write(_json_dumps(payload, ensure_ascii=True, allow_nan=False) + "\n")
+ stream.flush()
+
+
+def main() -> int:
+ protocol_in, protocol_out = _protocol_streams()
+ if len(sys.argv) != 2:
+ _send(protocol_out, _error(None, ValueError("expected candidate path")))
+ return 2
+ try:
+ candidate = _load_candidate(Path(sys.argv[1]).resolve())
+ except BaseException as exc:
+ _send(protocol_out, _error(None, exc))
+ return 1
+
+ _send(protocol_out, {"id": None, "ok": True, "ready": True})
+ for line in protocol_in:
+ request_id: Any = None
+ try:
+ request = _json_loads(line)
+ if not isinstance(request, dict):
+ raise TypeError("request must be a JSON object")
+ request_id = request.get("id")
+ operation = request.get("op")
+ if operation == "design":
+ cases = request.get("cases")
+ designs = candidate.design_laminates(cases)
+ response = {"id": request_id, "ok": True, "designs": designs}
+ elif operation == "shutdown":
+ _send(protocol_out, {"id": request_id, "ok": True})
+ return 0
+ else:
+ raise ValueError(f"unknown operation: {operation!r}")
+ _send(protocol_out, response)
+ except BaseException as exc:
+ try:
+ _send(protocol_out, _error(request_id, exc))
+ except BaseException:
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/evaluator.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/evaluator.py
new file mode 100644
index 00000000..94cdecf3
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/evaluator.py
@@ -0,0 +1,221 @@
+"""Evaluator for the CompositeLaminateStacking benchmark."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from functools import lru_cache
+from pathlib import Path
+from typing import Any, Mapping
+
+import numpy as np
+
+try:
+ from .design_runtime import DesignRuntime
+ from .mechanics import (
+ evaluate_base_angles,
+ load_config,
+ normalize_base_angles,
+ public_cases,
+ )
+except ImportError: # Direct execution: python verification/evaluator.py ...
+ from design_runtime import DesignRuntime
+ from mechanics import (
+ evaluate_base_angles,
+ load_config,
+ normalize_base_angles,
+ public_cases,
+ )
+
+
+ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_CANDIDATE = ROOT / "scripts" / "init.py"
+
+
+@lru_cache(maxsize=1)
+def _anchor_rows() -> dict[str, dict[str, float]]:
+ config = load_config()
+ anchor = list(config["anchor_base_angles_deg"])
+ return {
+ str(case["case_id"]): evaluate_base_angles(anchor, case, config)
+ for case in public_cases(config)
+ }
+
+
+def _case_score(reserve_factor: float, anchor_reserve: float) -> float:
+ if reserve_factor <= 0.0 or anchor_reserve <= 0.0:
+ return 0.0
+ ratio = reserve_factor / anchor_reserve
+ return float(np.clip(50.0 + 50.0 * math.tanh(math.log(ratio) / 0.5), 0.0, 100.0))
+
+
+def _robust_score(scores: list[float]) -> float:
+ if not scores:
+ return 0.0
+ values = np.asarray(scores, dtype=float)
+ return float(0.75 * np.mean(values) + 0.25 * np.quantile(values, 0.20))
+
+
+def _schema_rows(
+ designs: Any, cases: list[dict[str, Any]], config: Mapping[str, Any]
+) -> tuple[dict[str, list[int]], dict[str, str]]:
+ if not isinstance(designs, dict):
+ return {}, {str(case["case_id"]): "candidate result must be a JSON object" for case in cases}
+ expected_ids = {str(case["case_id"]) for case in cases}
+ returned_ids = {str(key) for key in designs}
+ extra = sorted(returned_ids - expected_ids)
+ global_error = f"unexpected case ids: {extra}" if extra else None
+ normalized: dict[str, list[int]] = {}
+ errors: dict[str, str] = {}
+ for case in cases:
+ case_id = str(case["case_id"])
+ if case_id not in designs:
+ errors[case_id] = f"missing design for case {case_id}"
+ continue
+ angles, error = normalize_base_angles(designs[case_id], config)
+ if error is not None:
+ errors[case_id] = error
+ elif global_error is not None:
+ errors[case_id] = global_error
+ else:
+ assert angles is not None
+ normalized[case_id] = angles
+ return normalized, errors
+
+
+def evaluate(candidate_path: Path, *, timeout_s: float = 12.0) -> dict[str, Any]:
+ config = load_config()
+ cases = public_cases(config)
+ rows: list[dict[str, Any]] = []
+ try:
+ with DesignRuntime(
+ candidate_path,
+ startup_timeout_s=min(3.0, timeout_s),
+ call_timeout_s=max(0.05, timeout_s - min(3.0, timeout_s) + 0.25),
+ total_timeout_s=timeout_s,
+ ) as runtime:
+ designs = runtime.design_laminates(cases)
+ normalized, schema_errors = _schema_rows(designs, cases, config)
+ except Exception as exc:
+ message = f"{type(exc).__name__}: {exc}"
+ normalized = {}
+ schema_errors = {str(case["case_id"]): message for case in cases}
+
+ anchors = _anchor_rows()
+ for case in cases:
+ case_id = str(case["case_id"])
+ if case_id in schema_errors:
+ rows.append(
+ {
+ "case_id": case_id,
+ "aspect_ratio": float(case["aspect_ratio"]),
+ "load_type": "biaxial" if float(case["Ny_N_per_mm"]) < 0.0 else "uniaxial",
+ "score": 0.0,
+ "feasible": False,
+ "error": schema_errors[case_id],
+ }
+ )
+ continue
+ try:
+ candidate = evaluate_base_angles(normalized[case_id], case, config)
+ anchor = anchors[case_id]
+ score = _case_score(candidate["reserve_factor"], anchor["reserve_factor"])
+ rows.append(
+ {
+ "case_id": case_id,
+ "aspect_ratio": float(case["aspect_ratio"]),
+ "load_type": "biaxial" if float(case["Ny_N_per_mm"]) < 0.0 else "uniaxial",
+ "score": score,
+ "feasible": True,
+ "base_angles_deg": normalized[case_id],
+ "candidate": candidate,
+ "anchor": anchor,
+ }
+ )
+ except Exception as exc:
+ rows.append(
+ {
+ "case_id": case_id,
+ "aspect_ratio": float(case["aspect_ratio"]),
+ "load_type": "biaxial" if float(case["Ny_N_per_mm"]) < 0.0 else "uniaxial",
+ "score": 0.0,
+ "feasible": False,
+ "error": f"{type(exc).__name__}: {exc}",
+ }
+ )
+
+ scores = [float(row["score"]) for row in rows]
+ diagnostic_score = _robust_score(scores)
+ valid = all(bool(row["feasible"]) and "error" not in row for row in rows)
+ return {
+ "combined_score": diagnostic_score if valid else 0.0,
+ "diagnostic_score": diagnostic_score,
+ "valid": float(valid),
+ "feasible_cases": float(sum(bool(row["feasible"]) for row in rows)),
+ "rows": rows,
+ }
+
+
+def _write_json(path: str | None, payload: dict[str, Any]) -> None:
+ if not path:
+ return
+ output = Path(path).expanduser().resolve()
+ output.parent.mkdir(parents=True, exist_ok=True)
+ output.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
+
+
+def _artifacts(result: dict[str, Any], candidate_path: Path) -> dict[str, Any]:
+ try:
+ label = candidate_path.relative_to(ROOT).as_posix()
+ except ValueError:
+ label = candidate_path.name
+ return {
+ "candidate_path": label,
+ "source_doi": load_config()["source"]["doi"],
+ "rows": result["rows"],
+ }
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Evaluate balanced symmetric laminate designs")
+ parser.add_argument("candidate", nargs="?", default=str(DEFAULT_CANDIDATE))
+ parser.add_argument("--timeout-s", type=float, default=12.0)
+ parser.add_argument("--metrics-out", default=None)
+ parser.add_argument("--artifacts-out", default=None)
+ args = parser.parse_args()
+ if args.timeout_s <= 0.0:
+ parser.error("--timeout-s must be positive")
+
+ candidate_path = Path(args.candidate).expanduser().resolve()
+ result = evaluate(candidate_path, timeout_s=float(args.timeout_s))
+ print("=== Composite Laminate Stacking ===")
+ for row in result["rows"]:
+ if "error" in row:
+ print(f"case={row['case_id']} score=0.00 feasible=false error={row['error']}")
+ else:
+ candidate = row["candidate"]
+ print(
+ f"case={row['case_id']} score={row['score']:.2f} feasible=true "
+ f"buckling={candidate['buckling_load_factor']:.3f} "
+ f"failure={candidate['failure_load_factor']:.3f} "
+ f"reserve={candidate['reserve_factor']:.3f}"
+ )
+ print("---")
+ print(f"feasible_cases: {result['feasible_cases']:.0f}/{len(result['rows'])}")
+ print(f"diagnostic_score: {result['diagnostic_score']:.4f}")
+ print(f"combined_score: {result['combined_score']:.4f}")
+
+ metrics = {
+ "combined_score": float(result["combined_score"]),
+ "diagnostic_score": float(result["diagnostic_score"]),
+ "valid": float(result["valid"]),
+ "feasible_cases": float(result["feasible_cases"]),
+ "num_cases": float(len(result["rows"])),
+ }
+ _write_json(args.metrics_out, metrics)
+ _write_json(args.artifacts_out, _artifacts(result, candidate_path))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/mechanics.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/mechanics.py
new file mode 100644
index 00000000..717e27b6
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/mechanics.py
@@ -0,0 +1,266 @@
+"""Independent CLT and Ritz verifier for balanced, symmetric laminates.
+
+Units are N, mm, MPa, and dimensionless strain throughout this module.
+The implementation deliberately does not depend on the candidate process.
+"""
+
+from __future__ import annotations
+
+import json
+import math
+from functools import lru_cache
+from pathlib import Path
+from typing import Any, Mapping, Sequence
+
+import numpy as np
+
+
+ROOT = Path(__file__).resolve().parents[1]
+CONFIG_PATH = ROOT / "references" / "config.json"
+
+
+@lru_cache(maxsize=1)
+def load_config() -> dict[str, Any]:
+ return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
+
+
+def public_cases(config: Mapping[str, Any] | None = None) -> list[dict[str, Any]]:
+ cfg = load_config() if config is None else config
+ return [
+ {
+ "case_id": str(case["case_id"]),
+ "aspect_ratio": float(case["aspect_ratio"]),
+ "a_mm": float(case["a_mm"]),
+ "b_mm": float(case["b_mm"]),
+ "Nx_N_per_mm": float(case["Nx_N_per_mm"]),
+ "Ny_N_per_mm": float(case["Ny_N_per_mm"]),
+ }
+ for case in cfg["cases"]
+ ]
+
+
+def normalize_base_angles(
+ value: Any, config: Mapping[str, Any] | None = None
+) -> tuple[list[int] | None, str | None]:
+ cfg = load_config() if config is None else config
+ laminate = cfg["laminate"]
+ expected = int(laminate["design_angles"])
+ if not isinstance(value, list):
+ return None, "design must be a JSON list"
+ if len(value) != expected:
+ return None, f"design must contain exactly {expected} base angles"
+
+ lower = int(laminate["angle_min_deg"])
+ upper = int(laminate["angle_max_deg"])
+ normalized: list[int] = []
+ for index, raw in enumerate(value):
+ if isinstance(raw, bool) or not isinstance(raw, (int, float)):
+ return None, f"angle[{index}] must be a finite integer"
+ number = float(raw)
+ if not math.isfinite(number) or not number.is_integer():
+ return None, f"angle[{index}] must be a finite integer"
+ angle = int(number)
+ if angle < lower or angle > upper:
+ return None, f"angle[{index}]={angle} is outside [{lower}, {upper}]"
+ normalized.append(angle)
+ return normalized, None
+
+
+def expand_balanced_symmetric(base_angles: Sequence[int | float]) -> list[float]:
+ half: list[float] = []
+ for raw in base_angles:
+ angle = float(raw)
+ half.extend((angle, -angle))
+ return half + list(reversed(half))
+
+
+def _reduced_stiffness(material: Mapping[str, Any]) -> np.ndarray:
+ e11 = float(material["E11_MPa"])
+ e22 = float(material["E22_MPa"])
+ nu12 = float(material["nu12"])
+ g12 = float(material["G12_MPa"])
+ nu21 = nu12 * e22 / e11
+ denominator = 1.0 - nu12 * nu21
+ return np.array(
+ [
+ [e11 / denominator, nu12 * e22 / denominator, 0.0],
+ [nu12 * e22 / denominator, e22 / denominator, 0.0],
+ [0.0, 0.0, g12],
+ ],
+ dtype=float,
+ )
+
+
+def transformed_stiffness(angle_deg: float, material: Mapping[str, Any]) -> np.ndarray:
+ q = _reduced_stiffness(material)
+ q11, q12, q22, q66 = q[0, 0], q[0, 1], q[1, 1], q[2, 2]
+ theta = math.radians(float(angle_deg))
+ m = math.cos(theta)
+ n = math.sin(theta)
+ m2, n2 = m * m, n * n
+ m4, n4 = m2 * m2, n2 * n2
+ m3n, mn3 = m2 * m * n, m * n2 * n
+ qbar11 = q11 * m4 + 2.0 * (q12 + 2.0 * q66) * m2 * n2 + q22 * n4
+ qbar22 = q11 * n4 + 2.0 * (q12 + 2.0 * q66) * m2 * n2 + q22 * m4
+ qbar12 = (q11 + q22 - 4.0 * q66) * m2 * n2 + q12 * (m4 + n4)
+ qbar16 = (q11 - q12 - 2.0 * q66) * m3n - (q22 - q12 - 2.0 * q66) * mn3
+ qbar26 = (q11 - q12 - 2.0 * q66) * mn3 - (q22 - q12 - 2.0 * q66) * m3n
+ qbar66 = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * m2 * n2 + q66 * (m4 + n4)
+ return np.array(
+ [
+ [qbar11, qbar12, qbar16],
+ [qbar12, qbar22, qbar26],
+ [qbar16, qbar26, qbar66],
+ ],
+ dtype=float,
+ )
+
+
+def laminate_abd(
+ stack_deg: Sequence[float], config: Mapping[str, Any] | None = None
+) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
+ cfg = load_config() if config is None else config
+ ply_t = float(cfg["laminate"]["ply_thickness_mm"])
+ thickness = ply_t * len(stack_deg)
+ z = np.linspace(-0.5 * thickness, 0.5 * thickness, len(stack_deg) + 1)
+ a = np.zeros((3, 3), dtype=float)
+ b = np.zeros((3, 3), dtype=float)
+ d = np.zeros((3, 3), dtype=float)
+ for index, angle in enumerate(stack_deg):
+ qbar = transformed_stiffness(float(angle), cfg["material"])
+ z0, z1 = float(z[index]), float(z[index + 1])
+ a += qbar * (z1 - z0)
+ b += 0.5 * qbar * (z1 * z1 - z0 * z0)
+ d += (1.0 / 3.0) * qbar * (z1**3 - z0**3)
+ return a, b, d
+
+
+def maximum_strain_load_factor(
+ stack_deg: Sequence[float],
+ case: Mapping[str, Any],
+ config: Mapping[str, Any] | None = None,
+) -> float:
+ cfg = load_config() if config is None else config
+ a, _, _ = laminate_abd(stack_deg, cfg)
+ design_factor = float(cfg["failure"]["design_load_factor"])
+ loads = design_factor * np.array(
+ [float(case["Nx_N_per_mm"]), float(case["Ny_N_per_mm"]), 0.0],
+ dtype=float,
+ )
+ strain = np.linalg.solve(a, loads)
+ allow_1 = float(cfg["failure"]["epsilon_1_allowable"])
+ allow_2 = float(cfg["failure"]["epsilon_2_allowable"])
+ allow_12 = float(cfg["failure"]["gamma_12_allowable"])
+ factors: list[float] = []
+ for angle_deg in stack_deg:
+ theta = math.radians(float(angle_deg))
+ m, n = math.cos(theta), math.sin(theta)
+ ex, ey, gxy = (float(item) for item in strain)
+ e1 = m * m * ex + n * n * ey + m * n * gxy
+ e2 = n * n * ex + m * m * ey - m * n * gxy
+ g12 = -2.0 * m * n * ex + 2.0 * m * n * ey + (m * m - n * n) * gxy
+ for allowable, actual in ((allow_1, e1), (allow_2, e2), (allow_12, g12)):
+ if abs(actual) > 1e-15:
+ factors.append(allowable / abs(actual))
+ if not factors:
+ return float("inf")
+ return float(min(factors))
+
+
+def _ritz_matrices(
+ d: np.ndarray,
+ case: Mapping[str, Any],
+ modes_x: int,
+ modes_y: int,
+ quadrature_order: int,
+) -> tuple[np.ndarray, np.ndarray]:
+ a = float(case["a_mm"])
+ b = float(case["b_mm"])
+ nx = max(0.0, -float(case["Nx_N_per_mm"]))
+ ny = max(0.0, -float(case["Ny_N_per_mm"]))
+ if nx + ny <= 0.0:
+ raise ValueError("buckling requires at least one compressive membrane load")
+
+ nodes, weights = np.polynomial.legendre.leggauss(quadrature_order)
+ xs = 0.5 * a * (nodes + 1.0)
+ ys = 0.5 * b * (nodes + 1.0)
+ scaled_weights = 0.25 * a * b * np.outer(weights, weights)
+ basis = [(m, n) for m in range(1, modes_x + 1) for n in range(1, modes_y + 1)]
+ size = len(basis)
+ stiffness = np.zeros((size, size), dtype=float)
+ geometric = np.zeros((size, size), dtype=float)
+
+ for ix, x in enumerate(xs):
+ for iy, y in enumerate(ys):
+ curvature = np.zeros((3, size), dtype=float)
+ grad_x = np.zeros(size, dtype=float)
+ grad_y = np.zeros(size, dtype=float)
+ for column, (mode_x, mode_y) in enumerate(basis):
+ alpha = mode_x * math.pi / a
+ beta = mode_y * math.pi / b
+ sin_x, cos_x = math.sin(alpha * x), math.cos(alpha * x)
+ sin_y, cos_y = math.sin(beta * y), math.cos(beta * y)
+ curvature[:, column] = (
+ alpha * alpha * sin_x * sin_y,
+ beta * beta * sin_x * sin_y,
+ -2.0 * alpha * beta * cos_x * cos_y,
+ )
+ grad_x[column] = alpha * cos_x * sin_y
+ grad_y[column] = beta * sin_x * cos_y
+ weight = float(scaled_weights[ix, iy])
+ stiffness += weight * (curvature.T @ d @ curvature)
+ geometric += weight * (
+ nx * np.outer(grad_x, grad_x) + ny * np.outer(grad_y, grad_y)
+ )
+ return 0.5 * (stiffness + stiffness.T), 0.5 * (geometric + geometric.T)
+
+
+def buckling_load_factor(
+ stack_deg: Sequence[float],
+ case: Mapping[str, Any],
+ config: Mapping[str, Any] | None = None,
+) -> float:
+ cfg = load_config() if config is None else config
+ _, _, d = laminate_abd(stack_deg, cfg)
+ buckling = cfg["buckling"]
+ k, g = _ritz_matrices(
+ d,
+ case,
+ int(buckling["modes_x"]),
+ int(buckling["modes_y"]),
+ int(buckling["quadrature_order"]),
+ )
+ eigenvalues_g, vectors_g = np.linalg.eigh(g)
+ tolerance = max(1e-12, 1e-10 * float(np.max(eigenvalues_g)))
+ keep = eigenvalues_g > tolerance
+ if not np.any(keep):
+ raise np.linalg.LinAlgError("geometric stiffness is singular")
+ transform = vectors_g[:, keep] / np.sqrt(eigenvalues_g[keep])[None, :]
+ reduced = transform.T @ k @ transform
+ eigenvalues = np.linalg.eigvalsh(0.5 * (reduced + reduced.T))
+ positive = eigenvalues[eigenvalues > 1e-9]
+ if positive.size == 0:
+ raise np.linalg.LinAlgError("no positive buckling eigenvalue")
+ return float(positive[0])
+
+
+def evaluate_base_angles(
+ base_angles: Sequence[int | float],
+ case: Mapping[str, Any],
+ config: Mapping[str, Any] | None = None,
+) -> dict[str, float]:
+ cfg = load_config() if config is None else config
+ stack = expand_balanced_symmetric(base_angles)
+ expected = int(cfg["laminate"]["num_plies"])
+ if len(stack) != expected:
+ raise ValueError(f"expanded stack has {len(stack)} plies, expected {expected}")
+ failure = maximum_strain_load_factor(stack, case, cfg)
+ buckling = buckling_load_factor(stack, case, cfg)
+ reserve = min(failure, buckling)
+ if not all(math.isfinite(value) and value > 0.0 for value in (failure, buckling, reserve)):
+ raise ValueError("mechanics produced a non-positive or non-finite load factor")
+ return {
+ "failure_load_factor": float(failure),
+ "buckling_load_factor": float(buckling),
+ "reserve_factor": float(reserve),
+ }
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/requirements.txt b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/requirements.txt
new file mode 100644
index 00000000..9f161aca
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/requirements.txt
@@ -0,0 +1 @@
+numpy>=1.24
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_evaluator.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_evaluator.py
new file mode 100644
index 00000000..b7e1dfc1
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_evaluator.py
@@ -0,0 +1,103 @@
+from __future__ import annotations
+
+import shutil
+import tempfile
+import unittest
+from pathlib import Path
+
+try:
+ from verification.evaluator import _case_score, evaluate
+except ModuleNotFoundError:
+ from evaluator import _case_score, evaluate
+
+
+class EvaluatorTests(unittest.TestCase):
+ def _candidate(self, source: str) -> Path:
+ directory = Path(tempfile.mkdtemp(prefix="laminate_candidate_test_"))
+ path = directory / "candidate.py"
+ path.write_text(source, encoding="utf-8")
+ self.addCleanup(lambda: shutil.rmtree(directory, ignore_errors=True))
+ return path
+
+ def test_anchor_candidate_is_valid_and_scores_fifty(self) -> None:
+ candidate = self._candidate(
+ "def design_laminates(cases):\n"
+ " x=[90,45,45,90,45,90,45,45,45,45,45,45]\n"
+ " return {c['case_id']: list(x) for c in cases}\n"
+ )
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 1.0)
+ self.assertAlmostEqual(result["combined_score"], 50.0, places=8)
+ self.assertTrue(all(abs(row["score"] - 50.0) < 1e-8 for row in result["rows"]))
+
+ def test_load_aware_design_has_measurable_improvement_space(self) -> None:
+ candidate = self._candidate(
+ "def design_laminates(cases):\n"
+ " anchor=[90,45,45,90,45,90,45,45,45,45,45,45]\n"
+ " mixed=[0,45,90,30,60,45,0,30,60,90,45,15]\n"
+ " out={}\n"
+ " for c in cases:\n"
+ " if c['Ny_N_per_mm'] == 0 and c['aspect_ratio'] <= 1:\n"
+ " out[c['case_id']] = [0]*12\n"
+ " elif c['Ny_N_per_mm'] == 0:\n"
+ " out[c['case_id']] = list(mixed)\n"
+ " else:\n"
+ " out[c['case_id']] = list(anchor)\n"
+ " return out\n"
+ )
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 1.0)
+ self.assertGreater(result["combined_score"], 60.0)
+
+ def test_missing_case_is_invalid_with_actionable_feedback(self) -> None:
+ candidate = self._candidate("def design_laminates(cases): return {}\n")
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 0.0)
+ self.assertEqual(result["combined_score"], 0.0)
+ self.assertIn("missing design for case", result["rows"][0]["error"])
+
+ def test_out_of_range_angle_is_invalid(self) -> None:
+ candidate = self._candidate(
+ "def design_laminates(cases):\n"
+ " return {c['case_id']: [91]*12 for c in cases}\n"
+ )
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 0.0)
+ self.assertIn("outside [0, 90]", result["rows"][0]["error"])
+
+ def test_candidate_stdout_does_not_break_protocol(self) -> None:
+ candidate = self._candidate(
+ "print('noise during import')\n"
+ "def design_laminates(cases):\n"
+ " print('noise during call')\n"
+ " x=[90,45,45,90,45,90,45,45,45,45,45,45]\n"
+ " return {c['case_id']: x for c in cases}\n"
+ )
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 1.0)
+
+ def test_candidate_cannot_monkeypatch_parent_score(self) -> None:
+ candidate = self._candidate(
+ "import sys\n"
+ "setattr(sys.modules['__main__'], '_case_score', lambda *a: 100.0)\n"
+ "def design_laminates(cases):\n"
+ " return {c['case_id']: [90]*12 for c in cases}\n"
+ )
+ result = evaluate(candidate)
+ self.assertNotEqual(result["combined_score"], 100.0)
+
+ def test_syntax_error_returns_invalid_result(self) -> None:
+ candidate = self._candidate("def broken(:\n")
+ result = evaluate(candidate)
+ self.assertEqual(result["valid"], 0.0)
+ self.assertEqual(result["combined_score"], 0.0)
+ self.assertIn("candidate import failed", result["rows"][0]["error"])
+
+ def test_score_is_monotone_and_anchor_is_fifty(self) -> None:
+ self.assertLess(_case_score(9.0, 10.0), 50.0)
+ self.assertAlmostEqual(_case_score(10.0, 10.0), 50.0)
+ self.assertGreater(_case_score(11.0, 10.0), 50.0)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_mechanics.py b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_mechanics.py
new file mode 100644
index 00000000..2a2a0877
--- /dev/null
+++ b/benchmarks/StructuralOptimization/CompositeLaminateStacking/verification/test_mechanics.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+import unittest
+
+import numpy as np
+
+try:
+ from verification.mechanics import (
+ evaluate_base_angles,
+ expand_balanced_symmetric,
+ laminate_abd,
+ load_config,
+ normalize_base_angles,
+ public_cases,
+ )
+except ModuleNotFoundError:
+ from mechanics import (
+ evaluate_base_angles,
+ expand_balanced_symmetric,
+ laminate_abd,
+ load_config,
+ normalize_base_angles,
+ public_cases,
+ )
+
+
+class MechanicsTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.config = load_config()
+ self.anchor = list(self.config["anchor_base_angles_deg"])
+
+ def test_expansion_is_balanced_and_symmetric(self) -> None:
+ stack = expand_balanced_symmetric(self.anchor)
+ self.assertEqual(len(stack), 48)
+ self.assertEqual(stack, list(reversed(stack)))
+ for angle in set(abs(value) for value in stack):
+ if angle == 0:
+ continue
+ self.assertEqual(stack.count(angle), stack.count(-angle))
+
+ def test_symmetric_laminate_has_negligible_coupling_matrix(self) -> None:
+ stack = expand_balanced_symmetric(self.anchor)
+ _, coupling, _ = laminate_abd(stack, self.config)
+ self.assertLess(float(np.max(np.abs(coupling))), 1e-7)
+
+ def test_reference_design_produces_positive_finite_factors(self) -> None:
+ for case in public_cases(self.config):
+ result = evaluate_base_angles(self.anchor, case, self.config)
+ for value in result.values():
+ self.assertTrue(np.isfinite(value))
+ self.assertGreater(value, 0.0)
+
+ def test_square_biaxial_case_matches_upstream_reference(self) -> None:
+ case = next(
+ item for item in public_cases(self.config) if item["case_id"] == "biaxial_ar_1_0"
+ )
+ result = evaluate_base_angles(self.anchor, case, self.config)
+ # The MIT-licensed source reports lambda_cs=10394.81 and lambda_cb=9998.19.
+ # Failure uses the same closed-form maximum-strain calculation. Buckling uses
+ # an independent Ritz implementation instead of the source BFSC finite element.
+ self.assertAlmostEqual(result["failure_load_factor"] / 10394.81, 1.0, delta=0.001)
+ self.assertAlmostEqual(result["buckling_load_factor"] / 9998.19, 1.0, delta=0.05)
+
+ def test_angle_schema_reports_specific_error(self) -> None:
+ angles, error = normalize_base_angles([45] * 11 + [90.5], self.config)
+ self.assertIsNone(angles)
+ self.assertEqual(error, "angle[11] must be a finite integer")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/benchmarks/StructuralOptimization/README.md b/benchmarks/StructuralOptimization/README.md
index 30215494..1684fba6 100644
--- a/benchmarks/StructuralOptimization/README.md
+++ b/benchmarks/StructuralOptimization/README.md
@@ -1,6 +1,6 @@
# Structural Optimization
-This domain covers structural engineering optimization problems derived from the **International Student Competition in Structural Optimization (ISCSO)**, organized by [Bright Optimizer](http://www.brightoptimizer.com/).
+This domain covers structural engineering optimization problems, including tasks derived from the **International Student Competition in Structural Optimization (ISCSO)**, organized by [Bright Optimizer](http://www.brightoptimizer.com/), and published laminate and topology benchmarks.
Structural optimization is a core discipline in civil, aerospace, and mechanical engineering, aiming to find the optimal design of load-bearing structures that minimizes material usage (weight) while satisfying safety constraints (stress and displacement limits).
@@ -12,17 +12,18 @@ Structural optimization is a core discipline in civil, aerospace, and mechanical
| `ISCSO2023` | 284-member 3D truss sizing optimization | 284 | Continuous, constrained, multi-load-case |
| `TopologyOptimization` | MBB beam 2D topology optimization (SIMP) | 1200 | Continuous, volume-constrained, compliance minimization |
| `PyMOTOSIMPCompliance` | pyMOTO-style SIMP compliance minimization for 2D beam topology design | 4800 | Continuous, volume-constrained, compliance minimization |
+| `CompositeLaminateStacking` | Balanced, symmetric 48-ply laminate design across plate geometries and load ratios | 120 | Discrete, multi-case, buckling + failure constrained |
## Why These Problems Are Suitable for Frontier-Engineering
-| Feature | ISCSO 2015 | ISCSO 2023 |
-| :--- | :--- | :--- |
-| High-dimensional continuous variables | Medium (54-D) | High (284-D) |
-| Real physical model (FEM) | Yes | Yes |
-| Deterministic evaluation | Yes | Yes |
-| Multi-load-case constraints | Yes (2 cases) | Yes (3 cases) |
-| Non-convex feasible region | Yes | Yes |
-| Industrial relevance | Yes | Yes |
+| Feature | ISCSO 2015 | ISCSO 2023 | Composite laminate |
+| :--- | :--- | :--- | :--- |
+| High-dimensional design variables | Medium (54-D) | High (284-D) | High (120 integer variables) |
+| Real physical model | FEM | FEM | Classical laminate theory + Ritz buckling |
+| Deterministic evaluation | Yes | Yes | Yes |
+| Multi-load-case constraints | Yes (2 cases) | Yes (3 cases) | Yes (10 cases) |
+| Non-convex feasible region | Yes | Yes | Yes |
+| Industrial relevance | Yes | Yes | Yes |
These benchmarks serve as:
diff --git a/benchmarks/StructuralOptimization/README_zh-CN.md b/benchmarks/StructuralOptimization/README_zh-CN.md
index a6615171..0cd1af89 100644
--- a/benchmarks/StructuralOptimization/README_zh-CN.md
+++ b/benchmarks/StructuralOptimization/README_zh-CN.md
@@ -1,6 +1,6 @@
# 结构优化
-本领域涵盖源自**国际结构优化学生竞赛(ISCSO)**的结构工程优化问题,竞赛由 [Bright Optimizer](http://www.brightoptimizer.com/) 组织。
+本领域涵盖结构工程优化问题,包括由 [Bright Optimizer](http://www.brightoptimizer.com/) 组织的**国际结构优化学生竞赛(ISCSO)**衍生任务,以及公开发表的复合材料铺层与拓扑优化 benchmark。
结构优化是土木、航空航天和机械工程中的核心学科,目标是在满足安全约束(应力和位移限值)的条件下,找到使材料用量(重量)最小的承载结构最优设计。
@@ -12,17 +12,18 @@
| `ISCSO2023` | 284 杆 3D 桁架尺寸优化 | 284 | 连续、有约束、多工况 |
| `TopologyOptimization` | MBB 束流二维拓扑优化 (SIMP) | 1200 | 连续、体积约束的合规最小化 |
| `PyMOTOSIMPCompliance` | 基于 pyMOTO 范式的二维梁 SIMP 柔度最小化拓扑设计 | 4800 | 连续、体积约束的合规最小化 |
+| `CompositeLaminateStacking` | 跨板几何与载荷比例的平衡、对称 48 层铺层设计 | 120 | 离散、多工况、屈曲与失效约束 |
## 为何适合 Frontier-Engineering
-| 特性 | ISCSO 2015 | ISCSO 2023 |
-| :--- | :--- | :--- |
-| 高维连续变量 | 中等 (54-D) | 高 (284-D) |
-| 真实物理模型 (FEM) | 是 | 是 |
-| 确定性评估 | 是 | 是 |
-| 多工况约束 | 是 (2 工况) | 是 (3 工况) |
-| 非凸可行域 | 是 | 是 |
-| 工业相关性 | 是 | 是 |
+| 特性 | ISCSO 2015 | ISCSO 2023 | 复合材料铺层 |
+| :--- | :--- | :--- | :--- |
+| 高维设计变量 | 中等 (54-D) | 高 (284-D) | 高(120 个整数变量) |
+| 真实物理模型 | FEM | FEM | 经典层合板理论 + Ritz 屈曲分析 |
+| 确定性评估 | 是 | 是 | 是 |
+| 多工况约束 | 是 (2 工况) | 是 (3 工况) | 是(10 工况) |
+| 非凸可行域 | 是 | 是 | 是 |
+| 工业相关性 | 是 | 是 | 是 |
这些 benchmark 可作为:
diff --git a/frontier_eval/conf/task/composite_laminate_stacking.yaml b/frontier_eval/conf/task/composite_laminate_stacking.yaml
new file mode 100644
index 00000000..325e5504
--- /dev/null
+++ b/frontier_eval/conf/task/composite_laminate_stacking.yaml
@@ -0,0 +1,6 @@
+name: unified
+benchmark: StructuralOptimization/CompositeLaminateStacking
+
+runtime:
+ env:
+ PYTHONUTF8: "1"