Esempio n. 1
0
def test_priority_queue():
    """Test import and use of priority queue."""
    from landlab.ca.cfuncs import PriorityQueue

    # Create a priority queue
    pq = PriorityQueue()

    # push a bunch of events
    pq.push(2, 2.2)
    pq.push(5, 5.5)
    pq.push(0, 0.11)
    pq.push(4, 4.4)
    pq.push(1, 1.1)
    pq.push(3, 3.3)

    # pop a bunch of events
    (priority, index, item) = pq.pop()
    assert priority == 0.11, "incorrect priority in PQ test"
    assert index == 2, "incorrect index in PQ test"
    assert item == 0, "incorrect item in PQ test"

    (priority, index, item) = pq.pop()
    assert priority == 1.1, "incorrect priority in PQ test"
    assert index == 4, "incorrect index in PQ test"
    assert item == 1, "incorrect item in PQ test"

    (priority, index, item) = pq.pop()
    assert priority == 2.2, "incorrect priority in PQ test"
    assert index == 0, "incorrect index in PQ test"
    assert item == 2, "incorrect item in PQ test"

    (priority, index, item) = pq.pop()
    assert priority == 3.3, "incorrect priority in PQ test"
    assert index == 5, "incorrect index in PQ test"
    assert item == 3, "incorrect item in PQ test"

    (priority, index, item) = pq.pop()
    assert priority == 4.4, "incorrect priority in PQ test"
    assert index == 3, "incorrect index in PQ test"
    assert item == 4, "incorrect item in PQ test"

    (priority, index, item) = pq.pop()
    assert priority == 5.5, "incorrect priority in PQ test"
    assert index == 1, "incorrect index in PQ test"
    assert item == 5, "incorrect item in PQ test"
Esempio n. 2
0
class CellLabCTSModel(object):
    """Link-type (or doublet-type) cellular automaton model.

    A CellLabCTSModel implements a link-type (or doublet-type) cellular
    automaton model. A link connects a pair of cells. Each cell has a state
    (represented by an integer code), and each link also has a state that is
    determined by the states of the cell pair.

    Parameters
    ----------
    model_grid : Landlab ModelGrid object
        Reference to the model's grid
    node_state_dict : dict
        Keys are node-state codes, values are the names associated with
        these codes
    transition_list : list of Transition objects
        List of all possible transitions in the model
    initial_node_states : array of ints (x number of nodes in grid)
        Starting values for node-state grid
    prop_data : array (x number of nodes in grid), optional
        Array of properties associated with each node/cell
    prop_reset_value : number or object, optional
        Default or initial value for a node/cell property (e.g., 0.0).
        Must be same type as *prop_data*.
    """
    def __init__(
        self,
        model_grid,
        node_state_dict,
        transition_list,
        initial_node_states,
        prop_data=None,
        prop_reset_value=None,
        seed=0,
    ):
        """Initialize the CA model.

        Parameters
        ----------
        model_grid : Landlab ModelGrid object
            Reference to the model's grid
        node_state_dict : dict
            Keys are node-state codes, values are the names associated with
            these codes
        transition_list : list of Transition objects
            List of all possible transitions in the model
        initial_node_states : array of ints (x number of nodes in grid)
            Starting values for node-state grid
        prop_data : array (x number of nodes in grid), optional
            Array of properties associated with each node/cell
        prop_reset_value : number or object, optional
            Default or initial value for a node/cell property (e.g., 0.0).
            Must be same type as *prop_data*.
        seed : int, optional
            Seed for random number generation.
        """

        # Keep a copy of the model grid
        self.grid = model_grid

        # Initialize random number generation
        np.random.seed(seed)

        # Create an array that knows which links are connected to a boundary
        # node
        self.bnd_lnk = np.zeros(self.grid.number_of_links, dtype=np.int8)
        for link_id in range(self.grid.number_of_links):
            if (self.grid.status_at_node[self.grid.node_at_link_tail[link_id]]
                    != _CORE or self.grid.status_at_node[
                        self.grid.node_at_link_head[link_id]] != _CORE):
                self.bnd_lnk[link_id] = True

        # Set up the initial node-state grid
        self.set_node_state_grid(initial_node_states)

        # Current simulation time starts out at zero
        self.current_time = 0.0

        # Figure out how many states there are, and make sure the input data
        # are self consistent.
        #   There are 2 x (N^2) link states, where N is the number of node
        # states. For example, if there are just two node states, 0 and 1, then
        # the possible oriented link pairs are listed below:
        #   0-0 0-1 1-0 1-1  0 0 1 1
        #                    0 1 0 1
        self.num_node_states = len(node_state_dict)
        self.num_node_states_sq = self.num_node_states * self.num_node_states
        self.num_link_states = self.number_of_orientations * self.num_node_states_sq

        assert type(transition_list) is list, "transition_list must be a list!"
        assert transition_list, "Transition list must contain at least one transition"
        last_type = None
        for t in transition_list:
            # TODO: make orientation optional for cases where
            # self.number_of_orientations = 1
            if isinstance(t.from_state, tuple) and isinstance(
                    t.to_state, tuple):
                this_type = tuple
            else:
                this_type = int

            if this_type is tuple:
                # added to allow from and to states to be tuples, not just ids
                for i in t.from_state[:-1]:
                    assert (i < self.num_node_states
                            ), "Transition from_state out of range"
                for i in t.to_state[:-1]:
                    assert i < self.num_node_states, "Transition to_state out of range"
                assert (
                    t.from_state[-1] < self.number_of_orientations
                ), "Encoding for orientation in from_state must be < number of orientations."
                assert (
                    t.to_state[-1] < self.number_of_orientations
                ), "Encoding for orientation in to_state must be < number of orientations."
            else:
                assert (t.from_state < self.num_link_states
                        ), "Transition from_state out of range"
                assert (t.to_state < self.num_link_states
                        ), "Transition to_state out of range"

            assert (
                last_type == this_type or last_type is None
            ), "All transition types must be either int IDs, or all tuples."
            # this test to ensure all entries are either IDs, or tuples, not
            # mixed
            last_type = this_type

        # Create priority queue for events and next_update array for links
        self.next_update = self.grid.add_zeros("link", "next_update_time")
        self.priority_queue = PriorityQueue()
        self.next_trn_id = -np.ones(self.grid.number_of_links, dtype=int)

        # Assign link types from node types
        self.create_link_state_dict_and_pair_list()

        # DEJH adds: convert transition_list to IDs if necessary
        # This is the new part that allows Transition from_ and to_ types
        # to be specified either as ints, or as tuples.
        transition_list_as_ID = transition_list[:]
        if type(transition_list[0].from_state) == tuple:
            # (then they all are..., because of the assertions in __init__)
            for i in range(len(transition_list)):
                transition_list_as_ID[i].from_state = self.link_state_dict[
                    transition_list[i].from_state]
                transition_list_as_ID[i].to_state = self.link_state_dict[
                    transition_list[i].to_state]

        # Set up the information needed to determine the orientation of links
        # in the lattice. The default method just creates an array of zeros
        # (all orientations considered the same), but this will be overridden
        # in subclasses that do use orientation.
        self.setup_array_of_orientation_codes()

        # Using the grid of node states, figure out all the link states
        self.assign_link_states_from_node_types()

        # Create transition data for links
        self.setup_transition_data(transition_list_as_ID)

        # Put the various transitions on the event queue
        self.push_transitions_to_event_queue()

        # In order to keep track of cell "properties", we create an array of
        # indices that refer to locations in the caller's code where properties
        # are tracked.
        self.propid = np.arange(self.grid.number_of_nodes)
        if prop_data is None:
            self.prop_data = np.zeros(self.grid.number_of_nodes)
            self.prop_reset_value = 0.0
        else:
            self.prop_data = prop_data
            self.prop_reset_value = prop_reset_value

    def set_node_state_grid(self, node_states):
        """Set the grid of node-state codes to node_states.

        Sets the grid of node-state codes to node_states. Also checks
        to make sure node_states is in the proper format, which is to
        say, it's a Numpy array of the same length as the number of nodes in
        the grid.

        **Creates**:

        *  self.node_state : 1D array of ints (x number of nodes in grid)
           The node-state array

        Parameters
        ----------
        node_states : 1D array of ints (x number of nodes in grid)

        Notes
        -----
        The node-state array is attached to the grid as a field with the name
        'node_state'.
        """
        assert (type(node_states) is
                np.ndarray), "initial_node_states must be a Numpy array"
        assert (
            len(node_states) == self.grid.number_of_nodes
        ), "length of initial_node_states must equal number of nodes in grid"
        self.grid.at_node["node_state"] = node_states
        self.node_state = node_states

    def create_link_state_dict_and_pair_list(self):
        """Create a dict of link-state to node-state.

        Creates a dictionary that can be used as a lookup table to find out
        which link state corresponds to a particular pair of node states. The
        dictionary keys are 3-element tuples, each of which represents the
        state of the TAIL node, the HEAD node, and the orientation of the link.
        The values are integer codes representing the link state numbers.

        Notes
        -----
        Performance note: making self.node_pair a tuple does not appear to
        change time to lookup values in update_node_states. Changing it to a
        2D array of int actually slows it down.
        """
        self.link_state_dict = {}
        self.node_pair = []
        k = 0
        for orientation in range(self.number_of_orientations):
            for tail_state in range(self.num_node_states):
                for head_state in range(self.num_node_states):
                    self.link_state_dict[(tail_state, head_state,
                                          orientation)] = k
                    self.node_pair.append(
                        (tail_state, head_state, orientation))
                    k += 1

    def setup_array_of_orientation_codes(self):
        """Create array of active link orientation codes.

        Creates and configures an array that contain the orientation code for
        each active link (and corresponding cell pair).

        **creates**:

        * ``self.link_orientation`` : 1D numpy array

        Notes
        -----

        The setup varies depending on the type of LCA. The default is
        non-oriented, in which case we just have an array of zeros. Subclasses
        will override this method to handle lattices in which orientation
        matters (for example, vertical vs. horizontal in an OrientedRasterLCA).
        """
        self.link_orientation = np.zeros(self.grid.number_of_links,
                                         dtype=np.int8)

    def assign_link_states_from_node_types(self):
        """Assign link-state code for each link.

        Takes lists/arrays of "tail" and "head" node IDs for each link, and a
        dictionary that associates pairs of node states (represented as a
        3-element tuple, comprising the TAIL state, FROM state, and
        orientation) to link states.

        **creates**:

        * ``self.link_state`` : 1D numpy array
        """
        self.link_state = np.zeros(self.grid.number_of_links, dtype=int)

        for i in self.grid.active_links:
            orientation = self.link_orientation[i]
            node_pair = (
                self.node_state[self.grid.node_at_link_tail[i]],
                self.node_state[self.grid.node_at_link_head[i]],
                orientation,
            )
            self.link_state[i] = self.link_state_dict[node_pair]

    def setup_transition_data(self, xn_list):
        """Create transition data arrays."""

        # First, create an array that stores the number of possible transitions
        # out of each state.
        n_xn = np.zeros(self.num_link_states, dtype=int)
        for xn in xn_list:
            n_xn[xn.from_state] += 1
        self.n_trn = np.zeros(self.num_link_states, dtype=int)

        # Now, create arrays to hold the "to state" and transition rate for each
        # transition. These arrays are dimensioned N x M where N is the number
        # of states, and M is the maximum number of transitions from a single
        # state (for example if state 3 could transition either to state 1 or
        # state 4, and the other states only had one or zero possible
        # transitions, then the maximum would be 2).
        max_transitions = np.max(n_xn)
        self.trn_id = np.zeros((self.num_link_states, max_transitions),
                               dtype=int)
        num_transitions = len(xn_list)
        self.trn_to = np.zeros(num_transitions, dtype=int)
        self.trn_rate = np.zeros(num_transitions)
        self.trn_propswap = np.zeros(num_transitions, dtype=np.int8)
        self.trn_prop_update_fn = np.zeros(num_transitions, dtype=object)

        for trn in range(num_transitions):
            self.trn_to[trn] = xn_list[trn].to_state
            self.trn_rate[trn] = xn_list[trn].rate
            self.trn_propswap[trn] = xn_list[trn].swap_properties
            if xn_list[trn].prop_update_fn is not None:
                self.trn_prop_update_fn[trn] = xn_list[trn].prop_update_fn
                self._use_propswap_or_callback = True
            from_state = xn_list[trn].from_state
            self.trn_id[from_state, self.n_trn[from_state]] = trn
            self.n_trn[from_state] += 1

    def push_transitions_to_event_queue(self):
        """Initializes the event queue by creating transition events for each
        cell pair that has one or more potential transitions and pushing these
        onto the queue. Also records scheduled transition times in the
        self.next_update array.

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.oriented_raster_cts import OrientedRasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 0, 0), 1.0))
        >>> trn_list.append(Transition((1, 0, 0), (0, 1, 0), 2.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 0, 1), 3.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 1, 1), 4.0))
        >>> ins = np.arange(15) % 2
        >>> cts = OrientedRasterCTS(grid, nsd, trn_list, ins)
        >>> ev0 = cts.priority_queue._queue[0]
        >>> np.round(100 * ev0[0])
        12.0
        >>> ev0[2]  # this is the link ID
        16
        >>> ev6 = cts.priority_queue._queue[6]
        >>> np.round(100 * ev6[0])
        27.0
        >>> ev6[2]  # this is the link ID
        6
        >>> cts.next_trn_id[ev0[2]]  # ID of the transition to occur at this link
        3
        >>> cts.next_trn_id[cts.grid.active_links]
        array([-1,  2, -1,  1,  0,  1,  0,  2, -1,  3])
        """
        push_transitions_to_event_queue(
            self.grid.number_of_active_links,
            self.grid.active_links,
            self.n_trn,
            self.link_state,
            self.trn_id,
            self.trn_rate,
            self.next_update,
            self.next_trn_id,
            self.priority_queue,
        )

    def update_link_state_new(self, link, new_link_state, current_time):
        """Implements a link transition by updating the current state of the
        link and (if appropriate) choosing the next transition event and
        pushing it on to the event queue.

        Parameters
        ----------
        link : int
            ID of the link to update
        new_link_state : int
            Code for the new state
        current_time : float
            Current time in simulation
        """

        # If the link connects to a boundary, we might have a different state
        # than the one we planned
        if self.bnd_lnk[link]:
            fns = self.node_state[self.grid.node_at_link_tail[link]]
            tns = self.node_state[self.grid.node_at_link_head[link]]
            orientation = self.link_orientation[link]
            new_link_state = int(orientation * self.num_node_states_sq +
                                 fns * self.num_node_states + tns)

        self.link_state[link] = new_link_state
        if self.n_trn[new_link_state] > 0:
            (event_time, trn_id) = get_next_event_new(
                link,
                new_link_state,
                current_time,
                self.n_trn,
                self.trn_id,
                self.trn_rate,
            )
            self.priority_queue.push(link, event_time)
            self.next_update[link] = event_time
            self.next_trn_id[link] = trn_id
        else:
            self.next_update[link] = _NEVER
            self.next_trn_id[link] = -1

    def update_component_data(self, new_node_state_array):
        """Update all component data.

        Call this method to update all data held by the component, if, for
        example, another component or boundary conditions modify the node
        statuses outside the component between run steps.

        This method updates all necessary properties, including both node and
        link states.

        *new_node_state_array* is the updated list of node states, which must
        still all be compatible with the state list originally supplied to
        this component.

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.raster_cts import RasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 1, 0), 1.0))
        >>> ins = np.zeros(15, dtype=int)
        >>> ca = RasterCTS(grid, nsd, trn_list, ins)
        >>> list(ca.node_state[6:9])
        [0, 0, 0]
        >>> list(ca.link_state[9:13])
        [0, 0, 0, 0]
        >>> len(ca.priority_queue._queue)  # there are no transitions
        0
        >>> nns = np.arange(15) % 2        # make a new node-state grid...
        >>> ca.update_component_data(nns)  # ...and assign it
        >>> list(ca.node_state[6:9])
        [0, 1, 0]
        >>> list(ca.link_state[9:13])
        [2, 1, 2, 1]
        >>> len(ca.priority_queue._queue)  # now there are 5 transitions
        5
        """
        self.set_node_state_grid(new_node_state_array)
        self.assign_link_states_from_node_types()
        self.push_transitions_to_event_queue()

    # @profile
    def run(self,
            run_to,
            node_state_grid=None,
            plot_each_transition=False,
            plotter=None):
        """Run the model forward for a specified period of time.

        Parameters
        ----------
        run_to : float
            Time to run to, starting from self.current_time
        node_state_grid : 1D array of ints (x number of nodes) (optional)
            Node states (if given, replaces model's current node state grid)
        plot_each_transition : bool (optional)
            Option to display the grid after each transition
        plotter : CAPlotter object (optional)
            Needed if caller wants to plot after every transition

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.oriented_raster_cts import OrientedRasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 0, 0), 1.0))
        >>> trn_list.append(Transition((1, 0, 0), (0, 1, 0), 2.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 0, 1), 3.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 1, 1), 4.0))
        >>> ins = np.arange(15) % 2
        >>> cts = OrientedRasterCTS(grid, nsd, trn_list, ins)
        """
        if node_state_grid is not None:
            self.set_node_state_grid(node_state_grid)

        self.current_time = run_cts_new(
            run_to,
            self.current_time,
            self.priority_queue,
            self.next_update,
            self.grid.node_at_link_tail,
            self.grid.node_at_link_head,
            self.node_state,
            self.next_trn_id,
            self.trn_to,
            self.grid.status_at_node,
            self.num_node_states,
            self.num_node_states_sq,
            self.bnd_lnk,
            self.link_orientation,
            self.link_state,
            self.n_trn,
            self.trn_id,
            self.trn_rate,
            self.grid.links_at_node,
            self.grid.active_link_dirs_at_node,
            self.trn_propswap,
            self.propid,
            self.prop_data,
            self.prop_reset_value,
            self.trn_prop_update_fn,
            self,
            plot_each_transition,
            plotter,
        )
Esempio n. 3
0
class CellLabCTSModel(object):

    """Link-type (or doublet-type) cellular automaton model.

    A CellLabCTSModel implements a link-type (or doublet-type) cellular
    automaton model. A link connects a pair of cells. Each cell has a state
    (represented by an integer code), and each link also has a state that is
    determined by the states of the cell pair.

    Parameters
    ----------
    model_grid : Landlab ModelGrid object
        Reference to the model's grid
    node_state_dict : dict
        Keys are node-state codes, values are the names associated with
        these codes
    transition_list : list of Transition objects
        List of all possible transitions in the model
    initial_node_states : array of ints (x number of nodes in grid)
        Starting values for node-state grid
    prop_data : array (x number of nodes in grid), optional
        Array of properties associated with each node/cell
    prop_reset_value : number or object, optional
        Default or initial value for a node/cell property (e.g., 0.0).
        Must be same type as *prop_data*.
    """

    def __init__(
        self,
        model_grid,
        node_state_dict,
        transition_list,
        initial_node_states,
        prop_data=None,
        prop_reset_value=None,
        seed=0,
    ):
        """Initialize the CA model.

        Parameters
        ----------
        model_grid : Landlab ModelGrid object
            Reference to the model's grid
        node_state_dict : dict
            Keys are node-state codes, values are the names associated with
            these codes
        transition_list : list of Transition objects
            List of all possible transitions in the model
        initial_node_states : array of ints (x number of nodes in grid)
            Starting values for node-state grid
        prop_data : array (x number of nodes in grid), optional
            Array of properties associated with each node/cell
        prop_reset_value : number or object, optional
            Default or initial value for a node/cell property (e.g., 0.0).
            Must be same type as *prop_data*.
        seed : int, optional
            Seed for random number generation.
        """

        # Keep a copy of the model grid
        self.grid = model_grid

        # Initialize random number generation
        np.random.seed(seed)

        # Create an array that knows which links are connected to a boundary
        # node
        self.bnd_lnk = np.zeros(self.grid.number_of_links, dtype=np.int8)
        for link_id in range(self.grid.number_of_links):
            if (
                self.grid.status_at_node[self.grid.node_at_link_tail[link_id]] != _CORE
                or self.grid.status_at_node[self.grid.node_at_link_head[link_id]]
                != _CORE
            ):
                self.bnd_lnk[link_id] = True

        # Set up the initial node-state grid
        self.set_node_state_grid(initial_node_states)

        # Current simulation time starts out at zero
        self.current_time = 0.0

        # Figure out how many states there are, and make sure the input data
        # are self consistent.
        #   There are 2 x (N^2) link states, where N is the number of node
        # states. For example, if there are just two node states, 0 and 1, then
        # the possible oriented link pairs are listed below:
        #   0-0 0-1 1-0 1-1  0 0 1 1
        #                    0 1 0 1
        self.num_node_states = len(node_state_dict)
        self.num_node_states_sq = self.num_node_states * self.num_node_states
        self.num_link_states = self.number_of_orientations * self.num_node_states_sq

        assert type(transition_list) is list, "transition_list must be a list!"
        assert transition_list, "Transition list must contain at least one transition"
        last_type = None
        for t in transition_list:
            # TODO: make orientation optional for cases where
            # self.number_of_orientations = 1
            if isinstance(t.from_state, tuple) and isinstance(t.to_state, tuple):
                this_type = tuple
            else:
                this_type = int

            if this_type is tuple:
                # added to allow from and to states to be tuples, not just ids
                for i in t.from_state[:-1]:
                    assert (
                        i < self.num_node_states
                    ), "Transition from_state out of range"
                for i in t.to_state[:-1]:
                    assert i < self.num_node_states, "Transition to_state out of range"
                assert (
                    t.from_state[-1] < self.number_of_orientations
                ), "Encoding for orientation in from_state must be < number of orientations."
                assert (
                    t.to_state[-1] < self.number_of_orientations
                ), "Encoding for orientation in to_state must be < number of orientations."
            else:
                assert (
                    t.from_state < self.num_link_states
                ), "Transition from_state out of range"
                assert (
                    t.to_state < self.num_link_states
                ), "Transition to_state out of range"

            assert (
                last_type == this_type or last_type is None
            ), "All transition types must be either int IDs, or all tuples."
            # this test to ensure all entries are either IDs, or tuples, not
            # mixed
            last_type = this_type

        # Create priority queue for events and next_update array for links
        self.next_update = self.grid.add_zeros("link", "next_update_time")
        self.priority_queue = PriorityQueue()
        self.next_trn_id = -np.ones(self.grid.number_of_links, dtype=np.int)

        # Assign link types from node types
        self.create_link_state_dict_and_pair_list()

        # DEJH adds: convert transition_list to IDs if necessary
        # This is the new part that allows Transition from_ and to_ types
        # to be specified either as ints, or as tuples.
        transition_list_as_ID = transition_list[:]
        if type(transition_list[0].from_state) == tuple:
            # (then they all are..., because of the assertions in __init__)
            for i in range(len(transition_list)):
                transition_list_as_ID[i].from_state = self.link_state_dict[
                    transition_list[i].from_state
                ]
                transition_list_as_ID[i].to_state = self.link_state_dict[
                    transition_list[i].to_state
                ]

        # Set up the information needed to determine the orientation of links
        # in the lattice. The default method just creates an array of zeros
        # (all orientations considered the same), but this will be overridden
        # in subclasses that do use orientation.
        self.setup_array_of_orientation_codes()

        # Using the grid of node states, figure out all the link states
        self.assign_link_states_from_node_types()

        # Create transition data for links
        self.setup_transition_data(transition_list_as_ID)

        # Put the various transitions on the event queue
        self.push_transitions_to_event_queue()

        # In order to keep track of cell "properties", we create an array of
        # indices that refer to locations in the caller's code where properties
        # are tracked.
        self.propid = np.arange(self.grid.number_of_nodes)
        if prop_data is None:
            self.prop_data = np.zeros(self.grid.number_of_nodes)
            self.prop_reset_value = 0.0
        else:
            self.prop_data = prop_data
            self.prop_reset_value = prop_reset_value

    def set_node_state_grid(self, node_states):
        """Set the grid of node-state codes to node_states.

        Sets the grid of node-state codes to node_states. Also checks
        to make sure node_states is in the proper format, which is to
        say, it's a Numpy array of the same length as the number of nodes in
        the grid.

        **Creates**:

        *  self.node_state : 1D array of ints (x number of nodes in grid)
           The node-state array

        Parameters
        ----------
        node_states : 1D array of ints (x number of nodes in grid)

        Notes
        -----
        The node-state array is attached to the grid as a field with the name
        'node_state'.
        """
        assert (
            type(node_states) is np.ndarray
        ), "initial_node_states must be a Numpy array"
        assert (
            len(node_states) == self.grid.number_of_nodes
        ), "length of initial_node_states must equal number of nodes in grid"
        self.grid.at_node["node_state"] = node_states
        self.node_state = node_states

    def create_link_state_dict_and_pair_list(self):
        """Create a dict of link-state to node-state.

        Creates a dictionary that can be used as a lookup table to find out
        which link state corresponds to a particular pair of node states. The
        dictionary keys are 3-element tuples, each of which represents the
        state of the TAIL node, the HEAD node, and the orientation of the link.
        The values are integer codes representing the link state numbers.

        Notes
        -----
        Performance note: making self.node_pair a tuple does not appear to
        change time to lookup values in update_node_states. Changing it to a
        2D array of int actually slows it down.
        """
        self.link_state_dict = {}
        self.node_pair = []
        k = 0
        for orientation in range(self.number_of_orientations):
            for tail_state in range(self.num_node_states):
                for head_state in range(self.num_node_states):
                    self.link_state_dict[(tail_state, head_state, orientation)] = k
                    self.node_pair.append((tail_state, head_state, orientation))
                    k += 1

    def setup_array_of_orientation_codes(self):
        """Create array of active link orientation codes.

        Creates and configures an array that contain the orientation code for
        each active link (and corresponding cell pair).

        **creates**:

        * ``self.link_orientation`` : 1D numpy array

        Notes
        -----

        The setup varies depending on the type of LCA. The default is
        non-oriented, in which case we just have an array of zeros. Subclasses
        will override this method to handle lattices in which orientation
        matters (for example, vertical vs. horizontal in an OrientedRasterLCA).
        """
        self.link_orientation = np.zeros(self.grid.number_of_links, dtype=np.int8)

    def assign_link_states_from_node_types(self):
        """Assign link-state code for each link.

        Takes lists/arrays of "tail" and "head" node IDs for each link, and a
        dictionary that associates pairs of node states (represented as a
        3-element tuple, comprising the TAIL state, FROM state, and
        orientation) to link states.

        **creates**:

        * ``self.link_state`` : 1D numpy array
        """
        self.link_state = np.zeros(self.grid.number_of_links, dtype=int)

        for i in self.grid.active_links:
            orientation = self.link_orientation[i]
            node_pair = (
                self.node_state[self.grid.node_at_link_tail[i]],
                self.node_state[self.grid.node_at_link_head[i]],
                orientation,
            )
            self.link_state[i] = self.link_state_dict[node_pair]

    def setup_transition_data(self, xn_list):
        """Create transition data arrays."""

        # First, create an array that stores the number of possible transitions
        # out of each state.
        n_xn = np.zeros(self.num_link_states, dtype=int)
        for xn in xn_list:
            n_xn[xn.from_state] += 1
        self.n_trn = np.zeros(self.num_link_states, dtype=np.int)

        # Now, create arrays to hold the "to state" and transition rate for each
        # transition. These arrays are dimensioned N x M where N is the number
        # of states, and M is the maximum number of transitions from a single
        # state (for example if state 3 could transition either to state 1 or
        # state 4, and the other states only had one or zero possible
        # transitions, then the maximum would be 2).
        max_transitions = np.max(n_xn)
        self.trn_id = np.zeros((self.num_link_states, max_transitions), dtype=np.int)
        num_transitions = len(xn_list)
        self.trn_to = np.zeros(num_transitions, dtype=np.int)
        self.trn_rate = np.zeros(num_transitions)
        self.trn_propswap = np.zeros(num_transitions, dtype=np.int8)
        self.trn_prop_update_fn = np.zeros(num_transitions, dtype=object)

        for trn in range(num_transitions):
            self.trn_to[trn] = xn_list[trn].to_state
            self.trn_rate[trn] = xn_list[trn].rate
            self.trn_propswap[trn] = xn_list[trn].swap_properties
            if xn_list[trn].prop_update_fn is not None:
                self.trn_prop_update_fn[trn] = xn_list[trn].prop_update_fn
                self._use_propswap_or_callback = True
            from_state = xn_list[trn].from_state
            self.trn_id[from_state, self.n_trn[from_state]] = trn
            self.n_trn[from_state] += 1

    def push_transitions_to_event_queue(self):
        """
        Initializes the event queue by creating transition events for each
        cell pair that has one or more potential transitions and pushing these
        onto the queue. Also records scheduled transition times in the
        self.next_update array.

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.oriented_raster_cts import OrientedRasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 0, 0), 1.0))
        >>> trn_list.append(Transition((1, 0, 0), (0, 1, 0), 2.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 0, 1), 3.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 1, 1), 4.0))
        >>> ins = np.arange(15) % 2
        >>> cts = OrientedRasterCTS(grid, nsd, trn_list, ins)
        >>> ev0 = cts.priority_queue._queue[0]
        >>> np.round(100 * ev0[0])
        12.0
        >>> ev0[2]  # this is the link ID
        16
        >>> ev6 = cts.priority_queue._queue[6]
        >>> np.round(100 * ev6[0])
        27.0
        >>> ev6[2]  # this is the link ID
        6
        >>> cts.next_trn_id[ev0[2]]  # ID of the transition to occur at this link
        3
        >>> cts.next_trn_id[cts.grid.active_links]
        array([-1,  2, -1,  1,  0,  1,  0,  2, -1,  3])
        """
        push_transitions_to_event_queue(
            self.grid.number_of_active_links,
            self.grid.active_links,
            self.n_trn,
            self.link_state,
            self.trn_id,
            self.trn_rate,
            self.next_update,
            self.next_trn_id,
            self.priority_queue,
        )

    def update_link_state_new(self, link, new_link_state, current_time):
        """
        Implements a link transition by updating the current state of the link
        and (if appropriate) choosing the next transition event and pushing it
        on to the event queue.

        Parameters
        ----------
        link : int
            ID of the link to update
        new_link_state : int
            Code for the new state
        current_time : float
            Current time in simulation
        """

        # If the link connects to a boundary, we might have a different state
        # than the one we planned
        if self.bnd_lnk[link]:
            fns = self.node_state[self.grid.node_at_link_tail[link]]
            tns = self.node_state[self.grid.node_at_link_head[link]]
            orientation = self.link_orientation[link]
            new_link_state = int(
                orientation * self.num_node_states_sq + fns * self.num_node_states + tns
            )

        self.link_state[link] = new_link_state
        if self.n_trn[new_link_state] > 0:
            (event_time, trn_id) = get_next_event_new(
                link,
                new_link_state,
                current_time,
                self.n_trn,
                self.trn_id,
                self.trn_rate,
            )
            self.priority_queue.push(link, event_time)
            self.next_update[link] = event_time
            self.next_trn_id[link] = trn_id
        else:
            self.next_update[link] = _NEVER
            self.next_trn_id[link] = -1

    def update_component_data(self, new_node_state_array):
        """Update all component data.

        Call this method to update all data held by the component, if, for
        example, another component or boundary conditions modify the node
        statuses outside the component between run steps.

        This method updates all necessary properties, including both node and
        link states.

        *new_node_state_array* is the updated list of node states, which must
        still all be compatible with the state list originally supplied to
        this component.

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.raster_cts import RasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 1, 0), 1.0))
        >>> ins = np.zeros(15, dtype=np.int)
        >>> ca = RasterCTS(grid, nsd, trn_list, ins)
        >>> list(ca.node_state[6:9])
        [0, 0, 0]
        >>> list(ca.link_state[9:13])
        [0, 0, 0, 0]
        >>> len(ca.priority_queue._queue)  # there are no transitions
        0
        >>> nns = np.arange(15) % 2        # make a new node-state grid...
        >>> ca.update_component_data(nns)  # ...and assign it
        >>> list(ca.node_state[6:9])
        [0, 1, 0]
        >>> list(ca.link_state[9:13])
        [2, 1, 2, 1]
        >>> len(ca.priority_queue._queue)  # now there are 5 transitions
        5
        """
        self.set_node_state_grid(new_node_state_array)
        self.assign_link_states_from_node_types()
        self.push_transitions_to_event_queue()

    # @profile
    def run(
        self, run_to, node_state_grid=None, plot_each_transition=False, plotter=None
    ):
        """Run the model forward for a specified period of time.

        Parameters
        ----------
        run_to : float
            Time to run to, starting from self.current_time
        node_state_grid : 1D array of ints (x number of nodes) (optional)
            Node states (if given, replaces model's current node state grid)
        plot_each_transition : bool (optional)
            Option to display the grid after each transition
        plotter : CAPlotter object (optional)
            Needed if caller wants to plot after every transition

        Examples
        --------
        >>> from landlab import RasterModelGrid
        >>> from landlab.ca.celllab_cts import Transition
        >>> from landlab.ca.oriented_raster_cts import OrientedRasterCTS
        >>> import numpy as np
        >>> grid = RasterModelGrid((3, 5))
        >>> nsd = {0 : 'zero', 1 : 'one'}
        >>> trn_list = []
        >>> trn_list.append(Transition((0, 1, 0), (1, 0, 0), 1.0))
        >>> trn_list.append(Transition((1, 0, 0), (0, 1, 0), 2.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 0, 1), 3.0))
        >>> trn_list.append(Transition((0, 1, 1), (1, 1, 1), 4.0))
        >>> ins = np.arange(15) % 2
        >>> cts = OrientedRasterCTS(grid, nsd, trn_list, ins)
        """
        if node_state_grid is not None:
            self.set_node_state_grid(node_state_grid)

        self.current_time = run_cts_new(
            run_to,
            self.current_time,
            self.priority_queue,
            self.next_update,
            self.grid.node_at_link_tail,
            self.grid.node_at_link_head,
            self.node_state,
            self.next_trn_id,
            self.trn_to,
            self.grid.status_at_node,
            self.num_node_states,
            self.num_node_states_sq,
            self.bnd_lnk,
            self.link_orientation,
            self.link_state,
            self.n_trn,
            self.trn_id,
            self.trn_rate,
            self.grid.links_at_node,
            self.grid.active_link_dirs_at_node,
            self.trn_propswap,
            self.propid,
            self.prop_data,
            self.prop_reset_value,
            self.trn_prop_update_fn,
            self,
            plot_each_transition,
            plotter,
        )