jthomas
jthomas

Reputation: 2583

OpenMDAO1.x: Difficulty accessing variables implicitly linked through multiple Groups

I am having trouble accessing variables that are implicitly linked through multiple layers of groups. According to the documentation:

In new OpenMDAO, Groups are NOT Components and do not have their own variables. Variables can be promoted to the Group level by passing the promotes arg to the add call, e.g.,

group = Group()
group.add('comp1', Times2(), promotes=['x'])

This will allow the variable x that belongs to comp1 to be accessed via group.params[‘x’].

However, when I try to access variables of sub-sub-groups I am getting errors. Please see example below that shows a working and non-working example:

from openmdao.api import Component, Group, Problem
import numpy as np


class Times2(Component):
    def __init__(self):
        super(Times2, self).__init__()
        self.add_param('x', 1.0, desc='my var x')
        self.add_output('y', 2.0, desc='my var y')

    def solve_nonlinear(self, params, unknowns, resids):
        unknowns['y'] = params['x'] * 2.0

    def linearize(self, params, unknowns, resids):
        J = {}
        J[('y', 'x')] = np.array([2.0])
        return J


class PassGroup1(Group):

    def __init__(self):
        super(PassGroup1, self).__init__()

        self.add('t1', Times2(), promotes=['*'])


class PassGroup2(Group):

    def __init__(self):
        super(PassGroup2, self).__init__()

        self.add('g1', PassGroup1(), promotes=['*'])


prob = Problem(root=Group())
prob.root.add('comp', PassGroup2(), promotes=['*'])
prob.setup()
prob.run()

# this works
print prob.root.comp.g1.t1.params['x']

# this does not
print prob.root.params['x']

Could you explain why this does not work, and how I can make variables available to the top level without a knowledge of the lower level groups?

Upvotes: 0

Views: 78

Answers (1)

Justin Gray
Justin Gray

Reputation: 5710

There are a few answers to your question. First, I'll point out that you have what we call a "hanging parameter". By this I mean, a parameter on a component (or linked to multiple components via promotion and/or connection) that has no ultimate src variable associated with it. So, just for a complete understanding, it needs to be stated that as far as OpenMDAO is concerned hanging parameters are not it's problem. As a convinence to the user, we provide an easy way for you to set its value in the problem instance, but we never do any data passing with it or anything during run time.

In the common case where x is a design variable for an optimizer, you would create an IndepVarComp to provide the src for this value. But since you don't have an optimizer in your example it is not technically wrong to leave out the IndepVarComp.

For a more direct answer to your question you shouldn't really be reaching down into the params dictionaries in any kind of sub-level. I can't think of a good reason to do that as a user. If you stick with problem['x'] you should never go wrong.

But since you asked, here is the details of whats really going on for a slightly modified case that allows there to be an actual parameter.

from openmdao.api import Component, Group, Problem
import numpy as np


class Plus1(Component): 
    def __init__(self): 
        super(Plus1, self).__init__()
        self.add_param('w', 4.0)
        self.add_output('x', 5.0)

    def solve_nonlinear(self, params, unknowns, resids): 
        unknowns['x'] = params['w'] + 1

    def linearize(self, params, unknowns, resids): 
        J = {}
        J['x', 'w'] = 1
        return J


class Times2(Component):
    def __init__(self):
        super(Times2, self).__init__()
        self.add_param('x', 1.0, desc='my var x')
        self.add_output('y', 2.0, desc='my var y')

    def solve_nonlinear(self, params, unknowns, resids):
        unknowns['y'] = params['x'] * 2.0

    def linearize(self, params, unknowns, resids):
        J = {}
        J[('y', 'x')] = np.array([2.0])
        return J


class PassGroup1(Group):

    def __init__(self):
        super(PassGroup1, self).__init__()

        self.add('t1', Times2(), promotes=['x','y'])


class PassGroup2(Group):

    def __init__(self):
        super(PassGroup2, self).__init__()

        self.add('g1', PassGroup1(), promotes=['x','y'])
        self.add('p1', Plus1(), promotes=['w','x'])


prob = Problem(root=Group())
prob.root.add('comp', PassGroup2(), promotes=['w','x','y'])
prob.setup()
prob.run()

# this works
print prob.root.comp.g1.t1.params['x']

# this does not
print prob.root.comp.params.keys()

Please note that in my example, 'x' is no longer free for the user to set. Its now computed by 'p1'. Instead 'w' is now the user set parameter. This was necessary in order to illustrate how params work.

Now that there is actually some data passing going on that OpenMDAO is responsible for you can see the actual pattern more clearly. At the root, there are no parameters at all (excluding any hanging params). Everything, from the roots perspective is an unknown, because everything has a src responsible for it at that level. Go down one level where there is p1 and g1, and now there is a parameter on g1 that p1 is the src for so some data passing has to happen at that level of the hiearchy. So g1 has an entry in its parameter dictionary, g1.t1.x. Why is it a full path? All book keeping for parameters is done with full path names for a variety of reasons outside the scope of this answer. But that is also another motivation for working through the shortcut in problem because that will work with relative (or promoted) names.

Upvotes: 2

Related Questions