Skip to content

Commit 63e20be

Browse files
authored
Merge pull request #3 from sheim/refactor
Refactor
2 parents 721c3b8 + e1822f3 commit 63e20be

28 files changed

+1163
-706
lines changed

.github/workflows/unit-tests.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
tests:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Check out repository
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.11"
21+
cache: pip
22+
cache-dependency-path: |
23+
pyproject.toml
24+
requirements.txt
25+
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
python -m pip install .
30+
python -m pip install pytest
31+
32+
- name: Run unit tests
33+
run: pytest tests/unit

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ A dynamical system is in a _viable_ state if there exist control inputs that all
1515
## Installation
1616
We recommend using a virtual environment. Note, `GPy` is only required for the safe learning examples, and can be safely removed from the requirements. Install from your terminal with
1717

18-
conda (recommended):
18+
[uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
19+
`uv venv .venv`
20+
`source .venv/bin/activate`
21+
`uv pip install -r requirements.txt`
22+
`uv pip install -e .`
23+
24+
conda:
1925
`conda create -n vibly python=3.9`
2026
`conda activate vibly`
2127
then follow the same instructions as for pip:
@@ -45,7 +51,7 @@ from measure import active_sampling
4551
Examples are shown in the `demos` folder. We recommend starting with `slip_demo.py`, and then `computeQ_slip.py`.
4652
The `viability` package contains:
4753
- `compute_Q_map`: a utility to compute a gridded transition map for N-dimensional systems. Note, this can be computationally intensives (it is essentially brute-forcing an N-dimensional problem). It typically works reasonably well for up to ~4 dimensions.
48-
- `parcompute_Q_map`: same as above, but parallelized. You typically want to use this, unless running a debugger.
54+
- `compute_Q_map(..., parallel=True)`: parallelised version via multiprocessing; typically preferable unless debugging.
4955
- `compute_QV`: computes the viability kernel and viable set to within conservative discrete approximation, using the grid generated by `compute_Q_map`.
5056
- `get_feasibility_mask`: this can be used to exclude parts of the grid which are infeasible (i.e. are not physically meaningful)
5157
- `project_Q2S`: Apply an operator (default is an orthogonal projection) from state-action space to state space. Used to compute measures.

demos/TAC11/computeQ_satellite.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,18 @@
3535
grids = {"states": s_grid, "actions": a_grid}
3636

3737
tictoc.tic()
38-
Q_map, Q_F, Q_coords = vibly.parcompute_Q_mapC(
39-
grids, p_map, verbose=1, check_grid=False, keep_coords=True
38+
result = vibly.compute_Q_map(
39+
grids,
40+
p_map,
41+
verbose=1,
42+
check_grid=False,
43+
keep_coords=True,
44+
parallel=True,
45+
bin_mode="nearest",
4046
)
47+
Q_map = result.q_map
48+
Q_F = result.q_fail
49+
Q_coords = result.q_reached
4150
# Q_map, Q_F, Q_on_grid = vibly.compute_Q_map(grids, p_map, check_grid=True)
4251

4352
# * compute_QV computes the viable set and viability kernel

demos/computeQ_daslip.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@
7171
a_grid = (np.linspace(20 / 180 * np.pi, 60 / 180 * np.pi, 25),)
7272

7373
grids = {"states": s_grid, "actions": a_grid}
74-
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map, verbose=2)
74+
result = vibly.compute_Q_map(grids, p_map, verbose=2, parallel=True)
75+
Q_map = result.q_map
76+
Q_F = result.q_fail
7577
Q_V, S_V = vibly.compute_QV(Q_map, grids)
7678
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
7779
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid=s_grid, Q_V=Q_V)

demos/computeQ_hovership.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@
3939

4040
# * compute_Q_map computes a gridded transition map, `Q_map`, which is used
4141
# * a look-up table for computing viable sets.
42-
# * Switch to `parcompute_Q_map` to use parallelized version
43-
# * (requires multiprocessing module)
42+
# * Enable `parallel=True` to use the multiprocessing version
4443
# * Q_F is a grid marking all failing state-action pairs
4544
# * Q_on_grid is a helper grid, which marks if a state has not moved at all
4645
# * this is used to catch corner cases, and is not important for most
4746
# * systems with interesting dynamics
4847
# * setting `check_grid` to False will omit Q_on_grid
49-
Q_map, Q_F, Q_on_grid = vibly.parcompute_Q_map(grids, p_map, check_grid=True)
50-
# Q_map, Q_F, Q_on_grid = vibly.compute_Q_map(grids, p_map, check_grid=True)
48+
result = vibly.compute_Q_map(grids, p_map, check_grid=True, parallel=True)
49+
Q_map = result.q_map
50+
Q_F = result.q_fail
51+
Q_on_grid = result.q_on_grid
5152

5253
# * compute_QV computes the viable set and viability kernel
5354
Q_V, S_V = vibly.compute_QV(Q_map, grids, ~Q_F, Q_on_grid=Q_on_grid)

demos/computeQ_lip.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
grids = {"states": s_grid, "actions": a_grid}
4646

4747
tictoc.tic()
48-
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map, verbose=1)
48+
result = vibly.compute_Q_map(grids, p_map, verbose=1, parallel=True)
49+
Q_map = result.q_map
50+
Q_F = result.q_fail
4951
time_elapsed = tictoc.toc()
5052
print("time elapsed: " + str(time_elapsed / 60.0))
5153
# * compute_QV computes the viable set and viability kernel

demos/computeQ_nslip.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from models import nslip
4+
import viability as vibly
5+
6+
7+
if __name__ == "__main__":
8+
p = {
9+
"mass": 80.0,
10+
"stiffness": 705.0,
11+
"resting_angle": 17 / 18 * np.pi,
12+
"gravity": 9.81,
13+
"angle_of_attack": 1 / 5 * np.pi,
14+
"upper_leg": 0.5,
15+
"lower_leg": 0.5,
16+
}
17+
18+
x0 = np.array([0.0, 0.85, 5.5, 0.0, 0.0, 0.0, 0.0])
19+
x0 = nslip.reset_leg(x0, p)
20+
p["x0"] = x0
21+
p["total_energy"] = nslip.compute_total_energy(x0, p)
22+
23+
p_map = nslip.p_map
24+
p_map.p = p
25+
p_map.x = x0
26+
p_map.sa2xp = nslip.sa2xp
27+
p_map.xp2s = nslip.xp2s
28+
29+
s_grid = np.linspace(0.1, 1.0, 61)
30+
s_grid = (s_grid[:-1],)
31+
a_grid = (np.linspace(-10 / 180 * np.pi, 70 / 180 * np.pi, 61),)
32+
grids = {"states": s_grid, "actions": a_grid}
33+
34+
result = vibly.compute_Q_map(grids, p_map, parallel=True)
35+
Q_map = result.q_map
36+
Q_F = result.q_fail
37+
Q_V, S_V = vibly.compute_QV(Q_map, grids)
38+
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
39+
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid, Q_V=Q_V)
40+
41+
import pickle
42+
import os
43+
44+
filename = "nslip_map.pickle"
45+
46+
if os.path.exists("data"):
47+
path_to_file = "data/dynamics/"
48+
else:
49+
path_to_file = "../data/dynamics/"
50+
if not os.path.exists(path_to_file):
51+
os.makedirs(path_to_file)
52+
53+
data2save = {
54+
"grids": grids,
55+
"Q_map": Q_map,
56+
"Q_F": Q_F,
57+
"Q_V": Q_V,
58+
"Q_M": Q_M,
59+
"S_M": S_M,
60+
"p": p,
61+
"x0": x0,
62+
}
63+
with open(path_to_file + filename, "wb") as outfile:
64+
pickle.dump(data2save, outfile)
65+
66+
plt.imshow(Q_map, origin="lower")
67+
plt.show()

demos/computeQ_slip.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
a_grid = (np.linspace(-10 / 180 * np.pi, 70 / 180 * np.pi, 161),)
2929
grids = {"states": s_grid, "actions": a_grid}
3030
# Q_map, Q_F = vibly.compute_Q_map(grids, p_map)
31-
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map)
31+
result = vibly.compute_Q_map(grids, p_map, parallel=True)
32+
Q_map = result.q_map
33+
Q_F = result.q_fail
3234
Q_V, S_V = vibly.compute_QV(Q_map, grids)
3335
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
3436
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid, Q_V=Q_V)

demos/computeQ_spaceship4.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,16 @@
4848
# * Q_on_grid is a helper grid, which marks if a state has not moved
4949
# * this is used to catch corner cases, and is not important for most systems
5050
# * setting `check_grid` to False will omit Q_on_grid
51-
Q_map, Q_F, Q_on_grid = vibly.parcompute_Q_map(
52-
grids, p_map, check_grid=True, verbose=1
51+
result = vibly.compute_Q_map(
52+
grids,
53+
p_map,
54+
check_grid=True,
55+
verbose=1,
56+
parallel=True,
5357
)
58+
Q_map = result.q_map
59+
Q_F = result.q_fail
60+
Q_on_grid = result.q_on_grid
5461
# * compute_QV computes the viable set and viability kernel
5562
Q_V, S_V = vibly.compute_QV(Q_map, grids, ~Q_F, Q_on_grid=Q_on_grid)
5663

demos/damping_study/compute_measure_damping.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def compute_viability(x0, p, name, visualise=False):
7575
grids = {"states": s_grid, "actions": a_grid}
7676

7777
# * compute transition matrix and boolean matrix of failures
78-
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map, verbose=1)
78+
result = vibly.compute_Q_map(grids, p_map, verbose=1, parallel=True)
79+
Q_map = result.q_map
80+
Q_F = result.q_fail
7981

8082
# * compute viable sets
8183
Q_V, S_V = vibly.compute_QV(Q_map, grids)

0 commit comments

Comments
 (0)