Author: eudoxos
Date: 2008-10-28 11:30:53 +0100 (Tue, 28 Oct 2008)
New Revision: 1562

Added:
   trunk/gui/py/yade-multi
   trunk/scripts/multi.py
   trunk/scripts/multi.table
Modified:
   trunk/core/MetaBody.cpp
   trunk/gui/SConscript
   trunk/gui/py/PythonUI_rc.py
   trunk/gui/py/utils.py
Log:
1. Commit simple parametric study interface, documented at 
http://yade.wikia.com/index.php?title=ScriptParametricStudy and wrapper script 
to queue jobs, which is installed as yade-trunk-multi, yade-trunk-opt-multi and 
so on.
2. Examples of such parametric "study" added in scripts/multi.py and 
scripts/multi.table
3. MetaBody now create Omega().tags['id'] at initialization like 
'20081028T102950p15498' (date, time, pid) that is unique



Modified: trunk/core/MetaBody.cpp
===================================================================
--- trunk/core/MetaBody.cpp     2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/core/MetaBody.cpp     2008-10-28 10:30:53 UTC (rev 1562)
@@ -52,6 +52,7 @@
        string gecos(pw->pw_gecos), gecos2; size_t p=gecos.find(","); 
if(p!=string::npos) boost::algorithm::erase_tail(gecos,gecos.size()-p); 
for(size_t i=0;i<gecos.size();i++){gecos2.push_back(((unsigned 
char)gecos[i])<128 ? gecos[i] : '?'); }
        
tags.push_back(boost::algorithm::replace_all_copy(string("author=")+gecos2+" 
("+string(pw->pw_name)+"@"+hostname+")"," ","~"));
        
tags.push_back(string("isoTime="+boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time())));
+       
tags.push_back(string("id="+boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time())+"p"+lexical_cast<string>(getpid())));
        tags.push_back(string("description="));
 }
 

Modified: trunk/gui/SConscript
===================================================================
--- trunk/gui/SConscript        2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/gui/SConscript        2008-10-28 10:30:53 UTC (rev 1562)
@@ -52,6 +52,8 @@
                        env.File('qt.py','qt3'),
                ])
 
+       
env.InstallAs('$PREFIX/bin/yade$SUFFIX-multi',env.File('yade-multi','py'))
+
        # python modules are one level deeper so that you can say: from 
yade.wrapper import *
        env.Install('$PREFIX/lib/yade$SUFFIX/gui/yade',[
                
env.SharedLibrary('wrapper',['py/yadeControl.cpp'],SHLIBPREFIX='',

Modified: trunk/gui/py/PythonUI_rc.py
===================================================================
--- trunk/gui/py/PythonUI_rc.py 2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/gui/py/PythonUI_rc.py 2008-10-28 10:30:53 UTC (rev 1562)
@@ -24,6 +24,7 @@
 import yade.PythonTCPServer
 srv=yade.PythonTCPServer.PythonTCPServer(minPort=9000)
 yade.runtime.cookie=srv.server.cookie
+sys.stdout.flush()
 
 ## run simulation if requested from the command line
 if runtime.simulation:
@@ -40,7 +41,9 @@
                import traceback
                traceback.print_exc()
                if(runtime.nonInteractive or runtime.stopAfter): sys.exit(1)
-if runtime.stopAfter: sys.exit(0)
+if runtime.stopAfter:
+       print "Finished, bye."
+       sys.exit(0)
 
 # run commands if requested from the command line
 #if yadeRunCommands:

Modified: trunk/gui/py/utils.py
===================================================================
--- trunk/gui/py/utils.py       2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/gui/py/utils.py       2008-10-28 10:30:53 UTC (rev 1562)
@@ -16,7 +16,7 @@
 # c++ implementations for performance reasons
 from yade._utils import *
 
-def saveVars(**kw):
+def saveVars(mark='',**kw):
        """Save passed variables into the simulation so that it can be 
recovered when the simulation is loaded again.
 
        For example, variables a=5, b=66 and c=7.5e-4 are defined. To save 
those, use
@@ -31,12 +31,12 @@
        and they will be defined in the __builtin__ namespace (i.e. available 
from anywhere in the python code).
        """
        import cPickle
-       Omega().tags['pickledPythonVariablesDictionary']=cPickle.dumps(kw)
+       Omega().tags['pickledPythonVariablesDictionary'+mark]=cPickle.dumps(kw)
 
-def loadVars():
+def loadVars(mark=''):
        import cPickle
        import __builtin__
-       d=cPickle.loads(Omega().tags['pickledPythonVariablesDictionary'])
+       d=cPickle.loads(Omega().tags['pickledPythonVariablesDictionary'+mark])
        for k in d: __builtin__.__dict__[k]=d[k]
 
 
@@ -241,14 +241,14 @@
        mainloop.run()
        pipeline.set_state(gst.STATE_NULL); pipeline.get_state()
 
-def readParamsFromTable(tableFile=None,tableLine=None,noTableOk=False,**kw):
+def readParamsFromTable(tableFileLine=None,noTableOk=False,**kw):
        """
        Read parameters from a file and assign them to __builtin__ variables.
 
        tableFile is a text file (with one value per blank-separated columns)
        tableLine is number of line where to get the values from
 
-               The format of the file is as follows (no comments, empty lines 
etc allowed…)
+               The format of the file is as follows (commens starting with # 
and empty lines allowed
                
                name1 name2 … # 0th line
                val1  val2  … # 1st line
@@ -261,17 +261,22 @@
        
        assigns Omega().tags['defaultParams']="unassignedName1=defaultValue1,…"
 
+       saves all parameters (default as well as settable) using 
saveVars('table')
+
        return value is the number of assigned parameters.
        """
        o=Omega()
        tagsParams=[]
+       dictDefaults,dictParams={},{}
        import os, __builtin__
-       if not tableFile and not os.environ.has_key('PARAM_TABLE'):
+       if not tableFileLine and not os.environ.has_key('PARAM_TABLE'):
                if not noTableOk: raise EnvironmentError("PARAM_TABLE is not 
defined in the environment")
+               o.tags['line']='l!'
        else:
-               if not tableFile: tableFile=os.environ['PARAM_TABLE']
-               if not tableLine: tableLine=int(os.environ['PARAM_LINE'])
-               ll=open(tableFile).readlines(); names=ll[0].split(); 
values=ll[tableLine].split()
+               if not tableFileLine: tableFileLine=os.environ['PARAM_TABLE']
+               tableFile,tableLine=tableFileLine.split(':')
+               o.tags['line']='l'+tableLine
+               ll=[l.split('#')[0] for l in open(tableFile).readlines()]; 
names=ll[0].split(); values=ll[int(tableLine)].split()
                assert(len(names)==len(values))
                for i in range(len(names)):
                        if names[i]=='description': 
o.tags['description']=values[i]
@@ -279,12 +284,13 @@
                                if names[i] not in kw.keys(): raise 
NameError("Parameter `%s' has no default value assigned"%names[i])
                                kw.pop(names[i])
                                eq="%s=%s"%(names[i],values[i])
-                               exec('__builtin__.%s=%s'%(names[i],values[i])); 
tagsParams+=['%s=%s'%(names[i],values[i])]
+                               exec('__builtin__.%s=%s'%(names[i],values[i])); 
tagsParams+=['%s=%s'%(names[i],values[i])]; dictParams[names[i]]=values[i]
        defaults=[]
        for k in kw.keys():
                exec("__builtin__.%s=%s"%(k,kw[k]))
-               defaults+=["%s=%s"%(k,kw[k])]
+               defaults+=["%s=%s"%(k,kw[k])]; dictDefaults[k]=kw[k]
        o.tags['defaultParams']=",".join(defaults)
        o.tags['params']=",".join(tagsParams)
+       dictParams.update(dictDefaults); saveVars('table',**dictParams)
        return len(tagsParams)
 

Added: trunk/gui/py/yade-multi
===================================================================
--- trunk/gui/py/yade-multi     2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/gui/py/yade-multi     2008-10-28 10:30:53 UTC (rev 1562)
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# encoding: utf-8
+#
+# portions © 2008 Václav Šmilauer <[EMAIL PROTECTED]>
+
+
+#
+# _MANY_ thanks to Mark Pettit for concurrent jobs handling!
+# http://code.activestate.com/recipes/534160/
+#
+
+import os, sys, thread, time
+
+def __concurrent_batch(cmd,thread_num,completion_status_dict,exit_code_dict):
+       """Helper routine for 'concurrent_batches."""
+
+       t0=time.time()
+       print '#%d started on %s'%(thread_num,time.asctime())
+       exit_code_dict[thread_num] = os.system(cmd)
+       completion_status_dict[thread_num] = 1  # for sum() routine
+       dt=int(time.time()-t0)
+       strDt='%02d:%02d:%02d'%(dt//3600,(dt%3600)//60,(dt%60))
+       strStatus='done   ' if exit_code_dict[thread_num]==0 else 'FAILED '
+       print "#%d %s (exit status %d), duration 
%s"%(thread_num,strStatus,exit_code_dict[thread_num],strDt)
+
+def concurrent_batches(batchlist,maxjobs=0,maxtime=0):
+       """Run a list of batch commands simultaneously.
+
+       'batchlist' is a list of strings suitable for submitting under 
os.system().
+       Each job will run in a separate thread, with the thread ending when the
+       subprocess ends.
+       'maxjobs' will, if greater then zero, be the maximum number of 
simultaneous
+       jobs which can concurrently run.  This would be used to limit the 
number of
+       processes where too many could flood a system, causing performance 
issues.
+       'maxtime', when greater than zero, be the maximum amount of time 
(seconds)
+       that we will wait for processes to complete.  After that, we will 
return,
+       but no jobs will be killed.  In other words, the jobs still running will
+       continue to run, and hopefully finish in the course of time.
+
+       example: concurrent_batches(("gzip abc","gzip def","gzip xyz"))
+
+       returns: a dictionary of exit status codes, but only when ALL jobs are
+                        complete, or the maximum time has been exceeded.
+                        Note that if returning due to exceeding time, the 
dictionary will
+                        continue to be updated by the threads as they complete.
+                        The key of the dictionary is the thread number, which 
matches the
+                        index of the list of batch commands.  The value is the 
result of
+                        the os.system call.
+
+       gotcha:  If both the maxjobs and maxtime is set, there is a possibility 
that
+                        not all jobs will be submitted.  The only way to 
detect this will be
+                        by checking for the absence of the KEY in the returned 
dictionary.
+       """
+
+       if not batchlist: return {}
+       completion_status_dict, exit_code_dict = {}, {}
+       num_jobs = len(batchlist)
+       start_time = time.time()
+       for thread_num, cmd in enumerate(batchlist):
+               exit_code_dict[thread_num] = None
+               completion_status_dict[thread_num] = 0 # for sum() routine
+               thread.start_new_thread(__concurrent_batch,
+                         
(cmd,thread_num,completion_status_dict,exit_code_dict))
+               while True:
+                       completed = sum(completion_status_dict.values())
+                       if num_jobs == completed:
+                               return exit_code_dict     # all done
+                       running = thread_num - completed + 1
+                       if maxtime > 0:
+                               if time.time() - start_time > maxtime:
+                                       return exit_code_dict
+                       if not maxjobs:
+                               if thread_num < num_jobs-1:  # have we 
submitted all jobs ?
+                                       break                             #  
no, so break to for cmd loop
+                               else:
+                                       time.sleep(.2)           #  yes, so 
wait until jobs are complete
+                                       continue
+                       if running < maxjobs and thread_num < num_jobs-1:
+                               break   # for next for loop
+                       time.sleep(.2)
+#
+# now begins the yade code
+#
+
+import sys,re,optparse
+def getNumCores(): return len([l for l in open('/proc/cpuinfo','r') if 
l.find('processor')==0])
+
+parser=optparse.OptionParser(usage='%prog [options] TABLE SIMULATION.py\n\n  
%prog runs yade simulation multiple times with different parameters.\n  See 
http://yade.wikia.com/wiki/ScriptParametricStudy for details.')
+parser.add_option('-j',dest='maxJobs',type='int',help="Maximum number of 
simultaneous jobs to run (default: number of cores, i.e. 
%d)"%getNumCores(),metavar='NUM',default=getNumCores())
+parser.add_option('--log',dest='logFormat',help='Format of log files -- must 
contain a %, which will be replaced by line number (default: 
SIMULATION.%.log)',metavar='FORMAT')
+parser.add_option('-l','--lines',dest='lineList',help='Lines of TABLE to use, 
in the format 2,3-5,8,11-13 (default: all available lines in 
TABLE)',metavar='LIST')
+parser.add_option('--nice',dest='nice',type='int',help='Nice value of spawned 
jobs (default: 10)',default=10)
+parser.add_option('--executable',dest='executable',help='Name of the program 
to run (default: %s)'%sys.argv[0][:-6],default=sys.argv[0][:-6],metavar='FILE') 
## strip the '-multi' extension
+opts,args=parser.parse_args()
+logFormat,lineList,maxJobs,nice,executable=opts.logFormat,opts.lineList,opts.maxJobs,opts.nice,opts.executable
+
+if len(args)!=2:
+       #print "Exactly two non-option arguments must be specified -- parameter 
table and script to be run.\n"
+       parser.print_help()
+       sys.exit(1)
+table,simul=args[0:2]
+if not logFormat: logFormat=(simul[:-3] if simul[-3:]=='.py' else 
simul)+".%.log"
+if not '%' in logFormat: raise StandardError("Log string must contain a `%'")
+
+print "Will run `%s' on `%s' with nice value %d, output redirected to `%s', %d 
jobs at a time."%(executable,simul,nice,logFormat,maxJobs)
+
+ll=open(table,'r').readlines()
+availableLines=[i for i in range(len(ll)) if not 
re.match(r'^\s*(#.*)?$',ll[i][:-1]) and i>0]
+
+print "Will use table `%s', with available lines"%(table),', '.join([str(i) 
for i in availableLines])+'.'
+
+if lineList:
+       useLines=[]
+       def numRange2List(s):
+               ret=[]
+               for l in s.split(','):
+                       if "-" in l: ret+=range(*[int(s) for s in 
l.split('-')]); ret+=[ret[-1]+1]
+                       else: ret+=[int(l)]
+               return ret
+       useLines0=numRange2List(lineList)
+       for l in useLines0:
+               if l not in availableLines: print "WARNING: skipping 
unavailable line %d that was requested from the command line."%l
+               elif l==0: print "WARNING: skipping line 0 that should contain 
variable labels"
+               else: useLines+=[l]
+else: useLines=availableLines
+print "Will use lines ",', '.join([str(i) for i in useLines])+'.'
+
+
+batches=[]
+for l in useLines:
+       logFile=logFormat.replace('%',str(l))
+       batches.append('PARAM_TABLE=%s:%d nice -n %d %s -N PythonUI -- -n -x %s 
> %s 2>&1'%(table,l,nice,executable,simul,logFile))
+print "Job summary:"
+for i in range(len(batches)):
+       print '   #%d:'%i, batches[i]
+# OK, go now
+concurrent_batches(batches,maxjobs=maxJobs)
+print 'All jobs finished, bye.'


Property changes on: trunk/gui/py/yade-multi
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/scripts/multi.py
===================================================================
--- trunk/scripts/multi.py      2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/scripts/multi.py      2008-10-28 10:30:53 UTC (rev 1562)
@@ -0,0 +1,45 @@
+#
+# see http://yade.wikia.com/wiki/ScriptParametricStudy#scripts.2Fmulti.py for 
explanations
+#
+## read parameters from table here
+from yade import utils
+utils.readParamsFromTable(gravity=-9.81,density=2400,initialSpeed=20,noTableOk=True)
+print gravity,density,initialSpeed
+
+o=Omega()
+o.initializers=[
+       StandAloneEngine('PhysicalActionContainerInitializer'),
+       
MetaEngine('BoundingVolumeMetaEngine',[EngineUnit('InteractingSphere2AABB'),EngineUnit('InteractingFacet2AABB'),EngineUnit('MetaInteractingGeometry2AABB')])
+       ]
+o.engines=[
+       StandAloneEngine('PhysicalActionContainerReseter'),
+       MetaEngine('BoundingVolumeMetaEngine',[
+               EngineUnit('InteractingSphere2AABB'),
+               EngineUnit('InteractingBox2AABB'),
+               EngineUnit('MetaInteractingGeometry2AABB')
+       ]),
+       StandAloneEngine('PersistentSAPCollider'),
+       MetaEngine('InteractionGeometryMetaEngine',[
+               
EngineUnit('InteractingSphere2InteractingSphere4SpheresContactGeometry'),
+               
EngineUnit('InteractingBox2InteractingSphere4SpheresContactGeometry')
+       ]),
+       
MetaEngine('InteractionPhysicsMetaEngine',[EngineUnit('SimpleElasticRelationships')]),
+       StandAloneEngine('ElasticContactLaw'),
+       DeusExMachina('GravityEngine',{'gravity':[0,0,gravity]}), ## here we 
use the 'gravity' parameter
+       DeusExMachina('NewtonsDampedLaw',{'damping':0.4})
+]
+o.bodies.append(utils.box([0,50,0],extents=[1,50,1],dynamic=False,color=[1,0,0],young=30e9,poisson=.3,density=density))
 ## here we use the density parameter
+o.bodies.append(utils.sphere([0,0,10],1,color=[0,1,0],young=30e9,poisson=.3,density=density))
 ## here again
+
+o.bodies[1].phys['velocity']=[0,initialSpeed,0] ## assign initial velocity
+
+o.dt=.8*utils.PWaveTimeStep()
+## o.saveTmp('initial')
+
+# run 30000 iterations
+o.run(30000,True)
+
+# write some results to a common file
+# (we rely on the fact that 2 processes will not write results at exactly the 
same time)
+# 'a' means to open for appending
+file('multi.out','a').write('%s %g %g %g 
%g\n'%(o.tags['description'],gravity,density,initialSpeed,o.bodies[1].phys.pos[1]))

Added: trunk/scripts/multi.table
===================================================================
--- trunk/scripts/multi.table   2008-10-27 21:41:21 UTC (rev 1561)
+++ trunk/scripts/multi.table   2008-10-28 10:30:53 UTC (rev 1562)
@@ -0,0 +1,9 @@
+description density initialSpeed ## comment here
+reference   2400    10
+hi_v           2400    20
+lo_v        2400     5  # comment there
+hi_rho      5000    10
+hi_rho_v    5000    20
+hi_rh0_lo_v 5000     5
+
+  # comment-only line


_______________________________________________
Mailing list: https://launchpad.net/~yade-dev
Post to     : [EMAIL PROTECTED]
Unsubscribe : https://launchpad.net/~yade-dev
More help   : https://help.launchpad.net/ListHelp
_______________________________________________
yade-dev mailing list
yade-dev@lists.berlios.de
https://lists.berlios.de/mailman/listinfo/yade-dev

Reply via email to