Skip to content

Commit d8dbadf

Browse files
committed
init parallel reach
1 parent c1eb838 commit d8dbadf

4 files changed

Lines changed: 169 additions & 8 deletions

File tree

pybdr/algorithm/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .reach_linear_zono_algo2 import ReachLinearZonoAlgo2
1515
from .reach_linear_zono_algo3 import ReachLinearZonoAlgo3
1616
from .reach_linear_interval_algo1 import ReachLinearIntervalAlgo1
17+
from .reach_linear_zono_algo1_parallel import ReachLinearZonoAlgo1Parallel
1718

1819
__all__ = ["ASB2008CDC",
1920
"HSCC2005",
@@ -30,4 +31,5 @@
3031
"ReachLinearZonoAlgo1",
3132
"ReachLinearZonoAlgo2",
3233
"ReachLinearZonoAlgo3",
33-
"ReachLinearIntervalAlgo1"]
34+
"ReachLinearIntervalAlgo1",
35+
"ReachLinearZonoAlgo1Parallel"]

pybdr/algorithm/reach_linear_zono_algo1.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ def reach_one_step(cls, e_ar, hr_cur):
8989
return e_ar @ hr_cur
9090

9191
@classmethod
92-
def reach(cls, lin_sys: LinearSystemSimple, opts: Settings):
92+
def reach(cls, lin_sys: LinearSystemSimple, opts: Settings, x0: Zonotope):
9393
assert opts.validation()
9494

9595
e_ar, f = cls.pre_compute(lin_sys, opts)
96-
hr_cur = cls.compute_hr(e_ar, opts.x0, f)
96+
hr_cur = cls.compute_hr(e_ar, x0, f)
9797

9898
# ri -> time interval reachable sets
9999
# rp -> time point reachable sets
100100
# ti -> time intervals
101101
# tp -> time points
102-
ri = [opts.x0, hr_cur]
102+
ri = [x0, hr_cur]
103103

104104
for k in range(opts.num_steps):
105105
hr_cur = cls.reach_one_step(e_ar, hr_cur)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
from pybdr.dynamic_system import LinearSystemSimple
5+
from pybdr.geometry import Interval, Geometry, Zonotope
6+
from pybdr.geometry.operation import enclose
7+
from dataclasses import dataclass
8+
from scipy.special import factorial
9+
from concurrent.futures import ProcessPoolExecutor, as_completed
10+
from functools import partial
11+
12+
"""
13+
Reachable Sets of Linear Time-invariant Systems without inputs
14+
"""
15+
16+
17+
class ReachLinearZonoAlgo1Parallel:
18+
@dataclass
19+
class Settings:
20+
t_end: float = 0
21+
step: float = 0
22+
eta: int = 4 # number of taylor terms in approximating using taylor series
23+
x0: Zonotope = None
24+
25+
def __init__(self):
26+
self._num_steps = 0
27+
28+
def validation(self):
29+
assert self.t_end >= self.step >= 0
30+
assert self.eta >= 3 # at least 3 considering accuracy
31+
self._num_steps = round(self.t_end / self.step)
32+
return True
33+
34+
@property
35+
def num_steps(self):
36+
return self._num_steps
37+
38+
@classmethod
39+
def compute_epsilon(cls, a, t, eta):
40+
assert eta > 0
41+
a_inf_norm = np.linalg.norm(a, np.inf)
42+
epsilon = (a_inf_norm * t) / (eta + 2)
43+
if epsilon >= 1:
44+
raise Exception("Epsilon must be less than 1")
45+
return epsilon
46+
47+
@classmethod
48+
def compute_et(cls, a, t, eta, epsilon):
49+
a_norm = np.linalg.norm(a, np.inf)
50+
cof = ((a_norm * t) ** (eta + 1) / factorial(eta + 1)) * 1 / (1 - epsilon)
51+
return Interval.identity(a.shape) * cof
52+
53+
@classmethod
54+
def compute_e_ar(cls, eta, r, a):
55+
# compute epsilon
56+
epsilon = cls.compute_epsilon(a, r, eta)
57+
# compute sums of taylor terms
58+
taylor_sums = 0
59+
for i in range(eta + 1):
60+
taylor_sums += 1 / factorial(i) * np.linalg.matrix_power(a * r, i)
61+
62+
er = cls.compute_et(a, r, eta, epsilon)
63+
64+
return taylor_sums + er, er
65+
66+
@classmethod
67+
def compute_f(cls, eta, a, er, r):
68+
# compute taylor sums
69+
taylor_sums = 0
70+
71+
for i in range(2, eta + 1):
72+
cof = np.linalg.matrix_power(a, i) / factorial(i)
73+
box_inf = (np.power(i, -i / (i - 1)) - np.power(i, -1 / (i - 1))) * np.power(r, i)
74+
box = Interval(box_inf, 0)
75+
taylor_sums += box * cof
76+
77+
return taylor_sums + er
78+
79+
@classmethod
80+
def pre_compute(cls, lin_sys: LinearSystemSimple, opts: Settings):
81+
e_ar, er = cls.compute_e_ar(opts.eta, opts.step, lin_sys.xa)
82+
f = cls.compute_f(opts.eta, lin_sys.xa, er, opts.step)
83+
return e_ar, f
84+
85+
@classmethod
86+
def compute_hr(cls, e_ar, x0, f):
87+
return enclose(x0, e_ar @ x0, Geometry.TYPE.ZONOTOPE) + f @ x0
88+
89+
@classmethod
90+
def reach_one_step(cls, e_ar, hr_cur):
91+
return e_ar @ hr_cur
92+
93+
@classmethod
94+
def _reach(cls, lin_sys: LinearSystemSimple, opts: Settings, x0: Zonotope):
95+
assert opts.validation()
96+
97+
e_ar, f = cls.pre_compute(lin_sys, opts)
98+
hr_cur = cls.compute_hr(e_ar, x0, f)
99+
100+
# ri -> time interval reachable sets
101+
# rp -> time point reachable sets
102+
# ti -> time intervals
103+
# tp -> time points
104+
ri = [x0, hr_cur]
105+
106+
for k in range(opts.num_steps):
107+
hr_cur = cls.reach_one_step(e_ar, hr_cur)
108+
ri.append(hr_cur)
109+
110+
return None, ri, None, None
111+
112+
@classmethod
113+
def reach(cls, lin_sys, opts: Settings, xs: [Zonotope]):
114+
with ProcessPoolExecutor() as executor:
115+
partial_reach = partial(cls._reach, lin_sys, opts)
116+
117+
futures = [executor.submit(partial_reach, x) for x in xs]
118+
119+
for future in as_completed(futures):
120+
try:
121+
return future.result()
122+
except Exception as exc:
123+
raise exc

test/algorithm/test_reach_linear_zono_algo1.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ def test_reach_linear_zono_algo1_case_00():
1919
opts.t_end = 5
2020
opts.step = 0.04
2121
opts.eta = 4
22-
opts.x0 = cvt2(Interval([0.9, 0.9], [1.1, 1.1]), Geometry.TYPE.ZONOTOPE)
22+
x0 = cvt2(Interval([0.9, 0.9], [1.1, 1.1]), Geometry.TYPE.ZONOTOPE)
2323

24-
_, ri, _, _ = ReachLinearZonoAlgo1.reach(lin_sys, opts)
24+
_, ri, _, _ = ReachLinearZonoAlgo1.reach(lin_sys, opts, x0)
2525

2626
plot(ri, [0, 1])
2727

@@ -35,11 +35,47 @@ def test_reach_linear_zono_algo1_case_01():
3535
opts.t_end = 5
3636
opts.step = 0.04
3737
opts.eta = 4
38-
opts.x0 = cvt2(Interval(0.9 * np.ones(5), 1.1 * np.ones(5)), Geometry.TYPE.ZONOTOPE)
38+
x0 = cvt2(Interval(0.9 * np.ones(5), 1.1 * np.ones(5)), Geometry.TYPE.ZONOTOPE)
3939

40-
_, ri, _, _ = ReachLinearZonoAlgo1.reach(lin_sys, opts)
40+
_, ri, _, _ = ReachLinearZonoAlgo1.reach(lin_sys, opts, x0)
4141

4242
plot(ri, [0, 1])
4343
plot(ri, [1, 2])
4444
plot(ri, [2, 3])
4545
plot(ri, [3, 4])
46+
47+
48+
def test_reach_linear_zono_algo1_case_02():
49+
from concurrent.futures import ProcessPoolExecutor, as_completed
50+
from pybdr.geometry.operation import boundary
51+
52+
xa = [[-1, -4], [4, -1]]
53+
ub = np.array([[1], [1]])
54+
55+
lin_sys = LinearSystemSimple(xa, ub)
56+
opts = ReachLinearZonoAlgo1.Settings()
57+
opts.t_end = 5
58+
opts.step = 0.04
59+
opts.eta = 4
60+
61+
x0 = Interval([0.9, 0.9], [1.1, 1.1])
62+
x0_bounds = boundary(x0, 0.1, Geometry.TYPE.ZONOTOPE)
63+
64+
def parallel_reach(x):
65+
return ReachLinearZonoAlgo1.reach(lin_sys, opts, x)
66+
67+
result = None
68+
69+
with ProcessPoolExecutor() as executor:
70+
futures = [executor.submit(parallel_reach, bound) for bound in x0_bounds]
71+
72+
for future in as_completed(futures):
73+
try:
74+
result = future.result()
75+
except Exception as exc:
76+
print(exc)
77+
78+
print(type(result))
79+
print(len(result))
80+
81+
# plot(ri, [0, 1])

0 commit comments

Comments
 (0)