Exemple #1
0
class VecWrapper(object):
    """
    A dict-like container of a collection of variables.

    Args
    ----
    pathname : str, optional
        the pathname of the containing `System`

    comm : an MPI communicator (real or fake)
        a communicator that can be used for distributed operations
        when running under MPI.  If not running under MPI, it is
        ignored

    Attributes
    ----------
    idx_arr_type : dtype, optional
        A dtype indicating how index arrays are to be represented.
        The value 'i' indicates an numpy integer array, other
        implementations, e.g., petsc, will define this differently.
    """

    idx_arr_type = 'i'

    def __init__(self, pathname='', comm=None):
        self.pathname = pathname
        self.comm = comm
        self.vec = None
        self._vardict = OrderedDict()
        self._slices = OrderedDict()

        # add a flat attribute that will have access method consistent
        # with non-flat access  (__getitem__)
        self.flat = _flat_dict(self._vardict)

        # Automatic unit conversion in target vectors
        self.deriv_units = False
        self.adj_accumulate_mode = False

    def metadata(self, name):
        """
        Returns the metadata for the named variable.

        Args
        ----
        name : str
            Name of variable to get the metadata for.

        Returns
        -------
        dict
            The metadata dict for the named variable.

        Raises
        -------
        KeyError
            If the named variable is not in this vector.
        """
        try:
            return self._vardict[name]
        except KeyError as error:
            msg = "Variable '{name}' does not exist".format(name=name)
            raise KeyError(msg)

    def _setup_prom_map(self):
        """
        Sets up the internal dict that maps absolute name to promoted name.
        """
        self._to_prom_name = {}
        for prom_name, meta in self.items():
            self._to_prom_name[meta['pathname']] = prom_name

    def __getitem__(self, name):
        """
        Retrieve unflattened value of named var.

        Args
        ----
        name : str
            Name of variable to get the value for.

        Returns
        -------
            The unflattened value of the named variable.
        """
        meta = self.metadata(name)

        if meta.get('pass_by_obj'):
            return meta['val'].val

        unitconv = meta.get('unit_conv')
        shape = meta.get('shape')

        # For dparam vector, getitem is disabled in adjoint mode.
        if self.adj_accumulate_mode == True:
            return numpy.zeros((shape))

        # Convert units
        elif unitconv:
            scale, offset = unitconv

            # Gradient is just the scale
            if self.deriv_units:
                offset = 0.0

            # if shape is 1, it's a float
            if shape == 1:
                return scale*(meta['val'][0] + offset)
            else:
                return scale*(meta['val'].reshape(shape) + offset)

        else:
            # if shape is 1, it's a float
            if shape == 1:
                return meta['val'][0]
            else:
                return meta['val'].reshape(shape)

    def __setitem__(self, name, value):
        """
        Set the value of the named variable.

        Args
        ----
        name : str
            Name of variable to get the value for.

        value :
            The unflattened value of the named variable.
        """
        meta = self.metadata(name)

        if meta.get('pass_by_obj'):
            meta['val'].val = value
            return

        unitconv = meta.get('unit_conv')

        # For dparam vector in adjoint mode, assignement behaves as +=.
        if self.adj_accumulate_mode is True:
            if self.deriv_units and unitconv:
                scale, offset = unitconv

                if isinstance(value, numpy.ndarray):
                    meta['val'][:] += scale*value.flat[:]
                else:
                    meta['val'][0] += scale*value

            else:
                if isinstance(value, numpy.ndarray):
                    meta['val'][:] += value.flat[:]
                else:
                    meta['val'][0] += value

        # Convert Units
        else:
            if self.deriv_units and unitconv:
                scale, offset = unitconv

                if isinstance(value, numpy.ndarray):
                    meta['val'][:] = scale*value.flat[:]
                else:
                    meta['val'][0] = scale*value

            else:
                if isinstance(value, numpy.ndarray):
                    meta['val'][:] = value.flat[:]
                else:
                    meta['val'][0] = value

    def __len__(self):
        """
        Returns
        -------
            The number of keys (variables) in this vector.
        """
        return len(self._vardict)

    def __contains__(self, key):
        """
        Returns
        -------
            A boolean indicating if the given key (variable name) is in this vector.
        """

        return key in self._vardict

    def __iter__(self):
        """
        Returns
        -------
            A dictionary iterator over the items in _vardict.
        """
        return self._vardict.__iter__()

    def keys(self):
        """
        Returns
        -------
        list or KeyView (python 3)
            the keys (variable names) in this vector.
        """
        return self._vardict.keys()

    def items(self):
        """
        Returns
        -------
        iterator
            Iterator returning the name and metadata dict for each variable.
        """
        return iteritems(self._vardict)

    def values(self):
        """
        Returns
        -------
        iterator
            Iterator returning a metadata dict for each variable.
        """
        for meta in self._vardict.values():
            yield meta

    def get_local_idxs(self, name, idx_dict):
        """
        Returns all of the indices for the named variable in this vector.

        Args
        ----
        name : str
            Name of variable to get the indices for.

        Returns
        -------
        size
            The size of the named variable.

        ndarray
            Index array containing all local indices for the named variable.
        """
        # TODO: add support for returning slice objects

        meta = self._vardict[name]
        if meta.get('pass_by_obj'):
            raise RuntimeError("No vector indices can be provided "
                               "for 'pass by object' variable '%s'" % name)

        if name not in self._slices:
            return meta['size'], self.make_idx_array(0, 0)

        start, end = self._slices[name]
        if name in idx_dict:
            idxs = self.to_idx_array(idx_dict[name]) + start
            if idxs.size > (end-start) or max(idxs) >= end:
                raise RuntimeError("Indices of interest specified for '%s'"
                                   "are too large" % name)
            return idxs.size, idxs
        else:
            return meta['size'], self.make_idx_array(start, end)

    def norm(self):
        """
        Calculates the norm of this vector.

        Returns
        -------
        float
            Norm of our internal vector.
        """
        return norm(self.vec)

    def get_view(self, sys_pathname, comm, varmap, relevance, var_of_interest):
        """
        Return a new `VecWrapper` that is a view into this one.

        Args
        ----
        sys_pathname : str
            pathname of the system for which the view is being created.

        comm : an MPI communicator (real or fake)
            A communicator that is used in the creation of the view.

        varmap : dict
            Mapping of variable names in the old `VecWrapper` to the names
            they will have in the new `VecWrapper`.

        Returns
        -------
        `VecWrapper`
            A new `VecWrapper` that is a view into this one.
        """
        view = self.__class__(sys_pathname, comm)
        view_size = 0

        start = -1
        for name, meta in self.items():
            if name in varmap:
                view._vardict[varmap[name]] = self._vardict[name]
                if not meta.get('pass_by_obj') and not meta.get('remote'):
                    pstart, pend = self._slices[name]
                    if start == -1:
                        start = pstart
                        end = pend
                    else:
                        assert pstart == end, \
                               "%s not contiguous in block containing %s" % \
                               (name, varmap.keys())
                    end = pend
                    view._slices[varmap[name]] = (view_size, view_size + meta['size'])
                    view_size += meta['size']

        if start == -1: # no items found
            view.vec = self.vec[0:0]
        else:
            view.vec = self.vec[start:end]

        view._setup_prom_map()

        return view

    def make_idx_array(self, start, end):
        """
        Return an index vector of the right int type for
        the current implementation.

        Args
        ----
        start : int
            The starting index.

        end : int
            The ending index.

        Returns
        -------
        ndarray of idx_arr_type
            index array containing all indices from start up to but
            not including end
        """
        return numpy.arange(start, end, dtype=self.idx_arr_type)

    def to_idx_array(self, indices):
        """
        Given some iterator of indices, return an index array of the
        right int type for the current implementation.

        Args
        ----
        indices : iterator of ints
            An iterator of indices.

        Returns
        -------
        ndarray of idx_arr_type
            Index array containing all of the given indices.

        """
        return numpy.array(indices, dtype=self.idx_arr_type)

    def merge_idxs(self, src_idxs, tgt_idxs):
        """
        Return source and target index arrays, built up from
        smaller index arrays and combined in order of ascending source
        index (to allow us to convert src indices to a slice in some cases).

        Args
        ----
        src_idxs : array
            Source indices.

        tgt_idxs : array
            Target indices.

        Returns
        -------
        ndarray of idx_arr_type
            Index array containing all of the merged indices.

        """
        assert(len(src_idxs) == len(tgt_idxs))

        # filter out any zero length idx array entries
        src_idxs = [i for i in src_idxs if len(i)]
        tgt_idxs = [i for i in tgt_idxs if len(i)]

        if len(src_idxs) == 0:
            return self.make_idx_array(0, 0), self.make_idx_array(0, 0)

        src_tups = list(enumerate(src_idxs))

        src_sorted = sorted(src_tups, key=lambda x: x[1].min())

        new_src = [idxs for i, idxs in src_sorted]
        new_tgt = [tgt_idxs[i] for i, _ in src_sorted]

        return idx_merge(new_src), idx_merge(new_tgt)

    def get_promoted_varname(self, abs_name):
        """
        Returns the relative pathname for the given absolute variable
        pathname.

        Args
        ----
        abs_name : str
            Absolute pathname of a variable.

        Returns
        -------
        rel_name : str
            Relative name mapped to the given absolute pathname.
        """
        try:
            return self._to_prom_name[abs_name]
        except KeyError:
            raise KeyError("Relative name not found for variable '%s'" % abs_name)

    def get_states(self):
        """
        Returns
        -------
        list
            A list of names of state variables.
        """
        return [n for n, meta in self.items() if meta.get('state')]

    def get_vecvars(self):
        """
        Returns
        -------
            A list of names of variables found in our 'vec' array.
        """
        return [(n, meta) for n, meta in self.items() if not meta.get('pass_by_obj')]

    def get_byobjs(self):
        """
        Returns
        -------
        list
            A list of names of variables that are passed by object rather than
            through scattering entries from one array to another.
        """
        return [(n, meta) for n, meta  in self.items() if meta.get('pass_by_obj')]

    def _scoped_abs_name(self, name):
        """
        Args
        ----
        name : str
            The absolute pathname of a variable.

        Returns
        -------
        str
            The given name as seen from the 'scope' of the `System` that
            contains this `VecWrapper`.
        """
        if self.pathname:
            start = len(self.pathname)+1
        else:
            start = 0
        return name[start:]

    def dump(self, out_stream=sys.stdout):
        """
        Args
        ----
        out_stream : file_like
            Where to send human readable output. Default is sys.stdout. Set to
            None to return a str.
        """

        if out_stream is None:
            out_stream = cStringIO()
            return_str = True
        else:
            return_str = False

        lens = [len(n) for n in self.keys()]
        nwid = max(lens) if lens else 10
        vlens = [len(repr(self[v])) for v in self.keys()]
        vwid = max(vlens) if vlens else 1
        if len(self.get_vecvars()) != len(self.keys()): # we have some pass by obj
            defwid = 8
        else:
            defwid = 1
        slens = [len('[{0[0]}:{0[1]}]'.format(self._slices[v])) for v in self.keys()
                       if v in self._slices]+[defwid]
        swid = max(slens)

        for v, meta in self.items():
            if meta.get('pass_by_obj') or meta.get('remote'):
                continue
            if v in self._slices:
                uslice = '[{0[0]}:{0[1]}]'.format(self._slices[v])
            else:
                uslice = ''
            template = "{0:<{nwid}} {1:<{swid}} {2:>{vwid}}\n"
            out_stream.write(template.format(v,
                                             uslice,
                                             repr(self[v]),
                                             nwid=nwid,
                                             swid=swid,
                                             vwid=vwid))

        for v, meta in self.items():
            if meta.get('pass_by_obj') and not meta.get('remote'):
                template = "{0:<{nwid}} {1:<{swid}} {2}\n"
                out_stream.write(template.format(v, '(by obj)',
                                                 repr(self[v]),
                                                 nwid=nwid,
                                                 swid=swid))
        if return_str:
            return out_stream.getvalue()

    def _set_adjoint_mode(self, mode=False):
        """ Turn on or off adjoint accumlate mode."""
        self.adj_accumulate_mode = mode