def get_bounds(obj, input_keys, output_keys, J): """ Returns a pair of dictionaries that contain the stop and end index for each input and output in a pair of lists. """ ibounds = {} nvar = 0 scope = getattr(obj, 'parent', None) for key in input_keys: # For parameter group, all should be equal so just get first. if not isinstance(key, tuple): key = [key] val = obj.get(key[0]) width = flattened_size('.'.join((obj.name, key[0])), val, scope=scope) shape = getattr(val, 'shape', None) for item in key: ibounds[item] = (nvar, nvar+width, shape) nvar += width num_input = nvar obounds = {} nvar = 0 for key in output_keys: val = obj.get(key) width = flattened_size('.'.join((obj.name, key)), val) shape = getattr(val, 'shape', None) obounds[key] = (nvar, nvar+width, shape) nvar += width num_output = nvar if num_input and num_output: # Give the user an intelligible error if the size of J is wrong. try: J_output, J_input = J.shape except ValueError as err: exc_type, value, traceback = sys.exc_info() msg = "Jacobian has the wrong dimensions. Expected 2D but got {}D." msg = msg.format(J.ndim) raise ValueError, ValueError(msg), traceback if num_output != J_output or num_input != J_input: msg = 'Jacobian is the wrong size. Expected ' + \ '(%dx%d) but got (%dx%d)' % (num_output, num_input, J_output, J_input) if ISystem.providedBy(obj): raise RuntimeError(msg) else: obj.raise_exception(msg, RuntimeError) return ibounds, obounds
def get_bounds(obj, input_keys, output_keys, J): """ Returns a pair of dictionaries that contain the stop and end index for each input and output in a pair of lists. """ ibounds = {} nvar = 0 scope = getattr(obj, 'parent', None) for key in input_keys: # For parameter group, all should be equal so just get first. if not isinstance(key, tuple): key = [key] val = obj.get(key[0]) width = flattened_size('.'.join((obj.name, key[0])), val, scope=scope) shape = getattr(val, 'shape', None) for item in key: ibounds[item] = (nvar, nvar + width, shape) nvar += width num_input = nvar obounds = {} nvar = 0 for key in output_keys: val = obj.get(key) width = flattened_size('.'.join((obj.name, key)), val) shape = getattr(val, 'shape', None) obounds[key] = (nvar, nvar + width, shape) nvar += width num_output = nvar if num_input and num_output: # Give the user an intelligible error if the size of J is wrong. try: J_output, J_input = J.shape except ValueError as err: exc_type, value, traceback = sys.exc_info() msg = "Jacobian has the wrong dimensions. Expected 2D but got {}D." msg = msg.format(J.ndim) raise ValueError, ValueError(msg), traceback if num_output != J_output or num_input != J_input: msg = 'Jacobian is the wrong size. Expected ' + \ '(%dx%d) but got (%dx%d)' % (num_output, num_input, J_output, J_input) if ISystem.providedBy(obj): raise RuntimeError(msg) else: obj.raise_exception(msg, RuntimeError) return ibounds, obounds
def applyJ(system, variables): """Multiply an input vector by the Jacobian. For an Explicit Component, this automatically forms the "fake" residual, and calls into the function hook "apply_deriv". """ J = system.J obj = system.inner() scope = system.scope is_sys = ISystem.providedBy(obj) arg = {} for item in system.list_states(): collapsed = scope.name2collapsed.get(item) if collapsed not in variables: continue key = item if not is_sys: key = item.partition('.')[-1] parent = system while True: if item in parent.vec['du']: arg[key] = parent.vec['du'][item] break parent = parent._parent_system for item in system.list_inputs(): collapsed = scope.name2collapsed.get(item) if collapsed not in variables: continue key = item if not is_sys: key = item.partition('.')[-1] parent = system while True: parent = parent._parent_system if item in parent.vec['dp']: arg[key] = parent.vec['dp'][item] break result = {} for item in system.list_outputs(): collapsed = scope.name2collapsed.get(item) if collapsed not in variables: continue key = item if not is_sys: key = item.partition('.')[-1] result[key] = system.rhs_vec[item] for item in system.list_residuals(): key = item if not is_sys: key = item.partition('.')[-1] result[key] = system.rhs_vec[item] # Bail if this component is not connected in the graph if len(arg) == 0 or len(result) == 0: return # Speedhack, don't call component's derivatives if incoming vector is zero. nonzero = False for key, value in arg.iteritems(): if any(value != 0): nonzero = True break if nonzero is False: #print 'applyJ', obj.name, arg, result return # If storage of the local Jacobian is a problem, the user can specify the # 'apply_deriv' function instead of provideJ. if J is None and hasattr(obj, 'apply_deriv'): # TODO - We shouldn't need to calculate the size of the full arrays, # so the cache shouldn't be needed. Cache is None for now. shape_cache = {} # The apply_deriv function expects the argument and result dicts for # each input and output to have the same shape as the input/output. resultkeys = sorted(result.keys()) for key in resultkeys: pre_process_dicts(obj, key, result, shape_cache, scope, is_sys) argkeys = arg.keys() for key in sorted(argkeys): pre_process_dicts(obj, key, arg, shape_cache, scope, is_sys) obj.apply_deriv(arg, result) # Result vector needs to be flattened. for key in reversed(resultkeys): post_process_dicts(key, result) # Arg is still called afterwards, so flatten it back. for key in argkeys: value = arg[key] if hasattr(value, 'flatten'): arg[key] = value.flatten() #print 'applyJ', obj.name, arg, result return if is_sys: input_keys = system.list_inputs() + system.list_states() output_keys = system.list_outputs() + system.list_residuals() elif IAssembly.providedBy(obj): input_keys = [item.partition('.')[-1] \ for item in system.list_inputs()] output_keys = [item.partition('.')[-1] \ for item in system.list_outputs()] else: input_keys, output_keys = list_deriv_vars(obj) #print 'J', input_keys, output_keys, J # The Jacobian from provideJ is a 2D array containing the derivatives of # the flattened output_keys with respect to the flattened input keys. We # need to find the start and end index of each input and output. if obj._provideJ_bounds is None: obj._provideJ_bounds = get_bounds(obj, input_keys, output_keys, J) ibounds, obounds = obj._provideJ_bounds for okey in result: odx = None if okey in obounds: o1, o2, osh = obounds[okey] else: basekey, _, odx = okey.partition('[') try: o1, o2, osh = obounds[basekey] except KeyError: if obj.missing_deriv_policy == 'error': msg = "does not provide analytical derivatives" + \ " for %s" % okey obj.raise_exception(msg, KeyError) continue tmp = result[okey] used = set() for ikey in arg: idx = None if ikey in ibounds: i1, i2, ish = ibounds[ikey] if (i1, i2) in used: continue used.add((i1, i2)) else: basekey, _, idx = ikey.partition('[') try: i1, i2, ish = ibounds[basekey] except KeyError: if obj.missing_deriv_policy == 'error': msg = "does not provide analytical derivatives" + \ " for %s" % ikey obj.raise_exception(msg, KeyError) continue if (i1, i2, idx) in used or (i1, i2) in used: continue used.add((i1, i2, idx)) Jsub = reduce_jacobian(J, i1, i2, idx, ish, o1, o2, odx, osh) #print ikey, okey, Jsub tmp += Jsub.dot(arg[ikey])