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
33 changes: 33 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Unit Tests

on:
push:
branches:
- main
pull_request:

jobs:
tests:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: |
pyproject.toml
requirements.txt

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install .
python -m pip install pytest

- name: Run unit tests
run: pytest tests/unit
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ A dynamical system is in a _viable_ state if there exist control inputs that all
## Installation
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

conda (recommended):
[uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
`uv venv .venv`
`source .venv/bin/activate`
`uv pip install -r requirements.txt`
`uv pip install -e .`

conda:
`conda create -n vibly python=3.9`
`conda activate vibly`
then follow the same instructions as for pip:
Expand Down Expand Up @@ -45,7 +51,7 @@ from measure import active_sampling
Examples are shown in the `demos` folder. We recommend starting with `slip_demo.py`, and then `computeQ_slip.py`.
The `viability` package contains:
- `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.
- `parcompute_Q_map`: same as above, but parallelized. You typically want to use this, unless running a debugger.
- `compute_Q_map(..., parallel=True)`: parallelised version via multiprocessing; typically preferable unless debugging.
- `compute_QV`: computes the viability kernel and viable set to within conservative discrete approximation, using the grid generated by `compute_Q_map`.
- `get_feasibility_mask`: this can be used to exclude parts of the grid which are infeasible (i.e. are not physically meaningful)
- `project_Q2S`: Apply an operator (default is an orthogonal projection) from state-action space to state space. Used to compute measures.
Expand Down
13 changes: 11 additions & 2 deletions demos/TAC11/computeQ_satellite.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,18 @@
grids = {"states": s_grid, "actions": a_grid}

tictoc.tic()
Q_map, Q_F, Q_coords = vibly.parcompute_Q_mapC(
grids, p_map, verbose=1, check_grid=False, keep_coords=True
result = vibly.compute_Q_map(
grids,
p_map,
verbose=1,
check_grid=False,
keep_coords=True,
parallel=True,
bin_mode="nearest",
)
Q_map = result.q_map
Q_F = result.q_fail
Q_coords = result.q_reached
# Q_map, Q_F, Q_on_grid = vibly.compute_Q_map(grids, p_map, check_grid=True)

# * compute_QV computes the viable set and viability kernel
Expand Down
4 changes: 3 additions & 1 deletion demos/computeQ_daslip.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
a_grid = (np.linspace(20 / 180 * np.pi, 60 / 180 * np.pi, 25),)

grids = {"states": s_grid, "actions": a_grid}
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map, verbose=2)
result = vibly.compute_Q_map(grids, p_map, verbose=2, parallel=True)
Q_map = result.q_map
Q_F = result.q_fail
Q_V, S_V = vibly.compute_QV(Q_map, grids)
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid=s_grid, Q_V=Q_V)
Expand Down
9 changes: 5 additions & 4 deletions demos/computeQ_hovership.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@

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

# * compute_QV computes the viable set and viability kernel
Q_V, S_V = vibly.compute_QV(Q_map, grids, ~Q_F, Q_on_grid=Q_on_grid)
Expand Down
4 changes: 3 additions & 1 deletion demos/computeQ_lip.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
grids = {"states": s_grid, "actions": a_grid}

tictoc.tic()
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map, verbose=1)
result = vibly.compute_Q_map(grids, p_map, verbose=1, parallel=True)
Q_map = result.q_map
Q_F = result.q_fail
time_elapsed = tictoc.toc()
print("time elapsed: " + str(time_elapsed / 60.0))
# * compute_QV computes the viable set and viability kernel
Expand Down
67 changes: 67 additions & 0 deletions demos/computeQ_nslip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import numpy as np
import matplotlib.pyplot as plt
from models import nslip
import viability as vibly


if __name__ == "__main__":
p = {
"mass": 80.0,
"stiffness": 705.0,
"resting_angle": 17 / 18 * np.pi,
"gravity": 9.81,
"angle_of_attack": 1 / 5 * np.pi,
"upper_leg": 0.5,
"lower_leg": 0.5,
}

x0 = np.array([0.0, 0.85, 5.5, 0.0, 0.0, 0.0, 0.0])
x0 = nslip.reset_leg(x0, p)
p["x0"] = x0
p["total_energy"] = nslip.compute_total_energy(x0, p)

p_map = nslip.p_map
p_map.p = p
p_map.x = x0
p_map.sa2xp = nslip.sa2xp
p_map.xp2s = nslip.xp2s

s_grid = np.linspace(0.1, 1.0, 61)
s_grid = (s_grid[:-1],)
a_grid = (np.linspace(-10 / 180 * np.pi, 70 / 180 * np.pi, 61),)
grids = {"states": s_grid, "actions": a_grid}

result = vibly.compute_Q_map(grids, p_map, parallel=True)
Q_map = result.q_map
Q_F = result.q_fail
Q_V, S_V = vibly.compute_QV(Q_map, grids)
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid, Q_V=Q_V)

import pickle
import os

filename = "nslip_map.pickle"

if os.path.exists("data"):
path_to_file = "data/dynamics/"
else:
path_to_file = "../data/dynamics/"
if not os.path.exists(path_to_file):
os.makedirs(path_to_file)

data2save = {
"grids": grids,
"Q_map": Q_map,
"Q_F": Q_F,
"Q_V": Q_V,
"Q_M": Q_M,
"S_M": S_M,
"p": p,
"x0": x0,
}
with open(path_to_file + filename, "wb") as outfile:
pickle.dump(data2save, outfile)

plt.imshow(Q_map, origin="lower")
plt.show()
4 changes: 3 additions & 1 deletion demos/computeQ_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
a_grid = (np.linspace(-10 / 180 * np.pi, 70 / 180 * np.pi, 161),)
grids = {"states": s_grid, "actions": a_grid}
# Q_map, Q_F = vibly.compute_Q_map(grids, p_map)
Q_map, Q_F = vibly.parcompute_Q_map(grids, p_map)
result = vibly.compute_Q_map(grids, p_map, parallel=True)
Q_map = result.q_map
Q_F = result.q_fail
Q_V, S_V = vibly.compute_QV(Q_map, grids)
S_M = vibly.project_Q2S(Q_V, grids, proj_opt=np.mean)
Q_M = vibly.map_S2Q(Q_map, S_M, s_grid, Q_V=Q_V)
Expand Down
11 changes: 9 additions & 2 deletions demos/computeQ_spaceship4.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@
# * Q_on_grid is a helper grid, which marks if a state has not moved
# * this is used to catch corner cases, and is not important for most systems
# * setting `check_grid` to False will omit Q_on_grid
Q_map, Q_F, Q_on_grid = vibly.parcompute_Q_map(
grids, p_map, check_grid=True, verbose=1
result = vibly.compute_Q_map(
grids,
p_map,
check_grid=True,
verbose=1,
parallel=True,
)
Q_map = result.q_map
Q_F = result.q_fail
Q_on_grid = result.q_on_grid
# * compute_QV computes the viable set and viability kernel
Q_V, S_V = vibly.compute_QV(Q_map, grids, ~Q_F, Q_on_grid=Q_on_grid)

Expand Down
4 changes: 3 additions & 1 deletion demos/damping_study/compute_measure_damping.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ def compute_viability(x0, p, name, visualise=False):
grids = {"states": s_grid, "actions": a_grid}

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

# * compute viable sets
Q_V, S_V = vibly.compute_QV(Q_map, grids)
Expand Down
120 changes: 0 additions & 120 deletions models/acrobot.py

This file was deleted.

47 changes: 0 additions & 47 deletions models/ardyn.py

This file was deleted.

Loading