Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug when approximating totals on a group caused derivs to be incorrect if a component under the group had partials declared with the 'val' argument. #1392

Merged
merged 1 commit into from May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 32 additions & 2 deletions openmdao/core/group.py
Expand Up @@ -1896,9 +1896,39 @@ def _solve_linear(self, vec_names, mode, rel_systems):
rel_systems : set of str
Set of names of relevant systems based on the current linear solve.
"""
vec_names = [v for v in vec_names if v in self._rel_vec_names]
if self._owns_approx_jac:
# No subsolves if we are approximating our jacobian. Instead, we behave like an
# ExplicitComponent and pass on the values in the derivatives vectors.
for vec_name in vec_names:
if vec_name in self._rel_vec_names:
d_outputs = self._vectors['output'][vec_name]
d_residuals = self._vectors['residual'][vec_name]

if mode == 'fwd':
if self._has_resid_scaling:
with self._unscaled_context(outputs=[d_outputs],
residuals=[d_residuals]):
d_outputs.set_vec(d_residuals)
else:
d_outputs.set_vec(d_residuals)

# ExplicitComponent jacobian defined with -1 on diagonal.
d_outputs *= -1.0

self._linear_solver.solve(vec_names, mode, rel_systems)
else: # rev
if self._has_resid_scaling:
with self._unscaled_context(outputs=[d_outputs],
residuals=[d_residuals]):
d_residuals.set_vec(d_outputs)
else:
d_residuals.set_vec(d_outputs)

# ExplicitComponent jacobian defined with -1 on diagonal.
d_residuals *= -1.0

else:
vec_names = [v for v in vec_names if v in self._rel_vec_names]
self._linear_solver.solve(vec_names, mode, rel_systems)

def _linearize(self, jac, sub_do_ln=True):
"""
Expand Down
80 changes: 80 additions & 0 deletions openmdao/core/tests/test_approx_derivs.py
Expand Up @@ -810,6 +810,86 @@ def test_opt_with_linear_constraint(self):

assert_near_equal(p['circle.area'], np.pi, 1e-6)

def test_bug_subsolve(self):
# There was a bug where a group with an approximation was still performing a linear
# solve on its subsystems, which led to partials declared with 'val' corrupting the
# results.

class DistParab(om.ExplicitComponent):

def initialize(self):

self.options.declare('arr_size', types=int, default=10,
desc="Size of input and output vectors.")

def setup(self):
arr_size = self.options['arr_size']

self.add_input('x', val=np.ones(arr_size))
self.add_output('f_xy', val=np.ones(arr_size))

self.declare_partials('f_xy', 'x')

def compute(self, inputs, outputs):
x = inputs['x']
outputs['f_xy'] = x**2

class NonDistComp(om.ExplicitComponent):

def initialize(self):
self.options.declare('arr_size', types=int, default=10,
desc="Size of input and output vectors.")

def setup(self):
arr_size = self.options['arr_size']

self.add_input('f_xy', val=np.ones(arr_size))

self.add_output('g', val=np.ones(arr_size))

# Make this wrong to see if it shows up in the answer.
mat = np.array([7.0, 13, 27])

row_col = np.arange(arr_size)
self.declare_partials('g', ['f_xy'], rows=row_col, cols=row_col, val=mat)
#self.declare_partials('g', ['f_xy'])

def compute(self, inputs, outputs):
x = inputs['f_xy']
outputs['g'] = x * np.array([3.5, -1.0, 5.0])

size = 3

prob = om.Problem()
model = prob.model

ivc = om.IndepVarComp()
ivc.add_output('x', np.ones((size, )))

model.add_subsystem('p', ivc, promotes=['*'])
sub = model.add_subsystem('sub', om.Group(), promotes=['*'])

sub.add_subsystem("parab", DistParab(arr_size=size), promotes=['*'])
sub.add_subsystem("ndp", NonDistComp(arr_size=size), promotes=['*'])

model.add_design_var('x', lower=-50.0, upper=50.0)
model.add_constraint('g', lower=0.0)

sub.approx_totals(method='fd')

prob.setup()

prob.run_model()

of = ['sub.ndp.g']
totals = prob.driver._compute_totals(of=of, wrt=['p.x'], return_format='dict')
assert_near_equal(totals['sub.ndp.g']['p.x'], np.diag([7.0, -2.0, 10.0]), 1e-6)

totals = prob.check_totals()

for key, val in totals.items():
assert_near_equal(val['rel error'][0], 0.0, 1e-6)


@unittest.skipUnless(MPI and PETScVector, "MPI and PETSc are required.")
class TestGroupFiniteDifferenceMPI(unittest.TestCase):
Expand Down