def get_width(self, attr): """Return the flattened width of the value of the given attribute.""" width = self._width_cache.get(attr, _missing) if width is _missing: param = from_PA_var(attr) self._width_cache[attr] = width = flattened_size( param, self.scope.get(param), self.scope) return width
def get_dependents(self, fixed_point=False): """Returns a list of current values of the dependents. This includes both constraints and severed sources. fixed_point: bool Set to True if we are doing fixed-point iteration instead of a more general solve. In such a case, we need to swap the order of the constraints to match the parameter order. We also may need to swap signs on the constraints. """ deps = self._parent.eval_eq_constraints(self.scope) # Reorder for fixed point if fixed_point is True: newdeps = zeros(len(deps)) eqcons = self._parent.get_eq_constraints() old_j = 0 for key, value in eqcons.iteritems(): con_targets = value.get_referenced_varpaths() new_j = 0 for params in self._parent.list_param_group_targets(): if params[0] == value.rhs.text: newdeps[new_j] = deps[old_j] elif params[0] == value.lhs.text: newdeps[new_j] = -deps[old_j] new_j += 1 old_j += 1 deps = newdeps sev_deps = [] for src, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) src = from_PA_var(src) src_val = self.scope.get(src) targ_val = self.scope.get(target) res = flattened_value(src, src_val) - flattened_value( target, targ_val) sev_deps.extend(res) return hstack((deps, sev_deps))
def get_dependents(self, fixed_point=False): """Returns a list of current values of the dependents. This includes both constraints and severed sources. fixed_point: bool Set to True if we are doing fixed-point iteration instead of a more general solve. In such a case, we need to swap the order of the constraints to match the parameter order. We also may need to swap signs on the constraints. """ deps = self._parent.eval_eq_constraints(self.scope) # Reorder for fixed point if fixed_point == True: newdeps = zeros(len(deps)) eqcons = self._parent.get_eq_constraints() old_j = 0 for key, value in eqcons.iteritems(): con_targets = value.get_referenced_varpaths() new_j = 0 for params in self._parent.list_param_group_targets(): if params[0] == value.rhs.text: newdeps[new_j] = deps[old_j] elif params[0] == value.lhs.text: newdeps[new_j] = -deps[old_j] new_j += 1 old_j += 1 deps = newdeps sev_deps = [] for src, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) src = from_PA_var(src) src_val = self.scope.get(src) targ_val = self.scope.get(target) res = flattened_value(src, src_val) - flattened_value(target, targ_val) sev_deps.extend(res) return hstack((deps, sev_deps))
def get_width(self, attr): """Return the flattened width of the value of the given attribute.""" width = self._width_cache.get(attr, _missing) if width is _missing: param = from_PA_var(attr) self._width_cache[attr] = width = flattened_size(param, self.scope.get(param), self.scope) return width
def set_independents(self, val): """Sets all dependent variables to the values in the input array `val`. This includes both parameters and severed targets. """ nparam = self._parent.total_parameters() if nparam > 0: self._parent.set_parameters(val[:nparam].flatten()) if len(self._severed_edges) > 0: i = nparam for src, targets in self._mapped_severed_edges: if isinstance(targets, str): targets = [targets] i1, i2 = self.get_bounds(src) if isinstance(i1, list): width = len(i1) else: width = i2-i1 i1 = i i2 = i + width for target in targets: target = from_PA_var(target) old_val = self.scope.get(target) if isinstance(old_val, float): new_val = float(val[i1:i2]) elif isinstance(old_val, ndarray): shape = old_val.shape if len(shape) > 1: new_val = val[i1:i2].copy() new_val = new_val.reshape(shape) else: new_val = val[i1:i2].copy() elif isinstance(old_val, VariableTree): new_val = old_val.copy() self._vtree_set(target, new_val, val[i1:i2], i1) else: msg = "Variable %s is of type %s." % (target, type(old_val)) + \ " This type is not supported by the MDA Solver." self.scope.raise_exception(msg, RuntimeError) i += width # Poke new value into the input end of the edge. self.scope.set(target, new_val, force=True) # Prevent OpenMDAO from stomping on our poked input. self.scope.set_valid([target.split('[',1)[0]], True)
def get_dependents(self): """Returns a list of current values of the dependents. This includes both constraints and severed sources. """ deps = self._parent.eval_eq_constraints(self.scope) sev_deps = [] for src, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) src = from_PA_var(src) src_val = self.scope.get(src) targ_val = self.scope.get(target) res = flattened_value(src, src_val) - flattened_value(target, targ_val) sev_deps.extend(res) return hstack((deps, sev_deps))
def set_independents(self, val): """Sets all dependent variables to the values in the input array `val`. This includes both parameters and severed targets. """ bounds = self._bounds_cache nparam = self._parent.total_parameters() if nparam > 0: self._parent.set_parameters(val[:nparam].flatten()) if len(self._severed_edges) > 0: i = nparam for src, targets in self._mapped_severed_edges: if isinstance(targets, str): targets = [targets] i1, i2 = bounds[src] if isinstance(i1, list): width = len(i1) else: width = i2 - i1 i1 = i i2 = i + width for target in targets: target = from_PA_var(target) old_val = self.scope.get(target) if isinstance(old_val, float): new_val = float(val[i1:i2]) elif isinstance(old_val, ndarray): shape = old_val.shape if len(shape) > 1: new_val = val[i1:i2].copy() new_val = new_val.reshape(shape) else: new_val = val[i1:i2].copy() elif isinstance(old_val, VariableTree): new_val = old_val.copy() self._vtree_set(target, new_val, val[i1:i2], i1) else: msg = "Variable %s is of type %s." % (target, type(old_val)) + \ " This type is not supported by the MDA Solver." self.scope.raise_exception(msg, RuntimeError) i += width # Poke new value into the input end of the edge. self.scope.set(target, new_val, force=True) # Prevent OpenMDAO from stomping on our poked input. self.scope.set_valid([target.split('[', 1)[0]], True)
def get_independents(self): """Returns a list of current values of the dependents. This includes both parameters and severed targets. """ indeps = self._parent.eval_parameters(self.scope) sev_indeps = [] for _, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) old_val = self.scope.get(target) sev_indeps.extend(flattened_value(target, old_val)) return hstack((indeps, sev_indeps))
def get_independents(self): """Returns a list of current values of the dependents. This includes both parameters and severed targets. """ indeps = self.parent.eval_parameters(self.scope) sev_indeps = [] for _, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) old_val = self.scope.get(target) sev_indeps.extend(flattened_value(target, old_val)) return hstack((indeps, sev_indeps))
def calc_gradient(self, inputs=None, outputs=None, upscope=False, mode='auto'): """Returns the gradient of the passed outputs with respect to all passed inputs. inputs: list of strings or tuples of strings List of input variables that we are taking derivatives with respect to. They must be within this workflow's scope. If no inputs are given, the parent driver's parameters are used. A tuple can be used to link inputs together. outputs: list of strings List of output variables that we are taking derivatives of. They must be within this workflow's scope. If no outputs are given, the parent driver's objectives and constraints are used. upscope: boolean This is set to True when our workflow is part of a subassembly that lies in a workflow that needs a gradient with respect to variables outside of this workflow, so that the caches can be reset. mode: string Set to 'forward' for forward mode, 'adjoint' for adjoint mode, 'fd' for full-model finite difference (with fake finite difference disabled), or 'auto' to let OpenMDAO determine the correct mode. """ self._J_cache = {} # User may request full-model finite difference. if self._parent.gradient_options.force_fd == True: mode = 'fd' # This function can be called from a parent driver's workflow for # assembly recursion. We have to clear our cache if that happens. # We also have to clear it next time we arrive back in our workflow. if upscope or self._upscoped: self._derivative_graph = None self._edges = None self._comp_edges = None self._upscoped = upscope dgraph = self.derivative_graph(inputs, outputs, fd=(mode == 'fd')) if 'mapped_inputs' in dgraph.graph: inputs = dgraph.graph['mapped_inputs'] outputs = dgraph.graph['mapped_outputs'] else: inputs = dgraph.graph['inputs'] outputs = dgraph.graph['outputs'] n_edge = self.initialize_residual() # cache Jacobians for comps that return them from provideJ # Size our Jacobian num_in = 0 for item in inputs: # For parameter groups, only size the first if not isinstance(item, basestring): item = item[0] try: i1, i2 = self.get_bounds(item) if isinstance(i1, list): num_in += len(i1) else: num_in += i2 - i1 except KeyError: val = self.scope.get(item) num_in += flattened_size(item, val, self.scope) num_out = 0 for item in outputs: try: i1, i2 = self.get_bounds(item) if isinstance(i1, list): num_out += len(i1) else: num_out += i2 - i1 except KeyError: val = self.scope.get(item) num_out += flattened_size(item, val, self.scope) shape = (num_out, num_in) # Auto-determine which mode to use based on Jacobian shape. if mode == 'auto': # TODO - additional determination based on presence of # apply_derivT if num_in > num_out: mode = 'adjoint' else: mode = 'forward' if mode == 'adjoint': J = calc_gradient_adjoint(self, inputs, outputs, n_edge, shape) elif mode in ['forward', 'fd']: J = calc_gradient(self, inputs, outputs, n_edge, shape) else: msg = "In calc_gradient, mode must be 'forward', 'adjoint', " + \ "'auto', or 'fd', but a value of %s was given." % mode self.scope.raise_exception(msg, RuntimeError) # Finally, we need to untransform the jacobian if any parameters have # scalers. #print 'edges:', self._edges if not hasattr(self._parent, 'get_parameters'): return J params = self._parent.get_parameters() if len(params) == 0: return J i = 0 for group in inputs: if isinstance(group, str): group = [group] name = group[0] if len(group) > 1: pname = tuple([from_PA_var(aname) for aname in group]) else: pname = from_PA_var(name) try: i1, i2 = self.get_bounds(name) except KeyError: continue if isinstance(i1, list): width = len(i1) else: width = i2 - i1 if pname in params: scaler = params[pname].scaler if scaler != 1.0: J[:, i:i + width] = J[:, i:i + width] * scaler i = i + width #print J return J
def initialize_residual(self): """Creates the array that stores the residual. Also returns the number of edges. """ dgraph = self.derivative_graph() if 'mapped_inputs' in dgraph.graph: inputs = dgraph.graph['mapped_inputs'] else: inputs = dgraph.graph['inputs'] basevars = set() edges = self.edge_list() implicit_edges = self.get_implicit_info() sortedkeys = sorted(implicit_edges) sortedkeys.extend(sorted(edges.keys())) nEdge = 0 for src in sortedkeys: if src in implicit_edges: targets = implicit_edges[src] is_implicit = True else: targets = edges[src] is_implicit = False if isinstance(targets, str): targets = [targets] # Implicit source edges are tuples. if is_implicit == True: impli_edge = nEdge for resid in src: unmap_src = from_PA_var(resid) val = self.scope.get(unmap_src) width = flattened_size(unmap_src, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 bound = (impli_edge, impli_edge + width) self.set_bounds(resid, bound) basevars.add(resid) impli_edge += width # Regular components else: # Only need to grab the source (or first target for param) to # figure out the size for the residual vector measure_src = src if '@in' in src: idx = int(src[3:].split('[')[0]) inp = inputs[idx] if not isinstance(inp, basestring): inp = inp[0] if inp in dgraph: measure_src = inp else: measure_src = targets[0] elif src == '@fake': for t in targets: if not t.startswith('@'): measure_src = t break else: raise RuntimeError("malformed graph!") # Find our width, etc. unmap_src = from_PA_var(measure_src) val = self.scope.get(unmap_src) width = flattened_size(unmap_src, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 # Special poke for boundary node if is_boundary_node(dgraph, measure_src) or \ is_boundary_node(dgraph, dgraph.base_var(measure_src)): bound = (nEdge, nEdge + width) self.set_bounds(measure_src, bound) src_noidx = src.split('[', 1)[0] # Poke our source data # Array slice of src that is already allocated if '[' in src and src_noidx in basevars: _, _, idx = src.partition('[') basebound = self.get_bounds(src_noidx) if not '@in' in src_noidx: unmap_src = from_PA_var(src_noidx) val = self.scope.get(unmap_src) shape = val.shape offset = basebound[0] istring, ix = flatten_slice(idx, shape, offset=offset, name='ix') bound = (istring, ix) # Already allocated width = 0 # Input-input connection to implicit state elif src_noidx in basevars: bound = self.get_bounds(src_noidx) width = 0 # Normal src else: bound = (nEdge, nEdge + width) self.set_bounds(src, bound) basevars.add(src) # Poke our target data impli_edge = nEdge for target in targets: # Handle States in implicit comps if is_implicit == True: if isinstance(target, str): target = [target] unmap_targ = from_PA_var(target[0]) val = self.scope.get(unmap_targ) imp_width = flattened_size(unmap_targ, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 for itarget in target: bound = (impli_edge, impli_edge + imp_width) self.set_bounds(itarget, bound) basevars.add(itarget) impli_edge += imp_width width = impli_edge - nEdge elif not target.startswith('@'): self.set_bounds(target, bound) #print input_src, src, target, bound, nEdge += width impli_edge = nEdge # Initialize the residual vector on the first time through, and also # if for some reason the number of edges has changed. if self.res is None or nEdge != self.res.shape[0]: self.res = zeros((nEdge, 1)) return nEdge
def calc_gradient(self, inputs=None, outputs=None, upscope=False, mode='auto'): """Returns the gradient of the passed outputs with respect to all passed inputs. inputs: list of strings or tuples of strings List of input variables that we are taking derivatives with respect to. They must be within this workflow's scope. If no inputs are given, the parent driver's parameters are used. A tuple can be used to link inputs together. outputs: list of strings List of output variables that we are taking derivatives of. They must be within this workflow's scope. If no outputs are given, the parent driver's objectives and constraints are used. upscope: boolean This is set to True when our workflow is part of a subassembly that lies in a workflow that needs a gradient with respect to variables outside of this workflow, so that the caches can be reset. mode: string Set to 'forward' for forward mode, 'adjoint' for adjoint mode, 'fd' for full-model finite difference (with fake finite difference disabled), or 'auto' to let OpenMDAO determine the correct mode. """ self._J_cache = {} # User may request full-model finite difference. if self._parent.gradient_options.force_fd == True: mode = 'fd' # This function can be called from a parent driver's workflow for # assembly recursion. We have to clear our cache if that happens. # We also have to clear it next time we arrive back in our workflow. if upscope or self._upscoped: self._derivative_graph = None self._edges = None self._comp_edges = None self._upscoped = upscope dgraph = self.derivative_graph(inputs, outputs, fd=(mode == 'fd')) if 'mapped_inputs' in dgraph.graph: inputs = dgraph.graph['mapped_inputs'] outputs = dgraph.graph['mapped_outputs'] else: inputs = dgraph.graph['inputs'] outputs = dgraph.graph['outputs'] n_edge = self.initialize_residual() # cache Jacobians for comps that return them from provideJ # Size our Jacobian num_in = 0 for item in inputs: # For parameter groups, only size the first if not isinstance(item, basestring): item = item[0] i1, i2 = self.get_bounds(item) if isinstance(i1, list): num_in += len(i1) else: num_in += i2-i1 num_out = 0 for item in outputs: i1, i2 = self.get_bounds(item) if isinstance(i1, list): num_out += len(i1) else: num_out += i2-i1 shape = (num_out, num_in) # Auto-determine which mode to use based on Jacobian shape. if mode == 'auto': # TODO - additional determination based on presence of # apply_derivT if num_in > num_out: mode = 'adjoint' else: mode = 'forward' if mode == 'adjoint': J = calc_gradient_adjoint(self, inputs, outputs, n_edge, shape) elif mode in ['forward', 'fd']: J = calc_gradient(self, inputs, outputs, n_edge, shape) else: msg = "In calc_gradient, mode must be 'forward', 'adjoint', " + \ "'auto', or 'fd', but a value of %s was given." % mode self.scope.raise_exception(msg, RuntimeError) # Finally, we need to untransform the jacobian if any parameters have # scalers. if not hasattr(self._parent, 'get_parameters'): return J params = self._parent.get_parameters() if len(params) == 0: return J i = 0 for group in inputs: if isinstance(group, str): group = [group] name = group[0] if len(group) > 1: pname = tuple([from_PA_var(aname) for aname in group]) else: pname = from_PA_var(name) i1, i2 = self.get_bounds(name) if isinstance(i1, list): width = len(i1) else: width = i2-i1 if pname in params: scaler = params[pname].scaler if scaler != 1.0: J[:, i:i+width] = J[:, i:i+width]*scaler i = i + width #print J return J
def initialize_residual(self): """Creates the array that stores the residual. Also returns the number of edges. """ dgraph = self.derivative_graph() if 'mapped_inputs' in dgraph.graph: inputs = dgraph.graph['mapped_inputs'] else: inputs = dgraph.graph['inputs'] basevars = set() edges = self.edge_list() implicit_edges = self.get_implicit_info() sortedkeys = sorted(implicit_edges) sortedkeys.extend(sorted(edges.keys())) nEdge = 0 for src in sortedkeys: if src in implicit_edges: targets = implicit_edges[src] is_implicit = True else: targets = edges[src] is_implicit = False if isinstance(targets, str): targets = [targets] # Implicit source edges are tuples. if is_implicit == True: impli_edge = nEdge for resid in src: unmap_src = from_PA_var(resid) val = self.scope.get(unmap_src) width = flattened_size(unmap_src, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 bound = (impli_edge, impli_edge+width) self.set_bounds(resid, bound) basevars.add(resid) impli_edge += width # Regular components else: # Only need to grab the source (or first target for param) to # figure out the size for the residual vector measure_src = src if '@in' in src: idx = int(src[3:].split('[')[0]) inp = inputs[idx] if not isinstance(inp, basestring): inp = inp[0] if inp in dgraph: measure_src = inp else: measure_src = targets[0] elif src == '@fake': for t in targets: if not t.startswith('@'): measure_src = t break else: raise RuntimeError("malformed graph!") # Find our width, etc. unmap_src = from_PA_var(measure_src) val = self.scope.get(unmap_src) width = flattened_size(unmap_src, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 # Special poke for boundary node if is_boundary_node(dgraph, measure_src) or \ is_boundary_node(dgraph, dgraph.base_var(measure_src)): bound = (nEdge, nEdge+width) self.set_bounds(measure_src, bound) src_noidx = src.split('[', 1)[0] # Poke our source data # Array slice of src that is already allocated if '[' in src and src_noidx in basevars: _, _, idx = src.partition('[') basebound = self.get_bounds(src_noidx) if not '@in' in src_noidx: unmap_src = from_PA_var(src_noidx) val = self.scope.get(unmap_src) shape = val.shape offset = basebound[0] istring, ix = flatten_slice(idx, shape, offset=offset, name='ix') bound = (istring, ix) # Already allocated width = 0 # Input-input connection to implicit state elif src_noidx in basevars: bound = self.get_bounds(src_noidx) width = 0 # Normal src else: bound = (nEdge, nEdge+width) self.set_bounds(src, bound) basevars.add(src) # Poke our target data impli_edge = nEdge for target in targets: # Handle States in implicit comps if is_implicit == True: if isinstance(target, str): target = [target] unmap_targ = from_PA_var(target[0]) val = self.scope.get(unmap_targ) imp_width = flattened_size(unmap_targ, val, self.scope) if isinstance(val, ndarray): shape = val.shape else: shape = 1 for itarget in target: bound = (impli_edge, impli_edge+imp_width) self.set_bounds(itarget, bound) basevars.add(itarget) impli_edge += imp_width width = impli_edge - nEdge elif not target.startswith('@'): self.set_bounds(target, bound) #print input_src, src, target, bound, nEdge += width impli_edge = nEdge # Initialize the residual vector on the first time through, and also # if for some reason the number of edges has changed. if self.res is None or nEdge != self.res.shape[0]: self.res = zeros((nEdge, 1)) return nEdge
def get_dependents(self, fixed_point=False): """Returns a list of current values of the dependents. This includes both constraints and severed sources. fixed_point: bool Set to True if we are doing fixed-point iteration instead of a more general solve. In such a case, we need to swap the order of the constraints to match the parameter order. We also may need to swap signs on the constraints. """ parent = self.parent deps = array(parent.eval_eq_constraints(self.scope)) # Reorder for fixed point if fixed_point is True: eqcons = parent.get_eq_constraints() rhs = {} lhs = {} i = 0 for value in eqcons.itervalues(): #make a mapping of position of each constraint rhs[value.rhs.text] = (i, value.size) lhs[value.lhs.text] = (i, value.size) i += value.size new_dep_index = empty(len(deps), dtype="int") new_dep_sign = empty(len(deps), dtype="int") k = 0 for params in parent.list_param_group_targets(): #for each param, grab the right map value and set the sign convention try: j, size = rhs[params[0]] new_dep_index[k:k + size] = j + arange(0, size, dtype="int") new_dep_sign[k:k + size] = ones((size, )) k += size except KeyError: #wasn't in the rhs dict, try the lhs try: j, size = lhs[params[0]] new_dep_index[k:k + size] = j + arange(0, size, dtype="int") new_dep_sign[k:k + size] = -1 * ones(size) k += size except KeyError: pass #TODO: need to throw an error here. Why was there a param that didn't show up in the constraint #reset the deps array to the new order and sign deps = deps[new_dep_index] * new_dep_sign sev_deps = [] for src, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) src = from_PA_var(src) src_val = self.scope.get(src) targ_val = self.scope.get(target) res = flattened_value(src, src_val) - flattened_value( target, targ_val) sev_deps.extend(res) return hstack((deps, sev_deps))
def get_dependents(self, fixed_point=False): """Returns a list of current values of the dependents. This includes both constraints and severed sources. fixed_point: bool Set to True if we are doing fixed-point iteration instead of a more general solve. In such a case, we need to swap the order of the constraints to match the parameter order. We also may need to swap signs on the constraints. """ parent = self.parent deps = array(parent.eval_eq_constraints(self.scope)) # Reorder for fixed point if fixed_point is True: eqcons = parent.get_eq_constraints() rhs = {} lhs = {} i = 0 for value in eqcons.itervalues(): #make a mapping of position of each constraint rhs[value.rhs.text] = (i, value.size) lhs[value.lhs.text] = (i, value.size) i += value.size new_dep_index = empty(len(deps), dtype="int") new_dep_sign = empty(len(deps), dtype="int") k = 0 for params in parent.list_param_group_targets(): #for each param, grab the right map value and set the sign convention try: j, size = rhs[params[0]] new_dep_index[k:k+size] = j+arange(0, size, dtype="int") new_dep_sign[k:k+size] = ones((size,)) k += size except KeyError: #wasn't in the rhs dict, try the lhs try: j, size = lhs[params[0]] new_dep_index[k:k+size] = j+arange(0, size, dtype="int") new_dep_sign[k:k+size] = -1*ones(size) k += size except KeyError: pass #TODO: need to throw an error here. Why was there a param that didn't show up in the constraint #reset the deps array to the new order and sign deps = deps[new_dep_index]*new_dep_sign sev_deps = [] for src, target in self._severed_edges: if not isinstance(target, str): target = target[0] target = from_PA_var(target) src = from_PA_var(src) src_val = self.scope.get(src) targ_val = self.scope.get(target) res = flattened_value(src, src_val) - flattened_value(target, targ_val) sev_deps.extend(res) return hstack((deps, sev_deps))