Exemple #1
0
def _plot(
    dset1: "Dataset", dset2: "Dataset", ddiff: "Dataset", common_fields: Set[str], figure_dir: "pathlib.PosixPath"
) -> None:
    """Generate plots

    Args:
        dset1:          First dataset containing the data.
        dset2:          Second dataset containing the data.
        ddiff:          Dataset containing the differences for each field between Dataset 'dset1' and Dataset 'dset2'
        common_fields:  Set with fields common in both datasets
        figure_dir:     Figure directory.
    """
    dset1_name = config.where.gnss_compare_datasets.get("dset1_name", default="dset1").str + ":"
    dset2_name = config.where.gnss_compare_datasets.get("dset2_name", default="dset2").str + ":"

    for field in common_fields:
        ylabel = FIELDS[field].label if field in FIELDS.keys() else field.lower()
        title = FIELDS[field].title if field in FIELDS.keys() else ""
        unit = Unit(FIELDS[field].unit).units if field in FIELDS.keys() else Unit(dset1.unit(field)).units
        options = _set_plot_config(title=title)

        plot_scatter_subplots(
            x_array=dset1.time.gps.datetime,
            y_arrays=[dset1[field], dset2[field], ddiff[field]],
            xlabel="Time [GPS]",
            ylabels=[f"{dset1_name} {ylabel}", f"{dset2_name} {ylabel}", f"Difference: {ylabel}"],
            colors=["steelblue", "darkorange", "limegreen"],
            y_units=[f"{unit:~P}", f"{unit:~P}", f"{unit:~P}"],
            figure_path=figure_dir / f"plot_{field}.{FIGURE_FORMAT}",
            opt_args=options,
        )
Exemple #2
0
def partial_vectors(dset, estimator_config_key):
    """Call all partials specified in the configuration and set up the corresponding state vector

    The list of partials to calculate is taken from the config file of the given technique. Each partial calculator is
    passed a :class:`~where.data.dataset.Dataset` with data for the modelrun and should return a tuple with the partial
    vectors and their names.

    Args:
        dset (Dataset):                 A Dataset containing model run data.
        estimator_config_key (String):  Key in config file with the name of the estimator.

    Returns:
        Dict: List of names of the partial derivatives for each partial config key.
    """
    partial_vectors = dict()
    prefix = dset.vars["pipeline"]

    # Delete values from previous iterations
    if "partial" in dset.fields:
        del dset.partial

    for config_key in estimators.partial_config_keys(estimator_config_key):
        partial_vectors[config_key] = list()
        partials = config.tech[config_key].list
        partial_data = plugins.call_all(package_name=__name__,
                                        plugins=partials,
                                        prefix=prefix,
                                        dset=dset)

        for param, (data, names, data_unit) in partial_data.items():
            param_unit_cfg = config.tech[param].unit
            if not param_unit_cfg.str:
                log.fatal(
                    f"No unit given for parameter {param!r} in {param_unit_cfg.source}"
                )

            display_unit = config.tech[param].display_unit.str
            display_unit = param_unit_cfg.str if not display_unit else display_unit
            partial_unit_str = f"{dset.unit('calc')[0]} / ({param_unit_cfg.str})"
            partial_unit = str(Unit(partial_unit_str).u)
            factor = Unit(data_unit, partial_unit)
            for values, name in zip(data.T, names):
                partial_name = f"{param}-{name}" if name else f"{param}"
                partial_vectors[config_key].append(partial_name)

                field_name = f"partial.{partial_name}"
                dset.add_float(field_name,
                               val=values * factor,
                               unit=partial_unit,
                               write_level="operational")
                dset.meta.add(partial_name,
                              display_unit,
                              section="display_units")

    return partial_vectors
Exemple #3
0
def get_field_by_attrs(dset: "Dataset", attrs: Tuple[str],
                       unit: str) -> np.ndarray:
    """Get field values of a Dataset specified by the field attributes

    If necessary the unit of the data fields are corrected to the defined 'output' unit.

    Args:
        dset:     Dataset, a dataset containing the data.
        attrs:    Field attributes (e.g. for Time object: (<scale>, <time format>)).
        unit:     Unit used for output.

    Returns:
        Array with Dataset field values
    """
    f = dset
    for attr in attrs:
        f = getattr(f, attr)

    # Convert 'unit' if necessary
    if unit:
        field = f"{'.'.join(attrs)}"
        if dset.unit(field):
            field_unit = dset.unit(field)[0]
            try:
                log.debug(
                    f"Convert dataset field {field} from unit {field_unit} to {unit}."
                )
                f = f * Unit(field_unit).to(unit).m
            except exceptions.UnitError:
                log.warn(f"Cannot convert from '{field_unit}' to '{unit}'.")

    return f
Exemple #4
0
def get_field(dset: "Dataset", field: str, attrs: Tuple[str], unit: str) -> np.ndarray:
    """Get field values of a Dataset specified by the field attributes

    If necessary the unit of the data fields are corrected to the defined 'output' unit.

    Args:
        dset:     Dataset, a dataset containing the data.
        field:    Field name.
        attrs:    Field attributes (e.g. for Time object: (<scale>, <time format>)).
        unit:     Unit used for output.

    Returns:
        Array with Dataset field values
    """
    f = dset[field]
    for attr in attrs:
        f = getattr(f, attr)
        
    # Convert 'unit' if necessary
    if unit:
        field_attrs = field if len(attrs) == 0 else f"{field}.{'.'.join(attrs)}"
        
        try:
            field_unit = dset.unit(field_attrs)[0]
        except (exceptions.UnitError, TypeError) as e:
            log.debug(f"Skip unit conversion for field '{field_attrs}'.")
            return f # Skip unit conversion for text fields, which do not have a unit.
        
        try:
            log.debug(f"Convert dataset field {field} from unit {field_unit} to {unit}.")
            f = f * Unit(field_unit).to(unit).m
        except (exceptions.UnitError):
            log.warn(f"Cannot convert from '{field_unit}' to '{unit}' for field {field}.")

    return f
Exemple #5
0
    def _extend(self, other_field, memo) -> None:
        """Add observations from another field"""
        if other_field.data.ndim != self.data.ndim:
            raise ValueError(
                f"Field '{self.name}' cannot be extended. Dimensions must be equal. ({other_field.data.ndim} != {self.data.ndim})"
            )

        try:
            factors = [
                Unit(from_unit, to_unit)
                for from_unit, to_unit in zip(other_field._unit, self._unit)
            ]
        except exceptions.UnitError:
            raise exceptions.UnitError(
                f"Cannot extend field '{self.name}'. {other_field._unit} cannot be converted to {self._unit}"
            )
        except TypeError:
            if self._unit == other_field._unit == None:
                factors = 1
            else:
                raise exceptions.UnitError(
                    f"Cannot extend field '{self.name}'. {other_field._unit} cannot be converted to {self._unit}"
                )
        old_id = id(self.data)
        self.data = np.insert(self.data,
                              self.num_obs,
                              other_field.data * factors,
                              axis=0)
        memo[old_id] = self.data
Exemple #6
0
    def _parse_observation(self, line: Dict[str, str],
                           cache: Dict[str, Any]) -> None:
        """Parse observations of COST records
        """

        # Skip slant sample lines
        if not line["hour"]:
            if float(line["minute"]) > 0:
                log.debug("Parsing of slant sample data is not implemented.")
            return

        if line["hour"][0].isalpha():
            return

        # Save data in cache
        for key, value in line.items():
            value = float(value) if value.replace('.', '').replace(
                '-', '').isnumeric() else value
            if key in UNIT_DEF.keys():
                value = value * Unit(UNIT_DEF[key].from_, UNIT_DEF[key].to_)

            cache.setdefault(f"data_{key}", list()).append(value)

        cache.setdefault(f"data_time", list()).append(
            datetime(
                cache["date_data"].year,
                cache["date_data"].month,
                cache["date_data"].day,
                int(line["hour"]),
                int(line["minute"]),
                int(line["second"]),
            ))
Exemple #7
0
 def as_cartesian(self):
     """Get Euler pole of tectonic plate in cartesian coordinates (X,Y,Z)
    
    Returns:
        Euler pole in cartesian coordinates (X, Y, Z) in [milliarcsecond/yr]
    """
     return np.array([self.pole.wx, self.pole.wy, self.pole.wz]) * Unit(
         self.pole.unit).to("milliarcsecond per year").m
Exemple #8
0
    def parse_obs(self, unit_in="meter", except_fields=()):
        """Read information about an observation

        Stores the information in the temporary cache-dict, which will be transfered to self.data when all information
        about this observation is parsed. If `unit_in` is given, all values will be converted to meter unless the field
        is listed in the `except_fields`-list.

        Args:
            unit_in (String):      Name of unit of values to be parsed.
            except_fields (Tuple): Names of fields where values should not be converted to meters.
        """
        # Find scale factor for converting to meter
        if unit_in == "meter":
            scale_factor = 1
        else:
            quantity = Unit(unit_in)
            try:
                scale_factor = quantity.to("meter").magnitude
            except Unit.DimensionalityError:
                # Try to convert between time and length by multiplying by the speed of light
                scale_factor = (
                    quantity * constant.c *
                    Unit("meters per second")).to("meter").magnitude
        obs = {}

        # Define the function doing the actual parsing
        def parse_func(line, cache):
            for field in line:
                if field.startswith("flag_"):  # Flags are currently ignored
                    continue
                try:
                    obs[field] = float(line[field])
                except ValueError:
                    obs[field] = 0
                    log.debug(
                        f"Could not convert {line['field']} to a number for {field}. Value set to 0.0"
                    )
                if field not in except_fields:
                    obs[field] *= scale_factor
            for field, value in obs.items():
                self.data.setdefault(field, list()).append(value)

        return parse_func
 def to_unit(self, unit: str) -> None:
     """Change rotation pole unit for all tectonic plates
     
     Args:
         unit:  Define unit of rotation pole (e.g. 'radian per year', 'milliarcsecond per year')
     """
     for plate, pole in self.poles.items():
         for entry in ["wx", "wy", "wz", "dwx", "dwy", "dwz"]:
             setattr(pole, entry, getattr(pole, entry) * Unit(pole.unit).to(unit).m)
         pole.unit = unit
         self.poles[plate] = replace(pole) # Make a copy of RotationPole object
Exemple #10
0
 def as_spherical(self):
     """Get Euler pole of tectonic plate in spherical coordinates, that means location in latitude and longitude and magnitude of rotation
    
    https://geo.libretexts.org/Courses/University_of_California_Davis/GEL_056%3A_Introduction_to_Geophysics/Geophysics_is_everywhere_in_geology.../04%3A_Plate_Tectonics/4.07%3A_Plate_Motions_on_a_Sphere
    
    Returns:
        Euler pole in spherical coordinates (latitude [deg], longitude [deg], magnitude of rotation [degree per million years])
    """
     return self.to_spherical(
         np.array([self.pole.wx, self.pole.wy, self.pole.wz]) *
         Unit(self.pole.unit).to("milliarcsecond per year").m)
def test_as_cartesian():
    """Test as_spherical() and to_cartesian() function
    """
    pm = PlateMotion(plate="eura", model="itrf2014")
    crt = pm.as_cartesian()
    expected_crt = np.array([pm.pole.wx, pm.pole.wy, pm.pole.wz]) * Unit(
        pm.pole.unit).to("milliarcsecond per year").m

    print(
        f"DEBUG test_as_cartesian: crt({crt[0]:.3f}, {crt[1]:.3f}, {crt[2]:.3f}); "
        f"expected_crt({expected_crt[0]:.3f}, {expected_crt[1]:.3f}, {expected_crt[2]:.3f}))"
    )
    np.testing.assert_allclose(crt, expected_crt, rtol=0, atol=1e-3)
Exemple #12
0
    def to_spherical(self, pole: np.ndarray) -> np.ndarray:
        """Convert Euler pole of tectonic plate from cartesian to spherical coordinates, that means location in latitude and longitude and magnitude of rotation
       
       https://geo.libretexts.org/Courses/University_of_California_Davis/GEL_056%3A_Introduction_to_Geophysics/Geophysics_is_everywhere_in_geology.../04%3A_Plate_Tectonics/4.07%3A_Plate_Motions_on_a_Sphere
       
       Args:
           pole: Euler pole array in cartesian coordinates (X,Y,Z) in [milliarcsecond per year])
       
       Returns:
           Euler pole in spherical coordinates (latitude [deg], longitude [deg], magnitude of rotation [degree per million years])
       """
        wx = pole[0] * Unit.milliarcsec2radian
        wy = pole[1] * Unit.milliarcsec2radian
        wz = pole[2] * Unit.milliarcsec2radian

        latitude = np.arctan2(wz, np.sqrt(wx**2 + wy**2)) * Unit.radian2degree
        longitude = np.arctan2(wy, wx) * Unit.radian2degree
        omega = np.sqrt(wx**2 + wy**2 + wz**2) * Unit("radian per year").to(
            "degree per year").m * 1000000

        return np.array([latitude, longitude, omega])
Exemple #13
0
    def to_cartesian(self, pole: np.ndarray) -> np.ndarray:
        """Convert Euler pole of tectonic plate from spherical to cartesian coordinates
       
       That means from location in latitude and longitude and magnitude of rotation to X, Y and Z coordinates.
       
       https://geo.libretexts.org/Courses/University_of_California_Davis/GEL_056%3A_Introduction_to_Geophysics/Geophysics_is_everywhere_in_geology.../04%3A_Plate_Tectonics/4.07%3A_Plate_Motions_on_a_Sphere
       
       Args:
           pole: Euler pole array in spherical coordinates (latitude [deg], longitude [deg], magnitude of rotation [degree per million years])
       
       Returns:
           Euler pole in cartesian coordinates (X, Y, Z) in [milliarcsecond/yr]
       """
        lat = pole[0] * Unit.degree2radian
        lon = pole[1] * Unit.degree2radian
        w = pole[2] * Unit("degree per year").to("radian per year").m / 1000000

        wx = w * np.cos(lat) * np.cos(lon) * Unit.radian2milliarcsecond
        wy = w * np.cos(lat) * np.sin(lon) * Unit.radian2milliarcsecond
        wz = w * np.sin(lat) * Unit.radian2milliarcsecond

        return np.array([wx, wy, wz])
    def get_pole(self, plate: str, unit: Union[None, str] = None ) -> "RotationPole":
        """Get rotation pole object for given tectonic plate
        
        Args:
            plate: Name of tectonic plate (e.g. eura)
            model: Plate motion model name
            unit:  Define unit of rotation pole (e.g. 'radian per year', 'milliarcsecond per year')
            
        Returns:
            Instance of RotationPole dataclass for chosen tectonic plate
        """
        try:
            pole = replace(self.poles[plate])  # Make a copy of RotationPole object
        except KeyError:
            plates = ", ".join(sorted(self.poles.keys()))
            raise exceptions.UnknownSystemError(f"Tectonic plate {plate!r} unknown in plate motion model {self.name}. Use one of {plates}")

        if unit:
            for entry in ["wx", "wy", "wz", "dwx", "dwy", "dwz"]:
                setattr(pole, entry, getattr(pole, entry) * Unit(pole.unit).to(unit).m)
            pole.unit = unit
            
        return pole
Exemple #15
0
    def _add_fields(self, dset, param_names):
        """Add fields to the given dataset

        Adds fields for state vectors and estimate vectors for each parameter. Parameters with names ending with an
        underscore, `_`, are not added to the dataset.

        Args:
            dset (Dataset):       The dataset.
            param_names (List):   Strings with names of parameters. Used to form field names.

        """
        # Delete values from previous iterations
        if "state" in dset.fields:
            del dset.state

        if "estimate" in dset.fields:
            del dset.estimate

        for idx, param_name in enumerate(param_names):
            if param_name.endswith("_"):
                continue

            # State vectors
            fieldname = f"state.{param_name}"
            fieldname_sigma = fieldname + "_sigma"
            value = self.x_smooth[:dset.num_obs, idx, 0]
            value_sigma = np.sqrt(self.x_hat_ferr[:dset.num_obs, idx])

            # Convert values to the display unit. It corresponds to "meter per <unit of partial>"
            partial_unit = dset.unit("partial.{}".format(param_name))
            to_unit = dset.meta["display_units"][param_name]
            from_unit = f"meter/({partial_unit[0]})"
            factor = Unit(from_unit, to_unit)
            dset.meta.add(param_name, factor, section="display_factors")
            dset.add_float(fieldname,
                           val=value * factor,
                           unit=to_unit,
                           write_level="operational")

            # Convert values to the display unit. It corresponds to "meter per <unit of partial>"
            partial_unit = dset.unit("partial.{}".format(param_name))
            to_unit = dset.meta["display_units"][param_name]
            from_unit = f"meter/({partial_unit[0]})"
            factor = Unit(from_unit, to_unit)
            dset.meta.add(param_name, factor, section="display_factors")
            dset.add_float(fieldname_sigma,
                           val=value_sigma * factor,
                           unit=to_unit,
                           write_level="operational")

            # Estimate vectors
            fieldname = f"estimate.{param_name}"
            value = self.h[:dset.num_obs, idx,
                           0] * self.x_smooth[:dset.num_obs, idx, 0]
            dset.add_float(fieldname,
                           val=value,
                           unit="meter",
                           write_level="analysis")

        value = (self.x_smooth.transpose(0, 2, 1) @ self.h)[:dset.num_obs, 0,
                                                            0]
        fieldname = "est"
        if fieldname in dset.fields:
            dset[fieldname][:] = value
        else:
            dset.add_float(fieldname,
                           val=value,
                           unit="meter",
                           write_level="operational")
Exemple #16
0
    def _difference(self, other, num_obs, self_idx, other_idx, copy_self_on_error=False, copy_other_on_error=False):
        """Perform the - operation for each field in self and other"""
        result = self.__class__()
        for fieldname, field in self._fields.items():
            if fieldname in other._fields:
                try:
                    factors = [Unit(_from, _to) for _to, _from in zip(field._unit, other._fields[fieldname]._unit)]
                except TypeError:
                    factors = None
                except exceptions.UnitError as err:
                    raise ValueError(f"Cannot compute difference for field `{fieldname}`: {err}")
                try:
                    if factors:
                        difference = self[fieldname][self_idx] - other[fieldname][other_idx] * np.array(factors)
                    else:
                        difference = self[fieldname][self_idx] - other[fieldname][other_idx]
                    fieldtype = fieldtypes.fieldtype(difference)
                    func = fieldtypes.function(fieldtype)
                    field = func(
                        num_obs=num_obs,
                        name=fieldname,
                        val=difference,
                        unit=field._unit,
                        write_level=field._write_level.name,
                    )
                    result.add_field(fieldname, field)
                except IndexError as err:
                    # fieldname is a collection
                    collection = self[fieldname]._difference(
                        other[fieldname],
                        num_obs,
                        self_idx,
                        other_idx,
                        copy_self_on_error=copy_self_on_error,
                        copy_other_on_error=copy_other_on_error,
                    )
                    fieldtype = fieldtypes.fieldtype(collection)
                    func = fieldtypes.function(fieldtype)
                    field = func(
                        num_obs=num_obs,
                        name=fieldname,
                        val=collection,
                        unit=field._unit,
                        write_level=field._write_level.name,
                    )
                    result.add_field(fieldname, field)
                except TypeError as err:
                    # Fields that do not support the - operator
                    if copy_self_on_error:
                        index_data = self[fieldname][self_idx]
                        fieldtype = fieldtypes.fieldtype(index_data)
                        func = fieldtypes.function(fieldtype)
                        self_fieldname = f"{fieldname}_self"
                        field = func(
                            num_obs=num_obs,
                            name=self_fieldname,
                            val=index_data,
                            unit=field._unit,
                            write_level=field._write_level.name,
                        )
                        result.add_field(self_fieldname, field)
                    if copy_other_on_error:
                        index_data = other[fieldname][other_idx]
                        fieldtype = fieldtypes.fieldtype(index_data)
                        func = fieldtypes.function(fieldtype)
                        other_fieldname = f"{fieldname}_other"
                        field = func(
                            num_obs=num_obs,
                            name=other_fieldname,
                            val=index_data,
                            unit=other._fields[fieldname]._unit,
                            write_level=other._fields[fieldname]._write_level.name,
                        )
                        result.add_field(other_fieldname, field)

        return result