Skip to content

Commit 7464009

Browse files
committed
fix examples
1 parent fac825f commit 7464009

7 files changed

Lines changed: 83 additions & 28 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ats.set_initial_states([ats.num_states - 1])
2424
umbi.ats.write(ats, "out.umb")
2525
```
2626

27-
More examples can be found in the [./examples](./examples) folder.
27+
More examples can be found in the [examples](./examples) folder.
2828

2929
## API
3030

@@ -34,7 +34,7 @@ More examples can be found in the [./examples](./examples) folder.
3434

3535
**[`ExplicitUmb`](umbi/umb/explicit_umb.py)** - in-memory representation of a typical umbfile. Attributes are standard Python objects (lists, dicts, dataclasses) providing a deserialized view of the file contents.
3636

37-
**[`ExplicitAts`](umbi/ats/explicit_ats.py)** - format-agnostic abstraction for annotated transition systems (states, transitions, annotations). Recommended for most use cases: easiest to use programmatically and remains stable across UMB format changes.
37+
**[`ExplicitAts`](umbi/ats/explicit_ats.py)** - format-agnostic abstraction for annotated transition systems (states, transitions, annotations). Recommended for most use cases: easiest to use programmatically and remains stable across UMB format changes. See [umbi.ats.examples](./umbi/ats/examples/) for usage examples.
3838

3939
## CLI
4040

umbi/ats/examples/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""ATS model examples."""
22

3-
from .grid import grid_ats
4-
from .random_game import random_game_ats
5-
from .random_walk import random_walk_ats
3+
from .grid import grid, random_grid_string
4+
from .random_game import random_game
5+
from .random_walk import random_walk
66

77
__all__ = [
8-
"grid_ats",
9-
"random_game_ats",
10-
"random_walk_ats",
8+
"random_grid_string",
9+
"grid",
10+
"random_game",
11+
"random_walk",
1112
]

umbi/ats/examples/grid.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""umbi demo: Create an ExplicitAts from a grid string."""
22

33
import logging
4+
import random
45
from fractions import Fraction
56

67
from ..explicit_ats import ExplicitAts, TimeType
@@ -9,7 +10,48 @@
910
logger = logging.getLogger(__name__)
1011

1112

12-
def grid_ats(grid: str) -> ExplicitAts:
13+
def random_grid_string(width: int, height: int, seed: int | None = None) -> str:
14+
"""
15+
Generate a random grid string of size width x height.
16+
17+
:param width: the width of the grid
18+
:param height: the height of the grid
19+
:param seed: optional seed for reproducible randomness
20+
:return: A grid string with:
21+
- exactly 1 'i' (initial state)
22+
- random number of 'g' (goal states)
23+
- random number of 'x' (obstacles)
24+
- rest are '.' (empty cells)
25+
"""
26+
if seed is not None:
27+
random.seed(seed)
28+
29+
total_cells = width * height
30+
cells = ["."] * total_cells
31+
32+
# Place exactly one 'i'
33+
i_idx = random.randint(0, total_cells - 1)
34+
cells[i_idx] = "i"
35+
36+
# Random number of 'g's (at least 1, up to ~5% of remaining cells)
37+
num_goals = random.randint(1, max(1, total_cells // 20))
38+
goal_positions = random.sample([i for i in range(total_cells) if i != i_idx], min(num_goals, total_cells - 1))
39+
for pos in goal_positions:
40+
cells[pos] = "g"
41+
42+
# Random number of 'x's from remaining cells (20 to ~50% of remaining cells)
43+
remaining_positions = [i for i in range(total_cells) if cells[i] == "."]
44+
num_obstacles = random.randint(max(1, len(remaining_positions) // 5), max(1, len(remaining_positions) // 2))
45+
obstacle_positions = random.sample(remaining_positions, min(num_obstacles, len(remaining_positions)))
46+
for pos in obstacle_positions:
47+
cells[pos] = "x"
48+
49+
# Convert to grid string (width x height, with newlines)
50+
grid_rows = ["".join(cells[i * width : (i + 1) * width]) for i in range(height)]
51+
return "\n".join(reversed(grid_rows)) # reverse for top-to-bottom display
52+
53+
54+
def grid(grid: str) -> ExplicitAts:
1355
"""
1456
Create a simple ATS from a rectangular grid string.
1557
@@ -52,6 +94,8 @@ def grid_ats(grid: str) -> ExplicitAts:
5294
elif c == "g":
5395
goal_states.add(state)
5496

97+
num_states = len(cell_to_state)
98+
ats.add_states(num_states)
5599
ats.set_initial_states(list(initial_states))
56100

57101
if ats.variable_valuations is None:
@@ -76,7 +120,6 @@ def grid_ats(grid: str) -> ExplicitAts:
76120
ats.num_choice_actions = len(ats.choice_action_to_name)
77121

78122
for (x, y), state in cell_to_state.items():
79-
ats.add_state()
80123
for direction, (dx, dy) in direction_dxdy.items():
81124
target = (x + dx, y + dy)
82125
choice = ats.add_state_choice(state=state)
@@ -90,10 +133,10 @@ def grid_ats(grid: str) -> ExplicitAts:
90133
choice.add_branch(target=state)
91134

92135
annotation = ats.add_ap_annotation(name="goal")
93-
annotation.set_state_values([s in goal_states for s in range(ats.num_states)])
136+
annotation.set_state_values([s in goal_states for s in ats.states])
94137

95138
annotation = ats.add_reward_annotation(name="step_cost")
96-
annotation.set_choice_values([1 for _ in range(ats.num_choices)])
139+
annotation.set_choice_values([1 for _ in ats.choices])
97140

98141
ats.validate()
99142
return ats

umbi/ats/examples/random_game.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def random_transition_function(
173173
return delta, actions_at_state, all_actions
174174

175175

176-
def random_game_ats(num_states: int, seed: int | None = None) -> ExplicitAts:
176+
def random_game(num_states: int, seed: int | None = None) -> ExplicitAts:
177177
"""
178178
Generate a random stochastic game connected from initial state.
179179
@@ -196,9 +196,6 @@ def random_game_ats(num_states: int, seed: int | None = None) -> ExplicitAts:
196196
# set player information
197197
ats.state_to_player = [0 if is_player0 else 1 for is_player0 in is_player0_state]
198198

199-
# set initial state to the state with index 0
200-
ats.set_initial_states([0])
201-
202199
# build the ATS structure from delta and actions_at_state
203200
ats.num_choice_actions = len(all_actions)
204201
ats.choice_action_to_name = all_actions
@@ -207,10 +204,13 @@ def random_game_ats(num_states: int, seed: int | None = None) -> ExplicitAts:
207204
logger.info("building CSR structures ...")
208205
build_start = time.time()
209206

210-
for s in range(num_states):
211-
state = ats.add_state()
207+
ats.add_states(num_states)
208+
# set initial state to the state with index 0
209+
ats.set_initial_states([0])
210+
211+
for s in ats.states:
212212
for action in sorted(actions_at_state[s]):
213-
choice = ats.add_state_choice(state=state)
213+
choice = ats.add_state_choice(state=s)
214214
choice.action = action
215215

216216
# get all branches for this (s, action) pair

umbi/ats/examples/random_walk.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,39 @@
88
logger = logging.getLogger(__name__)
99

1010

11-
def random_walk_ats(num_states: int) -> ExplicitAts:
11+
def random_walk(num_states: int) -> ExplicitAts:
1212
# create ATS
1313
ats = ExplicitAts()
1414

1515
# basic parameters
1616
ats.time = TimeType.DISCRETE
1717
ats.num_players = 0
18-
ats.num_initial_states = 1
19-
ats.set_initial_states([num_states // 2]) # start in the middle state
2018

2119
# two actions: left (0) and right (1), each with 0.9 success prob
2220
ats.num_choice_actions = 2
2321
ats.choice_action_to_name = ["left", "right"]
2422

23+
ats.add_states(num_states)
24+
ats.set_initial_states([num_states // 2]) # start in the middle state
25+
ats.state_is_markovian = [True] * ats.num_states
26+
ats.state_to_exit_rate = [1] * ats.num_states # type: ignore[assignment]
27+
2528
# build structure
2629
for state in range(num_states):
27-
ats.add_state()
28-
2930
# left action
3031
choice = ats.add_state_choice(state=state)
32+
choice.action = 0
3133
left = max(0, state - 1)
3234
choice.add_branch(target=left, prob=Fraction(9, 10))
3335
choice.add_branch(target=state, prob=Fraction(1, 10))
3436

3537
# right action
3638
choice = ats.add_state_choice(state=state)
39+
choice.action = 1
3740
right = min(num_states - 1, state + 1)
3841
choice.add_branch(target=right, prob=Fraction(9, 10))
3942
choice.add_branch(target=state, prob=Fraction(1, 10))
4043

41-
ats.state_is_markovian = [True] * ats.num_states
42-
ats.state_to_exit_rate = [1] * ats.num_states # type: ignore[assignment]
43-
4444
# example: APs
4545
annotation = ats.add_ap_annotation(
4646
name="is_terminal", alias="Terminal State", description="Indicates whether the state is terminal."

umbi/ats/explicit_ats.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ def add_state(self) -> int:
230230
self._state_choices.append([])
231231
return new_state
232232

233+
def add_states(self, num: int) -> list[int]:
234+
"""Add the given number of new states and return their indices."""
235+
new_states = []
236+
for _ in range(num):
237+
new_state = self.add_state()
238+
new_states.append(new_state)
239+
return new_states
240+
233241
def remove_state(self, state: int):
234242
"""Remove the given state."""
235243
self._check_state(state)
@@ -505,7 +513,7 @@ def validate(self):
505513
}[entity_class]
506514
if not valuations.num_entities == num_entities:
507515
raise ValueError(
508-
f"{entity_class} valuations has {valuations.num_entities} entities, expected {num_entities}"
516+
f"{entity_class.value} valuations has {valuations.num_entities} {entity_class.value}, expected {num_entities}"
509517
)
510518
valuations.validate()
511519

umbi/ats/variable_valuations.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ def observation_valuations(self) -> EntityValuations:
382382
def player_valuations(self) -> EntityValuations:
383383
return self.get_valuations_for(EntityClass.PLAYERS)
384384

385+
def add_state_valuation(self):
386+
return self.get_valuations_for(EntityClass.STATES)
387+
385388
def set_state_valuations(self, values: EntityValuations):
386389
self.set_valuations_for(EntityClass.STATES, values)
387390

0 commit comments

Comments
 (0)