cModel.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. import numpy
  2. import json
  3. import os
  4. import scipy.interpolate
  5. #for partial function specializations
  6. import functools
  7. import function
  8. import importlib
  9. importlib.reload(function)
  10. class model:
  11. def __init__(self):
  12. self.compartments={}
  13. self.seJ={}
  14. self.scaled=[]
  15. def add_input(self,compartmentName,parameterName):
  16. self.compartments[compartmentName]['input']=parameterName
  17. def add_compartment(self,compartmentName):
  18. self.compartments[compartmentName]={}
  19. self.compartments[compartmentName]['targets']={}
  20. self.compartments[compartmentName]['sensTargets']={}
  21. def getTimeUnit(self):
  22. try:
  23. return self.mod['timeUnit']
  24. except KeyError:
  25. return 's'
  26. def getParameters(self):
  27. """Returns the model parameters."""
  28. return self.parSetup['parameters']
  29. def updateParameters(self, param_updates, jsonFile, newFile):
  30. """
  31. Updates the parameters directly within the pars structure and writes them to a new file,
  32. keeping the original structure intact.
  33. Parameters:
  34. param_updates (dict): Dictionary where keys are parameter names and values are new parameter values.
  35. jsonFile (str): Path to the original JSON file (for structure reference).
  36. newFile (str): Path to the new file where updated parameters will be saved.
  37. """
  38. # Update the parameters
  39. pars = self.parSetup['parameters']
  40. for parName, newValue in param_updates.items():
  41. if parName in pars:
  42. if "value" in pars[parName]:
  43. pars[parName]["value"] = newValue
  44. print(f"Parameter '{parName}' updated to {newValue}.")
  45. else:
  46. print(f"Parameter '{parName}' does not have a 'value' field.")
  47. else:
  48. print(f"Parameter '{parName}' not found in the model. Adding it.")
  49. pars[parName] = {"value": newValue}
  50. # Read the original file to get the structure
  51. with open(jsonFile, 'r') as file:
  52. data = json.load(file)
  53. # Merge updated parameters into the original data structure
  54. data['parameters'] = self.parSetup['parameters']
  55. # Save the updated data to a new file (not overwriting the original file)
  56. with open(newFile, 'w') as file:
  57. json.dump(data, file, indent=4)
  58. # Get relative path from the 'rezultati' directory onwards
  59. relative_path = os.path.relpath(newFile, start='/home/jakob/Documents/Sola/IJS/PBPK_ociscen')
  60. print(f"Parameters have been written to the new file: {relative_path}")
  61. def bind(self,src,target,qName,pcName):
  62. #establish a flow from source compartment to the target
  63. #the source equation (where we subtract the current)
  64. #in fact, this is the diagonal element
  65. #get volume names
  66. srcVName=self.getVolumePar(src)
  67. #generate coupling object (w/derivatives)
  68. pSrc=self.couplingObject(-1,qName,pcName,srcVName)
  69. #this includes derivatives and value!
  70. self.addValueObject(src,src,pSrc)
  71. #special target which is not part of calculation
  72. if target=='dump':
  73. return
  74. #the target equation (where we add the current)
  75. #get volume names
  76. targetVName=self.getVolumePar(target)
  77. #generate coupling object
  78. pTarget=self.couplingObject(1,qName,pcName,targetVName)
  79. #equation is for target compartment, but binding for source
  80. self.addValueObject(target,src,pTarget)
  81. def addValueObject(self,targetName,srcName,cObject):
  82. #always binds equation id and a variable
  83. targetList=self.compartments[targetName]['targets']
  84. addValue(targetList,srcName,cObject["value"])
  85. der=cObject["derivatives"]
  86. for d in der:
  87. targetSE=self.getSEJ_comp(d,targetName)
  88. addValue(targetSE,srcName,der[d])
  89. def couplingObject(self,sign, qParName, pcParName, vParName):
  90. qPar=self.get(qParName)
  91. pcPar=self.get(pcParName)
  92. vPar=self.get(vParName)
  93. q=qPar['value']
  94. pc=pcPar['value']
  95. v=vPar['value']
  96. DPC=pcPar['derivatives']
  97. DQ=qPar['derivatives']
  98. DV=vPar['derivatives']
  99. if any(['function' in qPar,'function' in pcPar, 'function' in vPar]):
  100. fq=function.to(q)
  101. fpc=function.to(pc)
  102. fv=function.to(v)
  103. f=lambda t,q=fq,pc=fpc,v=fv,s=sign:s*q(t)/v(t)/pc(t)
  104. dfdPC=lambda t,f=f,pc=fpc:-f(t)/pc(t)
  105. dPC=function.generate(dfdPC,DPC)
  106. dfdQ=lambda t,f=f,q=fq: f(t)/q(t)
  107. dQ=function.generate(dfdQ,DQ)
  108. dfdV=lambda t,f=f,v=fv: -f(t)/v(t)
  109. dV=function.generate(dfdV,DV)
  110. return function.Object(f,[dPC,dQ,dV])
  111. else:
  112. f=sign*q/v/pc
  113. #print('Adding: {}->{} [{}] {}*{}/{}/{}: {}'.format(qParName,vParName,pcParName,sign,q,v,pc,f))
  114. return function.derivedObject(sign*q/v/pc,\
  115. [{'df':-f/pc,'D':DPC},\
  116. {'df':sign/v/pc,'D':DQ},\
  117. {'df':-f/v,'D':DV}])
  118. #derivatives is the combination of the above
  119. def getVolumePar(self,compartment):
  120. #returnis volume name, if found and useVolume is directed,
  121. #or a standard parameter one
  122. try:
  123. return self.mod["volumes"][compartment]
  124. #parV=self.mod["parameters"][parVName]
  125. except KeyError:
  126. pass
  127. return "one"
  128. def build(self):
  129. comps=self.compartments
  130. self.n=len(comps)
  131. #numeric representation of the input
  132. self.fu=numpy.zeros((self.n))
  133. #dictionary that holds potential input function objects
  134. self.du={}
  135. self.lut={c:i for (i,c) in zip(range(self.n),comps.keys())}
  136. self.dM={}
  137. self.fM=numpy.zeros((self.n,self.n))
  138. self.inputDerivatives={}
  139. self.uTotal=[]
  140. for c in comps:
  141. comp=comps[c]
  142. if 'input' in comp:
  143. qs=self.get(comp['input'])
  144. self.uTotal.append(qs["value"])
  145. qV=self.getVolumePar(c)
  146. #input is a quotient (amount of exogen per unit time per volume(mass) of input compartment)
  147. qs1=function.ratio(qs,self.get(qV))
  148. if function.isFunction(qs1):
  149. self.du[self.lut[c]]=qs1
  150. else:
  151. self.fu[self.lut[c]]=qs1['value']
  152. #let buildSE know we have to include this derivatives
  153. self.inputDerivatives[c]=qs1['derivatives']
  154. for t in comp['targets']:
  155. arr=comp['targets'][t]
  156. if function.contains(arr):
  157. try:
  158. self.dM[self.lut[c]][self.lut[t]]=\
  159. function.sumArray(arr)
  160. except (KeyError,TypeError):
  161. self.dM[self.lut[c]]={}
  162. self.dM[self.lut[c]][self.lut[t]]=\
  163. function.sumArray(arr)
  164. else:
  165. #just set once
  166. self.fM[self.lut[c],self.lut[t]]=sum(arr)
  167. #print('Setting: {}->{}: {}'.format(c,t,self.fM[self.lut[c],self.lut[t]]))
  168. #generate total from self.uTotal
  169. #ignore derivatives; uTotal is just a scaling shorthand
  170. if function.contains(self.uTotal):
  171. self.du[self.lut['total']]=function.Object(function.sumArray(self.uTotal),[])
  172. else:
  173. self.fu[self.lut['total']]=sum(self.uTotal)
  174. #build SE part
  175. self.buildSE()
  176. def buildSE(self):
  177. #check which parameterst to include
  178. parList=[]
  179. pars=self.parSetup['parameters']
  180. #add derivatives to jacobi terms
  181. parCandidates=list(self.seJ.keys())
  182. #add derivatives of input terms
  183. for x in self.inputDerivatives:
  184. D=self.inputDerivatives[x]
  185. parCandidates.extend(list(D.keys()))
  186. for x in self.du:
  187. D=self.du[x]['derivatives']
  188. parCandidates.extend(list(D.keys()))
  189. #get rid of duplicates
  190. parCandidates=list(set(parCandidates))
  191. for parName in parCandidates:
  192. #print(par)
  193. par=pars[parName]
  194. usePar=calculateDerivative(par)
  195. #print('[{}]: {}'.format(usePar,par))
  196. if not usePar:
  197. continue
  198. parList.append(parName)
  199. #print(parList)
  200. self.m=len(parList)
  201. self.lutSE={c:i for (i,c) in zip(range(self.m),parList)}
  202. w=self.getWeights(self.lutSE)
  203. w=numpy.sqrt(w)
  204. self.qSS={}
  205. self.SS=numpy.zeros((self.m,self.n,self.n))
  206. #elements of SS will be w_p*dM_i,j/dp
  207. for parName in parList:
  208. try:
  209. sources=self.seJ[parName]
  210. except KeyError:
  211. continue
  212. for compartment in sources:
  213. targets=sources[compartment]
  214. for t in targets:
  215. k=self.lutSE[parName]
  216. i=self.lut[compartment]
  217. j=self.lut[t]
  218. #print('[{} {} {}] {}'.format(parName,compartment,t,targets[t]))
  219. arr=targets[t]
  220. if not function.contains(arr):
  221. self.SS[k,i,j]=w[k]*sum(arr)
  222. continue
  223. ft=function.sumArray(arr,w[k])
  224. try:
  225. self.qSS[k][i][j]=ft
  226. except (KeyError,TypeError):
  227. try:
  228. self.qSS[k][i]={}
  229. self.qSS[k][i][j]=ft
  230. except (KeyError,TypeError):
  231. self.qSS[k]={}
  232. self.qSS[k][i]={}
  233. self.qSS[k][i][j]=ft
  234. #derivatives of inputs
  235. #time dependent derivatives are handled in self.Su(t)
  236. self.fSu=numpy.zeros((self.m,self.n))
  237. for x in self.inputDerivatives:
  238. D=self.inputDerivatives[x]
  239. for p in D:
  240. if p in parList:
  241. k=self.lutSE[p]
  242. self.fSu[self.lutSE[p],self.lut[x]]=D[p]*w[k]
  243. #use fM to build static part of fJ
  244. N=self.n*(self.m+1)
  245. self.fJ=numpy.zeros((N,N))
  246. for i in range(self.m+1):
  247. self.fJ[i*self.n:(i+1)*self.n,i*self.n:(i+1)*self.n]=self.fM
  248. def inspect(self):
  249. comps=self.compartments
  250. pars=self.parSetup['parameters']
  251. #pars=self.mod['parameters']
  252. tu=self.getTimeUnit()
  253. print('Time unit: {}'.format(tu))
  254. print('Compartments')
  255. for c in comps:
  256. print('{}/{}:'.format(c,self.lut[c]))
  257. comp=comps[c]
  258. if 'input' in comp:
  259. print('\tinput\n\t\t{}'.format(comp['input']))
  260. print('\ttargets')
  261. for t in comp['targets']:
  262. print('\t\t{}[{},{}]: {}'.format(t,self.lut[c],self.lut[t],\
  263. comp['targets'][t]))
  264. print('Flows')
  265. for f in self.flows:
  266. fName=self.flows[f]
  267. fParName=self.mod['flows'][fName]
  268. fPar=pars[fParName]
  269. print('\t{}[{}]:{} [{}]'.format(f,fName,fParName,self.get(fParName)))
  270. print('Volumes')
  271. for v in self.mod['volumes']:
  272. vParName=self.mod['volumes'][v]
  273. vPar=pars[vParName]
  274. print('\t{}:{} [{}]'.format(v,vParName,self.get(vParName)))
  275. print('Partition coefficients')
  276. for pc in self.mod['partitionCoefficients']:
  277. pcParName=self.mod['partitionCoefficients'][pc]
  278. pcPar=pars[pcParName]
  279. print('\t{}:{} [{}]'.format(pc,pcParName,self.get(pcParName)))
  280. def inspectSE(self):
  281. print('SE parameters')
  282. for p in self.seJ:
  283. print(p)
  284. sources=self.seJ[p]
  285. for compartment in sources:
  286. targets=sources[compartment]
  287. for t in targets:
  288. print('\t SE bind {}/{}:{}'.format(compartment,t,targets[t]))
  289. def parse(self,setupFile,parameterFile):
  290. with open(setupFile,'r') as f:
  291. self.mod=json.load(f)
  292. with open(parameterFile,'r') as f:
  293. self.parSetup=json.load(f)
  294. self.mod['compartments'].append('total')
  295. for m in self.mod['compartments']:
  296. self.add_compartment(m)
  297. for m in self.mod['scaled']:
  298. self.scaled.append(m)
  299. self.add_default_parameters()
  300. self.applyValues()
  301. def clearCompartments(self):
  302. for c in self.compartments:
  303. for t in self.compartments[c]:
  304. self.compartments[c][t]={}
  305. #clear SE compartments
  306. for parName in self.seJ:
  307. for c in self.seJ[parName]:
  308. self.seJ[parName][c]={}
  309. def applyValues(self):
  310. self.clearCompartments()
  311. #print(self.compartments)
  312. #standard parameters such as one,zero etc.
  313. for s in self.mod['inputs']:
  314. #src=mod['inputs'][s]
  315. self.add_input(s,self.mod['inputs'][s])
  316. self.flows={}
  317. #pars=self.mod['parameters']
  318. pars=self.parSetup['parameters']
  319. for f in self.mod['flows']:
  320. #skip comments
  321. if f.find(':')<0:
  322. continue
  323. comps=f.split(':')
  324. c0=splitVector(comps[0])
  325. c1=splitVector(comps[1])
  326. for x in c0:
  327. for y in c1:
  328. pairName='{}:{}'.format(x,y)
  329. self.flows[pairName]=f
  330. for b in self.mod['bindings']['diffusion']:
  331. #whether to scale transfer constants to organ volume
  332. #default is true, but changing here will assume no scaling
  333. comps=b.split('->')
  334. try:
  335. pcParName=self.mod['partitionCoefficients'][b]
  336. except KeyError:
  337. pcParName="one"
  338. kParName=self.mod['bindings']['diffusion'][b]
  339. #operate with names to allow for value/function/derived infrastructure
  340. self.bind(comps[0],comps[1],kParName,pcParName)
  341. for q in self.mod['bindings']['flow']:
  342. comps=q.split('->')
  343. srcs=splitVector(comps[0])
  344. tgts=splitVector(comps[1])
  345. for cs in srcs:
  346. for ct in tgts:
  347. #get partition coefficient
  348. try:
  349. pcParName=self.mod['partitionCoefficients'][cs]
  350. except KeyError:
  351. pcParName="one"
  352. #get flow (direction could be reversed)
  353. try:
  354. qName=self.flows['{}:{}'.format(cs,ct)]
  355. except KeyError:
  356. qName=self.flows['{}:{}'.format(ct,cs)]
  357. flowParName=self.mod['flows'][qName]
  358. #flowPar=pars[flowParName]
  359. self.bind(cs,ct,flowParName,pcParName)
  360. self.build()
  361. def add_default_parameters(self):
  362. pars=self.parSetup['parameters']
  363. pars['one']={'value':1}
  364. pars['zero']={'value':0}
  365. pars['two']={'value':2}
  366. def M(self,t,y=numpy.array([])):
  367. for i in self.dM:
  368. for j in self.dM[i]:
  369. self.fM[i,j]=self.dM[i][j](t)
  370. #create an array and fill it with outputs of function at t
  371. if (y.size==0):
  372. return self.fM
  373. self.set_scaledM(t,y)
  374. return self.fM
  375. def set_scaledM(self,t,y):
  376. #prevent zero division
  377. eps=1e-8
  378. for c in self.scaled:
  379. i=self.lut[c]
  380. it=self.lut['total']
  381. try:
  382. k=numpy.copy(self.originalK[i])
  383. except AttributeError:
  384. k=numpy.copy(self.fM[i,:])
  385. self.originalK={}
  386. self.originalK[i]=k
  387. #make another copy
  388. k=numpy.copy(self.originalK[i])
  389. except KeyError:
  390. k=numpy.copy(self.fM[i,:])
  391. self.originalK[i]=k
  392. #make another copy
  393. k=numpy.copy(self.originalK[i])
  394. k[i]=k[i]-self.u(t)[it]
  395. #scale all inputs by total input mass
  396. for j in range(self.n):
  397. self.fM[i,j]=k[j]/(y[it]+eps)
  398. def u(self,t):
  399. for x in self.du:
  400. self.fu[x]=self.du[x]['value'](t)
  401. #this should be done previously
  402. return self.fu
  403. def Su(self,t):
  404. w=self.getWeights(self.lutSE)
  405. w=numpy.sqrt(w)
  406. #add time dependent values
  407. for x in self.du:
  408. D=self.du[x]['derivatives']
  409. for p in D:
  410. k=self.lutSE[p]
  411. #print(f'[{p}]: {k}')
  412. self.fSu[k,x]=w[k]*D[p](t)
  413. return self.fSu
  414. def jacobiFull(self,t):
  415. #update jacobi created during build phase with time dependent values
  416. for i in self.dM:
  417. for j in self.dM[i]:
  418. for k in range(self.m+1):
  419. self.fJ[k*self.n+i,k*self.n+j]=self.dM[i][j](t)
  420. return self.fJ
  421. def fSS(self,t,y=numpy.array([])):
  422. for k in self.qSS:
  423. for i in self.qSS[k]:
  424. for j in self.qSS[k][i]:
  425. #print('[{},{},{}] {}'.format(k,i,j,self.qSS[k][i][j]))
  426. self.SS[k,i,j]=(self.qSS[k][i][j])(t)
  427. if y.size==0:
  428. return self.SS
  429. self.set_scaledSS(t,y)
  430. return self.SS
  431. def set_scaledSS(self,t,y):
  432. #prevent zero division
  433. eps=1e-8
  434. for c in self.scaled:
  435. it=self.lut['total']
  436. i=self.lut[c]
  437. try:
  438. dkdp=numpy.copy(self.originalSS[i])
  439. except AttributeError:
  440. dkdp=numpy.copy(self.SS[:,i,:])
  441. self.originalSS={}
  442. self.originalSS[i]=dkdp
  443. dkdp=numpy.copy(self.originalSS[i])
  444. except KeyError:
  445. dkdp=numpy.copy(self.SS[:,i,:])
  446. self.originalSS[i]=dkdp
  447. dkdp=numpy.copy(self.originalSS[i])
  448. self.SS[:,i,:]=dkdp/(y[it]+eps)
  449. #should add error on u!
  450. def fSY(self,y,t):
  451. #M number of sensitivity parameters
  452. #N number of equations
  453. #fSS is MxNxN
  454. #assume a tabulated solution y(t) at t spaced intervals
  455. qS=self.fSS(t,y).dot(y)
  456. #qS is MxN
  457. #but NxM is expected, so do a transpose
  458. #for simultaneous calculation, a Nx(M+1) matrix is expected
  459. tS=numpy.zeros((self.n,self.m+1))
  460. #columns from 2..M+1 are the partial derivatives
  461. tS[:,1:]=numpy.transpose(qS)
  462. #first column is the original function
  463. tS[:,0]=self.u(t)
  464. return tS
  465. def fS(self,t):
  466. #M number of sensitivity parameters
  467. #N number of equations
  468. #fSS is MxNxN
  469. #assume a tabulated solution y(t) at t spaced intervals
  470. qS=self.fSS(t).dot(self.getY(t))
  471. return numpy.transpose(qS)
  472. def getSEJ(self,parName):
  473. #find the sensitivity (SE) derivative of Jacobi with
  474. #respect to parameter
  475. try:
  476. return self.seJ[parName]
  477. except KeyError:
  478. self.seJ[parName]={}
  479. return self.seJ[parName]
  480. def getSEJ_comp(self,parName,compartmentName):
  481. #find equation dictating concentration in compartmentName
  482. #for jacobi-parameter derivative
  483. seJ=self.getSEJ(parName)
  484. try:
  485. return seJ[compartmentName]
  486. except KeyError:
  487. seJ[compartmentName]={}
  488. return seJ[compartmentName]
  489. def setY(self,t,y):
  490. self.tck=[None]*self.n
  491. for i in range(self.n):
  492. self.tck[i] = scipy.interpolate.splrep(t, y[:,i], s=0)
  493. def getY(self,t):
  494. fY=numpy.zeros(self.n)
  495. for i in range(self.n):
  496. fY[i]=scipy.interpolate.splev(t, self.tck[i], der=0)
  497. return fY
  498. def getWeight(self,parName):
  499. pars=self.parSetup['parameters']
  500. par=pars[parName]
  501. #self.get parses the units
  502. v=self.get(parName)["value"]
  503. #if par['dist']=='lognormal':
  504. #this is sigma^2_lnx
  505. #sln2=numpy.log(par["cv"]*par["cv"]+1)
  506. #have to multiplied by value to get the derivative
  507. #with respect to lnx
  508. #return sln2*v*v
  509. #else:
  510. #for Gaussian, cv is sigma/value; get sigma by value multiplication
  511. try:
  512. return par["cv"]*par["cv"]*v*v
  513. except KeyError:
  514. return 0
  515. def getMax(lutSE):
  516. fm=-1
  517. for x in lutSE:
  518. if int(lutSE[x])>fm:
  519. fm=lutSE[x]
  520. return fm
  521. def getWeights(self,lutSE):
  522. #pars=self.parSetup['parameters']
  523. wts=numpy.zeros((model.getMax(lutSE)+1))
  524. for parName in lutSE:
  525. j=lutSE[parName]
  526. wts[j]=self.getWeight(parName)
  527. return wts
  528. def getVolumes(self):
  529. m=numpy.zeros((len(self.lut)))
  530. for p in self.lut:
  531. m[self.lut[p]]=self.getVolume(p)
  532. return m
  533. def getVolume(self,p):
  534. pV=self.getVolumePar(p)
  535. return self.get(pV)['value']
  536. def getDerivatives(self,se,i):
  537. #return latest point derivatives
  538. fse=se[-1][i]
  539. #fse is an m-vector
  540. return fse*fse
  541. def calculateUncertainty(self,s):
  542. #s2out=numpy.zeros(s1.shape[1:])
  543. s2=numpy.multiply(s,s)
  544. #w=self.getWeights(self.lutSE)
  545. w=numpy.ones((self.m))
  546. return numpy.sqrt(numpy.dot(s2,w))
  547. def getParameter(self,parName):
  548. #return parameter (this actually returns a reference, so changing it changes the value of internal parameter)
  549. try:
  550. return self.parSetup['parameters'][parName]
  551. except:
  552. print(f'Failed to find parameter {parName}')
  553. return None
  554. def setParameter(self,parName,parObj):
  555. #replace parameter with a new object
  556. #typically change distribution or cv along with the value
  557. self.parSetup['parameters'][parName]=parObj
  558. def setValue(self, parName, parValue):
  559. #change a single parameter parName to value parValue
  560. #should run applyValues after all values are set
  561. pars=self.parSetup['parameters']
  562. try:
  563. par=pars[parName]
  564. except:
  565. print(f'Failed to find parameter {parName}')
  566. return False
  567. try:
  568. par['value']=parValue
  569. except KeyError:
  570. print(f'Failed to set value for {parName}')
  571. return False
  572. return True
  573. def setValues(self,parNames,parValues):
  574. #change a set of parameters in list parNames to
  575. #accordingly ordered set of values parValues
  576. #also recalculates the matrix
  577. for p,v in zip(parNames,parValues):
  578. self.setValue(p,v)
  579. self.applyValues()
  580. def get(self,parName):
  581. pars=self.parSetup['parameters']
  582. par=pars[parName]
  583. par['name']=parName
  584. if "value" in par:
  585. return self.getValue(par)
  586. if "function" in par:
  587. return self.getFunction(par)
  588. if "derived" in par:
  589. return self.getDerived(par)
  590. print('Paramter {} not found!'.format(parName))
  591. def getValue(self,par):
  592. v=par["value"]
  593. parName=par['name']
  594. #convert to seconds
  595. try:
  596. parUnits=par['unit'].split('/')
  597. except (KeyError,IndexError):
  598. #no unit given
  599. return valueObject(v,parName)
  600. timeUnit=self.getTimeUnit()
  601. try:
  602. if parUnits[1]==timeUnit:
  603. return valueObject(v,parName)
  604. except IndexError:
  605. #no / in unit name
  606. return valueObject(v,parName)
  607. if parUnits[1]=='min' and timeUnit=='s':
  608. return valueObject(v/60,parName)
  609. if parUnits[1]=='s' and timeUnit=='min':
  610. return valueObject(60*v,parName)
  611. if parUnits[1]=='day' and timeUnit=='min':
  612. return valueObject(v/24/60,parName)
  613. if parUnits[1]=='hour' and timeUnit=='min':
  614. return valueObject(v/60,parName)
  615. #no idea what to do
  616. return valueObject(v,parName)
  617. def getFunction(self,par):
  618. fName=par['function']
  619. #print('[{}]: getFunction({})'.format(par['name'],par['function']))
  620. df=self.parSetup['functions'][fName]
  621. skip=['type']
  622. par1={x:self.get(df[x]) for x in df if x not in skip}
  623. if df['type']=='linearGrowth':
  624. #print(par1)
  625. return function.linearGrowth(par1)
  626. if df['type']=='linearGrowthFixedSlope':
  627. return function.linearGrowthFixedSlope(par1)
  628. if df['type']=='exp':
  629. return function.exp(par1)
  630. print('Function {}/{} not found!'.format(fName,df))
  631. def getDerived(self,par):
  632. dName=par['derived']
  633. d=self.parSetup['derivedParameters'][dName]
  634. #print('Derived [{}]: type {}'.format(dName,d['type']))
  635. if d['type']=='product':
  636. return function.product(self.get(d['a']),self.get(d['b']))
  637. if d['type']=='power':
  638. return function.power(self.get(d['a']),self.get(d['n']))
  639. if d['type']=='ratio':
  640. return function.ratio(pA=self.get(d['a']),pB=self.get(d['b']))
  641. if d['type']=='sum':
  642. return function.add(pA=self.get(d['a']),pB=self.get(d['b']))
  643. def calculateDerivative(par):
  644. #add derivatives if dist(short for distribution) is specified
  645. return "dist" in par
  646. def valueObject(v,parName):
  647. #convert everything to functions
  648. d0={parName:1}
  649. return {'value':v,'derivatives':{parName:1}}
  650. def splitVector(v):
  651. if v.find('(')<0:
  652. return [v]
  653. return v[1:-1].split(',')
  654. def addValue(qdict,compName,v):
  655. #add function to compName of dictionary qdict,
  656. #check if compName exists and handle the potential error
  657. #lambda functions can't be summed directly, so qdict is a list
  658. #that will be merged at matrix generation time
  659. try:
  660. qdict[compName].append(v)
  661. except KeyError:
  662. qdict[compName]=[v]
  663. #also add derivatives
  664. #
  665. # for d in dTarget:
  666. # ctarget=self.getSEJ_comp(d,target)
  667. # addValue(ctarget,target,dTarget[d])
  668. def get(timeUnit,par):
  669. v=par["value"]
  670. #convert to seconds
  671. try:
  672. parUnits=par['unit'].split('/')
  673. except (KeyError,IndexError):
  674. #no unit given
  675. return v
  676. try:
  677. if parUnits[1]==timeUnit:
  678. return v
  679. except IndexError:
  680. #no / in unit name
  681. return v
  682. if parUnits[1]=='min' and timeUnit=='s':
  683. return v/60
  684. if parUnits[1]=='s' and timeUnit=='min':
  685. return 60*v
  686. #no idea what to do
  687. return v