runSolver.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import ivp
  2. import solveMatrix
  3. import os
  4. import cModel
  5. import sys
  6. import json
  7. import numpy
  8. import time
  9. import scipy.interpolate
  10. import shutil
  11. import importlib
  12. importlib.reload(solveMatrix)
  13. defaultValues={\
  14. 'method':'LSODA',\
  15. 'atol':1e-4,\
  16. 'rtol':1e-4,\
  17. 't0':0,\
  18. 'tmax':1,\
  19. 'mode':'IVPSimultaneous',\
  20. 'nt':201,
  21. 'tUnit':'day',\
  22. 'network':'labkey-klimt.json',\
  23. 'startFromRef':'None',\
  24. 'project':'Analysis/Run',\
  25. 'schema':'lists',\
  26. 'query':'runs',\
  27. 'view':'WithStrings',
  28. 'exposureScaleRef':1}
  29. def get(setup,par):
  30. try:
  31. return setup[par]
  32. except KeyError:
  33. pass
  34. return defaultValues[par]
  35. def parseSetup(setupFile):
  36. with open(setupFile,'r') as f:
  37. setup=json.load(f)
  38. out={}
  39. for p in defaultValues:
  40. out[p]=get(setup,p)
  41. #q=[x if not x in out for x in setup]
  42. q=[x for x in setup if not x in out]
  43. for x in q:
  44. out[x]=setup[x]
  45. return out
  46. def getScale(setup):
  47. tUnit=setup['tUnit']
  48. return convertUnit(tUnit)
  49. def convertUnit(tUnit):
  50. if tUnit=='min':
  51. return 1
  52. if tUnit=='hour':
  53. return 60
  54. if tUnit=='day':
  55. return 24*60
  56. if tUnit=='month':
  57. return 24*60*30
  58. if tUnit=='year':
  59. return 24*60*365
  60. return 1
  61. def getStrides(setup):
  62. tmax=float(setup['tmax'])*convertUnit(setup['tUnit'])
  63. t0=setup['t0']
  64. print('t0 {} tmax {}'.format(t0,tmax))
  65. try:
  66. stride=setup['stride']
  67. except KeyError:
  68. return [{'label':'','length':tmax-t0}]
  69. #cover tmax with steps of length stride['length']
  70. strides=[]
  71. strideLength=stride['length']*convertUnit(stride['tUnit'])
  72. i=0
  73. while t0<tmax:
  74. strides.append({'length':strideLength,'label':'_step{:03d}'.format(i)})
  75. t0+=strideLength
  76. i+=1
  77. return strides
  78. def getFiles(setup, getAll=False):
  79. strides=getStrides(setup)
  80. readable=['t','sol','se','qt','sOut']
  81. #get last stride
  82. steps=[strides[-1]]
  83. if getAll:
  84. steps=strides
  85. inFiles=[]
  86. for s in steps:
  87. l=s['label']
  88. inFiles.append({x:'{}{}.txt'.format(x,l) for x in readable})
  89. return inFiles
  90. def findStartPoint(startDir,setup):
  91. if startDir!='NONE':
  92. print('Using solution from {}'.format(startDir))
  93. #t0,y0,s0,lut,lutSE=getStartPointFromDir(startDir)
  94. startPoint=getStartPointFromDir(startDir)
  95. print('t0={}'.format(startPoint['t0']))
  96. return startPoint
  97. try:
  98. print('Using solution from {}'.format(setup['startFromRef']))
  99. except KeyError:
  100. pass
  101. startPoint=getStartPoint(setup)
  102. print('t0={}'.format(startPoint['t0']))
  103. return startPoint
  104. def checkForExistingSolution(model,setup,step,jobDir):
  105. useStored=setup.get('useStored',True)
  106. if not useStored:
  107. return {'status':False}
  108. #check if solution exists
  109. readable=['t','sol','se','qt','sOut']
  110. l=step['label']
  111. files=[os.path.join(jobDir,'{}{}.txt'.format(x,l)) for x in readable]
  112. filesPresent=all( [os.path.isfile(x) for x in files])
  113. if not filesPresent:
  114. return {'status':False}
  115. #set t0,y0,sIn
  116. latestFiles={x:'{}{}.txt'.format(x,l) for x in readable}
  117. data=loadSolutionFromFiles(setup,jobDir,[latestFiles])
  118. copyFields=['t','se','sol','qt']
  119. out={x:data[x] for x in copyFields}
  120. out.update({'status':True})
  121. s0=data['sOut']#Nxnxm
  122. se=data['se']
  123. #could be that sOut is ordered differently than in model
  124. out['s1']=numpy.zeros(s0.shape)
  125. for x in model.lutSE:
  126. out['s1'][:,:,model.lutSE[x]]=s0[:,:,data['lutSE'][x]]
  127. return out
  128. def solve(model,setup,startPoint,jobDir):
  129. #only uses values of parameters from memory
  130. #we should reorder S1 to match model.lutSE
  131. t0=startPoint['t0']
  132. y0=startPoint['y0']
  133. s0=startPoint['s0']
  134. #S1 is nxm, so we should reorder columns
  135. sIn=numpy.zeros(s0.shape)
  136. if s0.size>0:
  137. for x in model.lutSE:
  138. #this could be modified for different ordering of lut as well
  139. sIn[:,model.lutSE[x]]=s0[:,startPoint['lutSE'][x]]
  140. #copy t0 to setup to help get the strides right
  141. exposureScale=setup['exposureScaleRef']
  142. y0*=exposureScale
  143. print('sIn {}'.format(sIn.shape))
  144. try:
  145. for x in model.lutSE:
  146. sIn[:,model.lutSE[x]]*=exposureScale
  147. except IndexError:
  148. #if there are no initial conditions, skip this part
  149. pass
  150. setup['t0']=t0
  151. strides=getStrides(setup)
  152. full_t=[t0]
  153. full_sol=[y0]
  154. full_sOut=[sIn]
  155. full_qt=[t0]
  156. for step in strides:
  157. obj=checkForExistingSolution(model,setup,step,jobDir)
  158. if obj['status']:
  159. print('Completed step {}'.format(step['label']))
  160. t=obj['t']
  161. sOut=obj['s1']
  162. sol=obj['sol']
  163. se=obj['se']
  164. qt=obj['qt']
  165. full_qt.extend(qt[1:])
  166. full_sOut.extend(sOut[1:])
  167. full_t.extend(t[1:])
  168. full_sol.extend(sol[1:])
  169. #set starting values for next step
  170. t0=obj['t'][-1]
  171. y0=obj['sol'][-1]
  172. sIn=obj['s1'][-1]
  173. continue
  174. print('Step required {}'.format(step['label']))
  175. t1=t0+step['length']
  176. start_time=time.time()
  177. if setup['mode']=='SequentialOdeint':
  178. t,sol,se,s1=ivp.solveSequentialOdeint(model,t1,setup['nt'],t0,y0,sIn)
  179. if setup['mode']=='SimultaneousOdeint':
  180. t,sol,se,s1=ivp.solveSimultaneousOdeint(model,t1,setup['nt'],t0,y0,sIn)
  181. if setup['mode']=='IVP':
  182. t,sol,se,s1=ivp.solveSequential(model,t1,\
  183. nt=setup['nt'],\
  184. atol=setup['atol'],\
  185. rtol=setup['rtol'],method=setup['method'],\
  186. t0=t0,y0=y0,sIn=sIn)
  187. if setup['mode']=='IVPSimultaneous':
  188. t,sol,se,s1=ivp.solveSimultaneous(model,t1,\
  189. nt=setup['nt'],\
  190. atol=setup['atol'],\
  191. rtol=setup['rtol'],method=setup['method'],\
  192. t0=t0,y0=y0,sIn=sIn)
  193. if setup['mode']=='solveMatrix':
  194. t,sol,se,s1=solveMatrix.solveMatrix(model,t1,\
  195. nt=setup['nt'],\
  196. t0=t0,y0=y0,sIn=sIn,method=setup['method'])
  197. end_time=time.time()
  198. #interpolate on s1
  199. #s1 has shape ft x n x m
  200. #where ft is LSODE driven number of points
  201. qt=numpy.linspace(t0,t1,setup['nt'])
  202. sOut=interpolate3d(qt,t,s1)
  203. print('Time: {:.3f} s'.format(end_time-start_time))
  204. y0=sol[-1]
  205. t0=t[-1]
  206. sIn=sOut[-1]
  207. #store
  208. writeable={'t':t,'sol':sol,'se':se,'qt':qt,'sOut':sOut}
  209. writeable={os.path.join(jobDir,'{}{}.txt'.format(x,step['label'])):writeable[x]\
  210. for x in writeable}
  211. for x in writeable:
  212. if x.find('sOut')>-1:
  213. write3D(x,writeable[x],model.lut,model.lutSE)
  214. else:
  215. numpy.savetxt(x,writeable[x])
  216. full_t.extend(t[1:])
  217. full_sol.extend(sol[1:])
  218. full_qt.extend(qt[1:])
  219. full_sOut.extend(sOut[1:])
  220. print('Completed step {}'.format(step['label']))
  221. print('Completed run')
  222. #interpolate on this
  223. setFirstElementToCorrectShape(full_sOut)
  224. setFirstElementToCorrectShape(full_sol)
  225. full_sOut=numpy.array(full_sOut)
  226. full_qt=numpy.array(full_qt)
  227. full_sol=numpy.array(full_sol)
  228. print(full_sOut.shape)
  229. qtFull=numpy.linspace(full_t[0],full_t[-1],setup['nt'])
  230. solFull=interpolate2d(qtFull,full_t,full_sol)
  231. sOutFull=interpolate3d(qtFull,full_qt,full_sOut)
  232. return qtFull,solFull,sOutFull
  233. def setFirstElementToCorrectShape(array):
  234. if not array[0].size:
  235. array[0]=numpy.zeros(array[-1].shape)
  236. def main(parFiles,jobDir,startDir='NONE'):
  237. print(parFiles)
  238. model=cModel.model()
  239. setupFile=parFiles[0]
  240. modelFile=parFiles[1]
  241. parameterFile=parFiles[2]
  242. model.parse(modelFile,parameterFile)
  243. setup=parseSetup(setupFile)
  244. scale=getScale(setup)
  245. tmax=float(setup['tmax'])*scale
  246. print('Using {}'.format(jobDir))
  247. # set start point
  248. startPoint=findStartPoint(startDir,setup)
  249. t,sol,sOut=solve(model,setup,startPoint,jobDir)
  250. print(t)
  251. writeSetup(jobDir,setup,modelFile,parameterFile)
  252. def writeSetup(jobDir,setup,modelFile,parameterFile):
  253. #update setup with new content (particularly t0)
  254. setupOut=os.path.join(jobDir,'setup.json')
  255. with open(setupOut,'w+') as f:
  256. f.write(json.dumps(setup))
  257. #this is 3D, so new routines
  258. print('Written {}'.format(setupOut))
  259. #write model and parameter file
  260. origFiles=[modelFile,parameterFile]
  261. outFiles=["model.json","parameters.json"]
  262. for x in zip(origFiles,outFiles):
  263. xOut=os.path.join(jobDir,x[1])
  264. shutil.copyfile(x[0],xOut)
  265. print('Written {}'.format(xOut))
  266. def interpolate3d(qt,t,s1):
  267. #interpolate on s1
  268. #s1 has shape ft x n x m
  269. #where ft is LSODE driven number of points
  270. fN=s1.shape[1]
  271. try:
  272. fM=s1.shape[2]
  273. except IndexError:
  274. return interpolate2d(qt,t,s1)
  275. sOut=numpy.zeros((len(qt),fN,fM))
  276. for i in range(fN):
  277. for j in range(fM):
  278. y=s1[:,i,j]
  279. f = scipy.interpolate.interp1d(t, y, kind='cubic')
  280. sOut[:,i,j]=f(qt)
  281. return sOut
  282. def interpolate2d(qt,t,y):
  283. yOut=numpy.zeros((len(qt),y.shape[1]))
  284. for i in range(y.shape[1]):
  285. f=scipy.interpolate.interp1d(t,y[:,i],kind='cubic')
  286. yOut[:,i]=f(qt)
  287. return yOut
  288. def invertLUT(lut):
  289. fcode=[None]*len(lut)
  290. for x in lut:
  291. fcode[lut[x]]=x
  292. return fcode
  293. def write3D(fName,a,lut,lutSE):
  294. with open(fName,'w') as f:
  295. f.write('#Shape {}\n'.format(a.shape))
  296. f.write('#Lut {}\n'.format(invertLUT(lut)))
  297. f.write('#Lut (SE) {}\n'.format(invertLUT(lutSE)))
  298. i=0
  299. for data_slice in a:
  300. f.write('#\t Slice {}\n'.format(i))
  301. numpy.savetxt(f,data_slice)
  302. i+=1
  303. def parseLUT(line,isSE=False):
  304. SE=''
  305. if isSE:
  306. SE='(SE)'
  307. header='#Lut {}'.format(SE)
  308. names=line.replace(header,'').\
  309. replace('[','').\
  310. replace(']','').\
  311. replace(' ','').\
  312. replace('\n','').\
  313. replace("'",'').\
  314. split(',')
  315. lut={names[i]:i for i in range(len(names))}
  316. return lut
  317. def parseShape(line):
  318. shape=line.replace('#Shape ','').\
  319. replace('\n','').\
  320. replace('(','').\
  321. replace(')','').\
  322. split(',')
  323. return [int(x) for x in shape]
  324. def read3D(fName):
  325. new_data=numpy.loadtxt(fName)
  326. #loadtxt will ignore lines starting with #, so that is where we store contextual information like shape
  327. #read and parse first three lines to get shape,lut and lutSE
  328. nLines=3
  329. with open(fName,'r') as f:
  330. lines=[f.readline() for i in range(nLines)]
  331. shape=parseShape(lines[0])
  332. lut=parseLUT(lines[1])
  333. lutSE=parseLUT(lines[2],isSE=True)
  334. return new_data.reshape(shape),lut,lutSE
  335. def loadSolutionFromRef(setup, loadAll=False):
  336. if setup['startFromRef']=='None':
  337. return {'t':0,'sol':numpy.array([]),'sOut':numpy.array([]),'x':numpy.array([]),'y':numpy.array([])}
  338. ref=setup['startFromRef']
  339. return loadSolutionFromRunRef(setup,ref,loadAll)
  340. def connect(setup):
  341. nixSuite=os.path.join(os.path.expanduser('~'),'software','src','nixSuite')
  342. sys.path.append(os.path.join(nixSuite,'wrapper'))
  343. import nixWrapper
  344. nixWrapper.loadLibrary('labkeyInterface')
  345. import labkeyInterface
  346. import labkeyFileBrowser
  347. import labkeyDatabaseBrowser
  348. net=labkeyInterface.labkeyInterface()
  349. fconfig=os.path.join(os.path.expanduser('~'),'.labkey',setup['network'])
  350. net.init(fconfig)
  351. #debug
  352. net.getCSRF()
  353. db=labkeyDatabaseBrowser.labkeyDB(net)
  354. fb=labkeyFileBrowser.labkeyFileBrowser(net)
  355. return db,fb
  356. def getRunRef(setup,key):
  357. #get solution from key rather than runRef (easier to copy, understand)
  358. db,fb=connect(setup)
  359. keyFilter={'variable':'Key','value':'{}'.format(key),'oper':'eq'}
  360. ds=db.selectRows(setup['project'],setup['schema'],setup['query'],[keyFilter],setup['view'])
  361. try:
  362. if len(ds['rows'])==0:
  363. return 'NONE'
  364. except KeyError:
  365. print(ds)
  366. return 'NONE'
  367. print('Found {} rows for {}'.format(len(ds['rows']),key))
  368. return ds['rows'][0]['runRef']
  369. def loadSolutionFromRunRef(setup,ref,loadAll=False):
  370. db,fb=connect(setup)
  371. remoteDir=fb.formatPathURL(setup['project'],'/'.join(['jobs',ref]))
  372. try:
  373. localDir=setup['localDir']
  374. except KeyError:
  375. localDir=os.path.join(os.path.expanduser('~'),'temp','jobDir')
  376. if not os.path.isdir(localDir):
  377. os.mkdir(localDir)
  378. #download setup
  379. fs=['setup.json','model.json','parameters.json']
  380. for f in fs:
  381. localPath=os.path.join(localDir,f)
  382. remotePath='/'.join([remoteDir,f])
  383. fb.readFileToFile(remotePath,localPath)
  384. setupFile=os.path.join(localDir,fs[0])
  385. setupOld=parseSetup(setupFile)
  386. inFiles=getFiles(setupOld,loadAll)
  387. #inFiles.extend(parFiles)
  388. i=0
  389. n=len(inFiles)
  390. for fSet in inFiles:
  391. for x in fSet:
  392. f=fSet[x]
  393. localPath=os.path.join(localDir,f)
  394. remotePath='/'.join([remoteDir,f])
  395. fb.readFileToFile(remotePath,localPath)
  396. print('Loaded {}/{} {}'.format(i+1,n,localDir))
  397. i+=1
  398. return loadSolutionFromDir(localDir,loadAll)
  399. def merge(a1,a2):
  400. try:
  401. return numpy.concatenate(a1,a2)
  402. except ValueError:
  403. return a2
  404. def loadSolutionFromDir(jobDir,loadAll=False):
  405. setupFile=os.path.join(jobDir,'setup.json')
  406. setup=parseSetup(setupFile)
  407. inFiles=getFiles(setup,loadAll)
  408. return loadSolutionFromFiles(setup,jobDir,inFiles)
  409. def loadSolutionFromFiles(setup,jobDir,inFiles):
  410. isFirst=True
  411. operators={x:numpy.loadtxt for x in inFiles[0]}
  412. operators['sOut']=read3D
  413. data={}
  414. i=0
  415. n=len(inFiles)
  416. for fSet in inFiles:
  417. idata={}
  418. print('Parsing [{}/{}]'.format(i+1,n))
  419. for f in fSet:
  420. path=os.path.join(jobDir,fSet[f])
  421. idata=operators[f](os.path.join(jobDir,fSet[f]))
  422. if f=='sOut':
  423. lut=idata[1]
  424. lutSE=idata[2]
  425. idata=idata[0]
  426. try:
  427. data[f]=numpy.concatenate((data[f],idata))
  428. except KeyError:
  429. data[f]=idata
  430. i+=1
  431. data['lut']=lut
  432. data['lutSE']=lutSE
  433. data['setup']=setup
  434. data['model']=os.path.join(jobDir,'model.json')
  435. data['parameters']=os.path.join(jobDir,'parameters.json')
  436. return data
  437. def getStartPointFromDir(jobDir):
  438. data=loadSolutionFromDir(jobDir)
  439. return startPointObject(data)
  440. def startPointObject(data):
  441. vrs={'t':'t0','sol':'y0','sOut':'s0'}
  442. out={vrs[x]:data[x][-1] for x in vrs}
  443. addVrs=['lut','lutSE']
  444. for x in addVrs:
  445. out[x]=data[x]
  446. return out
  447. def getStartPoint(setup):
  448. if setup['startFromRef']=='None':
  449. return {'t0':0,'y0':numpy.array([]),'s0':numpy.array([]),'lut':[],'lutSE':[]}
  450. data=loadSolutionFromRef(setup)
  451. return startPointObject(data)
  452. def storeStartPointObject(fname,obj):
  453. numpy.savez(fname,startobj=obj)
  454. def loadStartPointObject(fname):
  455. return numpy.load(fname,allow_pickle=True)['startobj'].item()
  456. if __name__=="__main__":
  457. parFiles=sys.argv[1].split(';')
  458. jobDir=sys.argv[2]
  459. main(parFiles,jobDir)