Ejemplo n.º 1
0
 def list_deriv_vars(self):
     if self.parametric_geometry is not None:
         ins, outs = list_deriv_vars(self.parametric_geometry)
         if ins or outs and 'geom_out' not in outs:
             return list(ins), list(outs)+['geom_out']
     else:
         return (), ()
Ejemplo n.º 2
0
 def list_deriv_vars(self):
     if self.parametric_geometry is not None:
         ins, outs = list_deriv_vars(self.parametric_geometry)
         if ins or outs and 'geom_out' not in outs:
             return list(ins), list(outs)+['geom_out']
     else:
         return (), ()
Ejemplo n.º 3
0
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])
Ejemplo n.º 4
0
def applyJT(obj, arg, result, residual, shape_cache, J=None):
    """Multiply an input vector by the transposed Jacobian.
    For an Explicit Component, this automatically forms the "fake"
    residual, and calls into the function hook "apply_derivT".
    """

    for key in arg:
        if key not in residual:
            result[key] = -arg[key]

    # If storage of the local Jacobian is a problem, the user can
    # specify the 'apply_derivT' function instead of provideJ.
    if J is None and hasattr(obj, 'apply_derivT'):

        # 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)

        argkeys = arg.keys()
        for key in sorted(argkeys):
            pre_process_dicts(obj, key, arg, shape_cache)

        obj.apply_derivT(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()

        return

    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)
    obounds, ibounds = obj._provideJ_bounds

    used = set()
    for okey in result:
        if okey in arg:
            continue

        odx = None
        if okey in obounds:
            o1, o2, osh = obounds[okey]
            if (o1, o2) in used:
                continue
            used.add((o1, o2))
        else:
            basekey, _, odx = okey.partition('[')
            o1, o2, osh = obounds[basekey]
            if (o1, o2, odx) in used or (o1, o2) in used:
                continue
            used.add((o1, o2, odx))

        tmp = result[okey]
        for ikey in arg:

            idx = None
            if ikey in ibounds:
                i1, i2, ish = ibounds[ikey]
            else:
                basekey, _, idx = ikey.partition('[')
                i1, i2, ish = ibounds[basekey]

            Jsub = reduce_jacobian(J, o1, o2, odx, osh,
                                      i1, i2, idx, ish).T
            #print ikey, okey, Jsub

            # for unit pseudocomps, just scalar multiply the args
            # by the conversion factor
            if isinstance(obj, PseudoComponent) and \
               obj._pseudo_type == 'units' and Jsub.shape == (1, 1):
                tmp += Jsub[0][0] * arg[ikey]
            else:
                tmp += Jsub.dot(arg[ikey])
Ejemplo n.º 5
0
    def derivative_graph(self,
                         inputs=None,
                         outputs=None,
                         fd=False,
                         severed=None,
                         group_nondif=True):
        """Returns the local graph that we use for derivatives.

        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.

        fd: boolean
            set to True to finite difference the whole model together with
            fake finite difference turned off. This is mainly for checking
            your model's analytic derivatives.

        severed: list
            If a workflow has a cylic connection, some edges must be severed.
            When a cyclic workflow calls this function, it passes a list of
            edges so that they can be severed prior to the topological sort.

        group_nondif: bool
            If True, collapse parts of the graph into PseudoAssemblies when
            necessary.
        """

        if self._derivative_graph is None or group_nondif is False:
            # when we call with group_nondif = False, we want the union of the
            # passed inputs/outputs plus the inputs/outputs from the solver
            if group_nondif is False:
                tmp_inputs = [] if inputs is None else inputs
                tmp_outputs = [] if outputs is None else outputs
                inputs = None
                outputs = None

            # If inputs aren't specified, use the parameters
            parent_deriv_vars = list_deriv_vars(self._parent.parent)
            if inputs is None:
                if hasattr(self._parent, 'list_param_group_targets'):
                    inputs = self._parent.list_param_group_targets()
                elif parent_deriv_vars[0]:
                    inputs = parent_deriv_vars[0]
                else:
                    msg = "No inputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            if group_nondif is False:
                inputs = list(set(tmp_inputs).union(inputs))

            # If outputs aren't specified, use the objectives and constraints
            if outputs is None:
                outputs = []
                if hasattr(self._parent, 'get_objectives'):
                    outputs.extend([
                        "%s.out0" % item.pcomp_name
                        for item in self._parent.get_objectives().values()
                    ])
                if hasattr(self._parent, 'get_constraints'):
                    outputs.extend([
                        "%s.out0" % item.pcomp_name
                        for item in self._parent.get_constraints().values()
                    ])

            if group_nondif is False:
                outputs = list(set(tmp_outputs).union(outputs))

            if len(outputs) == 0:
                if parent_deriv_vars[1]:
                    outputs = parent_deriv_vars[1]
                else:
                    msg = "No outputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            graph = self.scope._depgraph

            # make a copy of the graph because it will be
            # modified by mod_for_derivs
            dgraph = graph.subgraph(graph.nodes())
            dgraph = mod_for_derivs(dgraph, inputs, outputs, self, fd)

            if group_nondif:
                self._derivative_graph = dgraph
                self._group_nondifferentiables(fd, severed)
            else:
                # we're being called to determine the deriv graph
                # for a subsolver, so get rid of @in and @out nodes
                dgraph.remove_nodes_from(
                    ['@in%d' % i for i in range(len(inputs))])
                dgraph.remove_nodes_from(
                    ['@out%d' % i for i in range(len(outputs))])
                dgraph.graph['inputs'] = inputs[:]
                dgraph.graph['outputs'] = outputs[:]
                return dgraph

        return self._derivative_graph
Ejemplo n.º 6
0
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])
Ejemplo n.º 7
0
def applyJT(obj, arg, result, residual, shape_cache, J=None):
    """Multiply an input vector by the transposed Jacobian.
    For an Explicit Component, this automatically forms the "fake"
    residual, and calls into the function hook "apply_derivT".
    """

    for key in arg:
        if key not in residual:
            result[key] = -arg[key]

    # 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:
        return

    # If storage of the local Jacobian is a problem, the user can
    # specify the 'apply_derivT' function instead of provideJ.
    if J is None and hasattr(obj, 'apply_derivT'):

        # 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)

        argkeys = arg.keys()
        for key in sorted(argkeys):
            pre_process_dicts(obj, key, arg, shape_cache)

        obj.apply_derivT(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()

        return

    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)
    obounds, ibounds = obj._provideJ_bounds

    used = set()
    for okey in result:
        if okey in arg:
            continue

        odx = None
        if okey in obounds:
            o1, o2, osh = obounds[okey]
            if (o1, o2) in used:
                continue
            used.add((o1, o2))
        else:
            basekey, _, odx = okey.partition('[')
            o1, o2, osh = obounds[basekey]
            if (o1, o2, odx) in used or (o1, o2) in used:
                continue
            used.add((o1, o2, odx))

        tmp = result[okey]
        for ikey in arg:

            idx = None
            if ikey in ibounds:
                i1, i2, ish = ibounds[ikey]
            else:
                basekey, _, idx = ikey.partition('[')
                i1, i2, ish = ibounds[basekey]

            Jsub = reduce_jacobian(J, o1, o2, odx, osh, i1, i2, idx, ish).T
            # print ikey, okey, Jsub

            # for unit pseudocomps, just scalar multiply the args
            # by the conversion factor
            if isinstance(obj, PseudoComponent) and \
                            obj._pseudo_type == 'units' and Jsub.shape == (1, 1):
                tmp += Jsub[0][0] * arg[ikey]
            else:
                tmp += Jsub.dot(arg[ikey])
Ejemplo n.º 8
0
    def derivative_graph(self, inputs=None, outputs=None, fd=False,
                         severed=None, group_nondif=True):
        """Returns the local graph that we use for derivatives.

        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.

        fd: boolean
            set to True to finite difference the whole model together with
            fake finite difference turned off. This is mainly for checking
            your model's analytic derivatives.

        severed: list
            If a workflow has a cylic connection, some edges must be severed.
            When a cyclic workflow calls this function, it passes a list of
            edges so that they can be severed prior to the topological sort.

        group_nondif: bool
            If True, collapse parts of the graph into PseudoAssemblies when
            necessary.
        """

        if self._derivative_graph is None or group_nondif is False:
            # when we call with group_nondif = False, we want the union of the
            # passed inputs/outputs plus the inputs/outputs from the solver
            if group_nondif is False:
                tmp_inputs = [] if inputs is None else inputs
                tmp_outputs = [] if outputs is None else outputs
                inputs = None
                outputs = None


            # If inputs aren't specified, use the parameters
            parent_deriv_vars = list_deriv_vars(self._parent.parent)
            if inputs is None:
                if hasattr(self._parent, 'list_param_group_targets'):
                    inputs = self._parent.list_param_group_targets()
                elif parent_deriv_vars[0]:
                    inputs = parent_deriv_vars[0]
                else:
                    msg = "No inputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            if group_nondif is False:
                inputs = list(set(tmp_inputs).union(inputs))

            # If outputs aren't specified, use the objectives and constraints
            if outputs is None:
                outputs = []
                if hasattr(self._parent, 'get_objectives'):
                    outputs.extend(["%s.out0" % item.pcomp_name for item in
                                    self._parent.get_objectives().values()])
                if hasattr(self._parent, 'get_constraints'):
                    outputs.extend(["%s.out0" % item.pcomp_name for item in
                                    self._parent.get_constraints().values()])

            if group_nondif is False:
                outputs = list(set(tmp_outputs).union(outputs))

            if len(outputs) == 0:
                if parent_deriv_vars[1]:
                    outputs = parent_deriv_vars[1]
                else:
                    msg = "No outputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            graph = self.scope._depgraph

            # make a copy of the graph because it will be
            # modified by mod_for_derivs
            dgraph = graph.subgraph(graph.nodes())
            dgraph = mod_for_derivs(dgraph, inputs, outputs, self, fd)

            if group_nondif:
                self._derivative_graph = dgraph
                self._group_nondifferentiables(fd, severed)
            else:
                # we're being called to determine the deriv graph
                # for a subsolver, so get rid of @in and @out nodes
                dgraph.remove_nodes_from(['@in%d' % i for i in range(len(inputs))])
                dgraph.remove_nodes_from(['@out%d' % i for i in range(len(outputs))])
                dgraph.graph['inputs'] = inputs[:]
                dgraph.graph['outputs'] = outputs[:]
                return dgraph

        return self._derivative_graph