Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- **`nsga_walking_optimization` and `topology_walking_optimization` no
longer print `RuntimeWarning: invalid value encountered in subtract`.** A
walker that fails scores `+inf` on purpose, and pymoo's crowding distance
then subtracts one infinity from another. Both now run pymoo under
`numpy.errstate(invalid="ignore")` (the single-worker multi-objective path
of the former goes through `pylinkage.optimization`, which has the matching
fix). Such a warning names the file that
raised it, which is how the private path of the machine that executed
`examples/05_topology_co_optimization.ipynb` ended up committed with its
outputs; the notebook is re-executed (against pylinkage's matching fix, as
it calls `pylinkage.optimization.co_optimize`), and a test now refuses a
notebook whose outputs name a home directory.

## [0.6.1] - 2026-09-12

Requires **pylinkage >= 1.2.1**.
Expand Down
122,473 changes: 75,923 additions & 46,550 deletions examples/05_topology_co_optimization.ipynb

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions src/leggedsnake/nsga_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,14 +435,17 @@ def _run_via_parallel_problem(
else:
algo = NSGA2(pop_size=cfg.pop_size)

# A walker that fails scores +inf by design, and pymoo's crowding
# distance then computes inf - inf: expected, not a bug.
try:
res = pymoo_minimize(
problem.problem,
algo,
("n_gen", cfg.n_generations),
seed=cfg.seed,
verbose=cfg.verbose,
)
with np.errstate(invalid="ignore"):
res = pymoo_minimize(
problem.problem,
algo,
("n_gen", cfg.n_generations),
seed=cfg.seed,
verbose=cfg.verbose,
)
finally:
problem.close()

Expand Down
18 changes: 10 additions & 8 deletions src/leggedsnake/topology_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,14 +1083,16 @@ def topology_walking_optimization(
else:
algo = NSGA2(pop_size=cfg.pop_size)

# Run optimization
res = pymoo_minimize(
problem.problem,
algo,
("n_gen", cfg.n_generations),
seed=cfg.seed,
verbose=cfg.verbose,
)
# Run optimization. A candidate that fails scores +inf by design, and
# pymoo's crowding distance then computes inf - inf: expected, not a bug.
with np.errstate(invalid="ignore"):
res = pymoo_minimize(
problem.problem,
algo,
("n_gen", cfg.n_generations),
seed=cfg.seed,
verbose=cfg.verbose,
)

# Package results
nsga_cfg = NsgaWalkingConfig(
Expand Down
44 changes: 44 additions & 0 deletions tests/test_notebook_outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""The committed example notebooks must not carry outputs from a private machine.

A warning printed while a notebook was executed embeds the path of the file
that raised it, and a path under a home directory then ships with the
notebook to GitHub and the rendered docs.
"""

import json
import re
from pathlib import Path

import pytest

NOTEBOOKS = sorted((Path(__file__).resolve().parents[1] / "examples").glob("*.ipynb"))

# A home directory on Linux, macOS or Windows.
LOCAL_PATH = re.compile(r"(/home/|/Users/|[A-Za-z]:\\+Users\\)")


def _output_texts(cell: dict) -> list[str]:
texts = []
for output in cell.get("outputs", ()):
if "text" in output:
texts.append("".join(output["text"]))
for mime, payload in output.get("data", {}).items():
if mime.startswith("text/"):
texts.append("".join(payload))
if output.get("output_type") == "error":
texts.append("\n".join(output.get("traceback", ())))
return texts


@pytest.mark.parametrize("notebook", NOTEBOOKS, ids=lambda path: path.name)
def test_outputs_do_not_name_a_local_path(notebook: Path) -> None:
cells = json.loads(notebook.read_text(encoding="utf-8"))["cells"]
leaks = [
f"cell {index}: {line}"
for index, cell in enumerate(cells)
if cell["cell_type"] == "code"
for text in _output_texts(cell)
for line in text.splitlines()
if LOCAL_PATH.search(line)
]
assert not leaks, "\n".join(leaks)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading