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