|
@@ -125,202 +125,13 @@ def removeImMatrix(A,eps):
|
|
|
f=removeImVector(f,eps)
|
|
f=removeImVector(f,eps)
|
|
|
return f.reshape(A.shape)
|
|
return f.reshape(A.shape)
|
|
|
|
|
|
|
|
-def solveMatrix(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.array([]),method='full'):
|
|
|
|
|
- if method=='full':
|
|
|
|
|
- obj=_solveMatrix(model,tmax,nt,t0,y0,sIn)
|
|
|
|
|
- if method=='separately':
|
|
|
|
|
- obj=_solveMatrixSeparately(model,tmax,nt,t0,y0,sIn)
|
|
|
|
|
-
|
|
|
|
|
|
|
+def solveMatrix(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.array([]),method='fraction'):
|
|
|
|
|
+#method=fraction sets output of excrement compartments to fractions of total ingested chemical
|
|
|
|
|
+#method=derivate sets output to amount excreted per unit time (in native units, ng/min)
|
|
|
|
|
+ obj=_solveMatrix(model,tmax,nt,t0,y0,sIn,method)
|
|
|
return obj['t'],obj['sol'],obj['se'],obj['s1']
|
|
return obj['t'],obj['sol'],obj['se'],obj['s1']
|
|
|
|
|
|
|
|
-def _solveMatrix(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.array([])):
|
|
|
|
|
- t=numpy.linspace(0,tmax-t0,nt)
|
|
|
|
|
-
|
|
|
|
|
-#solve system Q at time points t for initial values y0
|
|
|
|
|
-#SS is gradient of system nxn matrix M with respect to m parameters p
|
|
|
|
|
- SS=model.fSS(0)
|
|
|
|
|
-#large (M+1)*N by (M+1)*N matrix
|
|
|
|
|
- m=len(model.lutSE)
|
|
|
|
|
- A=removeScaled(model,model.M(0))
|
|
|
|
|
-#adjusted n where only non-terminal(scaled) compartments are counted
|
|
|
|
|
- n=A.shape[0]
|
|
|
|
|
- SM=numpy.zeros(((m+1)*n,(m+1)*n))
|
|
|
|
|
- for k in range(m):
|
|
|
|
|
-#A is on diagonal
|
|
|
|
|
- SM[k*n:(k+1)*n,k*n:(k+1)*n]=A
|
|
|
|
|
-#on the far right, gradient matrix SS sits
|
|
|
|
|
- SM[k*n:(k+1)*n,-n:]=removeScaled(model,SS[k])
|
|
|
|
|
-#for original solutions, matrix A is used
|
|
|
|
|
- SM[-n:,-n:]=A
|
|
|
|
|
-#matrix SM is now of full rank and can be diagonalized
|
|
|
|
|
-#print('{}/{}'.format(numpy.linalg.matrix_rank(SM),(m+1)*n))
|
|
|
|
|
-
|
|
|
|
|
-#RHS
|
|
|
|
|
-#SU has shape mxn
|
|
|
|
|
-#we have to remove scaled columns
|
|
|
|
|
-#Add plain rhs
|
|
|
|
|
- SU=removeScaled(model,model.Su(0),mode='columnsOnly').flatten()
|
|
|
|
|
- u=removeScaled(model,model.u(0),mode='vector')
|
|
|
|
|
- print('{} {}'.format(SU.shape,u.shape))
|
|
|
|
|
- SU=numpy.concatenate((SU,u))
|
|
|
|
|
- print(SU.shape)
|
|
|
|
|
-#diagonalize matrix
|
|
|
|
|
- lam,P=numpy.linalg.eig(SM)
|
|
|
|
|
-#no effect down to eps=1e-2
|
|
|
|
|
- #eps=1e-4
|
|
|
|
|
- #print(f'Using eps={eps}')
|
|
|
|
|
- #lam=removeImVector(lam,eps)
|
|
|
|
|
- #P=removeImMatrix(P,eps)
|
|
|
|
|
- P1=numpy.linalg.inv(P)
|
|
|
|
|
- #P1=removeImMatrix(P1,eps)
|
|
|
|
|
- D=numpy.diag(lam)
|
|
|
|
|
-
|
|
|
|
|
-#verify
|
|
|
|
|
-#print(numpy.max(P @ D @ P1-M))
|
|
|
|
|
-
|
|
|
|
|
-#shift input to diagonal system
|
|
|
|
|
- v=P1 @ SU
|
|
|
|
|
- #also no effect
|
|
|
|
|
- #maxV=numpy.max(numpy.abs(v))
|
|
|
|
|
- #v=v*(numpy.abs(v)>eps*maxV)
|
|
|
|
|
- #v=removeImVector(v,eps)
|
|
|
|
|
-
|
|
|
|
|
- #print(f'v={v}')
|
|
|
|
|
- #print(f'lam={lam}')
|
|
|
|
|
-
|
|
|
|
|
-#set initial values if missing
|
|
|
|
|
- if not y0.size:
|
|
|
|
|
- y0p=numpy.zeros(n)
|
|
|
|
|
- else:
|
|
|
|
|
-#should move this to diagonal space
|
|
|
|
|
- y0p=removeScaled(model,y0,mode='vector')
|
|
|
|
|
-
|
|
|
|
|
- if not sIn.size:
|
|
|
|
|
- s0=numpy.zeros(n*m)
|
|
|
|
|
- else:
|
|
|
|
|
-#sIn is n by m matrix, so transpose before reshaping
|
|
|
|
|
- s0=numpy.transpose(sIn)
|
|
|
|
|
- s0=removeScaled(model,s0,mode='columnsOnly')
|
|
|
|
|
- s0=s0.flatten()
|
|
|
|
|
- print('s0 {} y0p {}'.format(s0.shape,y0p.shape))
|
|
|
|
|
- S0=numpy.concatenate((s0,y0p))
|
|
|
|
|
- S0= P1 @ S0
|
|
|
|
|
-
|
|
|
|
|
-#present results as a (n*(m+1) x N array)
|
|
|
|
|
-#n number of variables/compartments
|
|
|
|
|
-#N number of time points
|
|
|
|
|
- W=numpy.outer(lam,t)
|
|
|
|
|
- tE=numpy.ones(t.shape)
|
|
|
|
|
- V0=numpy.outer(S0,tE)
|
|
|
|
|
- V=numpy.outer(v/lam,tE)
|
|
|
|
|
- y1=(V0+V)*numpy.exp(W)-V
|
|
|
|
|
-
|
|
|
|
|
-#convert back to true system
|
|
|
|
|
- y=P @ y1
|
|
|
|
|
-
|
|
|
|
|
-#reinsert scaled into array
|
|
|
|
|
-#start with y of shape (m+1)*n by N where N is number of time-points and n are only non-scaled compartments
|
|
|
|
|
-#should end up with y of shape (m+1)*n by N, where n is now number of ALL compartments
|
|
|
|
|
- originalMap=model.lut
|
|
|
|
|
- updatedMap=skipRedundant(model)
|
|
|
|
|
- n0=len(originalMap)
|
|
|
|
|
- n=len(updatedMap)
|
|
|
|
|
- N=y.shape[1]
|
|
|
|
|
- sOut=numpy.zeros((N,n0,m+1))
|
|
|
|
|
- for k in range(m+1):
|
|
|
|
|
- for c in updatedMap:
|
|
|
|
|
- i=updatedMap[c]
|
|
|
|
|
- i0=originalMap[c]
|
|
|
|
|
- sOut[:,i0,k]=y[k*n+i,:]
|
|
|
|
|
-
|
|
|
|
|
-#print('Shape: {}, n={} N={} m={}'.format(yout.shape,n0,N,m))
|
|
|
|
|
-#equivalent of Sout (N,n,m)
|
|
|
|
|
-#return numpy.transpose(yout).reshape(N,n0,m+1)
|
|
|
|
|
- sOut=calculateScaled(model,t,sOut,sIn,y0)
|
|
|
|
|
- sol=sOut[:,:,-1]
|
|
|
|
|
- s1=sOut[:,:,:-1]
|
|
|
|
|
- se=getSE(s1)
|
|
|
|
|
- return {'t':t+t0,'sol':sol,'se':se,'s1':s1,'P':P,'D':D,'SM':SM}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def calculateScaled(model,t,sOut,sIn=numpy.zeros(0),y0=numpy.zeros(0)):
|
|
|
|
|
-
|
|
|
|
|
-#update sOut w/ calculated scaled values
|
|
|
|
|
-#sIn,yIn are initial values
|
|
|
|
|
-#sIn is of shape (n,m)
|
|
|
|
|
-#yIn is (n,)
|
|
|
|
|
-#sOut is (N,n,m+1)
|
|
|
|
|
- lutSE=model.lutSE
|
|
|
|
|
- lut=model.lut
|
|
|
|
|
-
|
|
|
|
|
-#if reading a model, keep the read lut
|
|
|
|
|
-#lutSE=Q['lutSE']
|
|
|
|
|
-#lut=Q['lut']
|
|
|
|
|
-
|
|
|
|
|
- m=len(lutSE)
|
|
|
|
|
- n=len(lut)
|
|
|
|
|
- N=len(t)
|
|
|
|
|
-
|
|
|
|
|
- if not sIn.size:
|
|
|
|
|
- sIn=numpy.zeros((n,m))
|
|
|
|
|
-
|
|
|
|
|
- if not y0.size:
|
|
|
|
|
- y0=numpy.zeros(n)
|
|
|
|
|
-
|
|
|
|
|
- #add column for initial values
|
|
|
|
|
- sIn=numpy.c_[sIn,y0]
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-#reshape to n*(m+1) by N
|
|
|
|
|
- y=numpy.zeros((n*(m+1),N))
|
|
|
|
|
-
|
|
|
|
|
- for k in range(m+1):
|
|
|
|
|
- for i in range(n):
|
|
|
|
|
- y[n*k+i,:]=sOut[:,i,k]
|
|
|
|
|
-
|
|
|
|
|
-#full version of SM
|
|
|
|
|
-
|
|
|
|
|
- SS=model.fSS(0)
|
|
|
|
|
- A=model.M(0)
|
|
|
|
|
- SM=numpy.zeros(((m+1)*n,(m+1)*n))
|
|
|
|
|
- for k in range(m):
|
|
|
|
|
-#A is on diagonal
|
|
|
|
|
- SM[k*n:(k+1)*n,k*n:(k+1)*n]=A
|
|
|
|
|
-#on the far right, gradient matrix SS sits
|
|
|
|
|
- SM[k*n:(k+1)*n,-n:]=model.SS[k]
|
|
|
|
|
-
|
|
|
|
|
- SM[-n:,-n:]=A
|
|
|
|
|
-
|
|
|
|
|
-#full version of RHS
|
|
|
|
|
- SU=model.Su(0).flatten()
|
|
|
|
|
- u=model.u(0)
|
|
|
|
|
-#n*(m+1) by N
|
|
|
|
|
- SU=numpy.outer(numpy.concatenate((SU,u)),numpy.ones(t.shape))
|
|
|
|
|
-
|
|
|
|
|
-#integral, n*(m+1) by N
|
|
|
|
|
- fI=integrate(t,y)
|
|
|
|
|
- fU=integrate(t,SU)
|
|
|
|
|
-
|
|
|
|
|
-#apply couplings
|
|
|
|
|
- fI= SM @ fI
|
|
|
|
|
-
|
|
|
|
|
-#update values; scale compartments to total exposure
|
|
|
|
|
- iT=lut['total']
|
|
|
|
|
- fTotal=y0[iT]+fU[m*n+iT,:]
|
|
|
|
|
- for k in range(m+1):
|
|
|
|
|
- for c in model.scaled:
|
|
|
|
|
- i=lut[c]
|
|
|
|
|
-#sOut[1:,i,k]=fI[k*n+i,:]+fU[k*n+i,:]
|
|
|
|
|
- sOut[1:,i,k]=(sIn[i,k]*y0[iT]+fI[k*n+i,:]+fU[k*n+i,:])/fTotal
|
|
|
|
|
- sOut[0,i,k]=sIn[i,k]
|
|
|
|
|
-#set total for solution only
|
|
|
|
|
- sOut[1:,iT,-1]=fTotal
|
|
|
|
|
- sOut[0,iT,-1]=y0[iT]
|
|
|
|
|
- return sOut
|
|
|
|
|
-
|
|
|
|
|
-def _solveMatrixSeparately(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.array([])):
|
|
|
|
|
|
|
+def _solveMatrix(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.array([]),method='fraction'):
|
|
|
#sIn (n,m)
|
|
#sIn (n,m)
|
|
|
#y0 (n,)
|
|
#y0 (n,)
|
|
|
t=numpy.linspace(0,tmax-t0,nt)
|
|
t=numpy.linspace(0,tmax-t0,nt)
|
|
@@ -342,8 +153,7 @@ def _solveMatrixSeparately(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.
|
|
|
|
|
|
|
|
sOut=numpy.zeros([N,n,m+1])
|
|
sOut=numpy.zeros([N,n,m+1])
|
|
|
#y is (N,n)
|
|
#y is (N,n)
|
|
|
- #y=solveUnit(model,t,y0,u0,model.u(0))
|
|
|
|
|
- y=solver.solve(y0,model.u(0))
|
|
|
|
|
|
|
+ y=solver.solve(y0,model.u(0),method=method)
|
|
|
sOut[:,:,-1]=y
|
|
sOut[:,:,-1]=y
|
|
|
#(N,n,1)
|
|
#(N,n,1)
|
|
|
y1=numpy.expand_dims(y,axis=2)
|
|
y1=numpy.expand_dims(y,axis=2)
|
|
@@ -364,12 +174,9 @@ def _solveMatrixSeparately(model,tmax,nt=201,t0=0,y0=numpy.array([]), sIn=numpy.
|
|
|
#yIn is (n,)
|
|
#yIn is (n,)
|
|
|
yIn=sIn[:,k]
|
|
yIn=sIn[:,k]
|
|
|
#return in (N,n)
|
|
#return in (N,n)
|
|
|
-# sOut[:,:,k]=solveUnit(model,t,yIn,u0,u,rhs)
|
|
|
|
|
- #sOut[:,:,k]=solver.solve(sIn[:,k],SU[k],rhs)
|
|
|
|
|
- sOut[:,:,k]=solver.solveS(SS[k],y1,y0,u=u,z0=sIn[:,k],r=SU[k])
|
|
|
|
|
|
|
+ sOut[:,:,k]=solver.solveS(SS[k],y,y0,u=u,z0=sIn[:,k],r=SU[k],method=method)
|
|
|
|
|
|
|
|
|
|
|
|
|
-# break
|
|
|
|
|
|
|
|
|
|
sol=sOut[:,:,-1]
|
|
sol=sOut[:,:,-1]
|
|
|
s1=sOut[:,:,:-1]
|
|
s1=sOut[:,:,:-1]
|
|
@@ -391,8 +198,10 @@ class solveUnitObj:
|
|
|
self.n=len(self.updatedMap)
|
|
self.n=len(self.updatedMap)
|
|
|
|
|
|
|
|
self.N=len(t)
|
|
self.N=len(t)
|
|
|
|
|
+
|
|
|
|
|
+ self.M=self.model.M(0)
|
|
|
|
|
|
|
|
- self.A=removeScaled(model,model.M(0))
|
|
|
|
|
|
|
+ self.A=removeScaled(model,self.M)
|
|
|
|
|
|
|
|
#diagonalize matrix
|
|
#diagonalize matrix
|
|
|
self.lam,self.P=numpy.linalg.eig(self.A)
|
|
self.lam,self.P=numpy.linalg.eig(self.A)
|
|
@@ -448,12 +257,11 @@ class solveUnitObj:
|
|
|
#
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
|
- def solve(self,y0=numpy.zeros(0),u=numpy.zeros(0),rhs=numpy.zeros(0)):
|
|
|
|
|
|
|
+ def solve(self,y0=numpy.zeros(0),u=numpy.zeros(0),method='fraction'):
|
|
|
|
|
|
|
|
-#solve system Q at time points t for initial values y0
|
|
|
|
|
-#u is the time independent nohomogeneous part
|
|
|
|
|
-#rhs is tabulated non-homogeneous part (N,n)
|
|
|
|
|
-#u0 is initial accumulated xenobiote
|
|
|
|
|
|
|
+#solve system
|
|
|
|
|
+#y0 (n,) initial values
|
|
|
|
|
+#u (n,) the time independent input
|
|
|
|
|
|
|
|
|
|
|
|
|
#use Green function theorem, since impulse response is simple
|
|
#use Green function theorem, since impulse response is simple
|
|
@@ -479,66 +287,35 @@ class solveUnitObj:
|
|
|
y0=numpy.zeros(u.shape)
|
|
y0=numpy.zeros(u.shape)
|
|
|
y0p=removeScaled(self.model,y0,mode='vector')
|
|
y0p=removeScaled(self.model,y0,mode='vector')
|
|
|
|
|
|
|
|
-#overload w/ rhs
|
|
|
|
|
-#RHS has a time component
|
|
|
|
|
-#typically of shape n by N
|
|
|
|
|
- if rhs.size:
|
|
|
|
|
-#to N by n shape
|
|
|
|
|
-# rhs=rhs.transpose()
|
|
|
|
|
-#remove trivial components
|
|
|
|
|
- rhsp=removeScaled(self.model,rhs,mode='columnsOnly')
|
|
|
|
|
-#to (N,n,1)
|
|
|
|
|
- rhsp=numpy.expand_dims(rhsp,axis=2)
|
|
|
|
|
- else:
|
|
|
|
|
- rhsp=numpy.zeros(0)
|
|
|
|
|
- #print(numpy.linalg.matrix_rank(A))
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-#(n,n) @ (N,n,n) @ (n,n) @ (n,)
|
|
|
|
|
-#y1 is (N,n)
|
|
|
|
|
-#sum works for (N,n)+(n,)
|
|
|
|
|
- #print(up)
|
|
|
|
|
- #print(P1 @ up)
|
|
|
|
|
- #print( (W-E)*invL @ P1 @ up)
|
|
|
|
|
|
|
+#(N,n)=(n,n) @ (N,n,n) @ (n,n) @ (n,) + (n,n) @ (N,n,n) @ (n,n) @ (n,)
|
|
|
y= self.P @ self.W @ self.P1 @ y0p + self.P @ self.Q @ self.P1 @ up
|
|
y= self.P @ self.W @ self.P1 @ y0p + self.P @ self.Q @ self.P1 @ up
|
|
|
|
|
|
|
|
- if rhsp.size:
|
|
|
|
|
-
|
|
|
|
|
-#time dependent component
|
|
|
|
|
-#(a,b,c,e)=(a,b,c,d) @ (b,d,e)
|
|
|
|
|
-#(N,N,n,1)=(n,n) @ (N,N,n,n) @ (n,n) @ (N,n,1)
|
|
|
|
|
-#Y1 is (N,N,n,1)
|
|
|
|
|
- Y1=numpy.real(self.P @ self.W1 @ self.P1 @ rhsp)
|
|
|
|
|
-#to apply Green's theorem, integral along axis=1 should be performed
|
|
|
|
|
-#(N,n,1)
|
|
|
|
|
-
|
|
|
|
|
- Y2=integrateFull(self.t,Y1,axis=1)
|
|
|
|
|
-#to remove last dimension, squeeze at the end
|
|
|
|
|
-#(N,n), same as y1
|
|
|
|
|
- y+=Y2.squeeze()
|
|
|
|
|
-
|
|
|
|
|
#back to original system, add trivial compartments
|
|
#back to original system, add trivial compartments
|
|
|
|
|
+#(N,n)
|
|
|
yOut=numpy.zeros((self.N,self.n0))
|
|
yOut=numpy.zeros((self.N,self.n0))
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+#shift compartments from modified index i (from y) to original index i0 (to yOut)
|
|
|
for c in self.updatedMap:
|
|
for c in self.updatedMap:
|
|
|
i=self.updatedMap[c]
|
|
i=self.updatedMap[c]
|
|
|
i0=self.originalMap[c]
|
|
i0=self.originalMap[c]
|
|
|
yOut[:,i0]=y[:,i]
|
|
yOut[:,i0]=y[:,i]
|
|
|
|
|
|
|
|
- #return yOut
|
|
|
|
|
-#here we need full sized y0,u,rhs, so make sure they are not overwritten by reduced versions
|
|
|
|
|
- return getScaled(self.model,self.t,yOut,self.u0,y0,u,rhs)
|
|
|
|
|
|
|
+#here we need original, full sized y0,u,rhs, so make sure they are not overwritten by reduced versions
|
|
|
|
|
+#content determined by method
|
|
|
|
|
+ if method=='fraction':
|
|
|
|
|
+ return getScaled(self.model,self.t,yOut,self.u0,y0,u)
|
|
|
|
|
+ if method=='derivative':
|
|
|
|
|
+ return setScaledDerivative(self.model,yOut,u)
|
|
|
|
|
|
|
|
def solveS(self,S,y,y0=numpy.zeros(0),u=numpy.zeros(0),\
|
|
def solveS(self,S,y,y0=numpy.zeros(0),u=numpy.zeros(0),\
|
|
|
- z0=numpy.zeros(0),r=numpy.zeros(0)):
|
|
|
|
|
|
|
+ z0=numpy.zeros(0),r=numpy.zeros(0),method='fraction'):
|
|
|
|
|
|
|
|
#solve gradient system (S) at time points t
|
|
#solve gradient system (S) at time points t
|
|
|
-#y (N,n,1) is solution of the system
|
|
|
|
|
|
|
+#y (N,n) is solution of the system
|
|
|
#y0 (n,) initial values of solution
|
|
#y0 (n,) initial values of solution
|
|
|
-#u (n,)is the non-homogenous (but constant) part of equation
|
|
|
|
|
-#z0 (n,)initial values of gradients
|
|
|
|
|
-#r (n,) is the time independent gradient of nonhomogenous part
|
|
|
|
|
|
|
+#u (n,) is the non-homogenous (but constant) part of equation
|
|
|
|
|
+#z0 (n,) initial values of gradients
|
|
|
|
|
+#r (n,) is the time independent gradient of nonhomogenous part / input (grad u)
|
|
|
|
|
|
|
|
#use Green function theorem, since impulse response is simple
|
|
#use Green function theorem, since impulse response is simple
|
|
|
#yGREEN=exp(lam*(t-t0) and integrate it for exponentail solutions
|
|
#yGREEN=exp(lam*(t-t0) and integrate it for exponentail solutions
|
|
@@ -581,25 +358,22 @@ class solveUnitObj:
|
|
|
#(N,n,n)
|
|
#(N,n,n)
|
|
|
HS= numpy.einsum('a,bc',self.tE,Spp) * self.H
|
|
HS= numpy.einsum('a,bc',self.tE,Spp) * self.H
|
|
|
|
|
|
|
|
-#z=P @ W @ P1 @ z0 + P @ Q @ P1 r +
|
|
|
|
|
-# + P @ (S * H) @ P1 @ y0 + P @ (S * H) @ D1 @ P1 @ u +P @ S @ Q @ D1 @ P1 @ u
|
|
|
|
|
-#(n,n) @ (N,n,n) @ (n,n) @ (n,)
|
|
|
|
|
-#y1 is (N,n)
|
|
|
|
|
-#sum works for (N,n)+(n,)
|
|
|
|
|
|
|
+#(N,n) = (n,n) @ (N,n,n) @ (n,n) @ (n,)
|
|
|
|
|
|
|
|
z1 = self.P @ self.W @ self.P1 @ z0p
|
|
z1 = self.P @ self.W @ self.P1 @ z0p
|
|
|
z2 = self.P @ self.Q @ self.P1 @ rp
|
|
z2 = self.P @ self.Q @ self.P1 @ rp
|
|
|
|
|
|
|
|
#intermediate variable for speed-up
|
|
#intermediate variable for speed-up
|
|
|
-#(n,n) @ (n,n) @(n,)
|
|
|
|
|
|
|
+#(n,) = (n,n) @ (n,n) @(n,)
|
|
|
_f=self.D1 @ self.P1 @ up
|
|
_f=self.D1 @ self.P1 @ up
|
|
|
|
|
|
|
|
#combine above for speed-up
|
|
#combine above for speed-up
|
|
|
-#(n,n) @ (N,n,n) @ ( (n,n) @ (n,) + (n,) )
|
|
|
|
|
|
|
+#(N,n) = (n,n) @ (N,n,n) @ ( (n,n) @ (n,) + (n,) )
|
|
|
z3 = self.P @ HS @ (self.P1 @ y0p + _f)
|
|
z3 = self.P @ HS @ (self.P1 @ y0p + _f)
|
|
|
-#(n,n) @ (n,n) @ (N,n,n) @ (n,)
|
|
|
|
|
|
|
+#(N,n) = (n,n) @ (n,n) @ (N,n,n) @ (n,)
|
|
|
z4 = self.P @ self.Q @ Spp @ _f
|
|
z4 = self.P @ self.Q @ Spp @ _f
|
|
|
|
|
|
|
|
|
|
+#(N,n)
|
|
|
z=z1+z2+(z3-z4)
|
|
z=z1+z2+(z3-z4)
|
|
|
|
|
|
|
|
#back to original system, add trivial compartments
|
|
#back to original system, add trivial compartments
|
|
@@ -610,134 +384,18 @@ class solveUnitObj:
|
|
|
i0=self.originalMap[c]
|
|
i0=self.originalMap[c]
|
|
|
sOut[:,i0]=z[:,i]
|
|
sOut[:,i0]=z[:,i]
|
|
|
|
|
|
|
|
- #return yOut
|
|
|
|
|
#here we need full sized y0,u,rhs, so make sure they are not overwritten by reduced versions
|
|
#here we need full sized y0,u,rhs, so make sure they are not overwritten by reduced versions
|
|
|
- #y = self.P @ self. W @ self.P1 @ y + self.P @ self.Q @ self.P1 @ u
|
|
|
|
|
- #(N,n0) = (n0,n0) @ (N,n0,1)
|
|
|
|
|
- rhs = (S @ y).squeeze()
|
|
|
|
|
- return getScaled(self.model,self.t,sOut,self.u0,z0,r,rhs)
|
|
|
|
|
|
|
+#(N,n) = (n,n) @ (N,n,1)
|
|
|
|
|
+ rhs = (S @ numpy.expand_dims(y,axis=2)).squeeze()
|
|
|
|
|
+ if method=='fraction':
|
|
|
|
|
+ return setScaled(self.model,self.t,sOut,self.u0,z0,r,rhs)
|
|
|
|
|
+ if method=='derivative':
|
|
|
|
|
+ return setScaledDerivative(self.model,sOut,r,rhs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-def solveUnit(model,t,y0=numpy.zeros(0),u0=0,u=numpy.zeros(0),rhs=numpy.zeros(0)):
|
|
|
|
|
-#solve system Q at time points t for initial values y0
|
|
|
|
|
-#u is the time independent nohomogeneous part
|
|
|
|
|
-#rhs is tabulated non-homogeneous part (N,n)
|
|
|
|
|
-#u0 is initial accumulated xenobiote
|
|
|
|
|
-
|
|
|
|
|
-#use Green function theorem, since impulse response is simple
|
|
|
|
|
-#yGREEN=exp(lam*(t-t0)
|
|
|
|
|
-
|
|
|
|
|
-#some dimensions
|
|
|
|
|
- originalMap=model.lut
|
|
|
|
|
- updatedMap=skipRedundant(model)
|
|
|
|
|
- n0=len(originalMap)
|
|
|
|
|
- n=len(updatedMap)
|
|
|
|
|
- N=len(t)
|
|
|
|
|
-
|
|
|
|
|
-#matrix solution works for reduced space where all
|
|
|
|
|
-#non-trivial components are reduced
|
|
|
|
|
-
|
|
|
|
|
-#typical variables are denoted with trailing p (for prime)
|
|
|
|
|
-#and have a smaller size (fewer compartments) than
|
|
|
|
|
-#full variables
|
|
|
|
|
-
|
|
|
|
|
-#get the matrix and remove trivial components
|
|
|
|
|
- A=removeScaled(model,model.M(0))
|
|
|
|
|
-#remove trivial components from non-homogeneous parts
|
|
|
|
|
- if not u.size:
|
|
|
|
|
- u=numpy.zeros(n0)
|
|
|
|
|
-
|
|
|
|
|
- up=removeScaled(model,u,mode='vector')
|
|
|
|
|
-
|
|
|
|
|
-#remove trivial components from initial values
|
|
|
|
|
- if not y0.size:
|
|
|
|
|
- y0=numpy.zeros(u.shape)
|
|
|
|
|
- y0p=removeScaled(model,y0,mode='vector')
|
|
|
|
|
-
|
|
|
|
|
-#overload w/ rhs
|
|
|
|
|
-#RHS has a time component
|
|
|
|
|
-#typically of shape n by N
|
|
|
|
|
- if rhs.size:
|
|
|
|
|
-#to N by n shape
|
|
|
|
|
-# rhs=rhs.transpose()
|
|
|
|
|
-#remove trivial components
|
|
|
|
|
- rhsp=removeScaled(model,rhs,mode='columnsOnly')
|
|
|
|
|
-#to (N,n,1)
|
|
|
|
|
- rhsp=numpy.expand_dims(rhsp,axis=2)
|
|
|
|
|
- else:
|
|
|
|
|
- rhsp=numpy.zeros(0)
|
|
|
|
|
- #print(numpy.linalg.matrix_rank(A))
|
|
|
|
|
-
|
|
|
|
|
-#diagonalize matrix
|
|
|
|
|
- lam,P=numpy.linalg.eig(A)
|
|
|
|
|
- P1=numpy.linalg.inv(P)
|
|
|
|
|
- D=numpy.diag(lam)
|
|
|
|
|
-
|
|
|
|
|
-#next are all (N,n,n)
|
|
|
|
|
- W=numpy.exp(numpy.einsum('a,bc',t,numpy.diag(lam)))
|
|
|
|
|
- tE=numpy.ones(t.shape)
|
|
|
|
|
- invL=numpy.einsum('a,bc',tE,numpy.diag(1/lam))
|
|
|
|
|
- E=numpy.ones(W.shape)
|
|
|
|
|
-
|
|
|
|
|
-#(n,n) @ (N,n,n) @ (n,n) @ (n,)
|
|
|
|
|
-#y1 is (N,n)
|
|
|
|
|
-#sum works for (N,n)+(n,)
|
|
|
|
|
- #print(up)
|
|
|
|
|
- #print(P1 @ up)
|
|
|
|
|
- #print( (W-E)*invL @ P1 @ up)
|
|
|
|
|
- y= y0p + P @ ((W-E)*invL) @ P1 @ up
|
|
|
|
|
-
|
|
|
|
|
- if rhsp.size:
|
|
|
|
|
-
|
|
|
|
|
-#time dependent component
|
|
|
|
|
-#t-t'
|
|
|
|
|
-#we have exp(lam*(t-t')) as Green's function
|
|
|
|
|
-#construct matrix (N,N,n,n) where the last part is diag(lam)
|
|
|
|
|
-#W[i,j,k,l]=delta(k,l)*exp(lam_k*(t_i-t'_j))
|
|
|
|
|
-#delta(i,j) is Kronecker's delta
|
|
|
|
|
- T=numpy.outer(t,tE)-numpy.outer(tE,t)
|
|
|
|
|
- T1=numpy.einsum('ab,cd',T,numpy.eye(n))
|
|
|
|
|
- F=numpy.einsum('ab,cd',T>=0,numpy.eye(n))
|
|
|
|
|
- L1=numpy.einsum('ab,cd',numpy.ones(T.shape),numpy.diag(lam))
|
|
|
|
|
- W1=numpy.zeros(F.shape,dtype='complex')
|
|
|
|
|
- W1[F>0]=numpy.exp(L1[F>0]*T1[F>0])
|
|
|
|
|
-#(a,b,c,e)=(a,b,c,d) @ (b,d,e)
|
|
|
|
|
-#(N,N,n,1)=(n,n) @ (N,N,n,n) @ (n,n) @ (N,n,1)
|
|
|
|
|
-#Y1 is (N,N,n,1)
|
|
|
|
|
- Y1=P @ W1 @ P1 @ rhsp
|
|
|
|
|
-#to apply Green's theorem, integral along axis=1 should be performed
|
|
|
|
|
-#(N,n,1)
|
|
|
|
|
-
|
|
|
|
|
- Y2=integrateFull(t,Y1,axis=1)
|
|
|
|
|
-#to remove last dimension, squeeze at the end
|
|
|
|
|
-#(N,n), same as y1
|
|
|
|
|
- y+=Y2.squeeze()
|
|
|
|
|
-
|
|
|
|
|
-#back to original system, add trivial compartments
|
|
|
|
|
- yOut=numpy.zeros((N,n0))
|
|
|
|
|
-
|
|
|
|
|
- for c in updatedMap:
|
|
|
|
|
- i=updatedMap[c]
|
|
|
|
|
- i0=originalMap[c]
|
|
|
|
|
- yOut[:,i0]=y[:,i]
|
|
|
|
|
-
|
|
|
|
|
-#here we need full sized y0,u,rhs, so make sure they are not overwritten by reduced versions
|
|
|
|
|
- return getScaled(model,t,yOut,u0,y0,u,rhs)
|
|
|
|
|
-
|
|
|
|
|
-def getTotal(model,t,u0=0):
|
|
|
|
|
- u=model.u(0)
|
|
|
|
|
- iT=model.lut['total']
|
|
|
|
|
- U=u[iT]
|
|
|
|
|
-#*numpy.ones(t.shape)
|
|
|
|
|
- fU=numpy.zeros(t.shape)
|
|
|
|
|
- fU[1:]=u0+integrate(t,U).squeeze()
|
|
|
|
|
- fU[0]=u0
|
|
|
|
|
- return fU
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def getScaled(model,t,y,u0=0,y0=numpy.zeros(0),u=numpy.zeros(0),rhs=numpy.zeros(0)):
|
|
|
|
|
|
|
+def setScaled(model,t,y,u0=0,y0=numpy.zeros(0),u=numpy.zeros(0),rhs=numpy.zeros(0)):
|
|
|
#update y with scaled containers, which were excluded from direct calculation to make M of full rank
|
|
#update y with scaled containers, which were excluded from direct calculation to make M of full rank
|
|
|
#u has shape (n,)
|
|
#u has shape (n,)
|
|
|
#rhs has shape (N,n)
|
|
#rhs has shape (N,n)
|
|
@@ -784,3 +442,35 @@ def getScaled(model,t,y,u0=0,y0=numpy.zeros(0),u=numpy.zeros(0),rhs=numpy.zeros(
|
|
|
|
|
|
|
|
return y
|
|
return y
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+def setScaledDerivative(model,y,u=numpy.zeros(0),rhs=numpy.zeros(0)):
|
|
|
|
|
+#update y with scaled containers, which were excluded from direct calculation to make M of full rank.
|
|
|
|
|
+#Rhs is added to the argument list so that the same routine can be used for Jacobi calculation
|
|
|
|
|
+#M is (n,n), the system matrix, M=model.M(0)
|
|
|
|
|
+#y is (N,n), the solution to the coupled compartments (ie. excluded excrement)
|
|
|
|
|
+#u is (n,), the input part
|
|
|
|
|
+#rhs is (N,n), for Jacobi, set it to (grad M) @ y, for k-th component of gradient
|
|
|
|
|
+
|
|
|
|
|
+#assemble non-homogenous part of equation
|
|
|
|
|
+ if not rhs.size:
|
|
|
|
|
+#(N,n)
|
|
|
|
|
+ rhs=numpy.zeros(y.shape)
|
|
|
|
|
+
|
|
|
|
|
+ if u.size:
|
|
|
|
|
+#rhs is (n,n) so adding (n,) should work, rhs remains (N,n)
|
|
|
|
|
+ rhs+=u
|
|
|
|
|
+
|
|
|
|
|
+#apply couplings and inputs
|
|
|
|
|
+#(n,n)
|
|
|
|
|
+ M=model.M(0)
|
|
|
|
|
+#(N,n)=[(n,n) @ (N,n,1)] + (N,n)
|
|
|
|
|
+ fI=( M @ numpy.expand_dims(y,axis=2) ).squeeze() + rhs
|
|
|
|
|
+
|
|
|
|
|
+#only set values back to y for excrement compartments (scaled)
|
|
|
|
|
+ for c in model.scaled:
|
|
|
|
|
+ i=model.lut[c]
|
|
|
|
|
+ y[:,i]=fI[:,i]
|
|
|
|
|
+
|
|
|
|
|
+#(N,n)
|
|
|
|
|
+ return y
|
|
|
|
|
+
|