:py:mod:`gnome.tamoc` ===================== .. py:module:: gnome.tamoc .. autoapi-nested-parse:: code for interfacing with TAMOC: https://github.com/socolofs/tamoc For now, this imports everythign from tamoc.py in this package Submodules ---------- .. toctree:: :titlesonly: :maxdepth: 1 tamoc_spill/index.rst Package Contents ---------------- Classes ~~~~~~~ .. autoapisummary:: gnome.tamoc.Release gnome.tamoc.Spill gnome.tamoc.Substance gnome.tamoc.GnomeOil gnome.tamoc.GnomeId gnome.tamoc.TamocSpill Functions ~~~~~~~~~ .. autoapisummary:: gnome.tamoc.asdatetime gnome.tamoc._valid_units .. py:class:: Release(release_time=None, num_elements=None, num_per_timestep=None, end_release_time=None, custom_positions=None, release_mass=0, retain_initial_positions=False, **kwargs) Bases: :py:obj:`gnome.gnomeobject.GnomeId` base class for Release classes. It contains interface for Release objects Required Arguments: :param release_time: time the LEs are released :type release_time: datetime.datetime or iso string. :param custom_positions: initial location(s) the elements are released :type custom_positions: iterable of (lon, lat, z) Optional arguments: .. note:: Either num_elements or num_per_timestep must be given. If both are None, then it defaults to num_elements=1000. If both are given a TypeError is raised because user can only specify one or the other, not both. :param num_elements: total number of elements to be released :type num_elements: integer default 1000 :param num_per_timestep: fixed number of LEs released at each timestep :type num_elements: integer :param end_release_time=None: optional -- for a time varying release, the end release time. If None, then release is instantaneous :type end_release_time: datetime.datetime :param release_mass=0: optional. This is the mass released in kilograms. :type release_mass: integer :param retain_initial_positions: Optional. If True, each LE will retain information about it's originally released position :type retain_initial_positions: boolean .. py:property:: centroid .. py:property:: release_mass .. py:property:: num_per_timestep .. py:property:: num_elements .. py:property:: release_duration duration over which particles are released in seconds .. py:property:: end_release_time .. py:attribute:: _schema .. py:method:: __repr__() Return repr(self). .. py:method:: rewind() .. py:method:: LE_timestep_ratio(ts) Returns the ratio .. py:method:: maximum_mass_error(ts) This function returns the maximum error in mass present in the model at any given time. In theory, this should be the mass of 1 LE .. py:method:: get_num_release_time_steps(ts) calculates how many time steps it takes to complete the release duration .. py:method:: generate_release_timeseries(num_ts, max_release, ts) Release timeseries describe release behavior as a function of time. _release_ts describes the number of LEs that should exist at time T PolygonRelease does not have a _pos_ts because it uses start_positions only All use TimeseriesData objects. .. py:method:: num_elements_after_time(current_time) Returns the number of elements expected to exist at current_time. Returns 0 if prepare_for_model_run has not been called. :param current_time: time of release :type current_time: datetime .. py:method:: prepare_for_model_run(ts) :param ts: timestep as integer seconds .. py:method:: initialize_LEs(to_rel, sc, start_time, end_time) set positions for new elements added by the SpillContainer .. note:: this releases all the elements at their initial positions at the end_time .. py:method:: initialize_LEs_post_substance(to_rel, sc, start_time, end_time, environment) .. py:class:: Spill(on=True, num_elements=1000, amount=0, units='kg', substance=None, release=None, water=None, amount_uncertainty_scale=0.0, **kwargs) Bases: :py:obj:`BaseSpill` Models a spill by combining Release and Substance objects Spills used by the gnome model. It contains a release object, which releases elements. It also contains a Substance which contains the type of substance spilled and it initializes data arrays to non-default values (non-zero). :param release: an object defining how elements are to be released :type release: derived from :class:`~gnome.spills.release.Release` :param substance: an object defining the substance of this spill. Defaults to :class:`~gnome.spills.substance.NonWeatheringSubstance` :type substance: derived from :class:`~gnome.spills.substance.Substance` **Optional parameters (kwargs):** :param name: Human-usable Name of this spill :type name: str :param on=True: Toggles the spill on/off. :type on: bool :param amount=None: mass or volume of oil spilled. :type amount: double (volume or mass) :param units=None: must provide units for amount spilled. :type units: str :param amount_uncertainty_scale=0.0: scale value in range 0-1 that adds uncertainty to the spill amount. Maximum uncertainty scale is (2/3) * spill_amount. :type amount_uncertainty_scale: float .. note:: Define either volume or mass in 'amount' attribute and provide appropriate 'units'. .. py:property:: all_array_types Need to add array types from Release and Substance .. py:property:: substance .. py:property:: release_time .. py:property:: end_release_time .. py:property:: release_duration .. py:property:: num_elements .. py:property:: start_position .. py:property:: end_position .. py:property:: amount .. py:property:: units Default units in which amount of oil spilled was entered by user. The 'amount' property is returned in these 'units' .. py:attribute:: _schema .. py:attribute:: valid_vol_units .. py:attribute:: valid_mass_units .. py:method:: __repr__() Return repr(self). .. py:method:: _check_units(units) Checks the user provided units are in list of valid volume or mass units .. py:method:: get_mass() Return the total mass released during the spill. .. py:method:: uncertain_copy() Returns a deepcopy of this spill for the uncertainty runs The copy has everything the same, including the spill_num, but it is a new object with a new id. Not much to this method, but it could be overridden to do something fancier in the future or a subclass. There are a number of python objects that cannot be deepcopied. - Logger objects So we copy them temporarily to local variables before we deepcopy our Spill object. .. py:method:: set_amount_uncertainty(up_or_down=None) This function shifts the spill amount based on a scale value in the range [0.0 ... 1.0]. The maximum uncertainty scale value is (2/3) * spill_amount. We determine either an upper uncertainty or a lower uncertainty multiplier. Then we shift our spill amount value based on it. Since we are irreversibly changing the spill amount value, we should probably do this only once. .. py:method:: rewind() rewinds the release to original status (before anything has been released). .. py:method:: prepare_for_model_run(timestep) array_types comes from all the other objects above in the model such as movers, weatherers, etc. The ones from the substance still need to be added .. py:method:: release_elements(sc, start_time, end_time, environment=None) Releases and partially initializes new LEs Note: this will have to be updated if we allow backwards runs for continuous spills .. py:method:: num_elements_to_release(current_time, time_step) Determines the number of elements to be released during: current_time + time_step It invokes the num_elements_to_release method for the the underlying release object: self.release.num_elements_to_release() :param current_time: current time :type current_time: datetime.datetime :param int time_step: the time step, sometimes used to decide how many should get released. :returns: the number of elements that will be released. This is taken by SpillContainer to initialize all data_arrays. .. py:method:: _attach_default_refs(ref_dict) !!!IMPORTANT!!! If this object requires default references (self._req_refs exists), this function will use the name of the references as keys into a reference dictionary to get a list of satisfactory references (objects that have obj._ref_as == self._req_refs). It will then attach the first object in the reference list to that attribute on this object. This behavior can be overridden if the object needs more specific attachment behavior than simply 'first in line' In addition, this function SHOULD BE EXTENDED if this object should provide default references to any contained child objects. When doing so, please be careful to respect already existing references. The reference attachment system should only act if the requested reference 'is None' when the function is invoked. See Model._attach_default_refs() for an example. .. py:class:: Substance(windage_range=None, windage_persist=None, standard_density=1000.0, *args, **kwargs) Bases: :py:obj:`gnome.gnomeobject.GnomeId` A class for assigning a unique ID for an object :param windage_range: Range of windages for the substance (leeway). Default: (.01, .04) :type windage_range: tuple of values between 0 and 1 :param windage_persist=900: persistence of windage settings in seconds. -1 or Inf means infinite. :type windage_persist: integer seconds. :param standard_density=1000.0: The density of the substance, used to convert mass to/from volume :type standard_density: float in units of kg/m^3 .. py:property:: all_array_types Fixme: should the initializers be what holds the array types? don't we know that this should have already? .. py:property:: is_weatherable .. py:property:: windage_range .. py:property:: windage_persist .. py:attribute:: _schema .. py:attribute:: _ref_as :value: 'substance' .. py:method:: get_initializer_by_name(name) get first initializer in list whose name matches 'name' .. py:method:: has_initializer(name) Returns True if an initializer is present in the list which sets the data_array corresponding with 'name', otherwise returns False .. py:method:: initialize_LEs(to_rel, arrs, environment=None) :param to_rel - number of new LEs to initialize :param arrs - dict-like of data arrays representing LEs .. py:method:: _pick_water(environment) .. py:method:: density_at_temp(temp=273.15) For non-weathering substance, we just return the standard density. .. py:method:: _attach_default_refs(ref_dict) !!!IMPORTANT!!! If this object requires default references (self._req_refs exists), this function will use the name of the references as keys into a reference dictionary to get a list of satisfactory references (objects that have obj._ref_as == self._req_refs). It will then attach the first object in the reference list to that attribute on this object. This behavior can be overridden if the object needs more specific attachment behavior than simply 'first in line' In addition, this function SHOULD BE EXTENDED if this object should provide default references to any contained child objects. When doing so, please be careful to respect already existing references. The reference attachment system should only act if the requested reference 'is None' when the function is invoked. See Model._attach_default_refs() for an example. .. py:class:: GnomeOil(oil_name=None, filename=None, water=None, **kwargs) Bases: :py:obj:`gnome.spills.substance.Substance` Class to create an oil for use in Gnome Initialize a GnomeOil: :param oil_name=None: Name of one of the sample oils provided by: ``gnome.spills.sample_oils`` :param filename=None: filename (Path) of JSON file in the Adios Oil Database format. :param water=None: Water object with environmental conditions -- Deprecated. Additional keyword arguments will be passed to Substance: e.g.: ``windage_range``, ``windage_persist=None``, A GnomeOil can be initialized in three ways: 1) From a sample oil name : ``GnomeOil(oil_name="sample_oil_name")`` the oils are available in gnome.spills.sample_oils 2) From a JSON file in the ADIOS Oil Database format: ``GnomeOil(filename="adios_oil.json")`` usually records from the ADIOS Oil Database (https://adios.orr.noaa.gov) 3) From the json : ``GnomeOil.new_from_dict(**json_)`` for loading save files, etc. (this is usually done under the hood) GnomeOil("sample_oil_name") ---works for test oils from sample_oils only GnomeOil(oil_name="sample_oil_name") GnomeOil(filename="oil.json") ---load from file using adios_db GnomeOil.new_from_dict(**json\_) ---webgnomeclient, savefiles, etc. GnomeOil("invalid_name") ---ValueError (not in sample oils) .. py:property:: standard_density Standard density is simply the density at 15C, which is the default temperature for density_at_temp() .. py:attribute:: _schema .. py:attribute:: _req_refs :value: ['water'] .. py:method:: from_adiosdb_file(filename, kwargs) .. py:method:: _set_up_array_types() .. py:method:: _init_from_json(*, api, pour_point, solubility, bullwinkle_fraction, original_bullwinkle_fraction=None, bullwinkle_time=None, original_bullwinkle_time=None, emulsion_water_fraction_max, densities, density_ref_temps, density_weathering, kvis, kvis_ref_temps, kvis_weathering, mass_fraction, boiling_point, molecular_weight, component_density, sara_type=None, adios_oil_id=None, k0y=None, num_components=None, **kwargs) .. py:method:: __hash__() needs to be hashable, so that it can be used in lru-cache Oils will only hash equal if they are the same object -- that's limiting, but OK. .. py:method:: __deepcopy__(memo) .. py:method:: get_GnomeOil(oil_info, max_cuts=None) :classmethod: #fixme: what is oil_info ??? Use this instead of get_oil_props .. py:method:: to_dict(json_=None) Returns a dictionary representation of this object. Uses the schema to determine which attributes are put into the dictionary. No extra processing is done to each attribute. They are presented as is. The ``json_`` parameter is ignored in this base class. 'save' is passed in when the schema is saving the object. This allows an override of this function to do any custom stuff necessary to prepare for saving. .. py:method:: initialize_LEs(to_rel, arrs, environment=None) :param to_rel - number of new LEs to initialize :param arrs - dict-like of data arrays representing LEs fixme: this shouldn't use water temp -- it should use standard density and STP temp -- and let weathering_data set it correctly .. note:: weathering data is currently broken for initial setting .. py:method:: _set_pc_values(prop, values) utility that sets a property to each pseudo component checks that it's the right size, and converts to an array .. py:method:: vapor_pressure(temp, atmos_pressure=101325.0) the vapor pressure on the PCs at a given temperature water_temp and boiling point units are Kelvin :param temp: temperature in K :returns: vapor_pressure array in SI units (Pascals) ## Fixme: shouldn't this be in the Evaporation code? .. py:method:: bounding_temperatures(obj_list, temperature) :classmethod: General Utility Function From a list of objects containing a ref_temp_k attribute, return the object(s) that are closest to the specified temperature(s) Specifically: - We want the ones that immediately bound our temperature. - If our temperature is high and out of bounds of the temperatures in our obj_list, then we return a range containing only the highest temperature. - If our temperature is low and out of bounds of the temperatures in our obj_list, then we return a range containing only the lowest temperature. We accept only a scalar temperature or a sequence of temperatures .. py:method:: get_densities() return a list of densities for the oil at a specified state of weathering. #fixme: this should not happen here! We include the API as a density if: - the specified weathering is 0 - the culled list of densities does not contain a measurement at 15C .. py:method:: density_at_temp(temperature=288.15) Get the oil density at a temperature or temperatures. .. note:: This is all kruft left over from the estimating code. At this point, a GnomeOil should already have what it needs. .. note:: There is a catch-22 which prevents us from getting the min_temp in some cases: - To estimate pour point, we need viscosities - If we need to convert dynamic viscosities to kinematic, we need density at 15C - To estimate density at temp, we need to estimate pour point - ...and then we recurse For this case we need to make an exception. .. note:: If we have a pour point that is higher than one or more of our reference temperatures, then the lowest reference temperature will become our minimum temperature. TODO: We are getting rid of the argument that specifies a weathering amount because it is currently implemented in an unusably precise manner. Robert would like us to implement a means of interpolating density using a combination of (temperature, weathering). But the algorithm for this is not defined at the moment. .. py:method:: _get_reference_densities(densities, temperature) Given a temperature, we return the best measured density, and its reference temperature, to be used in calculation. For our purposes, it is the density closest to the given temperature. .. py:method:: _vol_expansion_coeff(densities, temperature) .. py:method:: closest_to_temperature(obj_list, temperature, num=1) :classmethod: General Utility Function From a list of objects containing a ref_temp_k attribute, return the object(s) that are closest to the specified temperature(s) We accept only a scalar temperature or a sequence of temperatures .. py:method:: kvis_at_temp(temp_k=288.15, weathering=0.0) 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 :param weathering: fraction weathered -- currently not implemented viscosity as a function of temp is given by: v = A exp(k_v2 / T) with constants determined from measured data .. py:method:: determine_visc_constants() viscosity as a function of temp is given by: v = A exp(k_v2 / T) The constants, A and k_v2 are determined from the viscosity data: If only one data point, a default value for k_vs is used: 2100 K, based on analysis of data in the ADIOS database as of 2018 If two data points, the two constants are directly computed If three or more, the constants are computed by a least squares fit. .. py:method:: get(prop) get oil props .. py:function:: asdatetime(dt) makes sure the input is a datetime.datetime object if it already is, it will be passed through. If not it will attempt to parse a string to make a datetime object. None will also be passed through silently .. py:class:: GnomeId(name=None, _appearance=None, *args, **kwargs) Bases: :py:obj:`AddLogger` A class for assigning a unique ID for an object .. py:property:: all_array_types Returns all the array types required by this object If this object contains or is composed of other gnome objects (Spill->Substance->Initializers for example) then override this function to ensure all array types get presented at the top level. See ``Spill`` for an example .. py:property:: id Override this method for more exotic forms of identification. :return: a unique ID assigned during construction .. py:property:: obj_type .. py:property:: name define as property in base class so all objects will have a name by default .. py:property:: _warn_pre standard text prepended to warning messages - not required for logging used by validate to prepend to message since it also returns a list of messages that were logged .. py:attribute:: _id .. py:attribute:: make_default_refs :value: True .. py:attribute:: _name .. py:attribute:: RTOL :value: 1e-05 .. py:attribute:: ATOL :value: 1e-38 .. py:method:: __create_new_id() Override this method for more exotic forms of identification. Used only for deep copy. Used to make a new object which is a copy of the original. .. py:method:: __deepcopy__(memo) The deepcopy implementation We need this, as we don't want the id of spill object and logger object copied, but do want everything else. Got the method from: http://stackoverflow.com/questions/3253439/python-copy-how-to-inherit-the-default-copying-behaviour Despite what that thread says for __copy__, the built-in deepcopy() ends up using recursion .. py:method:: __copy__() might as well have copy, too. .. py:method:: gather_ref_as(src, refs) Gathers refs from single or collection of GnomeId objects. :param src: GnomeId object or collection of GnomeId :param refs: dictionary of str->list of GnomeId :returns {'ref1': [list of GnomeId], 'ref2 : [list of GnomeId], ...} .. py:method:: _attach_default_refs(ref_dict) !!!IMPORTANT!!! If this object requires default references (self._req_refs exists), this function will use the name of the references as keys into a reference dictionary to get a list of satisfactory references (objects that have obj._ref_as == self._req_refs). It will then attach the first object in the reference list to that attribute on this object. This behavior can be overridden if the object needs more specific attachment behavior than simply 'first in line' In addition, this function SHOULD BE EXTENDED if this object should provide default references to any contained child objects. When doing so, please be careful to respect already existing references. The reference attachment system should only act if the requested reference 'is None' when the function is invoked. See Model._attach_default_refs() for an example. .. py:method:: validate_refs(refs=['wind', 'water', 'waves']) level is the logging level to use for messages. Default is 'warning' but if called from prepare_for_model_run, we want to use error and raise exception. .. py:method:: validate() All pygnome objects should be able to validate themselves. Many py_gnome objects reference other objects like wind, water, waves. These may not be defined when object is created so they can be None at construction time; however, they should reference valid objects when running in the model. If make_default_refs is True, then object is valid because the model will set these up at runtime. To raise an exception for missing references at runtime, directly call validate_refs(level='error') 'wind', 'water', 'waves' attributes also have special meaning. An object containing this attribute references the corresponding object. Logs warnings: :returns: a tuple of length two containing: (a list of messages that were logged, isvalid bool) If any references are missing and make_default_refs is False, object is not valid .. note: validate() only logs warnings since it designed to be used to validate before running model. To log these as errors during model run, invoke validate_refs() directly. .. py:method:: new_from_dict(dict_) :classmethod: creates a new object from dictionary This is base implementation and can be over-ridden by classes using this mixin .. py:method:: to_dict(json_=None) Returns a dictionary representation of this object. Uses the schema to determine which attributes are put into the dictionary. No extra processing is done to each attribute. They are presented as is. The ``json_`` parameter is ignored in this base class. 'save' is passed in when the schema is saving the object. This allows an override of this function to do any custom stuff necessary to prepare for saving. .. py:method:: update_from_dict(dict_, refs=None) .. py:method:: update(*args, **kwargs) .. py:method:: _attr_changed(current_value, received_value) :staticmethod: Checks if an attribute passed back in a ``dict_`` from client has changed. Returns True if changed, else False .. py:method:: _check_type(other) check basic type equality .. py:method:: __eq__(other) .. function:: __eq__(other) Since this class is designed as a mixin with one objective being to save _state of the object, then recreate a new object with the same _state. Defines a base implementation of __eq__ so an object before persistence can be compared with a new object created after it is persisted. It can be overridden by the class with which it is mixed. It looks at attributes defined in self._state and checks that the values match It uses allclose() check for floats and numpy arrays, to avoid floating point tolerances: set to: RTOL=1e-05, ATOL=1e-08 :param other: another GnomeObject used for comparison in obj1 == other NOTE: super is not used. .. py:method:: _diff(other, fail_early=False) Returns a list of differences between this GnomeObject and another GnomeObject :param other: other object to compare to. :param fail_early=False: If true, it will return on the first error .. py:method:: __ne__(other) Return self!=value. .. py:method:: serialize(options={}) Returns a json serialization of this object ("webapi" mode only) .. py:method:: deserialize(json_, refs=None) :classmethod: classmethod takes json structure as input, deserializes it using a colander schema then invokes the new_from_dict method to create an instance of the object described by the json schema. We also need to accept sparse json objects, in which case we will not treat them, but just send them back. .. py:method:: save(saveloc='.', refs=None, overwrite=True) Save object state as json to user specified saveloc :param saveloc: A directory, file path, open zipfile.ZipFile, or None. If a directory, it will place the zip file there, overwriting if specified. If a file path, it will write the file there as follows: If the file does not exist, it will create the zip archive there. If the saveloc is a zip file or ``zipfile.Zipfile`` object and overwrite is False, it will append there. Otherwise, it will overwrite the file if allowed. If set to None, this function will instead return an open ``zipfile.Zipfile`` object linked to a temporary file. The zip file will be named [object.name].zip if a directory is specified :param refs: dictionary of references to objects :param overwrite: If True, overwrites the file at the saveloc :returns (obj_json, saveloc, refs): ``obj_json`` is the json that is written to this object's file in the zipfile. For example if saving a Model named Model1, obj_json will contain the contents of the Model1.json in the save file. ``saveloc`` will be the string path passed in EXCEPT if None was passed in. In this case, it will be an open ``zipfile.ZipFile`` based on a temporary file. ``refs`` will be a dict containing all the objects that were saved in the save file, keyed by object id. It will also contain the reference to the object that called ``.save`` itself. .. py:method:: load(saveloc='.', filename=None, refs=None) :classmethod: Load an instance of this class from an archive or folder :param saveloc: Can be an open zipfile.ZipFile archive, a folder, or a filename. If it is an open zipfile or folder, it must contain a .json file that describes an instance of this object type. If 'filename' is not specified, it will load the first instance of this object discovered. If a filename, it must be a zip archive or a json file describing an object of this type. :param filename: If saveloc is an open zipfile or folder, this indicates the name of the file to be loaded. If saveloc is a filename, this parameter is ignored. :param refs: A dictionary of id -> object instances that will be used to complete references, if available. .. py:function:: _valid_units(unit_type) return all the units for a given unit type :param unit_type: unit type, e.g. "Mass" or "Temperature" :type unit_type: str NOTE: this is just a wrapper for nucos.get_supported_names .. py:class:: TamocSpill(num_elements=1000, num_per_timestep=None, start_position=(0.0, 0.0, 1000.0), release_time=datetime.now(), release_rate=0.0, release_duration=timedelta(hours=1), units='bbl/day', substance=None, release=None, water=None, gor=None, d0=0.0, phi_0=-np.pi / 2.0, theta_0=0.0, windage_range=(0.01, 0.04), windage_persist=900, on=True, name=None, **kwargs) Bases: :py:obj:`gnome.spills.spill.Spill` Models a TAMOC spill by combining the near-field model with Release and Substance objects # This really should be based in a BaseSpill Class! Spills used by the gnome model. It contains a release object, which releases elements. It also contains a Substance which contains the type of substance spilled and it initializes data arrays to non-default values (non-zero). :param release: an object defining how elements are to be released :type release: derived from :class:`~gnome.spills.release.Release` :param substance: an object defining the substance of this spill. Defaults to :class:`~gnome.spills.substance.NonWeatheringSubstance` :type substance: derived from :class:`~gnome.spills.substance.Substance` **Optional parameters (kwargs):** :param name: Human-usable Name of this spill :type name: str :param on=True: Toggles the spill on/off. :type on: bool :param amount=None: mass or volume of oil spilled. :type amount: double (volume or mass) :param units=None: must provide units for amount spilled. :type units: str :param amount_uncertainty_scale=0.0: scale value in range 0-1 that adds uncertainty to the spill amount. Maximum uncertainty scale is (2/3) * spill_amount. :type amount_uncertainty_scale: float .. note:: Define either volume or mass in 'amount' attribute and provide appropriate 'units'. .. py:property:: substance first try to use get_oil_props using 'val'. If this fails, then assume user has provided a valid OilProps object and use it as is .. py:attribute:: release_time .. py:attribute:: end_release_time .. py:attribute:: start_position .. py:attribute:: num_elements .. py:method:: rewind() rewinds the release to original status (before anything has been released).