def add_stem_properties(phenomenon): # Property-set for properties attached to tree stems. Stems # are located in space using stationary 2D points. # Property-set space_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.point) space_coordinate_datatype = np.dtype(np.float32) stem_points = phenomenon.add_property_set( "stems", space_configuration, space_coordinate_datatype, rank) # Space domain space_domain = stem_points.space_domain space_points = np.arange(nr_trees * rank, dtype=space_coordinate_datatype).reshape( nr_trees, 2) space_domain.value.expand(nr_trees)[:] = space_points # Property tree_kind = stem_points.add_property("kind", dtype=np.dtype(np.uint8)) tree_kind.value.expand(nr_trees)[:] = \ (10 * np.random.rand(nr_trees)).astype(np.uint8) return stem_points
def test_case_study(self): dataset = lue.create_dataset("areas.lue") areas = dataset.add_phenomenon("areas") nr_areas = 10 # IDs ids = numpy.arange(nr_areas, dtype=numpy.uint64) areas.object_id.expand(nr_areas)[:] = ids space_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.box) coordinate_datatype = numpy.dtype(numpy.float32) rank = 2 area_boxes = areas.add_property_set("areas", space_configuration, coordinate_datatype, rank) # Space domain space_domain = area_boxes.space_domain boxes = numpy.arange(nr_areas * rank * 2, dtype=coordinate_datatype).reshape(nr_areas, 4) space_domain.value.expand(nr_areas)[:] = boxes # Discretization property count_datatype = lue.dtype.Count discretization = area_boxes.add_property("discretization", dtype=count_datatype, shape=(rank, )) shapes = numpy.arange(nr_areas * rank, dtype=count_datatype).reshape(nr_areas, 2) discretization.value.expand(nr_areas)[:] = shapes # Elevation property elevation_datatype = numpy.dtype(numpy.float32) elevation = area_boxes.add_property("elevation", dtype=elevation_datatype, rank=rank) grids = elevation.value.expand(ids, shapes) for a in range(nr_areas): grids[ids[a]][:] = \ (10 * numpy.random.rand(*shapes[a])).astype(elevation_datatype) # Link elevation to discretization elevation.set_space_discretization( lue.SpaceDiscretization.regular_grid, discretization) lue.assert_is_valid(dataset)
import numpy as np import lue nr_areas = 10 rank = 2 dataset = lue.create_dataset("elevation.lue") area = dataset.add_phenomenon("area") # id = [2, 4, 6, 8, 10, 9, 7, 5, 3, 1] # area.object_id.expand(nr_areas)[:] = id time_configuration = lue.TimeConfiguration(lue.TimeDomainItemType.box) clock = lue.Clock(lue.Unit.day, 1) space_configuration = lue.SpaceConfiguration(lue.Mobility.stationary, lue.SpaceDomainItemType.box) variable = area.add_property_set("variable", time_configuration, clock, space_configuration, space_coordinate_dtype=np.dtype(np.float32), rank=rank) object_tracker = variable.object_tracker ### # Time domain contains 1D boxes, with a resolution of 1 day ### time_domain = located.create_time_box_domain(areas, lue.Clock(lue.unit.day, 1)) ### nr_time_boxes = 4 ### time_boxes = time_domain.reserve(nr_time_boxes) ### # A box is defined by a begin and end time point (two coordinates per box) ### # Here, we configure time boxes with a duration of 10 days. The time
def discharge_property(phenomenon): nr_outlets = 10 ids = numpy.arange(nr_outlets, dtype=numpy.uint64) phenomenon.object_id.expand(nr_outlets)[:] = ids # Time domain time_configuration = lue.TimeConfiguration( lue.TimeDomainItemType.box) clock = lue.Clock(lue.Unit.day, 1) # Space domain space_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.point) space_coordinate_datatype = numpy.dtype(numpy.float64) rank = 2 # Property set outlet_points = phenomenon.add_property_set( "outlets", time_configuration, clock, space_configuration, space_coordinate_datatype, rank) # IDs object_tracker = outlet_points.object_tracker object_tracker.active_set_index.expand(nr_time_boxes) active_set_sizes = numpy.random.randint( 0, nr_outlets, nr_time_boxes).astype(dtype=lue.dtype.Count) active_set_idxs = numpy.empty(nr_time_boxes, dtype=lue.dtype.Index) active_object_ids = numpy.empty(active_set_sizes.sum(), dtype=lue.dtype.ID) lue.test.select_random_ids(active_set_sizes, active_set_idxs, active_object_ids, nr_outlets) object_tracker.active_object_id.expand(len(active_object_ids)) active_set_idx = numpy.uint64(0) for s in range(nr_time_boxes): active_set_size = active_set_sizes[s] object_tracker.active_set_index[s] = active_set_idxs[s] object_tracker.active_object_id[ active_set_idx:active_set_idx + active_set_size] = \ active_object_ids[active_set_idx:active_set_idx + active_set_size] active_set_idx += active_set_size # Time domain time_domain = outlet_points.time_domain time_coordinate_datatype = lue.dtype.TickPeriodCount time_boxes = numpy.arange(nr_time_boxes * 2, dtype=time_coordinate_datatype).reshape( nr_time_boxes, 2) time_domain.value.expand(nr_time_boxes)[:] = time_boxes # Space domain space_domain = outlet_points.space_domain space_points = numpy.arange( nr_outlets * rank, dtype=space_coordinate_datatype).reshape(nr_outlets, 2) space_domain.value.expand(nr_outlets)[:] = space_points # Property discharge_datatype = numpy.dtype(numpy.float32) discharge = outlet_points.add_property( "discharge", dtype=discharge_datatype, rank=1, shape_per_object=lue.ShapePerObject.same, shape_variability=lue.ShapeVariability.variable) discharge_values = [ numpy.arange(shapes[t] * active_set_sizes[t], dtype=discharge_datatype) for t in range(nr_time_boxes) ] for t in range(nr_time_boxes): discharge.value.expand( t, active_set_sizes[t], (shapes[t],))[:] = \ discharge_values[t] return discharge
def add_crown_properties(phenomenon): # Property-set for properties attached to tree crowns. Crowns # are located in space using stationary 2D boxes within # which the presence is discretized. The presence of the # growing crowns changes through time. # So, even though the boxes are stationary, presence can # still vary through time. # Property set time_configuration = lue.TimeConfiguration( lue.TimeDomainItemType.box) clock = lue.Clock(lue.Unit.week, 1) space_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.box) space_coordinate_datatype = np.dtype(np.float32) crown_boxes = phenomenon.add_property_set( "crowns", time_configuration, clock, space_configuration, space_coordinate_datatype, rank) # [0, nr_trees, 2 * nr_tree, ..., t * nr_trees] crown_boxes.object_tracker.active_set_index.expand( nr_time_boxes)[:] = \ np.array( [t * nr_trees for t in range(nr_time_boxes)], dtype=lue.dtype.Index) # [0, 0, 0, ..., nr_time_boxes-1, nr_time_boxes-1] crown_boxes.object_tracker.active_object_index.expand( nr_time_boxes * nr_trees)[:] = \ np.repeat( np.arange(0, nr_time_boxes, dtype=lue.dtype.Index), repeats=nr_trees) # [id1, id2, ..., idn, ..., id1, id2, ...idn] crown_boxes.object_tracker.active_object_id.expand( nr_time_boxes * nr_trees)[:] = \ np.repeat( ids.reshape((1, nr_trees)), repeats=nr_time_boxes, axis=0).flatten() # Space domain space_domain = crown_boxes.space_domain boxes = np.arange( nr_trees * rank * 2, dtype=space_coordinate_datatype) \ .reshape(nr_trees, 4) space_domain.value.expand(nr_trees)[:] = boxes # Time domain time_domain = crown_boxes.time_domain time_coordinate_datatype = lue.dtype.TickPeriodCount boxes = np.arange( nr_time_boxes * 2, dtype=time_coordinate_datatype) \ .reshape(nr_time_boxes, 2) time_domain.value.expand(nr_time_boxes)[:] = boxes presence = discretized_presence(crown_boxes) crown_boxes.space_domain.set_presence_discretization(presence) return crown_boxes
def test_case_study2(self): dataset = lue.create_dataset("forest.lue") # We are assuming here that we can model biomass of trees in a # forest on a daily basis, during the growth season. This allows # us to use multiple discretized time boxes. time_cell_configuration = lue.TimeConfiguration( lue.TimeDomainItemType.cell) clock = lue.Clock(lue.Unit.day, 1) time_coordinate_datatype = lue.dtype.TickPeriodCount # Trees usually don't move. Forests neither. stationary_space_point_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.point) stationary_space_box_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.box) space_coordinate_datatype = np.dtype(np.float32) space_rank = 2 count_datatype = lue.dtype.Count cell_size = 0.5 # m max_size_of_crown = int(20 / cell_size) # 20 m in nr of cells nr_cells_in_crown = max_size_of_crown**2 biomass_datatype = np.dtype(np.float32) # Trees ---------------------------------------------------------------- trees = dataset.add_phenomenon("trees") stems = trees.add_property_set("stems", stationary_space_point_configuration, space_coordinate_datatype, space_rank) crowns = trees.add_property_set("crowns", time_cell_configuration, clock, stationary_space_box_configuration, space_coordinate_datatype, space_rank) crowns_biomass = crowns.add_property( "biomass", dtype=biomass_datatype, shape=(max_size_of_crown, max_size_of_crown), value_variability=lue.ValueVariability.variable) # Biomass discretization # Each biomass value is a 2D value with the same shape # (max_size_of_crown x max_size_of_crown). In this value biomass # values are stored per cell. The discretization of each extent # is stored in the crown discretization property. This property # only needs to store a single value with nr_rows/nr_cols of each # crown extent. This value is the same for each crown and does # not change through time. It can therefore be stored as a static # property in the collection property sets of the trees phenomenon. trees_globals = trees.add_collection_property_set("globals") crowns_biomass_discretization = trees_globals.add_property( "biomass_discretization", dtype=count_datatype, shape=(space_rank, )) crowns_biomass_discretization.value.expand(1)[:] = np.array( [max_size_of_crown, max_size_of_crown], dtype=count_datatype).reshape(1, space_rank) # Link biomass to discretization crowns_biomass.set_space_discretization( lue.SpaceDiscretization.regular_grid, crowns_biomass_discretization) # Forests -------------------------------------------------------------- # TODO Each forest could be represented by a boolean space # grid in the property set. If this is not necessary, biomass # can be a discretized property within an extent. forests = dataset.add_phenomenon("forests") areas = forests.add_property_set("areas", crowns.time_domain, stationary_space_box_configuration, space_coordinate_datatype, space_rank) areas_biomass = areas.add_property( "biomass", dtype=biomass_datatype, rank=space_rank, shape_per_object=lue.ShapePerObject.different, shape_variability=lue.ShapeVariability.constant) # Forest biomass discretization forests_globals = forests.add_collection_property_set("globals") forests_biomass_discretization = forests_globals.add_property( "biomass_discretization", dtype=count_datatype, shape=(space_rank, )) # TODO The extent of the forest must be known beforehand nr_forests = 1 forest_ids = np.arange(nr_forests, dtype=lue.dtype.ID) forest_shapes = \ np.arange(nr_forests * space_rank, dtype=count_datatype) \ .reshape(nr_forests, space_rank) + 10 forests_biomass_discretization.value.expand(nr_forests)[:] = np.array( [100, 100], dtype=count_datatype).reshape(nr_forests, space_rank) # Link biomass to discretization areas_biomass.set_space_discretization( lue.SpaceDiscretization.regular_grid, forests_biomass_discretization) # Iterate over time boxes (only growth season) # Iterate over time cells # - Calculate birth and death of trees # - Birth: # - Add ID to trees phenomenon # - Add location to stem property-set # - Add ID to crowns property set object tracker # - Center max_crown_extent sized space box # around stem and add to crowns property-set # - Store presence in boolean presence space grid # and for all true cell store a biomass, or # - store biomass distribution as a discretized # 2D value # - Death # - Stop writing ID to crowns property set # object tracker # - Stop writing (presence grid and) biomass # property values # - Calculate tree growth of living trees # - Calculate the distribution of biomass within the forest # - Per forest, write current biomass to biomass # property # TODO Fix handling of time # Per year, only changes in state during the growth season # are stored nr_years = 10 nr_days_per_year = 365 start_of_growth_season = 50 # Within each year nr_days_per_growth_season = 200 crowns_time_boxes = crowns.time_domain.value.expand(nr_years) crowns_time_cell_counts = \ crowns.time_domain.value.count.expand(nr_years) crowns_active_set_index = crowns.object_tracker.active_set_index crowns_active_object_id = crowns.object_tracker.active_object_id areas_active_set_index = areas.object_tracker.active_set_index areas_active_object_id = areas.object_tracker.active_object_id areas_active_object_idx = areas.object_tracker.active_object_index for id_ in forest_ids: areas_biomass.value.expand(forest_ids[id_], tuple(forest_shapes[id_]), nr_years * nr_days_per_growth_season) for y in range(nr_years): # Time domain item for this growth season t_start = (y * nr_days_per_year) + start_of_growth_season t_end = t_start + nr_days_per_growth_season time_box = np.array([t_start, t_end], dtype=time_coordinate_datatype) crowns_time_boxes[y] = time_box crowns_time_cell_counts[y] = nr_days_per_growth_season for d in range(nr_days_per_growth_season): d_idx = y * nr_days_per_growth_season + d # TODO # Determine collection of starting trees: # - ID # - Stem location # - Crown extent starting_tree_ids = np.array([d_idx], dtype=lue.dtype.ID) nr_starting_trees = len(starting_tree_ids) starting_tree_stem_locations = np.arange( nr_starting_trees * space_rank, dtype=space_coordinate_datatype).reshape( nr_starting_trees, space_rank) starting_tree_crown_locations = np.arange( nr_starting_trees * space_rank * 2, dtype=space_coordinate_datatype).reshape( nr_starting_trees, space_rank * 2) # Store IDs of starting trees in collection of IDs trees.object_id.expand(nr_starting_trees)[-nr_starting_trees] = \ starting_tree_ids # Store stem locations of starting trees stems.space_domain.value.expand(nr_starting_trees)[-nr_starting_trees] = \ starting_tree_stem_locations # Store crown extents of starting trees crowns.space_domain.value.expand(nr_starting_trees)[-nr_starting_trees] = \ starting_tree_crown_locations # Determine collection of stopping trees: # - ID stopping_tree_ids = np.array([], dtype=lue.dtype.ID) # Determine collection of active trees # - ID # - Biomass active_tree_ids = starting_tree_ids # TODO remove stopping trees # Store IDs of active trees in the active set object_index = crowns_active_object_id.nr_ids crowns_active_set_index.expand(1)[-1] = object_index nr_active_trees = len(active_tree_ids) crowns_active_object_id.expand(nr_active_trees)[object_index:] = \ active_tree_ids # For all active trees, calculate new biomass values and # write them to the biomass property crowns_biomass_values = \ np.arange( nr_active_trees * nr_cells_in_crown, dtype=biomass_datatype).reshape( nr_active_trees, max_size_of_crown, max_size_of_crown) crowns_biomass.value.expand(nr_active_trees)[object_index:] = \ crowns_biomass_values # Store IDs of active forests in the active set # Currently this is the one and only forest containing # all trees. active_forest_ids = np.array([0], dtype=lue.dtype.ID) active_forest_idxs = np.array([d], dtype=lue.dtype.Index) object_index = areas_active_object_id.nr_ids areas_active_set_index.expand(1)[-1] = object_index nr_active_forests = len(active_forest_ids) areas_active_object_id.expand(nr_active_forests)[object_index:] = \ active_forest_ids areas_active_object_idx.expand(nr_active_forests)[object_index:] = \ active_forest_idxs # For all forests, calculate new biomass values and # write them to the biomass property for id_ in forest_ids: forest_shape = tuple(forest_shapes[id_]) nr_cells_in_forest = forest_shape[0] * forest_shape[1] areas_biomass.value[id_][y] = \ np.arange(nr_cells_in_forest, dtype=biomass_datatype) \ .reshape(*forest_shape) # Initialize forests. All trees should be located within one of # the forests. For now, we assume all trees are part of a # single forest. The extent of this forest is the bounding box # around all tree crowns. nr_trees = len(crowns.space_domain.value) if nr_trees > 0: # Store IDs of starting trees in collection of IDs forests.object_id.expand(1)[-1] = 0 min_x, min_y, max_x, max_y = crowns.space_domain.value[0] for crown_space_box in crowns.space_domain.value[1:]: min_x, min_y, max_x, max_y = \ min(min_x, crown_space_box[0]), \ min(min_y, crown_space_box[1]), \ max(max_x, crown_space_box[2]), \ max(max_y, crown_space_box[3]) forest_space_box = np.array([min_x, min_y, max_x, max_y], dtype=space_coordinate_datatype) areas.space_domain.value.expand(1)[-1] = forest_space_box lue.assert_is_valid(dataset)
def test_case_study(self): # Time series as implemented here: # - Discharge at catchment outlets # - Located at fixed points in space # - Variable number of outlets per time cell # - Presence of outlets is discretized within multiple time boxes # - Time domain contains time cells # - Space domain contains space points # - Property values are same_shape::constant_shape (shape of value is # related to what is stored per cell) # - Property values are not discretized # - Per time cell the set of active objects is tracked # - Use this approach if the active set is variable within a # time box # - - Additional storage required for tracking active sets, # compared to Time series I # - + Possible to let objects be 'born' and 'die' during # iterative simulation dataset = lue.create_dataset("outlets2.lue") phenomenon = dataset.add_phenomenon("areas") # Assume we are simulating some temporal variable (discharge at # catchment outlets). # The existance of the objects is modelled using time cells, # which are discretized time boxes (daily time steps). Per cell we # can store which objects are active. # Property values are located in time at time cells. # Property values are located in space at stationary space points. # Time domain time_configuration = lue.TimeConfiguration(lue.TimeDomainItemType.cell) epoch = lue.Epoch(lue.Epoch.Kind.common_era, "2019-01-01", lue.Calendar.gregorian) clock = lue.Clock(epoch, lue.Unit.day, 1) time_coordinate_datatype = lue.dtype.TickPeriodCount # Space domain space_configuration = lue.SpaceConfiguration( lue.Mobility.stationary, lue.SpaceDomainItemType.point) space_coordinate_datatype = numpy.dtype(numpy.float32) rank = 2 # Property set outlet_points = phenomenon.add_property_set("outlets", time_configuration, clock, space_configuration, space_coordinate_datatype, rank) time_domain = outlet_points.time_domain space_domain = outlet_points.space_domain active_set_index = outlet_points.object_tracker.active_set_index active_object_id = outlet_points.object_tracker.active_object_id # Property discharge_datatype = numpy.dtype(numpy.float32) discharge = outlet_points.add_property( "discharge", dtype=discharge_datatype, shape=(1, ), value_variability=lue.ValueVariability.variable) nr_time_boxes = 5 max_nr_objects = 100 # Iterate over the time boxes for t in range(nr_time_boxes): # Store additional time box and count time_box = numpy.array([t, t + 1], dtype=time_coordinate_datatype) time_domain.value.expand(1)[-1] = time_box count = int(10 * random.random()) time_domain.value.count.expand(1)[-1] = count # Iterate over the time cells within each time box for c in range(count): # Store IDs of objects in the active set object_index = active_object_id.nr_ids active_set_index.expand(1)[-1] = object_index nr_objects = int(random.random() * max_nr_objects) object_id = numpy.empty(nr_objects, dtype=lue.dtype.ID) lue.test.select_random_ids(object_id, max_nr_objects) active_object_id.expand(nr_objects)[object_index:] = object_id # Store property values of active objects discharge_values = \ numpy.arange(nr_objects, dtype=discharge_datatype) discharge.value.expand(nr_objects)[object_index:] = \ discharge_values lue.assert_is_valid(dataset) del dataset dataset = lue.open_dataset("outlets2.lue") phenomenon = dataset.phenomena["areas"] outlet_points = phenomenon.property_sets["outlets"] time_domain = outlet_points.time_domain clock = time_domain.clock self.assertEqual(clock.epoch.kind, lue.Epoch.Kind.common_era) self.assertEqual(clock.epoch.origin, "2019-01-01") self.assertEqual(clock.epoch.calendar, lue.Calendar.gregorian) self.assertEqual(clock.unit, lue.Unit.day) self.assertEqual(clock.nr_units, 1)