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]])
    )
Beispiel #6
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])
Beispiel #9
0
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)