def test_reduce_with_reducer(): layers = EventLayers(3) layers.add([2, 2, 2], age=1.0) layers.add([2, 2, 2], age=3.0) layers.add([2, 2, 2], age=4.0) layers.reduce(1, 3, age=np.mean) assert_array_equal(layers["age"], [[1.0, 1.0, 1.0], [3.5, 3.5, 3.5]])
def test_reduce_with_attrs(): layers = EventLayers(3) layers.add([1, 1, 1], age=0.0) layers.add([1, 2, 5], age=1.0) layers.add([2, 2, 2], age=2.0) layers.reduce(age=np.sum) assert_array_equal(layers["age"], [[3.0, 3.0, 3.0]])
def test__str__(): layers = EventLayers(5) layers.add(1.0, age=3.0) vals = str(layers) assert vals.splitlines() == [ "number_of_layers: 1", "number_of_stacks: 5", "tracking: age" ]
def test_reduce_with_start(): layers = EventLayers(3) layers.add([3.0, 4.0, 5.0]) layers.add([1.0, 2.0, 0.5]) layers.add([1.0, 2.0, 0.5]) layers.reduce(1, 3) assert_array_equal(layers.dz, [[3.0, 4.0, 5.0], [2.0, 4.0, 1.0]])
def test_reduce_with_no_args(): layers = EventLayers(3) layers.add(1.5) layers.reduce() assert_array_equal(layers.dz, [[1.5, 1.5, 1.5]]) layers.add(2.5) layers.reduce() assert_array_equal(layers.dz, [[4.0, 4.0, 4.0]])
def test_reduce_with_stop_less_than_start(): layers = EventLayers(3) layers.add([3.0, 4.0, 5.0]) layers.add([1.0, 2.0, 0.5]) layers.add([1.0, 2.0, 0.5]) layers.add([2.0, 5.0, 6.0]) with pytest.raises(ValueError): layers.reduce(3, 1)
def test_setitem_with_scalar(): layers = EventLayers(5) layers.add(1., age=3.) layers.add(2., age=4.) truth = np.array([[3., 3., 3., 3., 3.], [4., 4., 4., 4., 4.]]) assert_array_equal(layers["age"], truth) layers["age"] = 2. truth = np.array([[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]]) assert_array_equal(layers["age"], truth)
def test_setitem_with_scalar(): layers = EventLayers(5) layers.add(1.0, age=3.0) layers.add(2.0, age=4.0) truth = np.array([[3.0, 3.0, 3.0, 3.0, 3.0], [4.0, 4.0, 4.0, 4.0, 4.0]]) assert_array_equal(layers["age"], truth) layers["age"] = 2.0 truth = np.array([[2.0, 2.0, 2.0, 2.0, 2.0], [2.0, 2.0, 2.0, 2.0, 2.0]]) assert_array_equal(layers["age"], truth)
def test_set_item_with_2d(): layers = EventLayers(5) layers.add(1., age=3.) layers.add(2., age=4.) truth = np.array([[3., 3., 3., 3., 3.], [4., 4., 4., 4., 4.]]) assert_array_equal(layers["age"], truth) layers["age"] = [[4., 4., 4., 4., 4.], [7., 7., 7., 7., 7.]] truth = np.array([[4., 4., 4., 4., 4.], [7., 7., 7., 7., 7.]]) assert_array_equal(layers["age"], truth)
def test_set_item_with_2d(): layers = EventLayers(5) layers.add(1.0, age=3.0) layers.add(2.0, age=4.0) truth = np.array([[3.0, 3.0, 3.0, 3.0, 3.0], [4.0, 4.0, 4.0, 4.0, 4.0]]) assert_array_equal(layers["age"], truth) layers["age"] = [[4.0, 4.0, 4.0, 4.0, 4.0], [7.0, 7.0, 7.0, 7.0, 7.0]] truth = np.array([[4.0, 4.0, 4.0, 4.0, 4.0], [7.0, 7.0, 7.0, 7.0, 7.0]]) assert_array_equal(layers["age"], truth)
def __init__(self, grid, thicknesses, ids, attrs, layer_type="MaterialLayers"): """Create a new instance of Lithology. Parameters ---------- grid : Landlab ModelGrid thicknesses : ndarray of shape `(n_layers, )` or `(n_layers, n_nodes)` Values of layer thicknesses from surface to depth. Layers do not have to have constant thickness. Layer thickness can be zero, though the entirety of Lithology must have non-zero thickness. ids : ndarray of shape `(n_layers, )` or `(n_layers, n_nodes)` Values of rock type IDs cooresponding to each layer specified in **thicknesses**. A single layer may have multiple rock types if specified by the user. attrs : dict Rock type property dictionary. See class docstring for example of required format. layer_type : str, optional Type of Landlab layers object used to store the layers. If MaterialLayers (default) is specified, then erosion removes material and does not create a layer of thickness zero. If EventLayers is used, then erosion removes material and creates layers of thickness zero. Thus, EventLayers may be appropriate if the user is interested in chronostratigraphy. Examples -------- >>> from landlab import RasterModelGrid >>> from landlab.components import Lithology >>> mg = RasterModelGrid(3, 3) >>> z = mg.add_zeros('node', 'topographic__elevation') Create a Lithology with uniform thicknesses that alternates between layers of type 1 and type 2 rock. >>> thicknesses = [1, 2, 4, 1] >>> ids = [1, 2, 1, 2] >>> attrs = {'K_sp': {1: 0.001, ... 2: 0.0001}} >>> lith = Lithology(mg, thicknesses, ids, attrs) After creating a Lithology, the model grid will have an at-node grid field set to the surface values of 'K_sp'. >>> mg.at_node['K_sp'] array([ 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001]) The surface values are also properties of the Lithology. >>> lith['K_sp'] array([ 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001]) We can access information about the Lithology like the total thickness or layer thicknesses. >>> lith.thickness array([ 8., 8., 8., 8., 8., 8., 8., 8., 8.]) >>> lith.dz array([[ 1., 1., 1., 1., 1., 1., 1., 1., 1.], [ 4., 4., 4., 4., 4., 4., 4., 4., 4.], [ 2., 2., 2., 2., 2., 2., 2., 2., 2.], [ 1., 1., 1., 1., 1., 1., 1., 1., 1.]]) This might look confusing -- that the layers are in reverse order, but it is OK. The last layers in the Lithology are those that are closest to the surface. The layers don't all have to have the same thickness as in the prior example. If the layers have non-uniform thickness, then they must be specified in an array of shape `(n_layer, n_nodes)`. In this case, the layer IDs must be specified in either an array of `(n_layer)` or `(n_layer, n_nodes)`. Here we make a layer that gets thicker as a function of the x value of the model grid. >>> layer_pattern = (0.5 * mg.x_of_node) + 1.0 >>> thicknesses = [1*layer_pattern, 2*layer_pattern, 4*layer_pattern] >>> ids = [1, 2, 1] >>> lith = Lithology(mg, thicknesses, ids, attrs) >>> lith.thickness array([ 7. , 10.5, 14. , 7. , 10.5, 14. , 7. , 10.5, 14. ]) >>> lith.dz array([[ 4. , 6. , 8. , 4. , 6. , 8. , 4. , 6. , 8. ], [ 2. , 3. , 4. , 2. , 3. , 4. , 2. , 3. , 4. ], [ 1. , 1.5, 2. , 1. , 1.5, 2. , 1. , 1.5, 2. ]]) """ # save reference to the grid and the last time steps's elevation. self._grid = grid try: self.last_elevation = self._grid['node'][ 'topographic__elevation'][:].copy() except KeyError: msg = ('Lithology requires that topographic__elevation already ' 'exists as an at-node field.') raise ValueError(msg) # save inital information about thicknesses, layers, attributes, and ids. self._init_thicknesses = np.asarray(thicknesses) self._attrs = attrs self._number_of_init_layers = self._init_thicknesses.shape[0] self._properties = list(attrs.keys()) self._rock_id_name = 'rock_type__id' # assert that thicknesses and ids are correct and consistent shapes # if thickness is a 2d array. if self._init_thicknesses.ndim == 2: # assert that the 2nd dimension is the same as the number of nodes. if self._init_thicknesses.shape[1] != self._grid.number_of_nodes: msg = ('Thicknesses provided to Lithology are ', 'inconsistent with the ModelGrid.') raise ValueError(msg) # if IDs is a 2d array assert that it is the same size as thicknesses if np.asarray(ids).ndim == 2: if self._init_thicknesses.shape != np.asarray(ids).shape: msg = ('Thicknesses and IDs provided to Lithology are ', 'inconsistent with each other.') raise ValueError(msg) # if tests pass set value of IDs. self._layer_ids = np.asarray(ids) # if IDS is a 1d array elif np.asarray(ids).ndim == 1: if np.asarray(ids).size != self._number_of_init_layers: msg = ('Number of IDs provided to Lithology is ', 'inconsistent with number of layers provided in ' 'thicknesses.') raise ValueError(msg) # if tests pass, broadcast ids to correct shape. self._layer_ids = np.broadcast_to( np.atleast_2d(np.asarray(ids)).T, self._init_thicknesses.shape) else: msg = ('IDs must be of shape `(n_layers, )` or `(n_layers, ' 'n_nodes)`. Passed array has more than 2 dimensions.') raise ValueError(msg) elif self._init_thicknesses.ndim == 1: if self._init_thicknesses.shape != np.asarray(ids).shape: msg = ('Thicknesses and IDs provided to Lithology are ', 'inconsistent with each other.') raise ValueError(msg) self._layer_ids = np.asarray(ids) else: msg = ( 'Thicknesses must be of shape `(n_layers, )` or `(n_layers, ' 'n_nodes)`. Passed array has more than 2 dimensions.') raise ValueError(msg) # assert that attrs are pointing to fields (or create them) for at in self._properties: if at not in grid.at_node: self._grid.add_empty('node', at) # add a field for the rock type id if self._rock_id_name not in self._grid.at_node: self._grid.add_empty('node', self._rock_id_name) # verify that all IDs have attributes. self._check_property_dictionary() # create a EventLayers instance if layer_type == 'EventLayers': self._layers = EventLayers(grid.number_of_nodes, self._number_of_init_layers) elif layer_type == 'MaterialLayers': self._layers = MaterialLayers(grid.number_of_nodes, self._number_of_init_layers) else: raise ValueError(('Lithology passed an invalid option for ' 'layer type.')) # From bottom to top, add layers to the Lithology with attributes. for i in range(self._number_of_init_layers - 1, -1, -1): try: self.add_layer(self._init_thicknesses[i, :], self._layer_ids[i, :]) except IndexError: self.add_layer(self._init_thicknesses[i], self._layer_ids[i])
def test_adding_untracked_layer(): layers = EventLayers(3) layers.add(1., type=3., size="sand") layers.add([0., 0., 1.], type=3., size="sand") with pytest.raises(ValueError): layers.add([1.], type=3., size="sand", spam="eggs")
def test__repr__(): layers = EventLayers(5) layers.add(1., age=3.) vals = repr(layers) assert vals == "EventLayers(5)"
def test__str__(): layers = EventLayers(5) layers.add(1., age=3.) vals = str(layers) assert vals == "number_of_layers: 1\nnumber_of_stacks: 5\ntracking: age"
def test_reduce_with_start_too_big(): layers = EventLayers(3) layers.add(1) layers.reduce(2, 4) assert_array_equal(layers.dz, [[1, 1, 1]])
def test_reduce_with_no_layers(): layers = EventLayers(3) layers.reduce() assert_array_equal(layers.dz, np.empty((0, 3)))
def test_reduce_with_one_layer(): layers = EventLayers(3) layers.add(1.0) layers.reduce() assert_array_equal(layers.dz, [[1, 1, 1]])
def test_reduce_partial_block(): layers = EventLayers(3) for layer in range(6): layers.add(layer) layers.reduce(1, 6, 4) assert_array_equal(layers.dz, [[0, 0, 0], [10, 10, 10], [5, 5, 5]])
def test_reduce_args(args): layers = EventLayers(3) for layer in range(4): layers.add(layer) layers.reduce(4) assert_array_equal(layers.dz, [[6, 6, 6]])
def test_reduce_with_missing_property(): layers = EventLayers(3) for layer in range(4): layers.add(layer, age=layer) with pytest.raises(TypeError): layers.reduce(1, 3)
def test_reduce_with_unknown_property(): layers = EventLayers(3) for layer in range(4): layers.add(layer) with pytest.raises(TypeError): layers.reduce(1, 3, age=np.mean)