Skip to content

Commit 08aee45

Browse files
committed
Merge branch 'develop'
2 parents d9d0b4a + ab3beb5 commit 08aee45

19 files changed

Lines changed: 102 additions & 41 deletions

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ name: "CodeQL"
1313

1414
on:
1515
push:
16-
branches: [ "master", "development" ]
16+
branches: [ "master", "development", "develop" ]
1717
pull_request:
18-
branches: [ "master" , "development" ]
18+
branches: [ "master" , "development", "develop" ]
1919
schedule:
2020
- cron: '30 13 * * 1'
2121

.github/workflows/ruff.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: "Ruff CI"
22

33
on:
44
push:
5-
branches: [ "master", "development" ]
5+
branches: [ "master", "development", "develop" ]
66
pull_request:
7-
branches: [ "master" , "development" ]
7+
branches: [ "master" , "development", "develop" ]
88

99
jobs:
1010
linter:

.github/workflows/ty.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: "ty CI"
22

33
on:
44
push:
5-
branches: [ "master", "development" ]
5+
branches: [ "master", "development", "develop" ]
66
pull_request:
7-
branches: [ "master" , "development" ]
7+
branches: [ "master" , "development", "develop" ]
88

99
jobs:
1010
type_checker:

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
cff-version: 1.2.0
55
title: SimOpt
6-
version: 1.2.1
6+
version: 1.2.2
77
message: >-
88
If you use this software, please cite it using the
99
metadata from this file

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
project = "SimOpt"
2828
copyright = "2025, simopt-admin" # noqa: A001
2929
author = "simopt-admin"
30-
release = "1.2.1"
30+
release = "1.2.2"
3131

3232
# -- General configuration ---------------------------------------------------
3333

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ exclude = ["build*"]
1111
[tool.setuptools.package-data]
1212
"simopt.data_farming" = ["*.npz"]
1313

14+
[tool.uv.sources]
15+
simopt-extensions = { git = "https://github.com/cenwangumass/simopt-extensions", rev = "2bcf002" }
16+
1417
[project]
1518
name = "simoptlib"
16-
version = "1.2.1"
19+
version = "1.2.2"
1720
authors = [
1821
{ name = "David Eckman", email = "eckman@tamu.edu" },
1922
{ name = "Shane Henderson", email = "sgh9@cornell.edu" },
@@ -53,6 +56,9 @@ dev = [
5356
]
5457
docs = ["sphinx>=8.2.3", "sphinx-autoapi>=3.6.1", "sphinx-rtd-theme>=3.0.2"]
5558
notebooks = ["ipykernel>=7.1.0"]
59+
ext = [
60+
"simopt-extensions",
61+
]
5662

5763
[project.urls]
5864
"Homepage" = "https://github.com/simopt-admin/simopt"

simopt/input_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import bisect
44
import itertools
55
import math
6-
import random
76
from abc import abstractmethod
87
from collections.abc import Sequence
8+
from random import Random
99
from typing import ParamSpec, Protocol, TypeVar
1010

1111
P = ParamSpec("P")
@@ -15,9 +15,9 @@
1515
class InputModel(Protocol[P, R]):
1616
"""Abstract base for input models used by simulations."""
1717

18-
rng: random.Random | None = None
18+
rng: Random | None = None
1919

20-
def set_rng(self, rng: random.Random) -> None:
20+
def set_rng(self, rng: Random) -> None:
2121
"""Attach a Python RNG to the input model.
2222
2323
Args:

simopt/models/_ext.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import importlib
2+
import os
3+
from collections.abc import Callable
4+
from types import ModuleType
5+
6+
from simopt.model import Model
7+
8+
9+
def find_model_patches(module: ModuleType) -> list[Callable]:
10+
patches: list[Callable] = []
11+
for attr in dir(module):
12+
if not attr.startswith("patch_model"):
13+
continue
14+
candidate = getattr(module, attr)
15+
if callable(candidate):
16+
patches.append(candidate)
17+
return patches
18+
19+
20+
def patch(model_class: type[Model], patch_function: Callable) -> None:
21+
# Ask the extension what class to patch and with what method
22+
class_name, method = patch_function()
23+
# Patch the method into the model_class if it matches
24+
full_name = model_class.__module__ + "." + model_class.__qualname__
25+
if full_name == class_name:
26+
model_class.replicate = method
27+
28+
29+
def load_module(module_name: str, model_class: type[Model]) -> None:
30+
# Import the specified library
31+
try:
32+
module = importlib.import_module(module_name)
33+
except ImportError as e:
34+
raise ImportError(f"SimOpt failed to load extension '{module_name}'") from e
35+
36+
# Find all patch_model* functions in the library
37+
patches = find_model_patches(module)
38+
if not patches:
39+
raise ImportError(f"'{module_name}' does not have any 'patch_model*' functions")
40+
41+
# Apply each patch to the model_class
42+
for p in patches:
43+
patch(model_class, p)
44+
45+
46+
def patch_model(model_class: type[Model]) -> None:
47+
env_var = os.environ.get("SIMOPT_EXT")
48+
if not env_var:
49+
return
50+
51+
# Assume that the user has specified a comma-separated list of libraries to import.
52+
# For example, SIMOPT_EXT="simopt_extension_a,simopt_extension_b"
53+
for part in env_var.split(","):
54+
module_name = part.strip()
55+
if module_name:
56+
load_module(module_name, model_class)

simopt/models/amusementpark.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
VariableType,
1818
)
1919
from simopt.input_models import Exp, Gamma, WeightedChoice
20+
from simopt.models._ext import patch_model
2021

2122
INF = float("inf")
2223

@@ -478,9 +479,10 @@ def set_completion(i: int, new_time: float) -> None:
478479
cumulative_util = [cumulative_util[i] / time_open for i in attraction_range]
479480

480481
# Calculate responses from simulation data.
482+
percent_departed = total_departed / total_visitors if total_visitors else 0
481483
responses = {
482484
"total_departed": total_departed,
483-
"percent_departed": total_departed / total_visitors,
485+
"percent_departed": percent_departed,
484486
"average_number_in_system": time_average / time_open,
485487
"attraction_utilization_percentages": cumulative_util,
486488
}
@@ -543,3 +545,6 @@ def get_random_solution(self, rand_sol_rng: MRG32k3a) -> tuple: # noqa: D102
543545
n_elements=num_elements, summation=summation, with_zero=False
544546
)
545547
return tuple(vector)
548+
549+
550+
patch_model(AmusementPark)

simopt/models/chessmm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
import random
5+
from random import Random
66
from typing import Annotated, ClassVar, Final
77

88
import numpy as np
@@ -111,7 +111,7 @@ class ChessAvgDifferenceConfig(BaseModel):
111111
class EloInputModel(InputModel):
112112
"""Input model for player Elo ratings."""
113113

114-
rng: random.Random | None = None
114+
rng: Random | None = None
115115

116116
def random(
117117
self, mean: float, std: float, min_rating: float, max_rating: float

0 commit comments

Comments
 (0)