cModel.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. import numpy
  2. import json
  3. import os
  4. import scipy.interpolate
  5. #for partial function specializations
  6. import functools
  7. class model:
  8. def __init__(self):
  9. self.compartments={}
  10. self.seJ={}
  11. def add_source(self,compartmentName,formula):
  12. self.compartments[compartmentName]['source']=formula
  13. def add_compartment(self,compartmentName):
  14. self.compartments[compartmentName]={}
  15. self.compartments[compartmentName]['targets']={}
  16. self.compartments[compartmentName]['sensTargets']={}
  17. def getTimeUnit(self):
  18. try:
  19. return self.mod['timeUnit']
  20. except KeyError:
  21. return 's'
  22. def bind(self,src,target,qName,pcName,useVolume=0):
  23. #establish a flow from source compartment to the target
  24. #get volume names
  25. srcVName=self.getVolumePar(src,useVolume)
  26. targetVName=self.getVolumePar(target,useVolume)
  27. #the source equation (where we subtract the current)
  28. #in fact, this is the diagonal element
  29. pSrc=self.couplingObject(-1,qName,pcName,srcVName)
  30. #this includes derivatives and value!
  31. self.addValueObject(src,src,pSrc)
  32. #the target equation (where we add the current)
  33. pTarget=self.couplingObject(1,qName,pcName,targetVName)
  34. #equation is for target compartment, but binding for source
  35. self.addValueObject(target,src,pTarget)
  36. def addValueObject(self,targetName,srcName,cObject):
  37. #always binds equation id and a variable
  38. targetList=self.compartments[targetName]['targets']
  39. addValue(targetList,srcName,cObject["value"])
  40. der=cObject["derivatives"]
  41. for d in der:
  42. targetSE=self.getSEJ_comp(d,targetName)
  43. addValue(targetSE,srcName,der[d])
  44. def couplingObject(self,sign, qParName, pcParName, vParName):
  45. qPar=self.get(qParName)
  46. pcPar=self.get(pcParName)
  47. vPar=self.get(vParName)
  48. q=qPar['value']
  49. pc=pcPar['value']
  50. v=vPar['value']
  51. f=lambda t,q=q,pc=pc,v=v,s=sign:s*q(t)/v(t)/pc(t)
  52. DPC=pcPar['derivatives']
  53. dfdPC=lambda t,f=f,pc=pc:-f(t)/pc(t)
  54. dPC={x:lambda t,df=dfdPC,D=DPC,x=x: df(t)*D[x](t) for x in DPC}
  55. DQ=qPar['derivatives']
  56. dfdQ=lambda t,f=f,q=q: f(t)/q(t)
  57. dQ={x:lambda t,df=dfdQ,D=DQ,x=x: df(t)*D[x](t) for x in DQ}
  58. DV=vPar['derivatives']
  59. dfdV=lambda t,f=f,v=v: -f(t)/v(t)
  60. dV={x:lambda t,df=dfdV,D=DV,x=x: df(t)*D[x](t) for x in DV}
  61. #derivatives is the combination of the above
  62. return derivedObject(f,[DPC,DQ,DV])
  63. def getVolumePar(self,compartment,useVolume=1):
  64. #returnis volume name, if found and useVolume is directed,
  65. #or a standard parameter one
  66. if not useVolume:
  67. return "one"
  68. try:
  69. return self.mod["volumes"][compartment]
  70. #parV=self.mod["parameters"][parVName]
  71. except KeyError:
  72. pass
  73. return "one"
  74. def build(self):
  75. comps=self.compartments
  76. self.n=len(comps)
  77. self.fu=[lambda t:0]*self.n
  78. self.lut={c:i for (i,c) in zip(range(self.n),comps.keys())}
  79. self.fM={}
  80. for c in comps:
  81. comp=comps[c]
  82. if 'source' in comp:
  83. self.fu[self.lut[c]]=parseFunction(comp['source'])
  84. for t in comp['targets']:
  85. try:
  86. self.fM[self.lut[c]][self.lut[t]]=\
  87. sumFunctionArray(comp['targets'][t])
  88. except (KeyError,TypeError):
  89. self.fM[self.lut[c]]={}
  90. self.fM[self.lut[c]][self.lut[t]]=\
  91. sumFunctionArray(comp['targets'][t])
  92. #build SE part
  93. self.buildSE()
  94. def buildSE(self):
  95. #check which parameterst to include
  96. parList=[]
  97. for parName in self.seJ:
  98. #print(par)
  99. par=self.mod['parameters'][parName]
  100. usePar=calculateDerivative(par)
  101. #print('[{}]: {}'.format(usePar,par))
  102. if not usePar:
  103. continue
  104. parList.append(parName)
  105. #print(parList)
  106. self.m=len(parList)
  107. self.lutSE={c:i for (i,c) in zip(range(self.m),parList)}
  108. self.qSS={}
  109. for parName in parList:
  110. sources=self.seJ[parName]
  111. for compartment in sources:
  112. targets=sources[compartment]
  113. for t in targets:
  114. k=self.lutSE[parName]
  115. i=self.lut[compartment]
  116. j=self.lut[t]
  117. #print('[{} {} {}] {}'.format(parName,compartment,t,targets[t]))
  118. ft=sumFunctionArray(targets[t])
  119. try:
  120. self.qSS[k][i][j]=ft
  121. except (KeyError,TypeError):
  122. try:
  123. self.qSS[k][i]={}
  124. self.qSS[k][i][j]=ft
  125. except (KeyError,TypeError):
  126. self.qSS[k]={}
  127. self.qSS[k][i]={}
  128. self.qSS[k][i][j]=ft
  129. def inspect(self):
  130. comps=self.compartments
  131. pars=self.mod['parameters']
  132. tu=self.getTimeUnit()
  133. print('Time unit: {}'.format(tu))
  134. print('Compartments')
  135. for c in comps:
  136. print('{}/{}:'.format(c,self.lut[c]))
  137. comp=comps[c]
  138. if 'source' in comp:
  139. print('\tsource\n\t\t{}'.format(comp['source']))
  140. print('\ttargets')
  141. for t in comp['targets']:
  142. print('\t\t{}[{},{}]: {}'.format(t,self.lut[c],self.lut[t],\
  143. comp['targets'][t]))
  144. print('Flows')
  145. for f in self.flows:
  146. fName=self.flows[f]
  147. fParName=self.mod['flows'][fName]
  148. fPar=pars[fParName]
  149. print('\t{}[{}]:{} [{}]'.format(f,fName,fParName,self.get(fParName)))
  150. print('Volumes')
  151. for v in self.mod['volumes']:
  152. vParName=self.mod['volumes'][v]
  153. vPar=pars[vParName]
  154. print('\t{}:{} [{}]'.format(v,vParName,self.get(vParName)))
  155. print('Partition coefficients')
  156. for pc in self.mod['partitionCoefficients']:
  157. pcParName=self.mod['partitionCoefficients'][pc]
  158. pcPar=pars[pcParName]
  159. print('\t{}:{} [{}]'.format(pc,pcParName,self.get(pcParName)))
  160. print('SE parameters')
  161. for p in self.seJ:
  162. print(p)
  163. sources=self.seJ[p]
  164. for compartment in sources:
  165. targets=sources[compartment]
  166. for t in targets:
  167. print('\t SE bind {}/{}:{}'.format(compartment,t,targets[t]))
  168. def parse(self,file):
  169. with open(file,'r') as f:
  170. self.mod=json.load(f)
  171. for m in self.mod['compartments']:
  172. self.add_compartment(m)
  173. self.add_default_parameters()
  174. #standard parameters such as one,zero etc.
  175. for s in self.mod['sources']:
  176. #src=mod['sources'][s]
  177. self.add_source(s,self.mod['sources'][s])
  178. self.flows={}
  179. pars=self.mod['parameters']
  180. for f in self.mod['flows']:
  181. #skip comments
  182. if f.find(':')<0:
  183. continue
  184. comps=f.split(':')
  185. c0=splitVector(comps[0])
  186. c1=splitVector(comps[1])
  187. for x in c0:
  188. for y in c1:
  189. pairName='{}:{}'.format(x,y)
  190. self.flows[pairName]=f
  191. for b in self.mod['bindings']['diffusion']:
  192. #whether to scale transfer constants to organ volume
  193. #default is true, but changing here will assume no scaling
  194. useVolume=1
  195. comps=b.split('->')
  196. try:
  197. pcParName=self.mod['partitionCoefficients'][b]
  198. except KeyError:
  199. pcParName="one"
  200. kParName=self.mod['bindings']['diffusion'][b]
  201. #operate with names to allow for value/function/derived infrastructure
  202. self.bind(comps[0],comps[1],kParName,pcParName,useVolume)
  203. for q in self.mod['bindings']['flow']:
  204. comps=q.split('->')
  205. srcs=splitVector(comps[0])
  206. tgts=splitVector(comps[1])
  207. for cs in srcs:
  208. for ct in tgts:
  209. #get partition coefficient
  210. try:
  211. pcParName=self.mod['partitionCoefficients'][cs]
  212. except KeyError:
  213. pcParName="one"
  214. #get flow (direction could be reversed)
  215. try:
  216. qName=self.flows['{}:{}'.format(cs,ct)]
  217. except KeyError:
  218. qName=self.flows['{}:{}'.format(ct,cs)]
  219. flowParName=self.mod['flows'][qName]
  220. #flowPar=pars[flowParName]
  221. self.bind(cs,ct,flowParName,pcParName,1)
  222. self.build()
  223. def add_default_parameters(self):
  224. self.mod['parameters']['one']={'value':1}
  225. self.mod['parameters']['zero']={'value':0}
  226. def M(self,t):
  227. Mb=numpy.zeros((self.n,self.n))
  228. for i in self.fM:
  229. for j in self.fM[i]:
  230. Mb[i,j]=self.fM[i][j](t)
  231. #create an array and fill it with outputs of function at t
  232. return Mb
  233. def u(self,t):
  234. ub=[f(t) for f in self.fu]
  235. return numpy.array(ub)
  236. def fSS(self,t):
  237. fSS=numpy.zeros((self.m,self.n,self.n))
  238. for k in self.qSS:
  239. for i in self.qSS[k]:
  240. for j in self.qSS[k][i]:
  241. #print('[{},{},{}] {}'.format(k,i,j,self.qSS[k][i][j]))
  242. fSS[k,i,j]=(self.qSS[k][i][j])(t)
  243. return fSS
  244. def fSY(self,y,t):
  245. #M number of sensitivity parameters
  246. #N number of equations
  247. #fSS is MxNxN
  248. #assume a tabulated solution y(t) at t spaced intervals
  249. qS=self.fSS(t).dot(y)
  250. #qS is MxN
  251. #but NxM is expected, so do a transpose
  252. #for simultaneous calculation, a Nx(M+1) matrix is expected
  253. tS=numpy.zeros((self.n,self.m+1))
  254. #columns from 2..M+1 are the partial derivatives
  255. tS[:,1:]=numpy.transpose(qS)
  256. #first column is the original function
  257. tS[:,0]=self.u(t)
  258. return tS
  259. def fS(self,t):
  260. #M number of sensitivity parameters
  261. #N number of equations
  262. #fSS is MxNxN
  263. #assume a tabulated solution y(t) at t spaced intervals
  264. qS=self.fSS(t).dot(self.getY(t))
  265. return numpy.transpose(qS)
  266. def getSEJ(self,parName):
  267. #find the sensitivity (SE) derivative of Jacobi with
  268. #respect to parameter
  269. try:
  270. return self.seJ[parName]
  271. except KeyError:
  272. self.seJ[parName]={}
  273. return self.seJ[parName]
  274. def getSEJ_comp(self,parName,compartmentName):
  275. #find equation dictating concentration in compartmentName
  276. #for jacobi-parameter derivative
  277. seJ=self.getSEJ(parName)
  278. try:
  279. return seJ[compartmentName]
  280. except KeyError:
  281. seJ[compartmentName]={}
  282. return seJ[compartmentName]
  283. def setY(self,t,y):
  284. self.tck=[None]*self.n
  285. for i in range(self.n):
  286. self.tck[i] = scipy.interpolate.splrep(t, y[:,i], s=0)
  287. def getY(self,t):
  288. fY=numpy.zeros(self.n)
  289. for i in range(self.n):
  290. fY[i]=scipy.interpolate.splev(t, self.tck[i], der=0)
  291. return fY
  292. def calculateUncertainty(self,sol,se):
  293. s2out=numpy.zeros(sol.shape)
  294. pars=self.mod['parameters']
  295. for parName in pars:
  296. par=pars[parName]
  297. if not calculateDerivative(par):
  298. continue
  299. v=par["value"]
  300. if par['dist']=='lognormal':
  301. #this is sigma^2_lnx
  302. sln2=numpy.log(par["cv"]*par["cv"]+1)
  303. #have to multiplied by value to get the derivative
  304. #with respect to lnx
  305. s2=sln2*v*v
  306. else:
  307. #for Gaussian, cv is sigma/value; get sigma by value multiplication
  308. s2=par["cv"]*par["cv"]*v*v
  309. j=self.lutSE[parName]
  310. fse=se[:,:,j]
  311. print('Calculating for {}/{}:{}'.format(parName,j,fse.shape))
  312. #se2 is nt x n
  313. se2=numpy.multiply(fse,fse)
  314. s2out+=se2*s2
  315. return numpy.sqrt(s2out)
  316. def get(self,parName):
  317. par=self.mod['parameters'][parName]
  318. par['name']=parName
  319. if "value" in par:
  320. return self.getValue(par)
  321. if "function" in par:
  322. return self.getFunction(par)
  323. if "derived" in par:
  324. return self.getDerived(par)
  325. def getValue(self,par):
  326. v=par["value"]
  327. parName=par['name']
  328. #convert to seconds
  329. try:
  330. parUnits=par['unit'].split('/')
  331. except (KeyError,IndexError):
  332. #no unit given
  333. return valueObject(v,parName)
  334. timeUnit=self.getTimeUnit()
  335. try:
  336. if parUnits[1]==timeUnit:
  337. return valueObject(v,parName)
  338. except IndexError:
  339. #no / in unit name
  340. return valueObject(v,parName)
  341. if parUnits[1]=='min' and timeUnit=='s':
  342. return valueObject(lambda t,v=v: v(t)/60,parName)
  343. if parUnits[1]=='s' and timeUnit=='min':
  344. return valueObject(lambda t,v=v: 60*v(t),parName)
  345. #no idea what to do
  346. return valueObject(v,parName)
  347. def getFunction(self,par):
  348. fName=par['function']
  349. #print('[{}]: getFunction({})'.format(par['name'],par['function']))
  350. df=self.mod['functions'][fName]
  351. if df['type']=='linearGrowth':
  352. skip=['type']
  353. par1={x:self.get(df[x]) for x in df if x not in skip}
  354. #print(par1)
  355. return linearGrowth(par1)
  356. def getDerived(self,par):
  357. dName=par['derived']
  358. d=self.mod['derivedParameters'][dName]
  359. #print('Derived [{}]: type {}'.format(dName,d['type']))
  360. if d['type']=='product':
  361. #print('{}*{}'.format(d['a'],d['b']))
  362. pA=self.get(d['a'])
  363. a=pA['value']
  364. DA=pA['derivatives']
  365. pB=self.get(d['b'])
  366. b=pB['value']
  367. DB=pB['derivatives']
  368. #even more generic -> df/dp=[df/dA*dA/dp+df/dB*dB/dp]
  369. f=lambda t,a=a,b=b,:a(t)*b(t)
  370. dfdA=lambda t,b=b: b(t)
  371. dfdB=lambda t,a=a: a(t)
  372. dA={x:lambda t,df=dfdA,D=DA,x=x: df(t)*D[x](t) for x in DA}
  373. dB={x:lambda t,df=dfdB,D=DB,x=x: df(t)*D[x](t) for x in DB}
  374. return derivedObject(f,[dA,dB])
  375. if d['type']=='power':
  376. #print('{}^{}'.format(d['a'],d['n']))
  377. pA=self.get(d['a'])
  378. a=pA['value']
  379. DA=pA['derivatives']
  380. pN=self.get(d['n'])
  381. n=pN['value']
  382. DN=pN['derivatives']
  383. f=lambda t,a=a,n=n:numpy.power(a(t),n(t))
  384. dfdA=lambda t,n=n,f=f,a=a:n(t)*f(t)/a(t)
  385. dfdN=lambda t,a=a,f=f:numpy.log(a(t))*f(t)
  386. dA={x:lambda t,df=dfdA,D=DA,x=x: df(t)*D[x](t) for x in DA}
  387. dN={x:lambda t,df=dfdN,D=DN,x=x: df(t)*D[x](t) for x in DN}
  388. return derivedObject(f,[dA,dN])
  389. if d['type']=='ratio':
  390. #print('{}/{}'.format(d['a'],d['b']))
  391. pA=self.get(d['a'])
  392. a=pA['value']
  393. DA=pA['derivatives']
  394. pB=self.get(d['b'])
  395. b=pB['value']
  396. DB=pB['derivatives']
  397. #even more generic -> df/dp=[df/dA*dA/dp+df/dB*dB/dp]
  398. f=lambda t,a=a,b=b,:a(t)/b(t)
  399. dfdA=lambda t,f=f,a=a: f(t)/a(t)
  400. dfdB=lambda t,f=f,b=b: -f(t)/b(t)
  401. dA={x:lambda t,df=dfdA,D=DA,x=x: df(t)*D[x](t) for x in DA}
  402. dB={x:lambda t,df=dfdB,D=DB,x=x: df(t)*D[x](t) for x in DB}
  403. return derivedObject(f,[dA,dB])
  404. if d['type']=='sum':
  405. #print('{}+{}'.format(d['a'],d['b']))
  406. pA=self.get(d['a'])
  407. a=pA['value']
  408. DA=pA['derivatives']
  409. pB=self.get(d['b'])
  410. b=pB['value']
  411. DB=pB['derivatives']
  412. #even more generic -> df/dp=[df/dA*dA/dp+df/dB*dB/dp]
  413. f=lambda t,a=a,b=b,:a(t)+b(t)
  414. dfdA=lambda t: 1
  415. dfdB=lambda t: 1
  416. dA={x:lambda t,df=dfdA,D=DA,x=x: df(t)*D[x](t) for x in DA}
  417. dB={x:lambda t,df=dfdB,D=DB,x=x: df(t)*D[x](t) for x in DB}
  418. return derivedObject(f,[dA,dB])
  419. def calculateDerivative(par):
  420. #add derivatives if dist(short for distribution) is specified
  421. return "dist" in par
  422. def sumValues(dArray,x):
  423. s=0
  424. for a in dArray:
  425. try:
  426. s+=a[x]
  427. except KeyError:
  428. continue
  429. return s
  430. def sumFunctions(dArray,x):
  431. #print('sumFunctions for {}'.format(x))
  432. fs=[]
  433. for a in dArray:
  434. #print('\tChecking {}'.format(a))
  435. try:
  436. #check if a[x] is a function with a dummy parameter
  437. v=a[x](1e7)
  438. fs.append(a[x])
  439. #print('\tAdding [{}]'.format(v))
  440. except KeyError:
  441. #print('\t{} not in table'.format(x))
  442. continue
  443. except TypeError:
  444. #print('\tConstructing lambda for {}'.format(x))
  445. fs.append(lambda t,a=a,x=x:a[x])
  446. return sumFunctionArray(fs)
  447. def sumFunctionArray(fs):
  448. return lambda t,fs=fs: sum([f(t) for f in fs])
  449. def valueObject(v,parName):
  450. #convert everything to functions
  451. d0={parName:lambda t:1}
  452. try:
  453. v(3)
  454. return {'value':v,'derivatives':d0}
  455. except TypeError:
  456. pass
  457. return {'value':lambda t,v=v:v,'derivatives':d0}
  458. def derivedObject(v,dArray):
  459. try:
  460. v(3)
  461. o={'value':v}
  462. except TypeError:
  463. o={'value':lambda t,v=v:v}
  464. allKeys=[]
  465. for x in dArray:
  466. allKeys.extend(x.keys())
  467. allKeys=list(set(allKeys))
  468. o['derivatives']={x:sumFunctions(dArray,x) for x in allKeys}
  469. return o
  470. def splitVector(v):
  471. if v.find('(')<0:
  472. return [v]
  473. return v[1:-1].split(',')
  474. def parseFunction(formula):
  475. if formula['name']=='exponential':
  476. c0=formula['constant']
  477. k=formula['k']
  478. return lambda t,c=c0,k=k:c*numpy.exp(k*t)
  479. if formula['name']=='constant':
  480. c0=formula['value']
  481. return lambda t,c0=c0:c0
  482. if formula['name']=='Heavyside':
  483. t0=formula['limit']
  484. v=formula['value']
  485. return lambda t,v=v,t0=t0:v if t<t0 else 0
  486. return lambda t:1
  487. def addValue(qdict,compName,v):
  488. #add function to compName of dictionary qdict,
  489. #check if compName exists and handle the potential error
  490. #lambda functions can't be summed directly, so qdict is a list
  491. #that will be merged at matrix generation time
  492. try:
  493. qdict[compName].append(v)
  494. except KeyError:
  495. qdict[compName]=[v]
  496. #also add derivatives
  497. #
  498. # for d in dTarget:
  499. # ctarget=self.getSEJ_comp(d,target)
  500. # addValue(ctarget,target,dTarget[d])
  501. def get(timeUnit,par):
  502. v=par["value"]
  503. #convert to seconds
  504. try:
  505. parUnits=par['unit'].split('/')
  506. except (KeyError,IndexError):
  507. #no unit given
  508. return v
  509. try:
  510. if parUnits[1]==timeUnit:
  511. return v
  512. except IndexError:
  513. #no / in unit name
  514. return v
  515. if parUnits[1]=='min' and timeUnit=='s':
  516. return v/60
  517. if parUnits[1]=='s' and timeUnit=='min':
  518. return 60*v
  519. #no idea what to do
  520. return v
  521. def logistic(par):
  522. t0=par['t0']['value']
  523. W=par['W']['value']
  524. L=lambda t,t0=t0,W=W:1/(1+numpy.exp(-(t-t0(t))/W(t)))
  525. #D1[x] could be a function !!!
  526. Dt0=par['t0']['derivatives']
  527. dt0={x:lambda t,L=L,W=W,D=Dt0,x=x: L(t)*(1-L(t))/W(t)*D[x](t) for x in Dt0}
  528. #D2[x] could be a function !!!
  529. DW=par['W']['derivatives']
  530. dW={x:lambda t,L=L,t0=t0,W=W,D=DW,x=x: L(t)*(1-L(t))*(t-t0(t))/W(t)/W(t)*D[x](t) for x in DW}
  531. #merge
  532. return derivedObject(L,[dt0,dW])
  533. def linearGrowth(par):
  534. #make sure we never return zero or negative
  535. C=par['C']['value']
  536. t1=par['t1']['value']
  537. t2=par['t2']['value']
  538. eps=1e-8*C(0)
  539. pL1=logistic({'t0':par['t1'],'W':par['W1']})
  540. pL2=logistic({'t0':par['t2'],'W':par['W2']})
  541. L1=pL1['value']
  542. L2=pL2['value']
  543. deltaW=lambda t,t1=t1,t2=t2:t2(t)-t1(t)
  544. deltaT=lambda t,t1=t1:t-t1(t)
  545. fq=lambda t,dt=deltaT,w=deltaW:dt(t)/w(t)
  546. f1=lambda t,eps=eps,q=fq,L1=L1,L2=L2,C=C:C(t)*q(t)*L1(t)*(1-L2(t))
  547. f2=lambda t,L1=L1,L2=L2,C=C: C(t)*L1(t)*L2(t)
  548. f=lambda t,eps=eps,f1=f1,f2=f2:eps+f1(t)+f2(t)
  549. Dt1=par['t1']['derivatives']
  550. dfdt1=lambda t,q=fq,C=C,w=deltaW,L1=L1,L2=L2:\
  551. -C(t)*L1(t)*(1-L2(t))*(1+q(t))/w(t)
  552. dt1={x:lambda t,df=dfdt1,D=Dt1,x=x:df(t)*D[x](t) for x in Dt1}
  553. Dt2=par['t2']['derivatives']
  554. dfdt2=lambda t,f1=f1,w=deltaW:-f1(t)/w(t)
  555. dt2={x:lambda t,df=dfdt2,D=Dt2,x=x:df(t)*D[x](t) for x in Dt2}
  556. DL1=pL1['derivatives']
  557. dfdL1=lambda t,C=C,L2=L2,q=fq:C(t)*(q(t)*(1-L2(t))+L2(t))
  558. dL1={x:lambda t,df=dfdL1,D=DL1,x=x:df(t)*D[x](t) for x in DL1}
  559. DL2=pL2['derivatives']
  560. dfdL2=lambda t,C=C,L1=L1,q=fq:C(t)*L1(t)*(1-q(t))
  561. dL2={x:lambda t,df=dfdL2,D=DL2,x=x: df(t)*D[x](t) for x in DL2}
  562. DC=par['C']['derivatives']
  563. dfdC=lambda t,L1=L1,L2=L2,q=fq:L1(t)*(q(t)*(1-L2(t))+L2(t))
  564. dC={x:lambda t,df=dfdC,D=DC,x=x:df(t)*D[x](t) for x in DC}
  565. return derivedObject(f,[dt1,dt2,dL1,dL2,dC])