# Copyright 2004-2008 Nanorex, Inc. See LICENSE file for details.
"""
SimSetup.py
Dialog for setting up to run the simulator.
@author: Mark
@version: $Id$
@copyright: 2004-2008 Nanorex, Inc. See LICENSE file for details.
Created by Mark, under the name runSim.py.
Bruce 050324 changed some comments and did some code cleanup
(and also moved a lot of existing code for actually "running the simulator"
into runSim.py, so that file still exists, but has all different code
than before).
Bruce 050325 renamed file and class to SimSetup, to fit naming
convention for other Dialog subclasses.
"""
import os
from PyQt4.Qt import QDialog
from PyQt4.Qt import QButtonGroup
from PyQt4.Qt import QAbstractButton
from PyQt4.Qt import SIGNAL
from PyQt4.Qt import QSize, QWhatsThis
import foundation.env as env
from simulation.SimSetupDialog import Ui_SimSetupDialog
from simulation.movie import Movie
from utilities.debug import print_compact_traceback
from widgets.prefs_widgets import connect_checkbox_with_boolean_pref
from utilities.prefs_constants import Potential_energy_tracefile_prefs_key
from utilities.prefs_constants import electrostaticsForDnaDuringDynamics_prefs_key
from utilities.debug_prefs import debug_pref, Choice_boolean_False
from utilities.qt4transition import qt4todo
from utilities.TimeUtilities import timeStamp
from utilities.icon_utilities import geticon
# class FakeMovie:
#
# wware 060406 bug 1471 (sticky dialog params) - don't need a real movie, just need to hold the sim parameters
# If the sim parameters change, they might need to be updated everywhere a comment says "SIMPARAMS".
#
#bruce 060601 moving this here, since it's really an aspect of this dialog
# (in terms of what params to store, when to store them, etc);
# also fixing bug 1840 (like 1471 but work even after a sim was not aborted),
# and making the stickyness survive opening of a new file rather than being stored in the assy.
class FakeMovie:
def __init__(self, realmovie):
self.totalFramesRequested = realmovie.totalFramesRequested
self.temp = realmovie.temp
self.stepsper = realmovie.stepsper
self.watch_motion = realmovie.watch_motion # note 060705: might use __getattr__ in real movie, but ordinary attr in self
self._update_data = realmovie._update_data
self.update_cond = realmovie.update_cond # probably not needed
self.print_energy = realmovie.print_energy
def fyi_reusing_your_moviefile(self, moviefile):
pass
def might_be_playable(self):
return False
pass
_stickyParams = None # sometimes this is a FakeMovie object
class SimSetup(QDialog, Ui_SimSetupDialog): # before 050325 this class was called runSim
"""
The "Run Dynamics" dialog class for setting up and launching a simulator run.
"""
def __init__(self, win, part, previous_movie = None, suffix = ""):
"""
use previous_movie (if passed) for default values,
otherwise use the same ones last ok'd by user
(whether or not that sim got aborted), or default values if that never happened in this session;
on success or failure, make a new Movie and store it as self.movie
"""
QDialog.__init__(self, win) # win is parent.
self.setupUi(self)
self.setWindowIcon(geticon('ui/border/RunDynamics.png'))
self.whatsthis_btn.setIcon(
geticon('ui/actions/Properties Manager/WhatsThis.png'))
self.whatsthis_btn.setIconSize(QSize(22, 22))
self.whatsthis_btn.setToolTip('Enter "What\'s This?" help mode')
self.connect(self.whatsthis_btn,
SIGNAL("clicked()"),
QWhatsThis.enterWhatsThisMode)
self.watch_motion_buttongroup = QButtonGroup()
self.watch_motion_buttongroup.setExclusive(True)
for obj in self.watch_motion_groupbox.children():
if isinstance(obj, QAbstractButton):
self.watch_motion_buttongroup.addButton(obj)
self.connect(self.run_sim_btn,SIGNAL("clicked()"),self.createMoviePressed)
self.connect(self.cancel_btn,SIGNAL("clicked()"),self.close)
qt4todo('self.connect(self.watch_motion_groupbox,SIGNAL("toggled(bool)"),self.setEnabled) ???')
self.watch_motion_groupbox.setEnabled(True)
## self.part = part
# not yet needed, though in future we might display info
# about this Part in the dialog, to avoid confusion
# if it's not the main Part.
connect_checkbox_with_boolean_pref(self.potential_energy_checkbox,
Potential_energy_tracefile_prefs_key)
connect_checkbox_with_boolean_pref(
self.electrostaticsForDnaDuringDynamics_checkBox,
electrostaticsForDnaDuringDynamics_prefs_key)
self.assy = part.assy # used only for assy.filename
self.suffix = suffix
self.previous_movie = previous_movie or _stickyParams or Movie(self.assy) # used only for its parameter settings
# note: as of bruce 060601 fixing bug 1840, previous_movie is no longer ever passed by caller.
self.movie = Movie(self.assy) # public attr used by client code after we return; always a Movie even on failure.
# (we need it here since no extra method runs on failure, tho that could probably be fixed)
# bruce 050325 changes:
# We make a new Movie here (but only when we return with success).
# But we use default param settings from prior movie.
# Caller should pass info about default filename (including uniqueness
# when on selection or in clipboard item) -- i.e. the suffix.
# We should set the params and filename using a Movie method, or warn it we did so,
# or do them in its init... not yet cleaned up. ###@@@
# self.movie is now a public attribute.
#bruce 050329 comment: couldn't we set .movie to None, until we learn we succeeded? ###e ###@@@
self.setup()
self.watch_motion_groupbox.setWhatsThis(
"""Watch motion in real time
Enables real time graphical updates during simulation runs.
""")
self.update_number_spinbox.setWhatsThis(
"""Update every n units.
Specify how often to update the model during the simulation.
This allows the user to monitor simulation results while the
simulation is running.
""")
self.update_units_combobox.setWhatsThis(
"""Update every n units.
Specify how often to update the model during the simulation.
This allows the user to monitor simulation results while the
simulation is running.
""")
self.update_every_rbtn.setWhatsThis(
"""Update every n units.
Specify how often to update the model during the simulation.
This allows the user to monitor simulation results while the
simulation is running.
""")
self.update_asap_rbtn.setWhatsThis(
"""Update as fast as possible
Update every 2 seconds, or faster (up to 20x/sec) if it doesn't
slow down the simulation by more than 20%.
""")
self.temperatureSpinBox.setWhatsThis(
"""Temperature
The temperature of the simulation in Kelvin
(300 K = room temperature)
""")
self.totalFramesSpinBox.setWhatsThis(
"""Total frames
The total number of (movie) frames to create for the simulation run.
""")
self.stepsPerFrameDoubleSpinBox.setWhatsThis(
"""Steps per frame
The time duration between frames in femtoseconds.
""")
self.setWhatsThis(
"""Run Dynamics
The is the main dialog for configuring and launching a
Molecular Dynamics simulation run. Specify the simulation parameters
and click Run Simulation to launch.
The Play Movie command can be used to play back the
simulation.
""")
if not debug_pref("GROMACS: Enable for Run Dynamics", Choice_boolean_False,
prefs_key=True):
# Hide the Simulation engine groupbox altogether.
self.md_engine_groupbox.setHidden(True)
self.exec_()
def setup(self):
self.movie.cancelled = True # We will assume the user will cancel
#bruce 050324: fixed KnownBug item 27 by making these call setValue, not assign to it:
# If the sim parameters change, they need to be updated in all places marked "SIMPARAMS"
# Movie.__init__ (movie.py), toward the end
# SimSetup.setup (SimSetup.py)
# FakeMovie.__init (runSim.py)
self.totalFramesSpinBox.setValue( self.previous_movie.totalFramesRequested )
self.temperatureSpinBox.setValue( self.previous_movie.temp )
self.stepsPerFrameDoubleSpinBox.setValue( self.previous_movie.stepsper / 10.0 )
# self.timestepSB.setValue( self.previous_movie.timestep ) # Not supported in Alpha
# new checkboxes for Alpha7, circa 060108
#self.create_movie_file_checkbox.setChecked( self.previous_movie.create_movie_file )
# whether to store movie file (see NFR/bug 1286). [bruce & mark 060108]
# create_movie_file_checkbox removed for A7 (bug 1729). mark 060321
##e the following really belongs in the realtime_update_controller,
# and the update_cond is not the best thing to set this from;
# but we can leave it here, then let the realtime_update_controller override it if it knows how. [now it does]
self.watch_motion_groupbox.setChecked( self.previous_movie.watch_motion ) # whether to move atoms in realtime
try:
#bruce 060705 use new common code, if it works
from widgets.widget_controllers import realtime_update_controller
self.ruc = realtime_update_controller(
( self.watch_motion_buttongroup, self.update_number_spinbox, self.update_units_combobox ),
self.watch_motion_groupbox
# no prefs key for checkbox
)
self.ruc.set_widgets_from_update_data( self.previous_movie._update_data ) # includes checkbox
except:
print_compact_traceback( "bug; reverting to older code in simsetep setup: ")
if self.previous_movie._update_data:
update_number, update_units, update_as_fast_as_possible_data, watchjunk = self.previous_movie._update_data
self.watch_motion_groupbox.setChecked(watchjunk) ###060705
self.watch_motion_groupbox.setButton( update_as_fast_as_possible_data)
self.update_number_spinbox.setValue( update_number)
self.update_units_combobox.setCurrentText( update_units)
#k let's hope this changes the current choice, not the popup menu item text for the current choice!
return
def createMoviePressed(self):
"""
Creates a DPB (movie) file of the current part.
[Actually only saves the params and filename which should be used
by the client code (in writemovie?) to create that file.]
The part does not have to be saved as an MMP file first, as it used to.
"""
###@@@ bruce 050324 comment: Not sure if/when user can rename the file.
QDialog.accept(self)
if self.simulation_engine_combobox.currentIndex() == 1:
# GROMACS was selected as the simulation engine.
#
# NOTE: This code is just for demo and prototyping purposes - the
# real approach will be architected and utilize plugins.
#
# Brian Helfrich 2007-04-06
#
from simulation.GROMACS.GROMACS import GROMACS
gmx = GROMACS(self.assy.part)
gmx.run("md")
else:
# NanoDynamics-1 was selected as the simulation engine
#
errorcode, partdir = self.assy.find_or_make_part_files_directory()
self.movie.cancelled = False # This is the only way caller can tell we succeeded.
self.movie.totalFramesRequested = self.totalFramesSpinBox.value()
self.movie.temp = self.temperatureSpinBox.value()
self.movie.stepsper = self.stepsPerFrameDoubleSpinBox.value() * 10.0
self.movie.print_energy = self.potential_energy_checkbox.isChecked()
# self.movie.timestep = self.timestepSB.value() # Not supported in Alpha
#self.movie.create_movie_file = self.create_movie_file_checkbox.isChecked()
# removed for A7 (bug 1729). mark 060321
self.movie.create_movie_file = True
# compute update_data and update_cond, using new or old code
try:
# try new common code for this, bruce 060705
ruc = self.ruc
update_cond = ruc.get_update_cond_from_widgets()
assert update_cond or (update_cond is False) ###@@@ remove when works, and all the others like this
# note, if those widgets are connected to env.prefs, that's not handled here or in ruc;
# I'm not sure if they are. Ideally we'd tell ruc the prefs_keys and have it handle that too,
# perhaps making it a long-lived object (though that might not be necessary).
update_data = ruc.get_update_data_from_widgets() # redundant, but we can remove it when ruc handles prefs
except:
print_compact_traceback("bug using realtime_update_controller in SimSetup, will use older code instead: ")
# this older code can be removed after A8 if we don't see that message
#bruce 060530 use new watch_motion rate parameters
self.movie.watch_motion = self.watch_motion_groupbox.isChecked() # [deprecated for setattr as of 060705]
if env.debug():
print "debug fyi: sim setup watch_motion = %r" % (self.movie.watch_motion,)
# This code works, but I'll try to replace it with calls to common code (above). [bruce 060705]
# first grab them from the UI
update_as_fast_as_possible_data = self.watch_motion_groupbox.selectedId() # 0 means yes, 1 means no (for now)
# ( or -1 means neither, but that's prevented by how the button group is set up, at least when it's enabled)
update_as_fast_as_possible = (update_as_fast_as_possible_data != 1)
update_number = self.update_number_spinbox.value() # 1, 2, etc (or perhaps 0??)
update_units = str(self.update_units_combobox.currentText()) # 'frames', 'seconds', 'minutes', 'hours'
# for sake of propogating them to the next sim run:
update_data = update_number, update_units, update_as_fast_as_possible_data, self.movie.watch_motion
## if env.debug():
## print "stored _update_data %r into movie %r" % (self.movie._update_data, self.movie)
## print "debug: self.watch_motion_groupbox.selectedId() = %r" % (update_as_fast_as_possible_data,)
## print "debug: self.update_number_spinbox.value() is %r" % self.update_number_spinbox.value() # e.g. 1
## print "debug: combox text is %r" % str(self.update_units_combobox.currentText()) # e.g. 'frames'
# Now figure out what these user settings mean our realtime updating algorithm should be,
# as a function to be used for deciding whether to update the 3D view when each new frame is received,
# which takes as arguments the time since the last update finished (simtime), the time that update took (pytime),
# and the number of frames since then (nframes, 1 or more), and returns a boolean for whether to draw this new frame.
# Notes:
# - The Qt progress update will be done independently of this, at most once per second (in runSim.py).
# - The last frame we expect to receive will always be drawn. (This func may be called anyway in case it wants
# to do something else with the info like store it somewhere, or it may not (check runSim.py for details #k),
# but its return value will be ignored if it's called for the last frame.)
# The details of these functions (and the UI feeding them) might be revised.
# This code for setting update_cond is duplicated (inexactly) in Minimize_CommandRun.doMinimize()
if update_as_fast_as_possible:
# This radiobutton might be misnamed; it really means "use the old code,
# i.e. not worse than 20% slowdown, with threshholds".
# It's also ambiguous -- does "fast" mean "fast progress"
# or "often" (which are opposites)? It sort of means "often".
update_cond = ( lambda simtime, pytime, nframes:
simtime >= max(0.05, min(pytime * 4, 2.0)) )
elif update_units == 'frames':
update_cond = ( lambda simtime, pytime, nframes, _nframes = update_number: nframes >= _nframes )
elif update_units == 'seconds':
update_cond = ( lambda simtime, pytime, nframes, _timelimit = update_number: simtime + pytime >= _timelimit )
elif update_units == 'minutes':
update_cond = ( lambda simtime, pytime, nframes, _timelimit = update_number * 60: simtime + pytime >= _timelimit )
elif update_units == 'hours':
update_cond = ( lambda simtime, pytime, nframes, _timelimit = update_number * 3600: simtime + pytime >= _timelimit )
else:
print "don't know how to set update_cond from (%r, %r)" % (update_number, update_units)
update_cond = None
# revision in this old code, 060705:
if not self.movie.watch_motion:
update_cond = False
del self.movie.watch_motion # let getattr do it
# now do this, however we got update_data and update_cond:
self.movie._update_data = update_data # for propogating them to the next sim run
self.movie.update_cond = update_cond # used this time
# end of 060705 changes
suffix = self.suffix
tStamp = timeStamp()
if self.assy.filename and not errorcode: # filename could be an MMP or PDB file.
import shutil
dir, fil = os.path.split(self.assy.filename)
fil, ext = os.path.splitext(fil)
self.movie.filename = os.path.join(partdir, fil + '.' + tStamp + suffix + '.dpb')
self.movie.origfile = os.path.join(partdir, fil + '.' + tStamp + '.orig' + ext)
shutil.copy(self.assy.filename, self.movie.origfile)
else:
self.movie.filename = os.path.join(self.assy.w.tmpFilePath, "Untitled.%s%s.dpb" % (tStamp, suffix))
# Untitled parts usually do not have a filename
#bruce 060601 fix bug 1840, also make params sticky across opening of new files
global _stickyParams
_stickyParams = FakeMovie(self.movie) # these will be used as default params next time, whether or not this gets aborted
return
pass # end of class SimSetup
# end