def get_mass(self, units=None): """ Return the mass released during the spill. User can also specify desired output units in the function. If units are not specified, then return in 'SI' units ('kg') If volume is given, then use density to find mass. Density is always at 15degC, consistent with API definition """ if self.amount is None: return self.amount # first convert amount to 'kg' if self.units in self.valid_mass_units: mass = uc.convert('Mass', self.units, 'kg', self.amount) elif self.units in self.valid_vol_units: water_temp = self.water.get('temperature') rho = self.element_type.substance.density_at_temp(water_temp) vol = uc.convert('Volume', self.units, 'm^3', self.amount) mass = rho * vol else: raise ValueError("{} is not a valid mass or Volume unit" .format(self.units)) if units is None or units == 'kg': return mass else: self._check_units(units) return uc.convert('Mass', 'kg', units, mass)
def get_mass(self, units=None): """ Return the mass released during the spill. User can also specify desired output units in the function. If units are not specified, then return in 'SI' units ('kg') If volume is given, then use density to find mass. Density is always at 15degC, consistent with API definition """ # fixme: This really should be re-factored to always store mass. if self.amount is None: return self.amount if self.units in self.valid_mass_units: # first convert amount to 'kg' mass = uc.convert('Mass', self.units, 'kg', self.amount) elif self.units in self.valid_vol_units: # need to convert to mass # DO NOT change this back! # for the UI to be consistent, the conversion needs to use standard # density -- not the current water temp. # water_temp = self.water.get('temperature') # ideally substance would have a "standard_density" attribute for this. std_rho = self.element_type.standard_density vol = uc.convert('Volume', self.units, 'm^3', self.amount) mass = std_rho * vol else: raise ValueError("{} is not a valid mass or Volume unit" .format(self.units)) if units is None or units == 'kg': return mass else: self._check_units(units) return uc.convert('Mass', 'kg', units, mass)
def at(self, points, time, units=None, extrapolate=False, **kwargs): ''' Interpolates this property to the given points at the given time with the units specified :param points: A Nx2 array of lon,lat points :param time: A datetime object. May be None; if this is so, the variable is assumed to be gridded but time-invariant :param units: The units that the result would be converted to ''' value = None if len(self.time) == 1: # single time time series (constant) value = np.full((points.shape[0], 1), self.data, dtype=np.float64) if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return value if not extrapolate: self.time.valid_time(time) t_index = self.time.index_of(time, extrapolate) if time > self.time.max_time: value = self.data[-1] if time <= self.time.min_time: value = self.data[0] if value is None: t_alphas = self.time.interp_alpha(time, extrapolate) d0 = self.data[t_index - 1] d1 = self.data[t_index] value = d0 + (d1 - d0) * t_alphas if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return np.full((points.shape[0], 1), value, dtype=np.float64)
def _init_rate_duration(self, avg_frac_oil=1): ''' burn duration based on avg_frac_oil content for LEs marked for burn __init__ invokes this to initialize all parameters assuming frac_water = 0.0 ''' # burn rate constant is defined as a thickness rate in m/sec _si_area = uc.convert('Area', self.area_units, 'm^2', self.area) # rate if efficiency is 100 % self._oilwater_thick_burnrate = self._burn_constant * avg_frac_oil self._oil_vol_burnrate = (self._burn_constant * avg_frac_oil ** 2 * _si_area) # burn duration is known once rate is known # reset current thickness to initial thickness whenever model is rerun self._oilwater_thickness = uc.convert('Length', self.thickness_units, 'm', self.thickness) burn_duration = ((self._oilwater_thickness - self._min_thickness) / self._oilwater_thick_burnrate) self._active_range = (self.active_range[0], self.active_range[0] + timedelta(seconds=burn_duration))
def at(self, points, time, units=None, extrapolate=False): ''' Interpolates this property to the given points at the given time with the units specified :param points: A Nx2 array of lon,lat points :param time: A datetime object. May be None; if this is so, the variable is assumed to be gridded but time-invariant :param units: The units that the result would be converted to ''' value = None if len(self.time) == 1: # single time time series (constant) value = np.full((points.shape[0], 1), self.data) value = unit_conversion.convert(self.units, units, value) return value if not extrapolate: self.time.valid_time(time) t_index = self.time.index_of(time, extrapolate) if time > self.time.max_time: value = self.data[-1] if time <= self.time.min_time: value = self.data[0] if value is None: t_alphas = self.time.interp_alpha(time, extrapolate) d0 = self.data[t_index - 1] d1 = self.data[t_index] value = d0 + (d1 - d0) * t_alphas if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return np.full((points.shape[0], 1), value)
def _get_mass(self, substance, amount, units): ''' return 'amount' in units of 'kg' for specified substance uses the density corresponding with API temperature ''' if units in self.valid_mass_units: rm_mass = uc.convert('Mass', units, 'kg', amount) else: # amount must be in volume units rm_vol = uc.convert('Volume', units, 'm^3', amount) rm_mass = substance.get_density() * rm_vol return rm_mass
def _convert_units(self, data, coord_sys, from_unit, to_unit): ''' method to convert units for the 'value' stored in the date/time value pair ''' if from_unit != to_unit: data[:, 0] = uc.convert('Velocity', from_unit, to_unit, data[:, 0]) if coord_sys == coord_systems.uv: data[:, 1] = uc.convert('Velocity', from_unit, to_unit, data[:, 1]) return data
def _convert_units(self, data, ts_format, from_unit, to_unit): ''' method to convert units for the 'value' stored in the date/time value pair ''' if from_unit != to_unit: data[:, 0] = uc.convert('Velocity', from_unit, to_unit, data[:, 0]) if ts_format == basic_types.ts_format.uv: # TODO: avoid clobbering the 'ts_format' namespace data[:, 1] = uc.convert('Velocity', from_unit, to_unit, data[:, 1]) return data
def test_new_api_oneshot(): """ just to make sure basic API works! and these are a few that have caused problems... """ assert isclose(unit_conversion.convert('meter', 'foot', 1), 3.28083989501) assert isclose(unit_conversion.convert('API', 'SG', 10), 1) assert isclose(unit_conversion.convert('meter second-1', 'knot', 1), 1.94384) assert isclose(unit_conversion.convert('m/s', 'knot', 1), 1.94384)
def at_temp(self, temp, unit='K'): """ density(s) at the provided temperature(s) :param temp: scalar or sequence of temp in K :param unit='K': unit of temperature densities will be returned as kg/m^3 """ temp = np.asarray(temp) scaler = True if temp.shape == () else False temp.shape = (-1, ) if unit != 'K': temp = uc.convert(unit, 'K', temp) densities = np.interp(temp, self.temps, self.densities, left=-np.inf, right=np.inf) left = (densities == -np.inf) densities[left] = self.densities[0] + (self.k_rho_default * (temp[left] - self.temps[0])) right = (densities == np.inf) densities[right] = self.densities[-1] + ( self.k_rho_default * (temp[right] - self.temps[-1])) return densities if not scaler else densities[0]
def _get_mass(self, substance, amount, units): """ return 'amount' in units of 'kg' for specified substance uses the density corresponding with API temperature """ if units in self.valid_mass_units: rm_mass = uc.convert("Mass", units, "kg", amount) else: # amount must be in volume units water_temp = self.water.get("temperature") rho = substance.density_at_temp(water_temp) rm_vol = uc.convert("Volume", units, "m^3", amount) rm_mass = rho * rm_vol return rm_mass
def sample_oil_to_mock_oil(max_cuts=None, **kwargs): ''' make an Oil object from _sample_oils Currently, this has only been tested on sample oils, but should be made more general. Assume the kwargs are attributes of Oil object This adds following attributes: 'densities' list containing only one density 'cuts' list containing equal mass in saturates/aromatics for max cuts 'resins' and 'asphaltene_content' to None ''' if max_cuts is None: max_cuts = 5 oil = Oil(**kwargs) # need to add densities list oil.densities = [Density(kg_m_3=uc.convert('density', 'api', 'kg/m^3', oil.api), ref_temp_k=288.15)] if 'kvis' in kwargs: for k in kwargs['kvis']: oil.kvis.append(KVis(**k)) add_resin_fractions(None, oil) add_asphaltene_fractions(None, oil) # add cuts - all mass goes into saturates/aromatics for now mass_left = 1.0 mass_left -= sum([f.fraction for f in oil.sara_fractions if f.sara_type in ('Resins', 'Asphaltenes')]) oil.cuts = [] prev_mass_frac = 0.0 summed_boiling_points = [] for t, f in get_boiling_points_from_api(max_cuts, mass_left, oil.api): added_to_sums = False for idx, [ut, summed_value] in enumerate(summed_boiling_points): if np.isclose(t, ut): summed_boiling_points[idx][1] += f added_to_sums = True break if added_to_sums is False: summed_boiling_points.append([t, f]) for t_i, fraction in summed_boiling_points: oil.cuts.append(Cut(fraction=prev_mass_frac + fraction, vapor_temp_k=t_i)) prev_mass_frac += fraction add_molecular_weights(None, oil) add_saturate_aromatic_fractions(None, oil) add_component_densities(None, oil) adjust_resin_asphaltene_fractions(None, oil) return oil
def test_prepare_for_model_run(self): ''' check _oilwater_thickness, _burn_duration is reset''' self.burn._oilwater_thickness = 0.002 # reached terminal thickness self.burn.prepare_for_model_run(self.sc) assert (self.burn._oilwater_thickness == uc.convert('Length', self.burn.thickness_units, 'm', self.burn.thickness))
def convert_to(self, new_unit): """ Convert this Measurement object to the specified new unit The object is mutated in place. If the conversion can not be performed, an Exception will be raised, and the object not altered. This will also return the object (self) -- but that is a deprecated feature -- do not use it! If you want a new object, use `converted_to` instead """ new_vals = { att: None for att in ('value', 'min_value', 'max_value', 'standard_deviation') } for attr in new_vals.keys(): val = getattr(self, attr) if val is not None: new_val = convert(self.unit_type, self.unit, new_unit, val) new_vals[attr] = new_val # if this was all successful new_vals['unit'] = new_unit self.__dict__.update(new_vals) return None
def get(self, attr, unit=None): ''' return value in desired unit. If None, then return the value in SI units. The user_unit are given in 'units' attribute and each attribute carries the value in as given in these user_units. ''' val = getattr(self, attr) if unit is None: # Note: salinity only have one units since we don't # have any conversions for them in unit_conversion yet - revisit # this per requirements if (attr not in self._si_units or self._si_units[attr] == self._units[attr]): return val else: unit = self._si_units[attr] if unit in self._units_type[attr][1]: return uc.convert(self._units_type[attr][0], self.units[attr], unit, val) else: # log to file if we have logger ex = uc.InvalidUnitError((unit, self._units_type[attr][0])) self.logger.error(str(ex)) raise ex
def show_uncategorized_oils(session): oils = (session.query(Oil) .filter(Oil.categories == None) .all()) fd = open('temp.txt', 'w') fd.write('adios_oil_id\t' 'product_type\t' 'api\t' 'viscosity\t' 'pour_point\t' 'name\n') logger.info('{0} oils uncategorized.'.format(len(oils))) for o in oils: o_estim = OilWithEstimation(o) if o.api >= 0: if o.api < 15: category_temp = 273.15 + 50 else: category_temp = 273.15 + 38 viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) else: viscosity = None fd.write('{0.imported.adios_oil_id}\t' '{0.imported.product_type}\t' '{0.api}\t' '{1}\t' '({0.pour_point_min_k}, {0.pour_point_max_k})\t' '{0.name}\n' .format(o, viscosity))
def link_refined_fuel_oil_6(session): ''' Category Name: - Fuel Oil #6/Bunker/Heavy Fuel Oil/Group V Sample Oils: - Bunker C - Residual Oil Density Criteria: - API < 15 Kinematic Viscosity Criteria: - 200.0 <= v cSt @ 50 degrees Celcius ''' top, categories = get_categories_by_names(session, 'Refined', ('Fuel Oil 6 (HFO)', 'Bunker', 'Heavy Fuel Oil', 'Group V')) oils = get_oils_by_api(session, 'Refined', api_min=0.0, api_max=15.0) count = 0 category_temp = 273.15 + 50 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity >= 200.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.' .format(count, top.name, [n.name for n in categories])) transaction.commit()
def link_refined_fuel_oil_2(session): ''' Category Name: - Fuel oil #2/Diesel/Heating Oil Sample Oils: - Diesel - Heating Oil - No. 2 Distillate Density Criteria: - 30 <= API < 39 Kinematic Viscosity Criteria: - 2.5 < v <= 4.0 cSt @ 38 degrees Celcius ''' top, categories = get_categories_by_names(session, 'Refined', ('Fuel Oil 2', 'Diesel', 'Heating Oil')) oils = get_oils_by_api(session, 'Refined', api_min=30.0, api_max=39.0) count = 0 category_temp = 273.15 + 38 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity > 2.5 or viscosity <= 4.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.' .format(count, top.name, [n.name for n in categories])) transaction.commit()
def link_refined_ifo(session): ''' Category Name: - Intermediate Fuel Oil Sample Oils: - IFO 180 - Fuel Oil #4 - Marine Diesel Density Criteria: - 15 <= API < 30 Kinematic Viscosity Criteria: - 4.0 < v < 200.0 cSt @ 38 degrees Celcius ''' top, categories = get_categories_by_names(session, 'Refined', ('Intermediate Fuel Oil',)) oils = get_oils_by_api(session, 'Refined', api_min=15.0, api_max=30.0) count = 0 category_temp = 273.15 + 38 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity > 4.0 or viscosity < 200.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.' .format(count, top.name, [n.name for n in categories])) transaction.commit()
def show_uncategorized_oils(session): oils = (session.query(Oil).filter(Oil.categories == None).all()) fd = open('temp.txt', 'w') fd.write('adios_oil_id\t' 'product_type\t' 'api\t' 'viscosity\t' 'pour_point\t' 'name\n') logger.info('{0} oils uncategorized.'.format(len(oils))) for o in oils: o_estim = OilWithEstimation(o) if o.api >= 0: if o.api < 15: category_temp = 273.15 + 50 else: category_temp = 273.15 + 38 viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) else: viscosity = None fd.write('{0.imported.adios_oil_id}\t' '{0.imported.product_type}\t' '{0.api}\t' '{1}\t' '({0.pour_point_min_k}, {0.pour_point_max_k})\t' '{0.name}\n'.format(o, viscosity))
def link_refined_fuel_oil_1(session): ''' Category Name: - Fuel oil #1/gasoline/kerosene Sample Oils: - gasoline - kerosene - JP-4 - avgas Density Criteria: - API >= 35 Kinematic Viscosity Criteria: - v <= 2.5 cSt @ 38 degrees Celcius ''' top, categories = get_categories_by_names( session, 'Refined', ('Light Products (Fuel Oil 1)', 'Gasoline', 'Kerosene')) oils = get_oils_by_api(session, 'Refined', api_min=35.0) category_temp = 273.15 + 38 count = 0 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity <= 2.5: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.'.format( count, top.name, [n.name for n in categories])) transaction.commit()
def test_update_active_start(self): ''' active stop should be updated if we update active start or thickness ''' burn = Burn(self.area, self.thick, active_range=(active_start, InfDateTime('inf')), name='test_burn', on=False) # this is ignored! # use burn constant for test - it isn't stored anywhere duration = ((uc.convert('Length', burn.thickness_units, 'm', burn.thickness) - burn._min_thickness) / burn._burn_constant) assert (burn.active_range[1] == burn.active_range[0] + timedelta(seconds=duration)) # after changing active start, active stop should still match the # duration. burn.active_range = (burn.active_range[0] + timedelta(days=1), burn.active_range[1]) assert (burn.active_range[1] == burn.active_range[0] + timedelta(seconds=duration))
def test_wind_circ_fixture(wind_circ): """ check timeseries of wind object created in 'wind_circ' """ wm = wind_circ['wind'] # output is in knots gtime_val = wm.get_wind_data(coord_sys='uv').view(dtype=np.recarray) assert np.all(gtime_val.time == wind_circ['uv'].time) assert np.allclose(gtime_val.value, wind_circ['uv'].value, rtol=rtol, atol=atol) # output is in meter per second gtime_val = (wm.get_wind_data( coord_sys='uv', units='meter per second').view(dtype=np.recarray)) expected = unit_conversion.convert('Velocity', wm.units, 'meter per second', wind_circ['uv'].value) assert np.all(gtime_val.time == wind_circ['uv'].time) assert np.allclose(gtime_val.value, expected, rtol=rtol, atol=atol)
def link_refined_fuel_oil_6(session): ''' Category Name: - Fuel Oil #6/Bunker/Heavy Fuel Oil/Group V Sample Oils: - Bunker C - Residual Oil Density Criteria: - API < 15 Kinematic Viscosity Criteria: - 200.0 <= v cSt @ 50 degrees Celcius ''' top, categories = get_categories_by_names( session, 'Refined', ('Fuel Oil 6 (HFO)', 'Bunker', 'Heavy Fuel Oil', 'Group V')) oils = get_oils_by_api(session, 'Refined', api_min=0.0, api_max=15.0) count = 0 category_temp = 273.15 + 50 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity >= 200.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.'.format( count, top.name, [n.name for n in categories])) transaction.commit()
def link_refined_ifo(session): ''' Category Name: - Intermediate Fuel Oil Sample Oils: - IFO 180 - Fuel Oil #4 - Marine Diesel Density Criteria: - 15 <= API < 30 Kinematic Viscosity Criteria: - 4.0 < v < 200.0 cSt @ 38 degrees Celcius ''' top, categories = get_categories_by_names(session, 'Refined', ('Intermediate Fuel Oil', )) oils = get_oils_by_api(session, 'Refined', api_min=15.0, api_max=30.0) count = 0 category_temp = 273.15 + 38 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity > 4.0 or viscosity < 200.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.'.format( count, top.name, [n.name for n in categories])) transaction.commit()
def link_refined_fuel_oil_2(session): ''' Category Name: - Fuel oil #2/Diesel/Heating Oil Sample Oils: - Diesel - Heating Oil - No. 2 Distillate Density Criteria: - 30 <= API < 39 Kinematic Viscosity Criteria: - 2.5 < v <= 4.0 cSt @ 38 degrees Celcius ''' top, categories = get_categories_by_names( session, 'Refined', ('Fuel Oil 2', 'Diesel', 'Heating Oil')) oils = get_oils_by_api(session, 'Refined', api_min=30.0, api_max=39.0) count = 0 category_temp = 273.15 + 38 for o in oils: o_estim = OilWithEstimation(o) viscosity = uc.convert('Kinematic Viscosity', 'm^2/s', 'cSt', o_estim.kvis_at_temp(category_temp)) if viscosity > 2.5 or viscosity <= 4.0: o.categories.extend(categories) count += 1 logger.info('{0} oils added to {1} -> {2}.'.format( count, top.name, [n.name for n in categories])) transaction.commit()
def Calculate(self, depth, gas_oil_ratio, oil_jet_velocity=None, oil_jet_density=None, source_pressure=None, output_metric=False): if oil_jet_velocity and oil_jet_density: # N/m^2 or Pa units (Pascals) # equavelent to 950 psi source_pressure = (oil_jet_density * (oil_jet_velocity ** 2)) / 2 elif not source_pressure: raise ValueError('need either ' 'oil_jet_velocity and oil_jet_density, ' 'or source_pressure') if self.metric_inputs: depth = convert('Length', 'meter', 'foot', depth) gas_oil_ratio = Volume.CubicMeterRatioToScfPerStb(gas_oil_ratio) source_pressure = Force.PascalsToPsi(source_pressure) # Start-Equation 1.5, page 8 # Calculating ambient pressure outside leak at depth in psi. # We will go off the document, but here are some considerations # regarding this calculation: # - The ambient atmospheric pressure at sea level is not constant. # It varies with the weather, but averages around 100 kPa # One bar is 100kPa or approximately ambient pressure at sea level # - One atmosphere(atm) is also approximately the ambient pressure # at sea level and is equal to 14.7 psi or 1.01325 bar # - Ambient water pressure increases linearly with depth. # Roughly, each 10 meters (33 ft) of depth adds another bar # to the ambient pressure. Assuming the density of sea water # to be 1025 kg/m^3 (in fact it is slightly variable), # pressure increases by 1 atm with each 10 m of depth ambient_pressure_at_depth = self.ambient_pressure_at_sea_level + (0.446533 * depth); # Start-Equation 1.4, page 8 # The relative pressure, deltaPrel, difference over the leak point is relative_pressure_delta = source_pressure / ambient_pressure_at_depth # Start- Table 1.3, page 11 # Maximum released volume fraction, frel max_release_fraction, max_release_occurrence = self.release_fraction_lu[relative_pressure_delta] # Start-Section 1.3.5 # # Table 1.4 GOR reduction factors, page 11 gor_reduction_factor = self.gor_reduction_factor_lu.get_gas_oil_reduction_factor(gas_oil_ratio, max_release_occurrence) if output_metric: source_pressure = Force.PsiToPascals(source_pressure) ambient_pressure_at_depth = Force.PsiToPascals(ambient_pressure_at_depth) max_release_occurrence = Volume.ScfPerStbToCubicMeterRatio(max_release_occurrence) return self.gor_results(source_pressure, ambient_pressure_at_depth, relative_pressure_delta, max_release_fraction, max_release_occurrence, gor_reduction_factor)
def convert_to_internal_volume(self): data = self.timeseries['value'] from_unit = self.units to_unit = 'cubic meter' if from_unit != to_unit: data[:, 0] = uc.convert('Volume', from_unit, to_unit, data[:, 0]) self.units = to_unit
def get_mass(self, units=None): ''' Return the mass released during the spill. User can also specify desired output units in the function. If units are not specified, then return in 'SI' units ('kg') If volume is given, then use density to find mass. Density is always at 15degC, consistent with API definition ''' # first convert amount to 'kg' if self.units in self.valid_mass_units: mass = uc.convert('Mass', self.units, 'kg', self.amount_released) if units is None or units == 'kg': return mass else: self._check_units(units) return uc.convert('Mass', 'kg', units, mass)
def test_old_api(unit_type, unit1, unit2, value, new_value): """ this is a parameterized test of all the known values, with the old API """ # now do the test: assert isclose(unit_conversion.convert(unit_type, unit1, unit2, value), new_value)
def test_invalid_unit_convert(): with pytest.raises(ValueError): unit_conversion.convert("length", "flintstones", "meters", 1.0) with pytest.raises(ValueError): unit_conversion.convert("length", "feet", "flintstones", 1.0) with pytest.raises(ValueError): unit_conversion.convert("temperature", "feet", "C", 1.0) with pytest.raises(ValueError): unit_conversion.convert("temperature", "f", "feet", 1.0)
def test_weather_elements(self, thick, avg_frac_water, units): ''' weather elements and test. frac_water is 0. Test thickness in units other than 'm'. 1) tests the expected burned mass equals 'burned' amount stored in mass_balance 2) also tests the mass_remaining is consistent with what we expect 3) tests the mass of LEs set for burn equals the mass of oil given avg_frac_water and the thickness, and area. Since we cannot have a fraction of an LE, the difference should be within the mass of one LE. Also sets the 'frac_water' to 0.5 for one of the tests just to ensure it works. ''' self.spill.set('num_elements', 500) thick_si = uc.convert('Length', units, 'm', thick) area = (0.5 * self.volume)/thick_si burn = Burn(area, thick, active_start, thickness_units=units, efficiency=1.0) # return the initial value of burn._oil_thickness - this is starting # thickness of the oil self._weather_elements_helper(burn, avg_frac_water) # following should finally hold true for entire run assert np.allclose(amount, self.sc.mass_balance['burned'] + self.sc['mass'].sum(), atol=1e-6) # want mass of oil thickness * area gives volume of oil-water so we # need to scale this by (1 - avg_frac_water) exp_burned = ((thick_si - burn._min_thickness) * (1 - avg_frac_water) * burn.area * self.op.get_density()) assert np.isclose(self.sc.mass_balance['burned'], exp_burned) mask = self.sc['fate_status'] & fate.burn == fate.burn # given LEs are discrete elements, we cannot add a fraction of an LE mass_per_le = self.sc['init_mass'][mask][0] exp_init_oil_mass = (burn.area * thick_si * (1 - avg_frac_water) * self.op.get_density()) assert (self.sc['init_mass'][mask].sum() - exp_init_oil_mass < mass_per_le and self.sc['init_mass'][mask].sum() - exp_init_oil_mass >= 0.0) exp_mass_remain = (burn._oilwater_thickness * (1 - avg_frac_water) * burn.area * self.op.get_density()) mass_remain_for_burn_LEs = self.sc['mass'][mask].sum() # since we don't adjust the thickness anymore need to use min_thick min_thick = .002 exp_mass_remain = min_thick * (1 - avg_frac_water) * burn.area * self.op.get_density() assert np.allclose(exp_mass_remain, mass_remain_for_burn_LEs) duration = (burn.active_stop-burn.active_start).total_seconds()/3600 print ('Current Thickness: {0:.3f}, ' 'Duration (hrs): {1:.3f}').format(burn._oilwater_thickness, duration)
def plume(distribution_type='droplet_size', distribution='weibull', windage_range=(.01, .04), windage_persist=900, substance_name=None, density=None, density_units='kg/m^3', **kwargs): """ Helper function returns an ElementType object containing 'rise_vel' and 'windages' initializer with user specified parameters for distribution. See below docs for details on the parameters. NOTE: substance_name or density must be provided :param str distribution_type: default 'droplet_size' available options: 1. 'droplet_size': Droplet size is samples from the specified distribution. Rise velocity is calculated. 2.'rise_velocity': rise velocity is directly sampled from the specified distribution. No droplet size is computed. :param distribution='weibull': :param windage_range=(.01, .04): :param windage_persist=900: :param substance_name='oil_conservative': :param float density = None: :param str density_units='kg/m^3': """ if density is not None: # Assume density is at 15 K - convert density to api api = uc.convert('density', density_units, 'API', density) if substance_name is not None: substance = build_oil_props({'name':substance_name, 'api': api}, 2) else: substance = build_oil_props({'api': api}, 2) elif substance_name is not None: # model 2 cuts if fake oil substance = get_oil_props(substance_name, 2) else: ex = ValueError() ex.message = ("plume substance density and/or name must be provided") raise ex if distribution_type == 'droplet_size': return ElementType([InitRiseVelFromDropletSizeFromDist( distribution=distribution, **kwargs), InitWindages(windage_range, windage_persist)], substance) elif distribution_type == 'rise_velocity': return ElementType([InitRiseVelFromDist(distribution=distribution, **kwargs), InitWindages(windage_range, windage_persist)], substance)
def test_invalid_unit_convert(): with pytest.raises(unit_conversion.InvalidUnitError): unit_conversion.convert("length", "flintstones", "meters", 1.0) with pytest.raises(unit_conversion.InvalidUnitError): unit_conversion.convert("length", "feet", "flintstones", 1.0) with pytest.raises(unit_conversion.InvalidUnitError): unit_conversion.convert("temperature", "feet", "C", 1.0) with pytest.raises(unit_conversion.InvalidUnitError): unit_conversion.convert("temperature", "f", "feet", 1.0)
def test_OilProps_sample_oil(oil, density, units): """ compare expected values with values stored in OilProps - make sure data entered correctly and unit conversion is correct """ o = get_oil_props(oil) d = uc.convert('density', units, 'kg/m^3', density) assert o.name == oil assert np.isclose(o.density_at_temp(273.15 + 15), d)
def test_OilProps_sample_oil(oil, density, units): """ compare expected values with values stored in OilProps - make sure data entered correctly and unit conversion is correct """ o = get_oil_props(oil) d = uc.convert('density', units, 'kg/m^3', density) assert o.name == oil assert np.isclose(get_density(o, 273.15 + 15), d)
def _at_2D(self, pts, data, slices=None, **kwargs): cur_dim = len(data.shape) - len(slices) if slices is not None else len(data.shape) if slices is not None and cur_dim != 2: raise ValueError("Data dimensions are incorrect! dimension is {0}".format(len(data.shape) - len(slices))) _hash = kwargs['_hash'] if '_hash' in kwargs else None units = kwargs['units'] if 'units' in kwargs else None value = self.grid.interpolate_var_to_points(pts, data, _hash=_hash[0], slices=slices, _memo=True) if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return value
def _get_density(self, salinity, temp): ''' use lru cache so we don't recompute if temp is not changing ''' temp_c = uc.convert('Temperature', self.units['temperature'], 'C', temp) # sea level pressure in decibar - don't expect atmos_pressure to change # also expect constants to have SI units rho = gsw.rho(salinity, temp_c, constants.atmos_pressure * 0.0001) return rho
def find_density_near_15C(self): """ Returns the density (in kg/m3) It will interpolate and extrapolate as needed """ try: return Density(self.oil).at_temp(uc.convert("C", "K", 15)) except ValueError: return None
def at_temp(self, temp, kvis_units='m^2/s', temp_units="K"): """ Compute the kinematic viscosity of the oil as a function of temperature :param temp_k: temperatures to compute at: can be scalar or array of values. Should be in Kelvin viscosity as a function of temp is given by: v = A exp(k_v2 / T) with constants determined from measured data """ temp = np.asarray(temp) temp = uc.convert('temperature', temp_units, 'K', temp) kvisc = self._visc_A * np.exp(self._k_v2 / temp) kvisc = uc.convert('kinematic viscosity', 'm^2/s', kvis_units, kvisc) return kvisc
def test_weather_elements(self, thick, avg_frac_water, units): ''' weather elements and test. frac_water is 0. Test thickness in units other than 'm'. 1) tests the expected burned mass equals 'burned' amount stored in mass_balance 2) also tests the mass_remaining is consistent with what we expect 3) tests the mass of LEs set for burn equals the mass of oil given avg_frac_water and the thickness, and area. Since we cannot have a fraction of an LE, the difference should be within the mass of one LE. Also sets the 'frac_water' to 0.5 for one of the tests just to ensure it works. ''' self.spill.set('num_elements', 500) thick_si = uc.convert('Length', units, 'm', thick) area = (0.5 * self.volume)/thick_si burn = Burn(area, thick, active_start, thickness_units=units, efficiency=1.0) # return the initial value of burn._oil_thickness - this is starting # thickness of the oil self._weather_elements_helper(burn, avg_frac_water) # following should finally hold true for entire run assert np.allclose(amount, self.sc.mass_balance['burned'] + self.sc['mass'].sum(), atol=1e-6) # want mass of oil thickness * area gives volume of oil-water so we # need to scale this by (1 - avg_frac_water) exp_burned = ((thick_si - burn._min_thickness) * (1 - avg_frac_water) * burn.area * self.op.get_density()) assert np.isclose(self.sc.mass_balance['burned'], exp_burned) mask = self.sc['fate_status'] & fate.burn == fate.burn # given LEs are discrete elements, we cannot add a fraction of an LE mass_per_le = self.sc['init_mass'][mask][0] exp_init_oil_mass = (burn.area * thick_si * (1 - avg_frac_water) * self.op.get_density()) assert (self.sc['init_mass'][mask].sum() - exp_init_oil_mass < mass_per_le and self.sc['init_mass'][mask].sum() - exp_init_oil_mass >= 0.0) exp_mass_remain = (burn._oilwater_thickness * (1 - avg_frac_water) * burn.area * self.op.get_density()) mass_remain_for_burn_LEs = self.sc['mass'][mask].sum() assert np.allclose(exp_mass_remain, mass_remain_for_burn_LEs) duration = (burn.active_stop-burn.active_start).total_seconds()/3600 print ('Current Thickness: {0:.3f}, ' 'Duration (hrs): {1:.3f}').format(burn._oilwater_thickness, duration)
def test_amount_mass_vol(amount, units): ''' ensure mass is being returned correctly when 'amount' is initialized wtih 'mass' or 'volume' ''' spill = Spill(Release(datetime.now()), amount=amount, units=units, substance=test_oil) assert spill.amount == amount assert spill.units == units if units in Spill.valid_vol_units: exp_mass = (spill.get('substance').get_density() * uc.convert('Volume', units, 'm^3', spill.amount)) else: exp_mass = uc.convert('Mass', units, 'kg', spill.amount) assert spill.get_mass() == exp_mass exp_mass_g = exp_mass * 1000 assert spill.get_mass('g') == exp_mass_g
def test_new_api(unit_type, unit1, unit2, value, new_value): """ this is a parameterized test of all the known values, but with the new API """ # filter out the ones that we know are eliminated if unit_conversion.Simplify(unit_type) in ('concentrationinwater', 'oilconcentration'): return # now do the test: assert isclose(unit_conversion.convert(unit1, unit2, value), new_value)
def check_for_valid_api(self): """ check is the API value is already valid """ API = self.oil.metadata.API density_at_15 = self.find_density_near_15C() if uc.convert("density", "kg/m^3", "API", density_at_15) == API: return True else: return False
def at(self, points, time, units=None, depth=-1, extrapolate=False): ''' Find the value of the property at positions P at time T :param points: Coordinates to be queried (P) :param time: The time at which to query these points (T) :param depth: Specifies the depth level of the variable :param units: units the values will be returned in (or converted to) :param extrapolate: if True, extrapolation will be supported :type points: Nx2 array of double :type time: datetime.datetime object :type depth: integer :type units: string such as ('m/s', 'knots', etc) :type extrapolate: boolean (True or False) :return: returns a Nx1 array of interpolated values :rtype: double ''' sg = False m = True if self.time is None: # special case! prop has no time variance v0 = self.grid.interpolate_var_to_points(points, self.data, slices=None, slice_grid=sg, _memo=m) return v0 t_alphas = s0 = s1 = value = None if not extrapolate: self.time.valid_time(time) t_index = self.time.index_of(time, extrapolate) if len(self.time) == 1: value = self.grid.interpolate_var_to_points(points, self.data, slices=[0], _memo=m) else: if time > self.time.max_time: value = self.data[-1] if time <= self.time.min_time: value = self.data[0] if extrapolate and t_index == len(self.time.time): s0 = [t_index] value = self.grid.interpolate_var_to_points(points, self.data, slices=s0, _memo=m) else: t_alphas = self.time.interp_alpha(time, extrapolate) s1 = [t_index] s0 = [t_index - 1] if len(self.data.shape) == 4: s0.append(depth) s1.append(depth) v0 = self.grid.interpolate_var_to_points(points, self.data, slices=s0, slice_grid=sg, _memo=m) v1 = self.grid.interpolate_var_to_points(points, self.data, slices=s1, slice_grid=sg, _memo=m) value = v0 + (v1 - v0) * t_alphas if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return value
def prepare_for_model_step(self, sc, time_step, model_time): ''' 1. set 'active' flag based on active start, and model_time 2. Mark LEs to be burned - do them in order right now. Assume all LEs that are released together will be burned together since they would be closer to each other in position. Assumes: there is more mass in water than amount of mass to be burned. The LEs marked for Burning are marked only once - during the very first step that the object becomes active ''' super(Burn, self).prepare_for_model_step(sc, time_step, model_time) if not self.active: return # if initial oilwater_thickness is < _min_thickness, then stop # don't want to deal with active start being equal to active stop, need # this incase user sets a bad initial value if self._oilwater_thickness <= self._min_thickness: self._active = False return # only when it is active, update the status codes if (sc['fate_status'] == bt_fate.burn).sum() == 0: substance = self._get_substance(sc) _si_area = uc.convert('Area', self.area_units, 'm^2', self.area) _si_thickness = uc.convert('Length', self.thickness_units, 'm', self.thickness) mass_to_remove = (self.efficiency * self._get_mass(substance, _si_area * _si_thickness, 'm^3')) self._update_LE_status_codes(sc, bt_fate.burn, substance, mass_to_remove) self._set_burn_params(sc, substance) # set timestep after active stop is set self._set__timestep(time_step, model_time)
def get_density(self, temp=None, out=None): ''' return density at a temperature do we want to do any unit conversions here? todo: memoize function :param temp: temperature in Kelvin. Could be an ndarray, list or scalar :type temp: scalar, list, tuple or ndarray - assumes it is in Kelvin ''' if temp: return get_density(self._r_oil, temp, out) else: return uc.convert('density', 'API', 'kg/m^3', self.api)