def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field): dt = timedelta(seconds=time_step) dt_s = dt.seconds t = model_time v0 = vel_field.at(pos, t, extrapolate=self.extrapolate).data d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos) v1 = vel_field.at(pos + d0, t + dt / 2, extrapolate=self.extrapolate).data d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos) v2 = vel_field.at(pos + d1, t + dt / 2, extrapolate=self.extrapolate).data d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos) v3 = vel_field.at(pos + d2, t + dt, extrapolate=self.extrapolate).data return dt_s / 6 * (v0 + 2 * v1 + 2 * v2 + v3)
def get_move(self, sc, time_step, model_time_datetime): """ Compute the move in (long,lat,z) space. It returns the delta move for each element of the spill as a numpy array of size (number_elements X 3) and dtype = gnome.basic_types.world_point_type Base class returns an array of numpy.nan for delta to indicate the get_move is not implemented yet. Each class derived from Mover object must implement it's own get_move :param sc: an instance of gnome.spill_container.SpillContainer class :param time_step: time step in seconds :param model_time_datetime: current model time as datetime object All movers must implement get_move() since that's what the model calls """ status = sc['status_codes'] != oil_status.in_water positions = sc['positions'] vels = self.grid.interpolated_velocities(model_time_datetime, positions[:, 0:2]) deltas = np.zeros_like(positions) deltas[:] = 0. deltas[:, 0:2] = vels * time_step deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions) deltas[status] = (0, 0, 0) pass return deltas
def test_uncertainty(): sp = sample_sc_release(num_elements=1000, start_pos=(0.0, 0.0, 0.0)) u_sp = sample_sc_release(num_elements=1000, start_pos=(0.0, 0.0, 0.0), uncertain=True) mover = simple_mover.SimpleMover(velocity=(10.0, 10.0, 0.0)) delta = mover.get_move(sp, time_step=100, model_time=None) u_delta = mover.get_move(u_sp, time_step=100, model_time=None) # expected = np.zeros_like(delta) expected = proj.meters_to_lonlat((1000.0, 1000.0, 0.0), (0.0, 0.0, 0.0)) assert np.alltrue(delta == expected) # but uncertain spills should be different: assert not np.alltrue(u_delta == expected) # the mean should be close: # this is teh smallest tolerance that consitantly passed -- good enough? assert np.allclose(np.mean(delta, 0), np.mean(u_delta, 0), rtol=1.7e-1)
def get_move(self, sc, time_step, model_time_datetime, num_method=None): """ Compute the move in (long,lat,z) space. It returns the delta move for each element of the spill as a numpy array of size (number_elements X 3) and dtype = gnome.basic_types.world_point_type Base class returns an array of numpy.nan for delta to indicate the get_move is not implemented yet. Each class derived from Mover object must implement it's own get_move :param sc: an instance of gnome.spill_container.SpillContainer class :param time_step: time step in seconds :param model_time_datetime: current model time as datetime object All movers must implement get_move() since that's what the model calls """ positions = sc['positions'] if self.active and len(positions) > 0: status = sc['status_codes'] != oil_status.in_water pos = positions[:] deltas = self.delta_method(num_method)(sc, time_step, model_time_datetime, pos, self.wind) deltas[:, 0] *= sc['windages'] * self.wind_scale deltas[:, 1] *= sc['windages'] * self.wind_scale deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions) deltas[status] = (0, 0, 0) else: deltas = np.zeros_like(positions) return deltas
def get_move(self, sc, time_step, model_time_datetime, num_method = None): """ Compute the move in (long,lat,z) space. It returns the delta move for each element of the spill as a numpy array of size (number_elements X 3) and dtype = gnome.basic_types.world_point_type Base class returns an array of numpy.nan for delta to indicate the get_move is not implemented yet. Each class derived from Mover object must implement it's own get_move :param sc: an instance of gnome.spill_container.SpillContainer class :param time_step: time step in seconds :param model_time_datetime: current model time as datetime object All movers must implement get_move() since that's what the model calls """ method = None if num_method is None: method = self.num_methods[self.default_num_method] else: method = self.num_method[num_method] status = sc['status_codes'] != oil_status.in_water positions = sc['positions'] deltas = np.zeros_like(positions) pos = positions[:, 0:2] deltas[:, 0:2] = method(sc, time_step, model_time_datetime, pos, self.wind) deltas[:,0] *= sc['windages'] deltas[:,1] *= sc['windages'] deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions) deltas[status] = (0, 0, 0) return deltas
def __init__(self, image_size, projection=None, viewport=None, preset_colors='BW', background_color='transparent', colordepth=8): """ create a new map image from scratch -- specifying the size :param image_size: (width, height) tuple of the image size in pixels Optional parameters :param projection=None: gnome.utilities.projections object to use. if None, it defaults to FlatEarthProjection() :param viewport: viewport of map -- what gets drawn and on what scale. Default is full globe: (((-180, -90), (180, 90))) :param preset_colors='BW': color set to preset. Options are: 'BW' - transparent, black, and white: transparent background 'web' - the basic named colors for the web: transparent background 'transparent' - transparent background, no other colors set None - no pre-allocated colors -- the first one you allocate will be the background color :param background_color = 'transparent': color for the background -- must be a color that exists :param colordepth=8: only 8 bit color supported for now maybe someday, 32 bit will be an option """ projection = (FlatEarthProjection() if projection is None else projection) self._image_size = image_size if colordepth != 8: raise NotImplementedError("only 8 bit color currently implemented") self.background_color = background_color self.create_images(preset_colors) self.projection = projection self._viewport = Viewport(((-180, -90), (180, 90))) if viewport is not None: self._viewport.BB = tuple(map(tuple, viewport)) self.projection.set_scale(self.viewport, self.image_size) self.graticule = GridLines(self._viewport, self.projection)
def get_delta_Trapezoid(self, sc, time_step, model_time, pos, vel_field): dt = timedelta(seconds=time_step) dt_s = dt.seconds t = model_time v0 = vel_field.at(pos, t, extrapolate=self.extrapolate).data d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s, pos) v1 = vel_field.at(pos + d0, t + dt, extrapolate=self.extrapolate).data return dt_s / 2 * (v0 + v1)
def test_north(): sp = sample_sc_release(num_elements=10, start_pos=(20, 0.0, 0.0)) mover = simple_mover.SimpleMover(velocity=(0.0, 10, 0.0)) delta = mover.get_move(sp, time_step=100.0, model_time=None) # expected = np.zeros_like(delta) expected = proj.meters_to_lonlat((0.0, 1000.0, 0.0), (0.0, 0.0, 0.0)) assert np.alltrue(delta == expected)
def _expected_move(self): """ Put the expected move logic in separate (fixture) if it gets used multiple times """ uv = r_theta_to_uv_wind(self.time_val['value']) exp = np.zeros((self.sc.num_released, 3)) exp[:, 0] = self.sc['windages'] * uv[0, 0] * self.time_step exp[:, 1] = self.sc['windages'] * uv[0, 1] * self.time_step xform = FlatEarthProjection.meters_to_lonlat(exp, self.sc['positions']) return xform
def get_delta_RK2(self, sc, time_step, model_time, pos, vel_field): dt = timedelta(seconds=time_step) dt_s = dt.seconds t = model_time v0 = vel_field.at(pos, t) d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s, pos) p1 = pos.copy() p1 += d0 v1 = vel_field.at(p1, t + dt) return dt_s / 2 * (v0 + v1)
def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field): dt = timedelta(seconds=time_step) dt_s = dt.seconds t = model_time v0 = vel_field.at(pos, t) d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos) p1 = pos.copy() p1 += d0 v1 = vel_field.at(p1, t + dt / 2) d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos) p2 = pos.copy() p2 += d1 v2 = vel_field.at(p2, t + dt / 2) d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos) p3 = pos.copy() p3 += d2 v3 = vel_field.at(p3, t + dt) return dt_s / 6 * (v0 + 2 * v1 + 2 * v2 + v3)
def test_basic_move(): # initilizes to long, lat, z = 0.0, 0.0, 0.0 sp = sample_sc_release(num_elements=5) mover = simple_mover.SimpleMover(velocity=(1.0, 10.0, 0.0)) delta = mover.get_move(sp, time_step=100.0, model_time=None) print delta # expected = np.zeros_like(delta) expected = proj.meters_to_lonlat((100.0, 1000.0, 0.0), (0.0, 0.0, 0.0)) assert np.alltrue(delta == expected)
def get_move(self, sc, time_step, model_time_datetime, num_method=None): """ Compute the move in (long,lat,z) space. It returns the delta move for each element of the spill as a numpy array of size (number_elements X 3) and dtype = gnome.basic_types.world_point_type Base class returns an array of numpy.nan for delta to indicate the get_move is not implemented yet. Each class derived from Mover object must implement it's own get_move :param sc: an instance of gnome.spill_container.SpillContainer class :param time_step: time step in seconds :param model_time_datetime: current model time as datetime object All movers must implement get_move() since that's what the model calls """ positions = sc['positions'] if self.active and len(positions) > 0: status = sc['status_codes'] != oil_status.in_water pos = positions[:] res = self.delta_method(num_method)(sc, time_step, model_time_datetime, pos, self.current) if res.shape[1] == 2: deltas = np.zeros_like(positions) deltas[:, 0:2] = res else: deltas = res deltas *= self.scale_value deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions) deltas[status] = (0, 0, 0) else: deltas = np.zeros_like(positions) return deltas
def test_variance1(start_loc, time_step): """ After a few timesteps the variance of the particle positions should be similar to the computed value: var = Dt """ num_le = 1000 start_time = datetime.datetime(2012, 11, 10, 0) sc = sample_sc_release(num_le, start_loc, start_time) D = 100000 num_steps = 10 rand = RandomMover(diffusion_coef=D) model_time = start_time for i in range(num_steps): model_time += datetime.timedelta(seconds=time_step) sc.release_elements(time_step, model_time) rand.prepare_for_model_step(sc, time_step, model_time) delta = rand.get_move(sc, time_step, model_time) # print "delta:", delta sc['positions'] += delta # print sc['positions'] # compute the variances: # convert to meters pos = FlatEarthProjection.lonlat_to_meters(sc['positions'], start_loc) var = np.var(pos, axis=0) # D converted to meters^s/s expected = 2.0 * (D * 1e-4) * num_steps * time_step assert np.allclose(var, (expected, expected, 0.), rtol=0.1)
def get_move(self, sc, time_step, model_time_datetime, num_method=None): """ Compute the move in (long,lat,z) space. It returns the delta move for each element of the spill as a numpy array of size (number_elements X 3) and dtype = gnome.basic_types.world_point_type Base class returns an array of numpy.nan for delta to indicate the get_move is not implemented yet. Each class derived from Mover object must implement it's own get_move :param sc: an instance of gnome.spill_container.SpillContainer class :param time_step: time step in seconds :param model_time_datetime: current model time as datetime object All movers must implement get_move() since that's what the model calls """ method = None if num_method is None: method = self.num_methods[self.default_num_method] else: method = self.num_method[num_method] status = sc["status_codes"] != oil_status.in_water positions = sc["positions"] pos = positions[:] res = method(sc, time_step, model_time_datetime, pos, self.current) if res.shape[1] == 2: deltas = np.zeros_like(positions) deltas[:, 0:2] = res else: deltas = res deltas = FlatEarthProjection.meters_to_lonlat(deltas, positions) deltas[status] = (0, 0, 0) return deltas
class SimpleMover(Mover): """ simple_mover a really simple mover -- moves all LEs a constant speed and direction (not all that different than a constant wind mover, now that I think about it) """ _schema = SimpleMoverSchema def __init__(self, velocity, uncertainty_scale=0.5, **kwargs): """ simple_mover (velocity) create a simple_mover instance :param velocity: a (u, v, w) triple -- in meters per second :param uncertainty_scale=0.5: the scale of the uncertainty of the velocity Remaining kwargs are passed onto Mover's __init__ using super. See Mover documentation for remaining valid kwargs. """ # use this, to be compatible with whatever we are using for location self.velocity = np.asarray(velocity, dtype=mover_type).reshape((3, )) self.uncertainty_scale = uncertainty_scale super(SimpleMover, self).__init__(**kwargs) def __repr__(self): return "SimpleMover(velocity={!r}, uncertainty_scale={!r})".format( self.velocity, self.uncertainty_scale) def get_move(self, spill, time_step, model_time): """ moves the particles defined in the spill object :param spill: spill is an instance of the gnome.spill.Spill class :param time_step: time_step in seconds :param model_time: current model time as a datetime object In this case, it uses the: positions status_code data arrays. :returns delta: Nx3 numpy array of movement -- in (long, lat, meters) units """ # Get the data: try: positions = spill['positions'] status_codes = spill['status_codes'] except KeyError, err: raise ValueError("The spill doesn't have the required " "data arrays\n{}".format(err)) # which ones should we move? in_water_mask = status_codes == oil_status.in_water # compute the move delta = np.zeros_like(positions) if self.active and self.on: delta[in_water_mask] = self.velocity * time_step # add some random stuff if uncertainty is on if spill.uncertain: num = sum(in_water_mask) scale = self.uncertainty_scale * self.velocity \ * time_step delta[in_water_mask, 0] += random.uniform(-scale[0], scale[0], num) delta[in_water_mask, 1] += random.uniform(-scale[1], scale[1], num) delta[in_water_mask, 2] += random.uniform(-scale[2], scale[2], num) # scale for projection # just the lat-lon... delta = proj.meters_to_lonlat(delta, positions) return delta
def set_newparticle_values(self, num_new_particles, current_time, time_step, data_arrays): """ SpillContainer will release elements and initialize all data_arrays to default initial value. The SpillContainer gets passed as input and the data_arrays for 'position' get initialized correctly by the release object: self.release.set_newparticle_positions() If a Spill Amount is given, the Spill object also sets the 'mass' data array; else 'mass' array remains '0' :param int num_new_particles: number of new particles that were added. Always greater than 0 :param current_time: current time :type current_time: datetime.datetime :param time_step: the time step, sometimes used to decide how many should get released. :type time_step: integer seconds :param data_arrays: dict of data_arrays provided by the SpillContainer. Look for 'positions' array in the dict and update positions for latest num_new_particles that are released :type data_arrays: dict containing numpy arrays for values Also, the set_newparticle_values() method for all element_type gets called so each element_type sets the values for its own data correctly """ mass_fluxes = [tam_drop.mass_flux for tam_drop in self.droplets] delta_masses, proportions, total_mass = self._get_mass_distribution(mass_fluxes, time_step) # set up LE distribution, the number of particles in each 'release point' LE_distribution = [int(num_new_particles * p) for p in proportions] diff = num_new_particles - sum(LE_distribution) for i in range(0, diff): LE_distribution[i % len(LE_distribution)] += 1 # compute release point location for each droplet positions = [self.start_position + FlatEarthProjection.meters_to_lonlat(d.position, self.start_position) for d in self.droplets] for p in positions: p[0][2] -= self.start_position[2] # for each release location, set the position and mass of the elements released at that location total_rel = 0 for mass_dist, n_LEs, pos, droplet in zip(delta_masses, LE_distribution, positions, self.droplets): start_idx = -num_new_particles + total_rel if start_idx == 0: break end_idx = start_idx + n_LEs if end_idx == 0: end_idx = None # print '{0} to {1}'.format(start_idx, end_idx) if start_idx == end_idx: continue data_arrays['positions'][start_idx:end_idx] = pos data_arrays['mass'][start_idx:end_idx] = mass_dist / n_LEs data_arrays['init_mass'][start_idx:end_idx] = mass_dist / n_LEs data_arrays['density'][start_idx:end_idx] = droplet.density data_arrays['droplet_diameter'][start_idx:end_idx] = np.random.normal(droplet.radius * 2, droplet.radius * 0.15, (n_LEs)) v = data_arrays['rise_vel'][start_idx:end_idx] rise_velocity_from_drop_size(v, data_arrays['density'][start_idx:end_idx], data_arrays['droplet_diameter'][start_idx:end_idx], 0.0000013, 1020) data_arrays['rise_vel'][start_idx:end_idx] = v total_rel += n_LEs self.num_released += num_new_particles self.amount_released += total_mass
class SimpleMover(Mover, serializable.Serializable): """ simple_mover a really simple mover -- moves all LEs a constant speed and direction (not all that different than a constant wind mover, now that I think about it) """ _state = copy.deepcopy(Mover._state) _state.add(update=['uncertainty_scale', 'velocity'], save=['uncertainty_scale', 'velocity']) def __init__(self, velocity, uncertainty_scale=0.5, **kwargs): """ simple_mover (velocity) create a simple_mover instance :param velocity: a (u, v, w) triple -- in meters per second Remaining kwargs are passed onto Mover's __init__ using super. See Mover documentation for remaining valid kwargs. """ self.velocity = np.asarray( velocity, dtype=basic_types.mover_type ).reshape( (3, ) ) # use this, to be compatible with whatever we are using for location self.uncertainty_scale = uncertainty_scale super(SimpleMover, self).__init__(**kwargs) def __repr__(self): return 'SimpleMover(<%s>)' % self.id def velocity_to_dict(self): """ convert velocity back into a tuple """ return tuple(self.velocity.tolist()) def get_move( self, spill, time_step, model_time, ): """ moves the particles defined in the spill object :param spill: spill is an instance of the gnome.spill.Spill class :param time_step: time_step in seconds :param model_time: current model time as a datetime object In this case, it uses the: positions status_code data arrays. :returns delta: Nx3 numpy array of movement -- in (long, lat, meters) units """ # Get the data: try: positions = spill['positions'] status_codes = spill['status_codes'] except KeyError, err: raise ValueError( 'The spill does not have the required data arrays\n' + err.message) # which ones should we move? in_water_mask = status_codes == basic_types.oil_status.in_water # compute the move delta = np.zeros_like(positions) if self.active and self.on: delta[in_water_mask] = self.velocity * time_step # add some random stuff if uncertainty is on if spill.uncertain: num = sum(in_water_mask) scale = self.uncertainty_scale * self.velocity \ * time_step delta[in_water_mask, 0] += random.uniform(-scale[0], scale[0], num) delta[in_water_mask, 1] += random.uniform(-scale[1], scale[1], num) delta[in_water_mask, 2] += random.uniform(-scale[2], scale[2], num) # scale for projection delta = proj.meters_to_lonlat(delta, positions) # just the lat-lon... return delta