I've tried to handle non-converging points in the compute method of my ExplicitComponent, by raising an AnalysisError, as suggested in What is the best way to tell openMDAO driver or solver that it is impossible to evaluate the model at some point? (originally I wanted to make a comment in this thread, but I wasn't allowed due to my low Stack Overflow reputation score). However, this doesn't seem to solve my problem. What I expected was that error would be caught, the design point would be skipped and that the optimizer would continue to evaluate other points in order to find a solution. It is correct that the error is caught, but for some reason, the error is then reraised in ScipyOptimizeDriver.run. What is the purpose of this?
This is an example script for reproducing the behaviour:
import numpy as np
from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent, ScipyOptimizeDriver, ScipyKrylov, AnalysisError
class Test1Comp(ExplicitComponent):
def setup(self):
self.add_input('design_x', 1.0)
self.add_input('design_y', 1.0)
self.add_input('design_z', 0.5)
self.add_output('y', val=0.1)
self.add_output('z', val=0.1)
self.add_output('obj', val=0.0)
self.declare_partials(of='*', wrt='*', method='fd', form='central', step=1.0e-4)
def compute(self, inputs, outputs):
design_z = inputs['design_z']
design_x = inputs['design_x']
design_y = inputs['design_y']
# Let's assume we have a model that has problems converging around design_x = 5.0
if 0.49999 < design_x < 0.500001:
raise AnalysisError()
z = 4/(design_z + 1)
y = - design_z - 2*z
obj = (y/5.833333 - design_x)**2 + z/2.666667*100*(design_y - design_x**2)**2
outputs["z"] = z
outputs["y"] = y
outputs['obj'] = obj
if __name__ == "__main__":
prob = Problem()
model = prob.model = Group()
model.add_subsystem('d1', IndepVarComp('design_x', 1.0))
model.add_subsystem('d2', IndepVarComp('design_y', 1.0))
model.add_subsystem('d3', IndepVarComp('design_z', 0.5))
model.add_subsystem('comp', Test1Comp())
model.connect('d1.design_x', 'comp.design_x')
model.connect('d2.design_y', 'comp.design_y')
model.connect('d3.design_z', 'comp.design_z')
prob.driver = ScipyOptimizeDriver()
prob.driver.options["optimizer"] = 'SLSQP'
prob.driver.options['tol'] = 1e-8
model.add_design_var("d1.design_x", lower=0.5, upper=1.5)
model.add_design_var("d2.design_y", lower=0.5, upper=1.5)
model.add_design_var("d3.design_z", lower=0.0, upper=1.0)
model.add_objective('comp.obj')
model.linear_solver = ScipyKrylov()
model.linear_solver.options['maxiter'] = int(1200)
model.linear_solver.options['restart'] = int(20)
# prob.model.approx_totals()
prob.setup()
prob.run_driver()
print(prob['comp.y'])
print(prob['comp.z'])
Futrthermore, when looking at ExplicitComponent._solve_nonlinear, which is the method calling ExplicitComponent.compute, it appears to me that the natural way of communicating to OpenMDAO that a point is not converging would be to have ExplicitComponent.compute return True. See the source code for the method:
def _solve_nonlinear(self):
"""
Compute outputs. The model is assumed to be in a scaled state.
Returns
-------
boolean
Failure flag; True if failed to converge, False is successful.
float
absolute error.
float
relative error.
"""
super(ExplicitComponent, self)._solve_nonlinear()
with Recording(self.pathname + '._solve_nonlinear', self.iter_count, self):
with self._unscaled_context(
outputs=[self._outputs], residuals=[self._residuals]):
self._residuals.set_const(0.0)
failed = self.compute(self._inputs, self._outputs)
return bool(failed), 0., 0.
In summary, could someone clarify what is the recommended way of handling non-converging computations in ExplicitComponent.compute?
I have looked at your code, and you specified everything the correct way for telling an optimizer that the component could not evaluate the design point. The problem is that scipy.minimize (which is the optimizer underneath theScipyOptimizeDriver) does not know what do do when it hits a failed point (other than raising an exception), and has no way to report a failure back (at least to my knowledge).
However, pyOptSparseDriver can do something when a point fails: it an try progressing again in the gradient direction, but with a smaller stepsize. I took your code, and exchanged the ScipyOptimizeDriver with a pyOptSparseDriver, used the "SLSQP" optimizer in that package, and it worked around that problematic point just fine, reaching what I assume is a good optimum:
[0.69651727]
[-5.]
[2.]
My driver option code looks like this:
prob.driver = pyOptSparseDriver()
prob.driver.options["optimizer"] = 'SLSQP'
prob.driver.opt_settings['ACC'] = 1e-8
If you don't already have pyoptsparse, which is a separate package not maintained by us, you can get it from https://github.com/mdolab/pyoptsparse -- and you can build and install it with:
python setup.py build
python setup.py install
For your other question, I looked around through our code and found that the failure flag return on _solve_nonlinear is never used for anything. So, the only way to communicate a non-converging status to a driver or a higher-level solver is to raise the AnalysisError. Solvers canraise an AE when they don't converge, if "err_on_maxiter" on its options is set to True (the default is False).
As a final note, I think that while our error handling mechanisms are being used, we haven't thought of everything and are always open to suggestions for improvements.
Related
I have encountered a theoretical question. I'm using pyqt5, but this is probably are very generalistic and framework independent question.
I have a QMainwindow sitting around waiting for the user to do stuff. The user can show / hide dialogues (subclasses of QDockwidgets) as he chooses using the QMenu and the associated shortcuts (it's a checkable QAction for each individual dialogue).
I have been struggling with showing / hiding the dialogues efficiently. Currently, I'm just initiating them all at start up, hiding those that I don't want to show up in the beginning. This makes triggering the dialogues easy, since I can just dialogue.show() /dialogue.hide() depending on the dialogues current visibility.
But I cannot believe that this is best practice and very efficient.
I have tried (I currently do not have my pyqt environment set up on this computer, so I had to strip down my actual code without being able to test if this runs):
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class InfoPanel(QDockWidget):
def __init__(self, title='Tool Box'):
QDockWidget.__init__(self, title)
self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
frame = QFrame()
layout = QGridLayout()
self.canvas = QGraphicsView()
self.canvas.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(40, 40, 40)))
layout.addWidget(self.canvas)
frame.setLayout(layout)
self.setWidget(frame)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.showpanelAct = QAction("&Show Panel", self, enabled=True,checkable=True, shortcut="F10")
self.showpanelAct.triggered.connect(lambda: self.showPanel(0))
self.viewMenu = QMenu("&View", self)
self.viewMenu.addAction(self.showpanelAct)
self.setDockOptions(QMainWindow.AnimatedDocks)
def showPanel(self,i:int = 0): # this is not so smart - should construct and deconstuct to save memory!?
if i == 0: #infopanel
dialogueExists = True
try: self.infoPanel
#except NameError: #does not catch the error
except:
dialogueExists = False
if dialogueExists:
print('destroy')
self.infoPanel.destroy()
else:
print('create')
self.infoPanel = InfoPanel() #init
self.infoPanel.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Which works the first time, but after that, it only seems to trigger the destruction of the dialogue (which, surprisingly, does not crash anything it just keeps on going).
Why is that and is there a standard way to approach the showing hiding of dialogues?
I took the exposed MCVE of OP and tried to make it running in my cygwin64 on Windows 10.
At first I had to apply little fixes. (OP stated that he was not able to test it at the time of publishing.)
First, I inserted a “hut” at first line for convenient start in bash:
#!/usr/bin/python3
Second, the self.viewMenu didn't appear. Hence, I inserted a line after
self.viewMenu = QMenu("&View", self)
self.viewMenu.addAction(self.showpanelAct)
to add the viewMenu to main menu bar:
self.menuBar().addMenu(self.viewMenu)
which fixed it.
Third, when clicking the menu item I got:
Traceback (most recent call last):
File "./testQDockPanelShowHide.py", line 27, in <lambda>
self.showpanelAct.triggered.connect(lambda: self.showPanel(0))
File "./testQDockPanelShowHide.py", line 45, in showPanel
self.infoPanel = InfoPanel() #init
File "./testQDockPanelShowHide.py", line 17, in __init__
self.canvas.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(40, 40, 40)))
NameError: name 'QtGui' is not defined
Aborted (core dumped)
I must admit that my Python knowledge is very limited. (I'm the guy who writes the Python bindings in C++ for the colleagues. So, my colleagues are the actual experts. At most, I play a little bit in Python when I test whether new implemented bindings do what's expected.) However, I modified
self.canvas.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(40, 40, 40)))
to:
self.canvas.setBackgroundBrush(QBrush(QColor(40, 40, 40)))
which fixed this issue.
After this, I got the behavior described by OP and did a closer look where I (and OP) suspected the error:
def showPanel(self,i:int = 0): # this is not so smart - should construct and deconstuct to save memory!?
if i == 0: #infopanel
dialogueExists = True
try: self.infoPanel
#except NameError: #does not catch the error
except:
dialogueExists = False
if dialogueExists:
print('destroy')
self.infoPanel.destroy()
else:
print('create')
self.infoPanel = InfoPanel() #init
self.infoPanel.show()
I strongly believe that try: self.infoPanel doesn't do what OP thinks it would.
It tries to access self.infoPanel which isn't existing until the first call of this method. (Please, be aware, the member variable self.infoPanel isn't existing.) So, the except: branch is executed and sets dialogueExists = False which a few lines later causes self.infoPanel = InfoPanel() #init. Now, the member variable self.infoPanel is existing, and the try: self.infoPanel will never fail again until destruction of this MainWindow.
Out of curiosity, I had a look at QWidget.destroy() (to be sure not to tell something wrong):
QWidget.destroy (self, bool destroyWindow = True, bool destroySubWindows = True)
Frees up window system resources. Destroys the widget window if destroyWindow is true.
destroy() calls itself recursively for all the child widgets, passing destroySubWindows for the destroyWindow parameter. To have more control over destruction of subwidgets, destroy subwidgets selectively first.
This function is usually called from the QWidget destructor.
It definitely doesn't destroy the member variable self.infoPanel.
After having understood this, a fix was easy and obvious:
def showPanel(self,i:int = 0): # this is not so smart - should construct and deconstuct to save memory!?
if i == 0: #infopanel
try: self.infoPanel
#except NameError: #does not catch the error
except:
print('create')
self.infoPanel = InfoPanel() #init
if self.infoPanel.isVisible():
self.infoPanel.hide()
else:
self.infoPanel.show()
Btw. I replaced destroy() by hide() which makes a re-creation of the InfoPanel() obsolete.
I tested this by toggling the menu item multiple times – it works as expected now (at least, it looks like).
The complete sample finally:
#!/usr/bin/python3
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class InfoPanel(QDockWidget):
def __init__(self, title='Tool Box'):
QDockWidget.__init__(self, title)
self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
frame = QFrame()
layout = QGridLayout()
self.canvas = QGraphicsView()
# self.canvas.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(40, 40, 40)))
self.canvas.setBackgroundBrush(QBrush(QColor(40, 40, 40)))
layout.addWidget(self.canvas)
frame.setLayout(layout)
self.setWidget(frame)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.showpanelAct = QAction("&Show Panel", self, enabled=True,checkable=True, shortcut="F10")
self.showpanelAct.triggered.connect(lambda: self.showPanel(0))
self.viewMenu = QMenu("&View", self)
self.viewMenu.addAction(self.showpanelAct)
self.menuBar().addMenu(self.viewMenu)
self.setDockOptions(QMainWindow.AnimatedDocks)
def showPanel(self,i:int = 0): # this is not so smart - should construct and deconstuct to save memory!?
if i == 0: #infopanel
try: self.infoPanel
#except NameError: #does not catch the error
except:
print('create')
self.infoPanel = InfoPanel() #init
if self.infoPanel.isVisible():
self.infoPanel.hide()
else:
self.infoPanel.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
After taking a break from coding the solution to my problem was very obvious. Going back to my original code (which did not produce the expected output of creating / destroying the dialogue self.infoPanel on demand):
dialogueExists = True
try: self.infoPanel
#except NameError: #does not catch the error
except:
dialogueExists = False
if dialogueExists:
print('destroy')
self.infoPanel.destroy()
else:
print('create')
self.infoPanel = InfoPanel() #init
self.infoPanel.show()
My main problem was that I confused two separate things. Qt destroyed the widget contained in the object self.infoPanel when I called self.infoPanel.destroy(). But that doesn't mean the object self.infoPanel does not exist (that's exactly what I use try: ... for, to see if the object exists). The simple and obvious way to create and destroy dialogues on demand obviously involves deleting the object from the environment (del self.infoPanel).
The working code is:
dialogueExists = True
try:
self.infoPanel.destroy() #not sure this is needed, but I guess it doesn't hurt
del self.infoPanel #this is the deletion of the actual object
except:
dialogueExists = False
if not dialogueExists :
self.infoPanel = InfoPanel()
Cheers and many thanks for the helpful advice on deciding whether to show / hide dialogues or to create / destroy them!
I am kind of new on Chainer and I have been struggling with a weird situation recently.
I have a Chain to compute a CNN which I feed with a labeledDataSet.
But no results appears when I use the extensions. When I display the observation value it is empty. But the loss is indeed calculated and the parameters updated (at least they change) so I don't know where is the connection problem.
def convert(batch, device):
return chainer.dataset.convert.concat_examples(batch, device, padding=0)
def print_obs(t):
print("trainer.observation", trainer.observation)
print("updater.loss", updater.loss_func)
print("conv1", model.predictor.conv1.W[0][0])
print("conv20", model.predictor.conv20.W[0][0])
model.predictor.train = True
model.predictor.finetune = False ####or True ??
cuda.get_device(0).use()
model.to_gpu()
optimizer = optimizers.MomentumSGD(lr=learning_rate, momentum=momentum)
optimizer.use_cleargrads()
optimizer.setup(model)
optimizer.add_hook(chainer.optimizer.WeightDecay(weight_decay))
train, test = imageNet_data.train_val_test()
train_iter = iterators.SerialIterator(train, batch_size)
test_iter = iterators.SerialIterator(test, batch_size, repeat=False,shuffle=False)
with chainer.using_config('debug', True):
# Set up a trainer
updater = training.StandardUpdater(train_iter, optimizer, loss_func=model, converter=convert)
trainer = training.Trainer(updater, (10, 'epoch'), out="./backup/result")
trainer.extend(print_obs, trigger=(3, 'iteration'))
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(
['epoch', 'main/loss', 'validation/main/loss',
'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))
trainer.run()
Maybe this is something is miss completely and which is quite obvious.. Thank you for all remarks that would help me a lot.
Chainer4.1, Ubuntu16
If you are using your own Link with the Trainer, you need to report metrics using chainer.report by your own.
See https://docs.chainer.org/en/stable/guides/report.html for instructions.
You can see some examples in Chainer repository:
https://github.com/chainer/chainer/blob/v4.1.0/chainer/links/model/classifier.py#L116
https://github.com/chainer/chainer/blob/v4.1.0/examples/imagenet/alex.py#L40
I am missing a point on how to get the 'output' out of a metamodel that is being used as a component in the problem.
It is clear the compute part should have an output but how. Below is the simple sin function as a metamodelunstructured component. I tried to modify the samples. But the error is :
File
"C:\Users\ebarlas\AppData\Local\Continuum\anaconda3\lib\site-packages\openmdao\core\group.py",
line 201, in _setup_procs
subsys._setup_procs(subsys.name, sub_comm)
TypeError: _setup_procs() missing 1 required positional argument:
'comm'
import numpy as np
from openmdao.api import Problem, Group, IndepVarComp
from openmdao.api import ScipyOptimizeDriver
from openmdao.api import MetaModelUnStructured, FloatKrigingSurrogate
# Below class syntax is not working
class trig(MetaModelUnStructured):
def setup(self):
self.add_input('x', 0., training_data=np.linspace(0,10,20))
self.add_output('sin_x', 0., surrogate=FloatKrigingSurrogate(),
training_data=.5*np.sin(np.linspace(0,10,20)))
self.declare_partials(of='sin_x', wrt='x', method='fd')
# If I uncomment 4 lines below and comment out the class above it works fine.
#trig = MetaModelUnStructured()
#trig.add_input('x', 0., training_data=np.linspace(0,10,20))
#trig.add_output('sin_x', 0., surrogate=FloatKrigingSurrogate(),
# training_data=.5*np.sin(np.linspace(0,10,20)))
#trig.declare_partials(of='sin_x', wrt='x', method='fd')
prob = Problem()
inputs_comp = IndepVarComp()
inputs_comp.add_output('x', 5)
prob.model.add_subsystem('inputs_comp', inputs_comp)
prob.model.add_subsystem('trig', trig)
prob.model.connect('inputs_comp.x', 'trig.x')
prob.driver = ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'COBYLA'
prob.driver.options['tol'] = 1e-3
prob.driver.options['disp'] = True
prob.model.add_design_var('inputs_comp.x', lower=4, upper=7)
prob.model.add_objective('trig.sin_x')
prob.setup(check=True)
prob.run_driver()
print(prob['trig.sin_x'])
print(prob['trig.x'])
You shouldn't implement the compute function for a MetaModel (outputs are predicted not computed).
See the docs here: http://openmdao.org/twodocs/versions/latest/features/building_blocks/components/metamodelunstructured.html
thanks for the clarification to the question. Note that the error in your updated example is for passing the class definition trig instead of an instance, but after correcting that I can see the problem.
This is a bug in MetaModelUnStructured... this will be fixed shortly.
You are using MetaModelUnstructured component incorrectly, but there is another bug getting in the way first.
The class trig you've defined is not the same thing as the instance named trig in your commented out code. One is a class, the other is an instance. You can't pass a class into add_subsystem.
However, even fixing that problem there is still a bug in MetaModelUnstructured when its sub-classed. We'll get a fix in before OpenMDAO 2.3, but for now the way you've done it in the commented out code will work.
I can set the unknowns and residuals in components' solve_nonlinear function. Can I also set the values of the params? Why or why not?
Edit
Here is my attempt at a "pure python" reader/writer component. My problem is that I can't read/write parameters from the top level.
$ cat test.py
from openmdao.api import Component, Group, Problem
class reader():
def __init__(self):
self.file_to_read = 'test.in'
self.file_data = 0
def execute(self):
dat = open(self.file_to_read, 'r')
self.file_data = dat.read()
class writer():
def __init__(self):
self.file_to_write = 'test.out'
self.data = -99
def execute(self):
dat = open(self.file_to_write, 'w')
dat.write(str(self.data))
class ReadWriteComp(Component):
def __init__(self):
super(ReadWriteComp, self).__init__()
self.reader = reader()
self.writer = writer()
self.reader.execute()
def solve_nonlinear(self, params, unknowns, resids):
self.writer.data = self.reader.file_data
self.writer.execute()
root = Group()
root.add('testio', ReadWriteComp())
prob = Problem(root)
prob.setup()
prob['testio.writer.file_to_write'] = 'newname' # "Variable 'testio.writer.file_to_write' not found."
prob.run()
$ cat test.in
8
Params are incoming values to a component. They are externally provided information. You can't/shouldn't change them because of this externality.
said another way:
If you have incoming connections, then your param's value is defined by the output (source) of that upstream component. Changing your param would be like changing the output of that upstream component.
I can't make a comment since my rep is too low, but this is more of a comment to the read/write components and not an answer to the question. I'd suggest to make the read/write and wrapper classes pure python and then only use openmdao at the top level with a component executing all three, perhaps with a group wrapped around if you need to parallelize cases.
I wrote the codes in Modelica as below:
model TestIniitial
extends Modelica.Icons.Example;
parameter Integer nWri= 2;
Real u[nWri](each start= 10, fixed=false);
Real uPre[nWri];
parameter Real _uStart[nWri] = fill(10, nWri);
parameter Modelica.SIunits.Time startTime = 0;
parameter Modelica.SIunits.Time samplePeriod = 1;
Boolean sampleTrigger "True, if sample time instant";
initial equation
u[1] = 1;
u[2] = 2;
equation
sampleTrigger = sample(startTime, samplePeriod);
when sampleTrigger then
for i in 1: nWri loop
uPre[i] = pre(u[i]);
end for;
end when;
for i in 1:nWri loop
u[i] = (i+1)*time;
end for;
end TestIniitial;
Basically I want to initialize the u before simulation. However, I got below complaints(the initialization of u is over-specified) from translation:
The Modelica Language Specification 3.2.1 specifies that if a real variable, v,
is appearing in an expression as pre(v), but not assigned by a when equation,
then the equation v = pre(v) should be added to the initialization problem.
For this problem the following equations were added:
u[1] = pre(u[1]);
u[2] = pre(u[2]);
I can't understand the complaints since pre(v) was assigned in when equation already. What can I do if I want to initialize the u in above codes?
Thanks.
Looking at this, my guess is that the error message is trying to provide you some diagnostics but it is incorrect about the source. I suspect (again, I do not know for sure) that it sees the fact that pre(u) appears in the model and that there is an initialization problem and assumes a specific issue.
My guess is that the issue stems from the fact that you have fixed=true set on u. I see no reason to do that and my guess is that it will lead to too many constraints on the initialization problem as well. Get rid of the fixed=true and see what happens. Report back if that doesn't address the problem.
Good luck.