def test_cycle_indices(): G = ModeGraph() G.add_nodes_from([5, 1, 2, 3, 4]) def order_fcn(n): if n == 5: return 0 else: return n G.add_edge(2, 1, modes=[0]) G.add_edge(1, 1, modes=[0]) G.add_edge(1, 2, modes=[1]) G.set_order_fcn(order_fcn) cycle1 = [(2, 0), (1, 0), (1, 1)] A1 = G.index_matrix(cycle1).todense() np.testing.assert_equal( A1, np.array([[0, 0, 0], [0, 1, 1], [1, 0, 0], [0, 0, 0], [0, 0, 0]]) )
def test_orderfcn_error(): G = ModeGraph() G.add_nodes_from([1, 2, 3]) G.add_edge(1, 3, modes=[1]) G.add_edge(1, 2, modes=[0]) G.set_order_fcn(lambda n: n)
def test_multimode_error(): G = ModeGraph() G.add_nodes_from([1, 2, 3]) G.add_edge(1, 3, modes=[1]) G.add_edge(1, 2, modes=[1]) G.check_valid()
def test_mode_graph(): G = ModeGraph() G.add_nodes_from([1, 2, 3]) G.add_path([3, 2, 1], modes=[0]) G.add_path([1, 2, 3], modes=[1]) np.testing.assert_equal(G.post(3, 0), 2) np.testing.assert_equal(G.post(2, 1), 3)
def test_system_matrix(): G = ModeGraph() G.add_nodes_from([1, 2, 3]) G.add_path([3, 2, 1], modes=[0]) G.add_path([1, 2, 3], modes=[1]) A = G.system_matrix().todense() np.testing.assert_equal( A, np.array([[0, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 1, 0]]) )
def __init__(self, lower_bounds, upper_bounds, eta, tau): self.eta = eta self.tau = float(tau) self.lower_bounds = np.array(lower_bounds, dtype=np.float64) self.upper_bounds = np.array(upper_bounds, dtype=np.float64) self.upper_bounds += (self.upper_bounds - self.lower_bounds) % eta # make domain number of eta's self.nextMode = 1 # number of discrete points along each dimension self.n_dim = np.round((self.upper_bounds - self.lower_bounds) / eta).astype(int) # Create a graph and populate it with nodes self.graph = ModeGraph() for midx in itertools.product(*[range(dim) for dim in self.n_dim]): cell_lower_bounds = self.lower_bounds + np.array(midx) * eta cell_upper_bounds = cell_lower_bounds + eta self.graph.add_node( midx, lower_bounds=tuple(cell_lower_bounds), upper_bounds=tuple(cell_upper_bounds), mid=(cell_lower_bounds + cell_upper_bounds) / 2, ) self.graph.set_order_fcn(self.node_to_idx)
def test_multi(): G1 = ModeGraph() G1.add_nodes_from([1, 2, 3]) G1.add_path([1, 3, 3], modes=['on']) G1.add_path([1, 2], modes=['off']) G1.add_path([2, 2], modes=['on']) # Set up the mode-counting problem cp = MultiCountingProblem(2) cp.T = 3 # Set up first class cp.graphs[0] = G1 cp.inits[0] = [4, 0, 0] cp.cycle_sets[0] = [[(3, 'on')], [(2, 'on')]] # Set up second class cp.graphs[1] = G1 cp.inits[1] = [4, 0, 0] cp.cycle_sets[1] = [[(3, 'on')], [(2, 'on')]] # Set up constraints # Count subsystems of class 0 that are at node `2` regardless of mode cc1 = CountingConstraint(2) cc1.X[0] = set([(2, 'on'), (2, 'off')]) cc1.X[1] = set() cc1.R = 0 cc2 = CountingConstraint(2) # Count subsystems of class 1 that are at node `3` regardless of mode cc2.X[0] = set() cc2.X[1] = set([(3, 'on'), (3, 'off')]) cc2.R = 0 cp.constraints += [cc1, cc2] cp.solve_prefix_suffix() cp.test_solution() xi = [[1, 1, 1, 1], [1, 1, 1, 1]] for t in range(40): actions = cp.get_input(xi, t) for k1 in range(4): xi[0][k1] = G1.post(xi[0][k1], actions[0][k1]) for k2 in range(4): xi[1][k2] = G1.post(xi[1][k2], actions[1][k2])
def test_comprehensive(): G = ModeGraph() G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) G.add_path([6, 5, 2, 1, 1], modes=[1]) G.add_path([8, 7, 4], modes=[1]) G.add_path([4, 3, 2], modes=[1]) G.add_path([1, 2, 3, 6], modes=[0]) G.add_path([5, 4, 6], modes=[0]) G.add_path([6, 7, 8, 8], modes=[0]) # Set up the mode-counting problem cp = MultiCountingProblem(1) cp.graphs[0] = G cp.inits[0] = [0, 1, 6, 4, 7, 10, 2, 0] cp.T = 5 cc1 = CountingConstraint(1) cc1.X[0] = set(product(G.nodes(), [0])) cc1.R = 16 cc2 = CountingConstraint(1) cc2.X[0] = set(product(G.nodes(), [1])) cc2.R = 30 - 15 cc3 = CountingConstraint(1) cc3.X[0] = set(product(G.nodes_with_selfloops(), [0, 1])) cc3.R = 0 cp.constraints += [cc1, cc2, cc3] def outg(c): return [G[c[i]][c[(i + 1) % len(c)]]['modes'][0] for i in range(len(c))] cp.cycle_sets[0] = [zip(c, outg(c)) for c in nx.simple_cycles(nx.DiGraph(G))] cp.solve_prefix_suffix() cp.test_solution() xi = sum([[i + 1] * cp.inits[0][i] for i in range(8)], []) for t in range(40): actions = cp.get_input([xi], t) for k1 in range(len(xi)): xi[k1] = G.post(xi[k1], actions[0][k1])
class Abstraction(object): """Discrete abstraction of the hyper box defined by *lower_bounds* and *upper_bounds*. Time discretization is *tau* and space discretization is given by *eta*. Mode transitions are added with :py:func:`add_mode`.""" def __init__(self, lower_bounds, upper_bounds, eta, tau): self.eta = eta self.tau = float(tau) self.lower_bounds = np.array(lower_bounds, dtype=np.float64) self.upper_bounds = np.array(upper_bounds, dtype=np.float64) self.upper_bounds += (self.upper_bounds - self.lower_bounds) % eta # make domain number of eta's self.nextMode = 1 # number of discrete points along each dimension self.n_dim = np.round((self.upper_bounds - self.lower_bounds) / eta).astype(int) # Create a graph and populate it with nodes self.graph = ModeGraph() for midx in itertools.product(*[range(dim) for dim in self.n_dim]): cell_lower_bounds = self.lower_bounds + np.array(midx) * eta cell_upper_bounds = cell_lower_bounds + eta self.graph.add_node( midx, lower_bounds=tuple(cell_lower_bounds), upper_bounds=tuple(cell_upper_bounds), mid=(cell_lower_bounds + cell_upper_bounds) / 2, ) self.graph.set_order_fcn(self.node_to_idx) def __len__(self): """ Size of abstraction """ return len(self.graph) def point_to_midx(self, point): """ Return the node multiindex corresponding to the continuous point *point*. """ assert len(point) == len(self.n_dim) if not self.contains(point): raise ("Point outside domain") midx = np.floor((np.array(point) - self.lower_bounds) / self.eta).astype(np.uint64) return tuple(midx) def contains(self, point): """ Return ``True`` if *point* is within the abstraction domain, ``False`` otherwise. """ if np.any(self.lower_bounds >= point) or np.any(self.upper_bounds <= point): return False return True def plot_planar(self): """ Plot a 2D abstraction. """ assert len(self.lower_bounds) == 2 ax = plt.axes() for node, attr in self.graph.nodes_iter(data=True): plt.plot(attr["mid"][0], attr["mid"][1], "ro") for n1, n2, mode in self.graph.edges_iter(data="mode"): mid1 = self.graph.node[n1]["mid"] mid2 = self.graph.node[n2]["mid"] col = "b" if mode == 1 else "g" ax.arrow(mid1[0], mid1[1], mid2[0] - mid1[0], mid2[1] - mid1[1], fc=col, ec=col) plt.show() def add_mode(self, vf, mode_name=None): """ Add new dynamic mode to the abstraction, given by the vector field *vf*. """ if mode_name is None: mode_name = self.nextMode self.nextMode += 1 def dummy_vf(z, t): return vf(z) tr_out = 0 for node, attr in self.graph.nodes_iter(data=True): x_fin = scipy.integrate.odeint(dummy_vf, attr["mid"], np.arange(0, self.tau, self.tau / 100)) if self.contains(x_fin[-1]): midx = self.point_to_midx(x_fin[-1]) if self.graph.has_edge(node, midx): # Edge already present, append mode self.graph[node][midx]["modes"] += mode_name else: # Create new edge with single mode self.graph.add_edge(node, midx, modes=[mode_name]) else: tr_out += 1 if tr_out > 0: print "Warning: ", tr_out, " transitions out of ", len( self.graph ), " in mode ", mode_name, " go out of domain" def node_to_idx(self, node): """ Given a node at discrete multiindex :math:`(x,y,z)`, return the index :math:`L_z ( L_y x + y ) + z`, where :math:`L_z, L_y` are the (discrete) lengths of the hyper box domain, and correspondingly for higher/lower dimensions. The function is a 1-1 mapping between the nodes in the abstraction and the positive integers, and thus suitable as order_function in :py:func:`prefix_suffix_feasible`. """ assert len(node) == len(self.n_dim) ret = node[0] for i in range(1, len(self.n_dim)): ret *= self.n_dim[i] ret += node[i] return ret def idx_to_node(self, idx): """ Inverse of :py:func:`node_to_idx` """ assert idx < np.product(self.n_dim) node = [0] * len(self.n_dim) for i in reversed(range(len(self.n_dim))): node[i] = int(idx % self.n_dim[i]) idx = np.floor(idx / self.n_dim[i]) return tuple(node)