diff --git a/coverage_model/coverage.py b/coverage_model/coverage.py index 5a98a78..9ffeaca 100644 --- a/coverage_model/coverage.py +++ b/coverage_model/coverage.py @@ -317,6 +317,7 @@ def append_parameter(self, parameter_context): if hasattr(pcontext, '_pval_callback'): pcontext._pval_callback = self.get_parameter_values pcontext._pctxt_callback = self.get_parameter_context + pcontext._pdir = self.persistence_dir self._range_dictionary.add_context(pcontext) s = self._persistence_layer.init_parameter(pcontext, self._bricking_scheme) @@ -2186,6 +2187,8 @@ def _doload(self): if hasattr(pc, '_pval_callback'): pc._pval_callback = self.get_parameter_values pc._pctxt_callback = self.get_parameter_context + pc._pdir = self.persistence_dir + self._range_dictionary.add_context(pc) if pc.param_type._value_class == 'SparseConstantValue': s = SparsePersistedStorage(md, mm, self._persistence_layer.brick_dispatcher, dtype=pc.param_type.storage_encoding, fill_value=pc.param_type.fill_value, mode=self.mode, inline_data_writes=inline_data_writes, auto_flush=auto_flush_values) diff --git a/coverage_model/parameter_functions.py b/coverage_model/parameter_functions.py index 44c7e47..13c900a 100644 --- a/coverage_model/parameter_functions.py +++ b/coverage_model/parameter_functions.py @@ -14,6 +14,7 @@ from numbers import Number from collections import OrderedDict from coverage_model.basic_types import AbstractBase +import os class ParameterFunctionException(Exception): @@ -269,3 +270,41 @@ def __eq__(self, other): ret = self.expression == other.expression return ret + +class ExternalFunction(AbstractFunction): + def __init__(self, name, external_guid, external_name): + self.external_name = external_name + param_map = {external_name : external_guid} + AbstractFunction.__init__(self, name, [], param_map) + + def load_coverage(self, pdir): + from coverage_model.coverage import AbstractCoverage + root_path, guid = os.path.split(pdir) + external_guid = self.param_map[self.external_name] + path = os.path.join(root_path, external_guid) + cov = AbstractCoverage.load(path, mode='r') + return cov + + def evaluate(self, pval_callback, pdir, slice_, fill_value=-9999): + return self.linear_map(pval_callback, pdir, slice_) + + def linear_map(self, pval_callback, pdir, slice_): + cov = self.load_coverage(pdir) + # TODO: Might not want to hard-code time + x = pval_callback('time', slice_) + x_i = cov.get_parameter_values('time') + y_i = cov.get_parameter_values(self.external_name) + + # Where in x_i does x fit in? + upper = np.searchsorted(x_i, x) + # Clip values not in [1, N-1] + upper = upper.clip(1, len(x_i)-1).astype(int) + lower = upper - 1 + + # Linear interpolation + w = (x - x_i[lower]) / (x_i[upper] - x_i[lower]) + y = y_i[lower] * (1-w) + y_i[upper] * w + return y + + + diff --git a/coverage_model/parameter_types.py b/coverage_model/parameter_types.py index 2382f52..e78bd75 100644 --- a/coverage_model/parameter_types.py +++ b/coverage_model/parameter_types.py @@ -591,6 +591,7 @@ def __init__(self, function, value_encoding=None, **kwargs): self._template_attrs['_pval_callback'] = None self._template_attrs['_pctxt_callback'] = None + self._template_attrs['_pdir'] = None self._gen_template_attrs() @@ -607,13 +608,14 @@ def get_function_map(self, parent_arg_name=None): def _todict(self, exclude=None): # Must exclude _cov_range_value from persistence - return super(ParameterFunctionType, self)._todict(exclude=['_pval_callback', '_pctxt_callback', '_fmap', '_iparams', '_dparams']) + return super(ParameterFunctionType, self)._todict(exclude=['_pdir', '_pval_callback', '_pctxt_callback', '_fmap', '_iparams', '_dparams']) @classmethod def _fromdict(cls, cmdict, arg_masks=None): ret = super(ParameterFunctionType, cls)._fromdict(cmdict, arg_masks=arg_masks) # Add the _pval_callback attribute, initialized to None ret._pval_callback = None + ret._pdir = None return ret def __eq__(self, other): diff --git a/coverage_model/parameter_values.py b/coverage_model/parameter_values.py index 7f3f9fc..f97c335 100644 --- a/coverage_model/parameter_values.py +++ b/coverage_model/parameter_values.py @@ -9,7 +9,7 @@ from ooi.logging import log from coverage_model.basic_types import AbstractBase, InMemoryStorage, VariabilityEnum, Span from coverage_model.numexpr_utils import is_well_formed_where, nest_wheres -from coverage_model.parameter_functions import ParameterFunctionException +from coverage_model.parameter_functions import ParameterFunctionException, ExternalFunction from coverage_model import utils import numpy as np import numexpr as ne @@ -267,6 +267,7 @@ def __init__(self, parameter_type, domain_set, storage=None, **kwargs): # Grab a local pointer to the coverage's _cov_range_value object self._pval_callback = self.parameter_type._pval_callback + self._pdir = self.parameter_type._pdir self._memoized_values = None @property @@ -292,7 +293,10 @@ def __getitem__(self, slice_): slice_ = utils.fix_slice(slice_, self.shape) try: - r = self.content.evaluate(self._pval_callback, slice_, self.parameter_type.fill_value) + if isinstance(self.parameter_type.function, ExternalFunction): + r = self.content.evaluate(self._pval_callback, self._pdir, slice_, self.parameter_type.fill_value) + else: + r = self.content.evaluate(self._pval_callback, slice_, self.parameter_type.fill_value) ve = self.parameter_type.value_encoding if hasattr(self.parameter_type, 'inner_encoding'): ve = self.parameter_type.inner_encoding diff --git a/coverage_model/test/test_complex_coverage.py b/coverage_model/test/test_complex_coverage.py index 3b13ae6..831503b 100644 --- a/coverage_model/test/test_complex_coverage.py +++ b/coverage_model/test/test_complex_coverage.py @@ -18,6 +18,7 @@ from copy import deepcopy from coverage_model.hdf_utils import HDFLockingFile from coverage_test_base import CoverageIntTestBase, get_props +from coverage_model.parameter_functions import ExternalFunction import time @@ -168,6 +169,27 @@ def test_aggregates(self): vcov = ViewCoverage(self.working_dir, create_guid(), 'view coverage', reference_coverage_location = ccov.persistence_dir) + @attr('INT', group='cov') + def test_external_refs(self): + + # Create a three param coverage + offset = NumexprFunction('offset', arg_list=['x'], expression='x + 1') + offset.param_map = {'x':'value_set'} + ctx = ParameterContext('offset', param_type=ParameterFunctionType(offset, value_encoding='