def add_conductance(G, kind, invivo, edges=None): """Adds conductance values to the edges of the graph (consult the relevant functions in the physiology module for more information. INPUT: G: Vascular graph in iGraph format. kind: The vessel kind. This can be either 'a' for artery or 'v' for vein. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations edges: (Optional.) The indices of the edges to be given a conductance value. If no indices are supplied, all edges are considered. """ P = Physiology(G['defaultUnits']) if edges is None: edgelist = G.es else: edgelist = G.es(edges) #for e in edgelist: #print('') #print(e['diameter']) #print(e['length']) #print(P.dynamic_blood_viscosity(e['diameter'],invivo,kind)) G.es(edgelist.indices)['conductance'] = \ [P.conductance(e['diameter'],e['length'], P.dynamic_blood_viscosity(e['diameter'], invivo,kind)) for e in edgelist]
def add_conductance(G,kind,invivo,edges=None): """Adds conductance values to the edges of the graph (consult the relevant functions in the physiology module for more information. INPUT: G: Vascular graph in iGraph format. kind: The vessel kind. This can be either 'a' for artery or 'v' for vein. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations edges: (Optional.) The indices of the edges to be given a conductance value. If no indices are supplied, all edges are considered. """ P = Physiology(G['defaultUnits']) if edges is None: edgelist = G.es else: edgelist = G.es(edges) #for e in edgelist: #print('') #print(e['diameter']) #print(e['length']) #print(P.dynamic_blood_viscosity(e['diameter'],invivo,kind)) G.es(edgelist.indices)['conductance'] = \ [P.conductance(e['diameter'],e['length'], P.dynamic_blood_viscosity(e['diameter'], invivo,kind)) for e in edgelist]
def __init__(self, G, **kwargs): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). pBC should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) withRBC: boolean if a fixed distribution of RBCs should be considered if 'htt' is an edge attribute the current distribution is used. Otherwise the value to be assinged for all vessels should be given. For arteries and veins a empirical value which is a function of the diameter is assigned. resistanceLength: boolean if diameter is not considered for the restistance and hence the resistance is only a function of the vessel length OUTPUT: A: Matrix A of the linear system, holding the conductance information. b: Vector b of the linear system, holding the boundary conditions. """ self._G = G self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() #Check if a arbirtrary distribution of RBCs should be considered if kwargs.has_key('withRBC'): if kwargs['withRBC']!=0: self._withRBC = kwargs['withRBC'] else: self._withRBC = 0 else: self._withRBC = 0 if kwargs.has_key('invivo'): if kwargs['invivo']!=0: self._invivo = kwargs['invivo'] else: self._invivo = 0 else: self._invivo = 0 if kwargs.has_key('resistanceLength'): if kwargs['resistanceLength']==1: self._resistanceLength = 1 print('Diameter not considered for calculation of resistance') else: self._resistanceLength = 0 else: self._resistanceLength = 0 if self._withRBC != 0: if 'htt' not in G.es.attribute_names(): G.es['htt']=[self._withRBC]*G.ecount() self._withRBC = 1 httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt']=[self._withRBC]*len(httNone) self.update(G) self._eps = np.finfo(float).eps
def add_pBCs(G,kind,vertices): """Adds pressure boundary conditions to the vascular graph. Pressure values are taken from literature (see function 'blood_pressure'). The pressure boundary vertices recieve a kind tag of either 'a' or 'v' to classify them as arteries or veins respectively. INPUT: G: Vascular graph in iGraph format. kind: The vertex kind. This can be either 'a' for arterial or 'v' for venous. vertices: The indices of the vertices for which the pressure boundary conditions are to be set. OUTPUT: G is modified in place. """ P = Physiology(G['defaultUnits']) for vertex in vertices: diameter = max([G.es[x]['diameter'] for x in G.adjacent(vertex,'all')]) G.vs[vertex]['pBC'] = P.blood_pressure(diameter,kind) G.vs[vertex]['kind'] = kind
def add_pBCs(G, kind, vertices): """Adds pressure boundary conditions to the vascular graph. Pressure values are taken from literature (see function 'blood_pressure'). The pressure boundary vertices recieve a kind tag of either 'a' or 'v' to classify them as arteries or veins respectively. INPUT: G: Vascular graph in iGraph format. kind: The vertex kind. This can be either 'a' for arterial or 'v' for venous. vertices: The indices of the vertices for which the pressure boundary conditions are to be set. OUTPUT: G is modified in place. """ P = Physiology(G['defaultUnits']) for vertex in vertices: diameter = max( [G.es[x]['diameter'] for x in G.adjacent(vertex, 'all')]) G.vs[vertex]['pBC'] = P.blood_pressure(diameter, kind) G.vs[vertex]['kind'] = kind
def __init__(self, G, withRBC = 0, invivo = 0, dMin_empirical = 3.5, htdMax_empirical = 0.6, verbose = True, diameterOverTime=[],**kwargs): """ Computes the flow and pressure field of a vascular graph without RBC tracking. It can be chosen between pure plasma flow, constant hematocrit or a given htt/htd distribution. The pressure boundary conditions (pBC) should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) invivo: boolean if the invivo or invitro empirical functions are used (default = 0) withRBC: = 0: no RBCs, pure plasma Flow (default) 0 < withRBC < 1 & 'htt' not in edgeAttributes: the given value is assigned as htt to all edges. 0 < withRBC < 1 & 'htt' in edgeAttributes: the given value is assigned as htt to all edges where htt = None. NOTE: Htd will be computed from htt and used to compute the resistance. If htd is already in the edge attributes, it will be overwritten. dMin_empiricial: lower limit for the diameter that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 3.5). htdMax_empirical: upper limit for htd that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 0.6). Maximum has to be 1. verbose: Bool if WARNINGS and setup information (INFO) is printed diameterOverTime: list of the diameterChanges over time. The length of the list is the number of time steps with diameter change. each diameter change should be provided as a tuple, e.g. two diameterChanges at the same time, a single diameter change afterwards. [[[edgeIndex1, edgeIndex1],[newDiameter1, newDiameter2]],[[edgeIndex3], [newDiameter3]]] OUTPUT: None, the edge properties htt is assgined and the function update is executed (see description for more details) """ self._G = G nVertices = G.vcount() self._b = np.zeros(nVertices) self._A = lil_matrix((nVertices,nVertices),dtype=float) self._eps = np.finfo(float).eps self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() self._withRBC = withRBC self._invivo = invivo self._verbose = verbose self._dMin_empirical = dMin_empirical self._htdMax_empirical = htdMax_empirical self._diameterOverTime = diameterOverTime self._timeSteps = len(diameterOverTime) self._scalingFactor = vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) if self._verbose: print('INFO: The limits for the compuation of the effective viscosity are set to') print('Minimum diameter %.2f' %self._dMin_empirical) print('Maximum discharge %.2f' %self._htdMax_empirical) if self._withRBC != 0: if self._withRBC < 1.: if 'htt' not in G.es.attribute_names(): G.es['htt']=[self._withRBC]*G.ecount() else: httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt']=[self._withRBC]*len(httNone) else: if self._verbose: print('WARNING: htt is already an edge attribute. \n Existing values are not overwritten!'+\ '\n If new values should be assigned htt has to be deleted beforehand!') else: print('ERROR: 0 < withRBC < 1') if 'rBC' not in G.vs.attribute_names(): G.vs['rBC'] = [None]*G.vcount() if 'pBC' not in G.vs.attribute_names(): G.vs['pBC'] = [None]*G.vcount() #Convert 'pBC' ['mmHG'] to default Units for v in G.vs(pBC_ne=None): v['pBC']=v['pBC']*self._scalingFactor if len(G.vs(pBC_ne=None)) > 0: if self._verbose: print('INFO: Pressure boundary conditions changed from mmHg --> default Units') self.update()
class LinearSystemTimeCourse(object): def __init__(self, G, withRBC = 0, invivo = 0, dMin_empirical = 3.5, htdMax_empirical = 0.6, verbose = True, diameterOverTime=[],**kwargs): """ Computes the flow and pressure field of a vascular graph without RBC tracking. It can be chosen between pure plasma flow, constant hematocrit or a given htt/htd distribution. The pressure boundary conditions (pBC) should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) invivo: boolean if the invivo or invitro empirical functions are used (default = 0) withRBC: = 0: no RBCs, pure plasma Flow (default) 0 < withRBC < 1 & 'htt' not in edgeAttributes: the given value is assigned as htt to all edges. 0 < withRBC < 1 & 'htt' in edgeAttributes: the given value is assigned as htt to all edges where htt = None. NOTE: Htd will be computed from htt and used to compute the resistance. If htd is already in the edge attributes, it will be overwritten. dMin_empiricial: lower limit for the diameter that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 3.5). htdMax_empirical: upper limit for htd that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 0.6). Maximum has to be 1. verbose: Bool if WARNINGS and setup information (INFO) is printed diameterOverTime: list of the diameterChanges over time. The length of the list is the number of time steps with diameter change. each diameter change should be provided as a tuple, e.g. two diameterChanges at the same time, a single diameter change afterwards. [[[edgeIndex1, edgeIndex1],[newDiameter1, newDiameter2]],[[edgeIndex3], [newDiameter3]]] OUTPUT: None, the edge properties htt is assgined and the function update is executed (see description for more details) """ self._G = G nVertices = G.vcount() self._b = np.zeros(nVertices) self._A = lil_matrix((nVertices,nVertices),dtype=float) self._eps = np.finfo(float).eps self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() self._withRBC = withRBC self._invivo = invivo self._verbose = verbose self._dMin_empirical = dMin_empirical self._htdMax_empirical = htdMax_empirical self._diameterOverTime = diameterOverTime self._timeSteps = len(diameterOverTime) self._scalingFactor = vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) if self._verbose: print('INFO: The limits for the compuation of the effective viscosity are set to') print('Minimum diameter %.2f' %self._dMin_empirical) print('Maximum discharge %.2f' %self._htdMax_empirical) if self._withRBC != 0: if self._withRBC < 1.: if 'htt' not in G.es.attribute_names(): G.es['htt']=[self._withRBC]*G.ecount() else: httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt']=[self._withRBC]*len(httNone) else: if self._verbose: print('WARNING: htt is already an edge attribute. \n Existing values are not overwritten!'+\ '\n If new values should be assigned htt has to be deleted beforehand!') else: print('ERROR: 0 < withRBC < 1') if 'rBC' not in G.vs.attribute_names(): G.vs['rBC'] = [None]*G.vcount() if 'pBC' not in G.vs.attribute_names(): G.vs['pBC'] = [None]*G.vcount() #Convert 'pBC' ['mmHG'] to default Units for v in G.vs(pBC_ne=None): v['pBC']=v['pBC']*self._scalingFactor if len(G.vs(pBC_ne=None)) > 0: if self._verbose: print('INFO: Pressure boundary conditions changed from mmHg --> default Units') self.update() #-------------------------------------------------------------------------- def update(self,esequence=None): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). INPUT: esequence: list of edges which need to be updated. Default=None, i.e. all edges will be updated OUTPUT: matrix A and vector b """ G = self._G A = self._A b = self._b htt2htd = self._P.tube_to_discharge_hematocrit nurel = self._P.relative_apparent_blood_viscosity # Compute nominal and specific resistance: self._update_nominal_and_specific_resistance(esequence=esequence) #edge and vertex List that need to be updated if esequence is None: es = G.es vertexList = G.vs else: es = G.es(esequence) vertexList = [] for edge in esequence: e = G.es[edge] vertexList.append(e.source) vertexList.append(e.target) vertexList = [int(v) for v in np.unique(vertexList)] vertexList = G.vs[vertexList] #if with RBCs compute effective resistance if self._withRBC: es['htd'] = [min(htt2htd(htt, d, self._invivo), 1.0) for htt,d in zip(es['htt'],es['diameter'])] es['effResistance'] =[ e['resistance'] * nurel(max(self._dMin_empirical,e['diameter']),\ min(e['htd'],self._htdMax_empirical),self._invivo) for e in es] es['conductance']=1/np.array(es['effResistance']) else: es['conductance'] = [1/e['resistance'] for e in es] for vertex in vertexList: i = vertex.index A.data[i] = [] A.rows[i] = [] b[i] = 0.0 if vertex['pBC'] is not None: A[i,i] = 1.0 b[i] = vertex['pBC'] else: aDummy=0 k=0 neighbors=[] for edge in G.incident(i,'all'): if G.is_loop(edge): continue j=G.neighbors(i)[k] k += 1 conductance = G.es[edge]['conductance'] neighbor = G.vs[j] # +=, -= account for multiedges aDummy += conductance if neighbor['pBC'] is not None: b[i] = b[i] + neighbor['pBC'] * conductance #elif neighbor['rBC'] is not None: # b[i] = b[i] + neighbor['rBC'] else: if j not in neighbors: A[i,j] = - conductance else: A[i,j] = A[i,j] - conductance neighbors.append(j) if vertex['rBC'] is not None: b[i] += vertex['rBC'] A[i,i]=aDummy self._A = A self._b = b #-------------------------------------------------------------------------- def solve(self, method): """Solves the linear system A x = b for the vector of unknown pressures x, either using a direct solver (obsolete) or an iterative GMRES solver. From the pressures, the flow field is computed. INPUT: method: This can be either 'direct' or 'iterative2' OUTPUT: None - G is modified in place. G_final.pkl & G_final.vtp: are save as output sampledict.pkl: is saved as output """ b = self._b A = self._A G = self._G htt2htd = self._P.tube_to_discharge_hematocrit A = self._A.tocsr() if method == 'direct': linalg.use_solver(useUmfpack=True) x = linalg.spsolve(A, b) elif method == 'iterative2': ml = rootnode_solver(A, smooth=('energy', {'degree':2}), strength='evolution' ) M = ml.aspreconditioner(cycle='V') # Solve pressure system x,info = gmres(A, self._b, tol=1000*self._eps, maxiter=200, M=M) if info != 0: print('ERROR in Solving the Matrix') print(info) G.vs['pressure'] = x G.es['flow'] = [abs(G.vs[edge.source]['pressure'] - G.vs[edge.target]['pressure']) * \ edge['conductance'] for edge in G.es] #Default Units - mmHg for pressure G.vs['pressure'] = [v['pressure']/self._scalingFactor for v in G.vs] if self._withRBC: G.es['v']=[e['htd']/e['htt']*e['flow']/(0.25*np.pi*e['diameter']**2) for e in G.es] else: G.es['v']=[e['flow']/(0.25*np.pi*e['diameter']**2) for e in G.es] #-------------------------------------------------------------------------- def _update_nominal_and_specific_resistance(self, esequence=None): """Updates the nominal and specific resistance of a given edge sequence. INPUT: esequence: Sequence of edge indices which have to be updated. If not provided, all edges are updated. OUTPUT: None, the edge properties 'resistance' and 'specificResistance' are updated (or created). """ G = self._G if esequence is None: es = G.es else: es = G.es(esequence) es['specificResistance'] = [128 * self._muPlasma / (np.pi * d**4) for d in es['diameter']] es['resistance'] = [l * sr for l, sr in zip(es['length'], es['specificResistance'])] #-------------------------------------------------------------------------- def evolve(self): """ The flow field is recomputed for changing vessel diameters over time. The changing vessel diameters have been provided as input in _init_ (diameterOverTime). OUTPUT: None - G is modified in place. sampledict.pkl: which saves the pressure for all vertices over time and flow, diameter and RBC velocity for all edges over time. """ G = self._G diameterOverTime = self._diameterOverTime flow_time_edges=[] diameter_time_edges=[] v_time_edges=[] pressure_time_edges=[] #First iteration for initial diameter distribution self.solve('iterative2') flow_time_edges.append(G.es['flow']) diameter_time_edges.append(G.es['diameter']) v_time_edges.append(G.es['v']) pressure_time_edges.append(G.vs['pressure']) for timeStep in range(self._timeSteps): edgeSequence = diameterOverTime[timeStep][0] G.es[edgeSequence]['diameter'] = diameterOverTime[timeStep][1] self.update(esequence=edgeSequence) self.solve('iterative2') flow_time_edges.append(G.es['flow']) diameter_time_edges.append(G.es['diameter']) v_time_edges.append(G.es['v']) pressure_time_edges.append(G.vs['pressure']) vgm.write_pkl(G,'G_'+str(timeStep)+'.pkl') flow_edges_time = np.transpose(flow_time_edges) diameter_edges_time = np.transpose(diameter_time_edges) v_edges_time = np.transpose(v_time_edges) pressure_edges_time = np.transpose(pressure_time_edges) #Write Output sampledict={} sampledict['flow']=flow_edges_time sampledict['diameter']=diameter_edges_time sampledict['v']=v_edges_time sampledict['pressure']=pressure_edges_time g_output.write_pkl(sampledict, 'sampledict.pkl') #Convert 'pBC' from default Units to mmHg pBCneNone=G.vs(pBC_ne=None) pBCneNone['pBC']=np.array(pBCneNone['pBC'])*(1/self._scalingFactor) if len(pBCneNone) > 0: if self._verbose: print('INFO: Pressure boundary conditions changed from default Units --> mmHg')
class LinearSystem(object): def __init__(self, G, **kwargs): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). pBC should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) withRBC: boolean if a fixed distribution of RBCs should be considered if 'htt' is an edge attribute the current distribution is used. Otherwise the value to be assinged for all vessels should be given. For arteries and veins a empirical value which is a function of the diameter is assigned. resistanceLength: boolean if diameter is not considered for the restistance and hence the resistance is only a function of the vessel length OUTPUT: A: Matrix A of the linear system, holding the conductance information. b: Vector b of the linear system, holding the boundary conditions. """ self._G = G self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() #Check if a arbirtrary distribution of RBCs should be considered if kwargs.has_key('withRBC'): if kwargs['withRBC']!=0: self._withRBC = kwargs['withRBC'] else: self._withRBC = 0 else: self._withRBC = 0 if kwargs.has_key('invivo'): if kwargs['invivo']!=0: self._invivo = kwargs['invivo'] else: self._invivo = 0 else: self._invivo = 0 if kwargs.has_key('resistanceLength'): if kwargs['resistanceLength']==1: self._resistanceLength = 1 print('Diameter not considered for calculation of resistance') else: self._resistanceLength = 0 else: self._resistanceLength = 0 if self._withRBC != 0: if 'htt' not in G.es.attribute_names(): G.es['htt']=[self._withRBC]*G.ecount() self._withRBC = 1 httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt']=[self._withRBC]*len(httNone) self.update(G) self._eps = np.finfo(float).eps #-------------------------------------------------------------------------- def update(self, newGraph=None): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). x will have the same units of [pressure] as the pBC vertices. Note that in this approach, A and b contain a mixture of dimensions, i.e. A and b have dimensions of [1.0] and [pressure] in the pBC case, [conductance] and [conductance*pressure] otherwise, the latter being rBCs. This has the advantage that no re-indexing is required as the matrices contain all vertices. INPUT: newGraph: Vascular graph in iGraph format to replace the previous self._G. (Optional, default=None.) OUTPUT: A: Matrix A of the linear system, holding the conductance information. b: Vector b of the linear system, holding the boundary conditions. """ htt2htd = self._P.tube_to_discharge_hematocrit nurel = self._P.relative_apparent_blood_viscosity if newGraph is not None: self._G = newGraph G = self._G if not G.vs[0].attributes().has_key('pBC'): G.vs[0]['pBC'] = None if not G.vs[0].attributes().has_key('rBC'): G.vs[0]['rBC'] = None #Convert 'pBC' ['mmHG'] to default Units pBCneNone=G.vs(pBC_ne=None).indices for i in pBCneNone: v=G.vs[i] v['pBC']=v['pBC']*vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) nVertices = G.vcount() b = np.zeros(nVertices) A = lil_matrix((nVertices,nVertices),dtype=float) # Compute nominal and specific resistance: self._update_nominal_and_specific_resistance() #if with RBCs compute effective resistance if self._withRBC: dischargeHt = [min(htt2htd(e, d, self._invivo), 1.0) for e,d in zip(G.es['htt'],G.es['diameter'])] G.es['effResistance'] =[ res * nurel(max(4.0,d),min(dHt,0.6),self._invivo) for res,dHt,d in zip(G.es['resistance'], \ dischargeHt,G.es['diameter'])] G.es['conductance']=1/np.array(G.es['effResistance']) else: # Compute conductance for e in G.es: e['conductance']=1/e['resistance'] #if not bound_cond is None: # self._conductance = [max(min(c, bound_cond[1]), bound_cond[0]) # for c in G.es['conductance']] #else: # self._conductance = G.es['conductance'] self._conductance = G.es['conductance'] for vertex in G.vs: i = vertex.index A.data[i] = [] A.rows[i] = [] b[i] = 0.0 if vertex['pBC'] is not None: A[i,i] = 1.0 b[i] = vertex['pBC'] else: aDummy=0 k=0 neighbors=[] for edge in G.adjacent(i,'all'): if G.is_loop(edge): continue j=G.neighbors(i)[k] k += 1 conductance = G.es[edge]['conductance'] neighbor = G.vs[j] # +=, -= account for multiedges aDummy += conductance if neighbor['pBC'] is not None: b[i] = b[i] + neighbor['pBC'] * conductance #elif neighbor['rBC'] is not None: # b[i] = b[i] + neighbor['rBC'] else: if j not in neighbors: A[i,j] = - conductance else: A[i,j] = A[i,j] - conductance neighbors.append(j) if vertex['rBC'] is not None: b[i] += vertex['rBC'] A[i,i]=aDummy self._A = A self._b = b #-------------------------------------------------------------------------- def solve(self, method, **kwargs): """Solves the linear system A x = b for the vector of unknown pressures x, either using a direct solver or an iterative AMG solver. From the pressures, the flow field is computed. INPUT: method: This can be either 'direct' or 'iterative' **kwargs precision: The accuracy to which the ls is to be solved. If not supplied, machine accuracy will be used. maxiter: The maximum number of iterations. The default value for the iterative solver is 250. OUTPUT: None - G is modified in place. """ b = self._b G = self._G htt2htd = self._P.tube_to_discharge_hematocrit A = self._A.tocsr() if method == 'direct': linalg.use_solver(useUmfpack=True) x = linalg.spsolve(A, b) elif method == 'iterative': if kwargs.has_key('precision'): eps = kwargs['precision'] else: eps = self._eps if kwargs.has_key('maxiter'): maxiter = kwargs['maxiter'] else: maxiter = 250 AA = pyamg.smoothed_aggregation_solver(A, max_levels=10, max_coarse=500) x = abs(AA.solve(self._b, x0=None, tol=eps, accel='cg', cycle='V', maxiter=maxiter)) # abs required, as (small) negative pressures may arise elif method == 'iterative2': # Set linear solver ml = rootnode_solver(A, smooth=('energy', {'degree':2}), strength='evolution' ) M = ml.aspreconditioner(cycle='V') # Solve pressure system x,info = gmres(A, self._b, tol=self._eps, maxiter=50, M=M) if info != 0: print('ERROR in Solving the Matrix') G.vs['pressure'] = x self._x = x conductance = self._conductance G.es['flow'] = [abs(G.vs[edge.source]['pressure'] - \ G.vs[edge.target]['pressure']) * \ conductance[i] for i, edge in enumerate(G.es)] for v in G.vs: v['pressure']=v['pressure']/vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) if self._withRBC: for e in G.es: dischargeHt = min(htt2htd(e['htt'], e['diameter'], self._invivo), 1.0) e['v']=dischargeHt/e['htt']*e['flow']/(0.25*np.pi*e['diameter']**2) else: for e in G.es: e['v']=e['flow']/(0.25*np.pi*e['diameter']**2) #Convert 'pBC' from default Units to mmHg pBCneNone=G.vs(pBC_ne=None).indices if 'diamCalcEff' in G.es.attribute_names(): del(G.es['diamCalcEff']) if 'effResistance' in G.es.attribute_names(): del(G.es['effResistance']) if 'conductance' in G.es.attribute_names(): del(G.es['conductance']) if 'resistance' in G.es.attribute_names(): del(G.es['resistance']) G.vs[pBCneNone]['pBC']=np.array(G.vs[pBCneNone]['pBC'])*(1/vgm.units.scaling_factor_du('mmHg',G['defaultUnits'])) vgm.write_pkl(G, 'G_final.pkl') vgm.write_vtp(G, 'G_final.vtp',False) #Write Output sampledict={} for eprop in ['flow', 'v']: if not eprop in sampledict.keys(): sampledict[eprop] = [] sampledict[eprop].append(G.es[eprop]) for vprop in ['pressure']: if not vprop in sampledict.keys(): sampledict[vprop] = [] sampledict[vprop].append(G.vs[vprop]) g_output.write_pkl(sampledict, 'sampledict.pkl') #-------------------------------------------------------------------------- def _verify_mass_balance(self): """Computes the mass balance, i.e. sum of flows at each node and adds the result as a vertex property 'flowSum'. INPUT: None OUTPUT: None (result added as vertex property) """ G = self._G G.vs['flowSum'] = [sum([G.es[e]['flow'] * np.sign(G.vs[v]['pressure'] - G.vs[n]['pressure']) for e, n in zip(G.adjacent(v), G.neighbors(v))]) for v in xrange(G.vcount())] for i in range(G.vcount()): if G.vs[i]['flowSum'] > self._eps: print('') print(i) print(G.vs['flowSum'][i]) #print(self._res[i]) print('ERROR') for j in G.adjacent(i): print(G.es['flow'][j]) #-------------------------------------------------------------------------- def _verify_p_consistency(self): """Checks for local pressure maxima at non-pBC vertices. INPUT: None. OUTPUT: A list of local pressure maxima vertices and the maximum pressure difference to their respective neighbors.""" G = self._G localMaxima = [] for i, v in enumerate(G.vs): if v['pBC'] is None: pdiff = [v['pressure'] - n['pressure'] for n in G.vs[G.neighbors(i)]] if min(pdiff) > 0: localMaxima.append((i, max(pdiff))) return localMaxima #-------------------------------------------------------------------------- def _residual_norm(self): """Computes the norm of the current residual. """ return np.linalg.norm(self._A * self._x - self._b) #-------------------------------------------------------------------------- def _update_nominal_and_specific_resistance(self, esequence=None): """Updates the nominal and specific resistance of a given edge sequence. INPUT: es: Sequence of edge indices as tuple. If not provided, all edges are updated. OUTPUT: None, the edge properties 'resistance' and 'specificResistance' are updated (or created). """ G = self._G if esequence is None: es = G.es else: es = G.es(esequence) if self._resistanceLength: G.es['specificResistance'] = [1]*G.ecount() else: G.es['specificResistance'] = [128 * self._muPlasma / (np.pi * d**4) for d in G.es['diameter']] G.es['resistance'] = [l * sr for l, sr in zip(G.es['length'], G.es['specificResistance'])]
def __init__(self, G, invivo=True, assert_pBCs=True, resetHtd=True, **kwargs): """Initializes a LinearSystemPries instance. INPUT: G: Vascular graph in iGraph format. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. resetHtd: (Optional, default=True.) Boolean whether or not to reset the discharge hematocrit of the VascularGraph at initialization. It may be useful to preserve the original htd-distribution, if only a minor change from the current state is to be expected (faster to obtain the solution). **kwargs: httBC: tube hematocrit boundary condition at inflow (edge) htdBC: discharge hematocrit boundary condition at inflow (vertex) plasmaType: if it is not given, the default value is used. option two: --> francesco: plasma value of francescos simulations species: what type of animal we are dealing with --> relevant for the rbc volume that is used, default is rat OUTPUT: None """ self._G = G self._invivo = invivo self._P = Physiology(G['defaultUnits']) self._eps = 1e-7 #finfo(float).eps * 1e4 htt2htd = self._P.tube_to_discharge_hematocrit if kwargs.has_key('httBC'): for vi in G['av']: for ei in G.adjacent(vi): G.es[ei]['httBC'] = kwargs['httBC'] htdBC = htt2htd(kwargs['httBC'], G.es[ei]['diameter'], invivo) G.vs[vi]['htdBC'] = htdBC if kwargs.has_key('htdBC'): for vi in G['av']: G.vs[vi]['htdBC'] = kwargs['htdBC'] if kwargs.has_key('plasmaType'): self._plasmaType = kwargs['plasmaType'] else: self._plasmaType = 'default' if kwargs.has_key('species'): self._species = kwargs['species'] else: self._species = 'rat' # Discharge hematocrit boundary conditions: if not 'htdBC' in G.vs.attribute_names(): for vi in G['av']: htdlist = [] for ei in G.adjacent(vi): if 'httBC' in G.es.attribute_names(): if G.es[ei]['httBC'] != None: htdBC = htt2htd(G.es[ei]['httBC'], G.es[ei]['diameter'], invivo) G.vs[vi]['htdBC'] = htdBC else: for ei in G.adjacent(vi): htdlist.append( self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) else: for ei in G.adjacent(vi): htdlist.append( self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) #Convert 'pBC' ['mmHg'] to default Units for v in G.vs: if v['pBC'] != None: v['pBC'] = v['pBC'] * vgm.units.scaling_factor_du( 'mmHg', G['defaultUnits']) # Initial RBC flow, hematocrit, conductance, pressure and flow: G.vs['pressure'] = [0.0 for v in G.vs] G.es['rbcFlow'] = [0.0 for e in G.es] if resetHtd: G.es['htd'] = [0.0 for e in G.es] if not G.vs[0].attributes().has_key('pBC'): G.vs[0]['pBC'] = None if not G.vs[0].attributes().has_key('rBC'): G.vs[0]['rBC'] = None nVertices = G.vcount() self._b = zeros(nVertices) self._A = lil_matrix((nVertices, nVertices), dtype=float) self._update_conductance_and_LS(G, assert_pBCs) self._linear_analysis('iterative2') self._rheological_analysis(None, True, 1.0) self._linear_analysis('iterative2')
class LinearSystemPries(object): """Solves a VascularGraph for pressure and flow. The influence of red blood cells is taken explicitly into account. This is an iterative method consisting of two main parts: the linear analysis and the rheological analysis. In the linear analysis part (which gives this class its name), a linear system Ax = b is constructed from current vessel conduction values and solved to yield pressure and flow. In the rheological analysis part, a hematocrit redistribution is performed based on current flow values and the empirical relations found by Pries et al. (1990). Note that the method as a whole is non-linear. """ def __init__(self, G, invivo=True, assert_pBCs=True, resetHtd=True, **kwargs): """Initializes a LinearSystemPries instance. INPUT: G: Vascular graph in iGraph format. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. resetHtd: (Optional, default=True.) Boolean whether or not to reset the discharge hematocrit of the VascularGraph at initialization. It may be useful to preserve the original htd-distribution, if only a minor change from the current state is to be expected (faster to obtain the solution). **kwargs: httBC: tube hematocrit boundary condition at inflow (edge) htdBC: discharge hematocrit boundary condition at inflow (vertex) plasmaType: if it is not given, the default value is used. option two: --> francesco: plasma value of francescos simulations species: what type of animal we are dealing with --> relevant for the rbc volume that is used, default is rat OUTPUT: None """ self._G = G self._invivo = invivo self._P = Physiology(G['defaultUnits']) self._eps = 1e-7 #finfo(float).eps * 1e4 htt2htd = self._P.tube_to_discharge_hematocrit if kwargs.has_key('httBC'): for vi in G['av']: for ei in G.adjacent(vi): G.es[ei]['httBC'] = kwargs['httBC'] htdBC = htt2htd(kwargs['httBC'], G.es[ei]['diameter'], invivo) G.vs[vi]['htdBC'] = htdBC if kwargs.has_key('htdBC'): for vi in G['av']: G.vs[vi]['htdBC'] = kwargs['htdBC'] if kwargs.has_key('plasmaType'): self._plasmaType = kwargs['plasmaType'] else: self._plasmaType = 'default' if kwargs.has_key('species'): self._species = kwargs['species'] else: self._species = 'rat' # Discharge hematocrit boundary conditions: if not 'htdBC' in G.vs.attribute_names(): for vi in G['av']: htdlist = [] for ei in G.adjacent(vi): if 'httBC' in G.es.attribute_names(): if G.es[ei]['httBC'] != None: htdBC = htt2htd(G.es[ei]['httBC'], G.es[ei]['diameter'], invivo) G.vs[vi]['htdBC'] = htdBC else: for ei in G.adjacent(vi): htdlist.append( self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) else: for ei in G.adjacent(vi): htdlist.append( self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) #Convert 'pBC' ['mmHg'] to default Units for v in G.vs: if v['pBC'] != None: v['pBC'] = v['pBC'] * vgm.units.scaling_factor_du( 'mmHg', G['defaultUnits']) # Initial RBC flow, hematocrit, conductance, pressure and flow: G.vs['pressure'] = [0.0 for v in G.vs] G.es['rbcFlow'] = [0.0 for e in G.es] if resetHtd: G.es['htd'] = [0.0 for e in G.es] if not G.vs[0].attributes().has_key('pBC'): G.vs[0]['pBC'] = None if not G.vs[0].attributes().has_key('rBC'): G.vs[0]['rBC'] = None nVertices = G.vcount() self._b = zeros(nVertices) self._A = lil_matrix((nVertices, nVertices), dtype=float) self._update_conductance_and_LS(G, assert_pBCs) self._linear_analysis('iterative2') self._rheological_analysis(None, True, 1.0) self._linear_analysis('iterative2') #-------------------------------------------------------------------------- def _update_conductance_and_LS(self, newGraph=None, assert_pBCs=True): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). x will have the same units of [pressure] as the pBC vertices. Note that in this approach, A and b contain a mixture of dimensions, i.e. A and b have dimensions of [1.0] and [pressure] in the pBC case, [conductance] and [conductance*pressure] otherwise, the latter being rBCs. This has the advantage that no re-indexing is required as the matrices contain all vertices. INPUT: newGraph: Vascular graph in iGraph format to replace the previous self._G. (Optional, default=None.) assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. OUTPUT: A: Matrix A of the linear system, holding the conductance information. b: Vector b of the linear system, holding the boundary conditions. """ if newGraph is not None: self._G = newGraph G = self._G P = self._P invivo = self._invivo cond = P.conductance nublood = P.dynamic_blood_viscosity G.es['conductance'] = [ cond( e['diameter'], e['length'], nublood(e['diameter'], invivo, discharge_ht=e['htd'], plasmaType=self._plasmaType)) for e in G.es ] G.es['conductance'] = [ max(min(c, 1e5), 1e-5) for c in G.es['conductance'] ] A = self._A b = self._b if assert_pBCs: # Ensure that the problem is well posed in terms of BCs. # This takes care of unconnected nodes as well as connected # components of the graph that have not been assigned a minimum of # one pressure boundary condition: for component in G.components(): if all(map(lambda x: x is None, G.vs(component)['pBC'])): i = component[0] G.vs[i]['pBC'] = 0.0 for vertex in G.vs: i = vertex.index A.data[i] = [] A.rows[i] = [] b[i] = 0.0 if vertex['pBC'] is not None: A[i, i] = 1.0 b[i] = vertex['pBC'] else: aDummy = 0 k = 0 neighbors = [] for edge in G.adjacent(i, 'all'): if G.is_loop(edge): continue j = G.neighbors(i)[k] k += 1 conductance = G.es[edge]['conductance'] neighbor = G.vs[j] # +=, -= account for multiedges aDummy += conductance if neighbor['pBC'] is not None: b[i] = b[i] + neighbor['pBC'] * conductance #elif neighbor['rBC'] is not None: # b[i] = b[i] + neighbor['rBC'] else: if j not in neighbors: A[i, j] = -conductance else: A[i, j] = A[i, j] - conductance neighbors.append(j) if vertex['rBC'] is not None: b[i] += vertex['rBC'] A[i, i] = aDummy self._A = A self._b = b self._G = G #-------------------------------------------------------------------------- def _update_rbc_flow(self, limiter=0.5): """Traverses all vertices ordered by pressure from high to low. Distributes the red cell flow of the mother vessel to the daughter vessels according to an empirical relation. Note that plasma and RBC flow are conserved at bifurcations, however, discharge hematocrit is not a conservative quantity. INPUT: limiter: Limits change from one iteration level to the next, if < 1.0, at limiter == 1.0 the change is unmodified. OUTPUT: None, edge properties 'rbcFlow' and 'htd' are modified in-place. """ # This limit ensures that the hematocrit stays within the physically # possible bounds: htdLimit = 0.99 # Short notation: G = self._G eps = self._eps pse = self._P.phase_separation_effect #pse = self._P.phase_separation_effect_step # Copy current htd: oldRbcFlow = copy.deepcopy(G.es['rbcFlow']) G.es['rbcFlow'] = [0.0 for e in G.es] G.es['htd'] = [0.0 for e in G.es] # Vertices sorted by pressure: pSortedVertices = sorted([(v['pressure'], v.index) for v in G.vs], reverse=True) # Loop through vertices, distributing discharge hematocrit: for vertexPressure, vertex in pSortedVertices: # Determine in- and outflow edges: outEdges = [] inEdges = [] for neighbor, edge in zip(G.neighbors(vertex, 'all'), G.adjacent(vertex, 'all')): if G.vs[neighbor]['pressure'] < vertexPressure - eps: outEdges.append(edge) elif G.vs[neighbor]['pressure'] > vertexPressure + eps: inEdges.append(edge) # The rbc flow is computed from htdBC and the red cells entering # from the mother vessels (if any exist). In case of multiple # mother vessels, each is distributed according to the empirical # relation. If there are more than two daughter edges, the # emperical relation is applied to all possible pairings and the # final fractional flow to each daughter is computed in a # hierarchical fashion (see below). trimmedOutEdges = copy.deepcopy(outEdges) for outEdge in outEdges: if G.es[outEdge]['flow'] <= eps: trimmedOutEdges.remove(outEdge) elif not (G.vs[vertex]['htdBC'] is None): G.es[outEdge]['rbcFlow'] = G.vs[vertex]['htdBC'] * \ G.es[outEdge]['flow'] G.es[outEdge]['htd'] = G.vs[vertex]['htdBC'] trimmedOutEdges.remove(outEdge) # Only edges without hematocrit BC are included in the distribution # algorithm: outEdges = copy.deepcopy(trimmedOutEdges) NoOutEdges = len(outEdges) if len(outEdges) == 0: continue elif len(outEdges) == 1: outEdge = outEdges[0] rbcFlowIn = 0.0 FlowIn = 0.0 for inEdge in inEdges: rbcFlowIn += G.es[inEdge]['rbcFlow'] FlowIn += G.es[inEdge]['flow'] if len(inEdges) == 0: G.es[outEdge]['htd'] = G.es[outEdge]['htdBC'] G.es[outEdge]['rbcFlow'] = G.es[outEdge]['flow'] * G.es[ outEdge]['htd'] else: G.es[outEdge]['rbcFlow'] = rbcFlowIn G.es[outEdge]['htd'] = min(rbcFlowIn / FlowIn, htdLimit) if G.es[outEdge]['htd'] < 0: print('ERROR 1 htd smaller than 0') else: rbcFlowIn = 0.0 edgepairs = list(itertools.combinations(outEdges, 2)) for inEdge in inEdges: df = G.es[inEdge]['diameter'] htdIn = G.es[inEdge]['htd'] rbcFlowIn += G.es[inEdge]['rbcFlow'] outFractions = dict(zip(outEdges, [[] for e in outEdges])) for edgepair in edgepairs: oe0, oe1 = G.es[edgepair] flowSum = sum(G.es[edgepair]['flow']) if flowSum > 0.0: relativeValue = oe0['flow'] / flowSum #stdout.write("\r oe0/flowSum = %f \n" % relativeValue) #if oe0['flow']/flowSum > 0.49 and oe0['flow']/flowSum < 0.51: #stdout.write("\r NOW \n") f0 = pse(oe0['flow'] / flowSum, oe0['diameter'], oe1['diameter'], df, htdIn) #f0 = pse(oe0['flow'] / flowSum, # 0.7, 0.7, 0.7, 0.64) f1 = 1 - f0 else: f0 = 0 f1 = 0 outFractions[oe0.index].append(f0) outFractions[oe1.index].append(f1) # Sort out-edges from highest to lowest out fraction and # distribute RBC flow accordingly: sortedOutEdges = sorted(zip( map(sum, outFractions.values()), outFractions.keys()), reverse=True) remainingFraction = 1.0 for i, soe in enumerate(sortedOutEdges[:-1]): outEdge = soe[1] outFractions[outEdge] = remainingFraction * \ sorted(outFractions[outEdge])[i] remainingFraction -= outFractions[outEdge] remainingFraction = max(remainingFraction, 0.0) outFractions[sortedOutEdges[-1][1]] = remainingFraction #Outflow in second outEdge is calculated by the difference of inFlow and OutFlow first #outEdge count = 0 for outEdge in outEdges: G.es[outEdge]['rbcFlow'] += G.es[inEdge]['rbcFlow'] * \ outFractions[outEdge] #stdout.write("\r outEdge = %g \n" %outEdge) #stdout.write("\r G.es[outEdge]['rbcFlow'] = %f \n" %G.es[outEdge]['rbcFlow']) if count == 0: G.es[outEdge]['rbcFlow'] = oldRbcFlow[outEdge] + \ (G.es[outEdge]['rbcFlow'] - oldRbcFlow[outEdge]) * limiter elif count == 1: G.es[outEdge]['rbcFlow'] = G.es[inEdge][ 'rbcFlow'] - G.es[outEdges[0]]['rbcFlow'] #stdout.write("\r LIMITED: G.es[outEdge]['rbcFlow'] = %f \n" %G.es[outEdge]['rbcFlow']) count += 1 # Limit change between iteration levels for numerical # stability. Note that this is only applied to the diverging # bifurcations: for outEdge in outEdges: if G.es[outEdge]['flow'] > eps: G.es[outEdge]['htd'] = min(G.es[outEdge]['rbcFlow'] / \ G.es[outEdge]['flow'], htdLimit) if G.es[outEdge]['htd'] < 0: print('ERROR 2 htd smaller than 0') #stdout.write("\r outEdge = %g \n" %outEdge) #stdout.write("\r outFractions[outEdge] = %f \n" %outFractions[outEdge]) #stdout.write("\r G.es[outEdge]['flow'] = %f \n" %G.es[outEdge]['flow']) #stdout.write("\r G.es[outEdge]['htd'] = %f \n" %G.es[outEdge]['htd']) #-------------------------------------------------------------------------- def _linear_analysis(self, method, **kwargs): """Performs the linear analysis, in which the pressure and flow fields are computed. INPUT: method: This can be either 'direct' or 'iterative' **kwargs precision: The accuracy to which the ls is to be solved. If not supplied, machine accuracy will be used. (This only applies to the iterative solver) OUTPUT: The maximum, mean, and median pressure change. Moreover, pressure and flow are modified in-place. """ G = self._G A = self._A.tocsr() if method == 'direct': linalg.use_solver(useUmfpack=True) x = linalg.spsolve(A, self._b) elif method == 'iterative': if kwargs.has_key('precision'): eps = kwargs['precision'] else: eps = self._eps AA = smoothed_aggregation_solver(A, max_levels=10, max_coarse=500) x = abs( AA.solve(self._b, x0=None, tol=eps, accel='cg', cycle='V', maxiter=150)) # abs required, as (small) negative pressures may arise elif method == 'iterative2': # Set linear solver ml = rootnode_solver(A, smooth=('energy', { 'degree': 2 }), strength='evolution') M = ml.aspreconditioner(cycle='V') # Solve pressure system #x,info = gmres(A, self._b, tol=self._eps, maxiter=50, M=M, x0=self._x) #x,info = gmres(A, self._b, tol=self._eps/10000000000000, maxiter=50, M=M) x, info = gmres(A, self._b, tol=self._eps / 10000, maxiter=50, M=M) if info != 0: print('SOLVEERROR in Solving the Matrix') pdiff = map(abs, [(p - xx) / p if p > 0 else 0.0 for p, xx in zip(G.vs['pressure'], x)]) maxPDiff = max(pdiff) meanPDiff = np.mean(pdiff) medianPDiff = np.median(pdiff) log.debug(np.nonzero(np.array(pdiff) == maxPDiff)[0]) G.vs['pressure'] = x G.es['flow'] = [abs(G.vs[edge.source]['pressure'] - \ G.vs[edge.target]['pressure']) * \ edge['conductance'] for edge in G.es] self._maxPDiff = maxPDiff self._meanPDiff = meanPDiff self._medianPDiff = medianPDiff return maxPDiff, meanPDiff, medianPDiff #-------------------------------------------------------------------------- def _rheological_analysis(self, newGraph=None, assert_pBCs=True, limiter=0.5): """Performs the rheological analysis, in which the discharge hematocrit and apparent viscosity (and thus the new conductances of the vessels) are computed. INPUT: newGraph: Vascular graph in iGraph format to replace the previous self._G. (Optional, default=None.) assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. limiter: Limits change from one iteration level to the next, if > 1.0, at limiter == 1.0 the change is unmodified. OUTPUT: None, edge properties 'htd' and 'conductance' are modified in-place. """ self._update_rbc_flow(limiter) self._update_conductance_and_LS(newGraph, assert_pBCs) #-------------------------------------------------------------------------- def solve(self, method, precision=None, maxIterations=1e4, limiter=0.5, **kwargs): """Solve for pressure, flow, hematocrit and conductance by iterating over linear and rheological analysis. Stop when either the desired accuracy has been achieved or the maximum number of iterations have been performed. INPUT: method: This can be either 'direct' or 'iterative' precision: Desired precision, measured in the maximum amount by which the pressure may change from one iteration level to the next. If the maximum change is below threshold, the iteration is aborted. maxIterations: The maximum number of iterations to perform. limiter: Limits change from one iteration level to the next, if > 1.0, at limiter == 1.0 the change is unmodified. **kwargs precisionLS: The accuracy to which the ls is to be solved. If not supplied, machine accuracy will be used. (This only applies to the iterative solver) OUTPUT: None, the vascular graph is modified in-place. """ G = self._G P = self._P invivo = self._invivo if precision is None: precision = self._eps iterationCount = 0 maxPDiff = 1e200 filename = 'iter_' + str(iterationCount) + '.vtp' g_output.write_vtp(G, filename, False) filenames = [filename] convergenceHistory = [] while iterationCount < maxIterations and maxPDiff > precision: stdout.write("\rITERATION %g \n" % iterationCount) self._rheological_analysis(None, False, limiter=0.5) maxPDiff, meanPDiff, medianPDiff = self._linear_analysis( method, **kwargs) #maxPDiff=self._maxPDiff #meanPDiff=self._meanPDiff #medianPDiff=self._medianPDiff log.info('Iteration %i of %i' % (iterationCount + 1, maxIterations)) log.info('maximum pressure change: %.2e' % maxPDiff) log.info('mean pressure change: %.2e' % meanPDiff) log.info('median pressure change: %.2e\n' % medianPDiff) convergenceHistory.append((maxPDiff, meanPDiff, medianPDiff)) iterationCount += 1 G.es['htt'] = [ P.discharge_to_tube_hematocrit(e['htd'], e['diameter'], invivo) for e in G.es ] vrbc = P.rbc_volume(self._species) G.es['nMax'] = [ np.pi * e['diameter']**2 / 4 * e['length'] / vrbc for e in G.es ] G.es['minDist'] = [e['length'] / e['nMax'] for e in G.es] G.es['nRBC'] = [ e['htt'] * e['length'] / e['minDist'] for e in G.es ] filename = 'iter_' + str(iterationCount) + '.vtp' g_output.write_vtp(G, filename, False) filenames.append(filename) if iterationCount >= maxIterations: stdout.write("\rMaximum number of iterations reached\n") elif maxPDiff <= precision: stdout.write("\rPrecision limit is reached\n") self.integrity_check() G.es['htt'] = [ P.discharge_to_tube_hematocrit(e['htd'], e['diameter'], invivo) for e in G.es ] vrbc = P.rbc_volume(self._species) G.es['nMax'] = [ np.pi * e['diameter']**2 / 4 * e['length'] / vrbc for e in G.es ] G.es['minDist'] = [e['length'] / e['nMax'] for e in G.es] G.es['nRBC'] = [e['htt'] * e['length'] / e['minDist'] for e in G.es] G.es['v'] = [ 4 * e['flow'] * P.velocity_factor(e['diameter'], invivo, tube_ht=e['htt']) / (np.pi * e['diameter']**2) if e['htt'] > 0 else 4 * e['flow'] / (np.pi * e['diameter']**2) for e in G.es ] for v in G.vs: v['pressure'] = v['pressure'] / vgm.units.scaling_factor_du( 'mmHg', G['defaultUnits']) filename = 'iter_final.vtp' g_output.write_vtp(G, filename, False) filenames.append(filename) g_output.write_pvd_time_series('sequence.pvd', filenames) vgm.write_pkl(G, 'G_final.pkl') stdout.flush() return convergenceHistory return G #-------------------------------------------------------------------------- def integrity_check(self): """Asserts that mass conservation is honored by writing the sum of in- and outflow as well as the sum of the in- and outgoing discharge hematocrit to the graph vertices as 'flowSum' and 'htdSum' INPUT: None OUTPUT: None, flow and htd sums added as vertex properties in-place. """ G = self._G eps = self._eps G.es['plasmaFlow'] = [(1 - e['htd']) * e['flow'] for e in G.es] for v in G.vs: vertex = v.index vertexPressure = v['pressure'] outEdges = [] inEdges = [] for neighbor, edge in zip(G.neighbors(vertex, 'all'), G.adjacent(vertex, 'all')): if G.vs[neighbor]['pressure'] < vertexPressure: outEdges.append(edge) elif G.vs[neighbor]['pressure'] >= vertexPressure: inEdges.append(edge) v['flowSum'] = sum(G.es[outEdges]['flow']) - \ sum(G.es[inEdges]['flow']) v['rbcFlowSum'] = sum(G.es[outEdges]['rbcFlow']) - \ sum(G.es[inEdges]['rbcFlow']) v['plasmaFlowSum'] = sum(G.es[outEdges]['plasmaFlow']) - \ sum(G.es[inEdges]['plasmaFlow']) for e in G.es: e['errorHtd'] = abs(e['rbcFlow'] / e['flow'] - e['htd']) \ if e['flow'] > eps else 0.0 log.info('Max error htd: %.1g' % max(G.es['errorHtd']))
def __init__(self, G, invivo=True,assert_pBCs=True, resetHtd=True,**kwargs): """Initializes a LinearSystemPries instance. INPUT: G: Vascular graph in iGraph format. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. resetHtd: (Optional, default=True.) Boolean whether or not to reset the discharge hematocrit of the VascularGraph at initialization. It may be useful to preserve the original htd-distribution, if only a minor change from the current state is to be expected (faster to obtain the solution). **kwargs: httBC: tube hematocrit boundary condition at inflow (edge) htdBC: discharge hematocrit boundary condition at inflow (vertex) plasmaType: if it is not given, the default value is used. option two: --> francesco: plasma value of francescos simulations species: what type of animal we are dealing with --> relevant for the rbc volume that is used, default is rat OUTPUT: None """ self._G = G self._invivo=invivo self._P = Physiology(G['defaultUnits']) self._eps = 1e-7 #finfo(float).eps * 1e4 htt2htd=self._P.tube_to_discharge_hematocrit if kwargs.has_key('httBC'): for vi in G['av']: for ei in G.adjacent(vi): G.es[ei]['httBC']=kwargs['httBC'] htdBC = htt2htd(kwargs['httBC'],G.es[ei]['diameter'],invivo) G.vs[vi]['htdBC']=htdBC if kwargs.has_key('htdBC'): for vi in G['av']: G.vs[vi]['htdBC']=kwargs['htdBC'] if kwargs.has_key('plasmaType'): self._plasmaType=kwargs['plasmaType'] else: self._plasmaType='default' if kwargs.has_key('species'): self._species=kwargs['species'] else: self._species='rat' # Discharge hematocrit boundary conditions: if not 'htdBC' in G.vs.attribute_names(): for vi in G['av']: htdlist = [] for ei in G.adjacent(vi): if 'httBC' in G.es.attribute_names(): if G.es[ei]['httBC'] != None: htdBC = htt2htd(G.es[ei]['httBC'],G.es[ei]['diameter'],invivo) G.vs[vi]['htdBC']=htdBC else: for ei in G.adjacent(vi): htdlist.append(self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) else: for ei in G.adjacent(vi): htdlist.append(self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) #Convert 'pBC' ['mmHg'] to default Units for v in G.vs: if v['pBC'] != None: v['pBC']=v['pBC']*vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) # Initial RBC flow, hematocrit, conductance, pressure and flow: G.vs['pressure'] = [0.0 for v in G.vs] G.es['rbcFlow'] = [0.0 for e in G.es] if resetHtd: G.es['htd'] = [0.0 for e in G.es] if not G.vs[0].attributes().has_key('pBC'): G.vs[0]['pBC'] = None if not G.vs[0].attributes().has_key('rBC'): G.vs[0]['rBC'] = None nVertices = G.vcount() self._b = zeros(nVertices) self._A = lil_matrix((nVertices,nVertices),dtype=float) self._update_conductance_and_LS(G, assert_pBCs) self._linear_analysis('iterative2') self._rheological_analysis(None, True, 1.0) self._linear_analysis('iterative2')
class LinearSystemPries(object): """Solves a VascularGraph for pressure and flow. The influence of red blood cells is taken explicitly into account. This is an iterative method consisting of two main parts: the linear analysis and the rheological analysis. In the linear analysis part (which gives this class its name), a linear system Ax = b is constructed from current vessel conduction values and solved to yield pressure and flow. In the rheological analysis part, a hematocrit redistribution is performed based on current flow values and the empirical relations found by Pries et al. (1990). Note that the method as a whole is non-linear. """ def __init__(self, G, invivo=True,assert_pBCs=True, resetHtd=True,**kwargs): """Initializes a LinearSystemPries instance. INPUT: G: Vascular graph in iGraph format. invivo: Boolean, whether the physiological blood characteristics are calculated using the invivo (=True) or invitro (=False) equations assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. resetHtd: (Optional, default=True.) Boolean whether or not to reset the discharge hematocrit of the VascularGraph at initialization. It may be useful to preserve the original htd-distribution, if only a minor change from the current state is to be expected (faster to obtain the solution). **kwargs: httBC: tube hematocrit boundary condition at inflow (edge) htdBC: discharge hematocrit boundary condition at inflow (vertex) plasmaType: if it is not given, the default value is used. option two: --> francesco: plasma value of francescos simulations species: what type of animal we are dealing with --> relevant for the rbc volume that is used, default is rat OUTPUT: None """ self._G = G self._invivo=invivo self._P = Physiology(G['defaultUnits']) self._eps = 1e-7 #finfo(float).eps * 1e4 htt2htd=self._P.tube_to_discharge_hematocrit if kwargs.has_key('httBC'): for vi in G['av']: for ei in G.adjacent(vi): G.es[ei]['httBC']=kwargs['httBC'] htdBC = htt2htd(kwargs['httBC'],G.es[ei]['diameter'],invivo) G.vs[vi]['htdBC']=htdBC if kwargs.has_key('htdBC'): for vi in G['av']: G.vs[vi]['htdBC']=kwargs['htdBC'] if kwargs.has_key('plasmaType'): self._plasmaType=kwargs['plasmaType'] else: self._plasmaType='default' if kwargs.has_key('species'): self._species=kwargs['species'] else: self._species='rat' # Discharge hematocrit boundary conditions: if not 'htdBC' in G.vs.attribute_names(): for vi in G['av']: htdlist = [] for ei in G.adjacent(vi): if 'httBC' in G.es.attribute_names(): if G.es[ei]['httBC'] != None: htdBC = htt2htd(G.es[ei]['httBC'],G.es[ei]['diameter'],invivo) G.vs[vi]['htdBC']=htdBC else: for ei in G.adjacent(vi): htdlist.append(self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) else: for ei in G.adjacent(vi): htdlist.append(self._P.discharge_hematocrit( G.es[ei]['diameter'], 'a')) G.vs[vi]['htdBC'] = np.mean(htdlist) #Convert 'pBC' ['mmHg'] to default Units for v in G.vs: if v['pBC'] != None: v['pBC']=v['pBC']*vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) # Initial RBC flow, hematocrit, conductance, pressure and flow: G.vs['pressure'] = [0.0 for v in G.vs] G.es['rbcFlow'] = [0.0 for e in G.es] if resetHtd: G.es['htd'] = [0.0 for e in G.es] if not G.vs[0].attributes().has_key('pBC'): G.vs[0]['pBC'] = None if not G.vs[0].attributes().has_key('rBC'): G.vs[0]['rBC'] = None nVertices = G.vcount() self._b = zeros(nVertices) self._A = lil_matrix((nVertices,nVertices),dtype=float) self._update_conductance_and_LS(G, assert_pBCs) self._linear_analysis('iterative2') self._rheological_analysis(None, True, 1.0) self._linear_analysis('iterative2') #-------------------------------------------------------------------------- def _update_conductance_and_LS(self, newGraph=None, assert_pBCs=True): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). x will have the same units of [pressure] as the pBC vertices. Note that in this approach, A and b contain a mixture of dimensions, i.e. A and b have dimensions of [1.0] and [pressure] in the pBC case, [conductance] and [conductance*pressure] otherwise, the latter being rBCs. This has the advantage that no re-indexing is required as the matrices contain all vertices. INPUT: newGraph: Vascular graph in iGraph format to replace the previous self._G. (Optional, default=None.) assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. OUTPUT: A: Matrix A of the linear system, holding the conductance information. b: Vector b of the linear system, holding the boundary conditions. """ if newGraph is not None: self._G = newGraph G = self._G P = self._P invivo=self._invivo cond = P.conductance nublood = P.dynamic_blood_viscosity G.es['conductance'] = [cond(e['diameter'], e['length'], nublood(e['diameter'], invivo,discharge_ht=e['htd'],plasmaType=self._plasmaType)) for e in G.es] G.es['conductance'] = [max(min(c, 1e5), 1e-5) for c in G.es['conductance']] A = self._A b = self._b if assert_pBCs: # Ensure that the problem is well posed in terms of BCs. # This takes care of unconnected nodes as well as connected # components of the graph that have not been assigned a minimum of # one pressure boundary condition: for component in G.components(): if all(map(lambda x: x is None, G.vs(component)['pBC'])): i = component[0] G.vs[i]['pBC'] = 0.0 for vertex in G.vs: i = vertex.index A.data[i] = [] A.rows[i] = [] b[i] = 0.0 if vertex['pBC'] is not None: A[i,i] = 1.0 b[i] = vertex['pBC'] else: aDummy=0 k=0 neighbors=[] for edge in G.adjacent(i,'all'): if G.is_loop(edge): continue j=G.neighbors(i)[k] k += 1 conductance = G.es[edge]['conductance'] neighbor = G.vs[j] # +=, -= account for multiedges aDummy += conductance if neighbor['pBC'] is not None: b[i] = b[i] + neighbor['pBC'] * conductance #elif neighbor['rBC'] is not None: # b[i] = b[i] + neighbor['rBC'] else: if j not in neighbors: A[i,j] = - conductance else: A[i,j] = A[i,j] - conductance neighbors.append(j) if vertex['rBC'] is not None: b[i] += vertex['rBC'] A[i,i]=aDummy self._A = A self._b = b self._G = G #-------------------------------------------------------------------------- def _update_rbc_flow(self, limiter=0.5): """Traverses all vertices ordered by pressure from high to low. Distributes the red cell flow of the mother vessel to the daughter vessels according to an empirical relation. Note that plasma and RBC flow are conserved at bifurcations, however, discharge hematocrit is not a conservative quantity. INPUT: limiter: Limits change from one iteration level to the next, if < 1.0, at limiter == 1.0 the change is unmodified. OUTPUT: None, edge properties 'rbcFlow' and 'htd' are modified in-place. """ # This limit ensures that the hematocrit stays within the physically # possible bounds: htdLimit = 0.99 # Short notation: G = self._G eps = self._eps pse = self._P.phase_separation_effect #pse = self._P.phase_separation_effect_step # Copy current htd: oldRbcFlow = copy.deepcopy(G.es['rbcFlow']) G.es['rbcFlow'] = [0.0 for e in G.es] G.es['htd'] = [0.0 for e in G.es] # Vertices sorted by pressure: pSortedVertices = sorted([(v['pressure'], v.index) for v in G.vs], reverse=True) # Loop through vertices, distributing discharge hematocrit: for vertexPressure, vertex in pSortedVertices: # Determine in- and outflow edges: outEdges = [] inEdges = [] for neighbor, edge in zip(G.neighbors(vertex, 'all'), G.adjacent(vertex, 'all')): if G.vs[neighbor]['pressure'] < vertexPressure - eps: outEdges.append(edge) elif G.vs[neighbor]['pressure'] > vertexPressure + eps: inEdges.append(edge) # The rbc flow is computed from htdBC and the red cells entering # from the mother vessels (if any exist). In case of multiple # mother vessels, each is distributed according to the empirical # relation. If there are more than two daughter edges, the # emperical relation is applied to all possible pairings and the # final fractional flow to each daughter is computed in a # hierarchical fashion (see below). trimmedOutEdges = copy.deepcopy(outEdges) for outEdge in outEdges: if G.es[outEdge]['flow'] <= eps: trimmedOutEdges.remove(outEdge) elif not (G.vs[vertex]['htdBC'] is None): G.es[outEdge]['rbcFlow'] = G.vs[vertex]['htdBC'] * \ G.es[outEdge]['flow'] G.es[outEdge]['htd'] = G.vs[vertex]['htdBC'] trimmedOutEdges.remove(outEdge) # Only edges without hematocrit BC are included in the distribution # algorithm: outEdges = copy.deepcopy(trimmedOutEdges) NoOutEdges=len(outEdges) if len(outEdges) == 0: continue elif len(outEdges) == 1: outEdge = outEdges[0] rbcFlowIn = 0.0 FlowIn = 0.0 for inEdge in inEdges: rbcFlowIn += G.es[inEdge]['rbcFlow'] FlowIn += G.es[inEdge]['flow'] if len(inEdges) == 0: G.es[outEdge]['htd']=G.es[outEdge]['htdBC'] G.es[outEdge]['rbcFlow']=G.es[outEdge]['flow']*G.es[outEdge]['htd'] else: G.es[outEdge]['rbcFlow'] = rbcFlowIn G.es[outEdge]['htd'] = min(rbcFlowIn/FlowIn,htdLimit) if G.es[outEdge]['htd'] < 0: print('ERROR 1 htd smaller than 0') else: rbcFlowIn = 0.0 edgepairs = list(itertools.combinations(outEdges, 2)) for inEdge in inEdges: df = G.es[inEdge]['diameter'] htdIn = G.es[inEdge]['htd'] rbcFlowIn += G.es[inEdge]['rbcFlow'] outFractions = dict(zip(outEdges, [[] for e in outEdges])) for edgepair in edgepairs: oe0, oe1 = G.es[edgepair] flowSum = sum(G.es[edgepair]['flow']) if flowSum > 0.0: relativeValue=oe0['flow']/flowSum #stdout.write("\r oe0/flowSum = %f \n" % relativeValue) #if oe0['flow']/flowSum > 0.49 and oe0['flow']/flowSum < 0.51: #stdout.write("\r NOW \n") f0 = pse(oe0['flow'] / flowSum, oe0['diameter'], oe1['diameter'], df, htdIn) #f0 = pse(oe0['flow'] / flowSum, # 0.7, 0.7, 0.7, 0.64) f1 = 1 - f0 else: f0 = 0 f1 = 0 outFractions[oe0.index].append(f0) outFractions[oe1.index].append(f1) # Sort out-edges from highest to lowest out fraction and # distribute RBC flow accordingly: sortedOutEdges = sorted(zip(map(sum, outFractions.values()), outFractions.keys()), reverse=True) remainingFraction = 1.0 for i, soe in enumerate(sortedOutEdges[:-1]): outEdge = soe[1] outFractions[outEdge] = remainingFraction * \ sorted(outFractions[outEdge])[i] remainingFraction -= outFractions[outEdge] remainingFraction = max(remainingFraction, 0.0) outFractions[sortedOutEdges[-1][1]] = remainingFraction #Outflow in second outEdge is calculated by the difference of inFlow and OutFlow first #outEdge count = 0 for outEdge in outEdges: G.es[outEdge]['rbcFlow'] += G.es[inEdge]['rbcFlow'] * \ outFractions[outEdge] #stdout.write("\r outEdge = %g \n" %outEdge) #stdout.write("\r G.es[outEdge]['rbcFlow'] = %f \n" %G.es[outEdge]['rbcFlow']) if count == 0: G.es[outEdge]['rbcFlow'] = oldRbcFlow[outEdge] + \ (G.es[outEdge]['rbcFlow'] - oldRbcFlow[outEdge]) * limiter elif count == 1: G.es[outEdge]['rbcFlow']=G.es[inEdge]['rbcFlow']-G.es[outEdges[0]]['rbcFlow'] #stdout.write("\r LIMITED: G.es[outEdge]['rbcFlow'] = %f \n" %G.es[outEdge]['rbcFlow']) count += 1 # Limit change between iteration levels for numerical # stability. Note that this is only applied to the diverging # bifurcations: for outEdge in outEdges: if G.es[outEdge]['flow'] > eps: G.es[outEdge]['htd'] = min(G.es[outEdge]['rbcFlow'] / \ G.es[outEdge]['flow'], htdLimit) if G.es[outEdge]['htd'] < 0: print('ERROR 2 htd smaller than 0') #stdout.write("\r outEdge = %g \n" %outEdge) #stdout.write("\r outFractions[outEdge] = %f \n" %outFractions[outEdge]) #stdout.write("\r G.es[outEdge]['flow'] = %f \n" %G.es[outEdge]['flow']) #stdout.write("\r G.es[outEdge]['htd'] = %f \n" %G.es[outEdge]['htd']) #-------------------------------------------------------------------------- def _linear_analysis(self, method, **kwargs): """Performs the linear analysis, in which the pressure and flow fields are computed. INPUT: method: This can be either 'direct' or 'iterative' **kwargs precision: The accuracy to which the ls is to be solved. If not supplied, machine accuracy will be used. (This only applies to the iterative solver) OUTPUT: The maximum, mean, and median pressure change. Moreover, pressure and flow are modified in-place. """ G = self._G A = self._A.tocsr() if method == 'direct': linalg.use_solver(useUmfpack=True) x = linalg.spsolve(A, self._b) elif method == 'iterative': if kwargs.has_key('precision'): eps = kwargs['precision'] else: eps = self._eps AA = smoothed_aggregation_solver(A, max_levels=10, max_coarse=500) x = abs(AA.solve(self._b, x0=None, tol=eps, accel='cg', cycle='V', maxiter=150)) # abs required, as (small) negative pressures may arise elif method == 'iterative2': # Set linear solver ml = rootnode_solver(A, smooth=('energy', {'degree':2}), strength='evolution' ) M = ml.aspreconditioner(cycle='V') # Solve pressure system #x,info = gmres(A, self._b, tol=self._eps, maxiter=50, M=M, x0=self._x) #x,info = gmres(A, self._b, tol=self._eps/10000000000000, maxiter=50, M=M) x,info = gmres(A, self._b, tol=self._eps/10000, maxiter=50, M=M) if info != 0: print('SOLVEERROR in Solving the Matrix') pdiff = map(abs, [(p - xx) / p if p > 0 else 0.0 for p, xx in zip(G.vs['pressure'], x)]) maxPDiff = max(pdiff) meanPDiff = np.mean(pdiff) medianPDiff = np.median(pdiff) log.debug(np.nonzero(np.array(pdiff) == maxPDiff)[0]) G.vs['pressure'] = x G.es['flow'] = [abs(G.vs[edge.source]['pressure'] - \ G.vs[edge.target]['pressure']) * \ edge['conductance'] for edge in G.es] self._maxPDiff=maxPDiff self._meanPDiff=meanPDiff self._medianPDiff=medianPDiff return maxPDiff, meanPDiff, medianPDiff #-------------------------------------------------------------------------- def _rheological_analysis(self, newGraph=None, assert_pBCs=True, limiter=0.5): """Performs the rheological analysis, in which the discharge hematocrit and apparent viscosity (and thus the new conductances of the vessels) are computed. INPUT: newGraph: Vascular graph in iGraph format to replace the previous self._G. (Optional, default=None.) assert_pBCs: (Optional, default=True.) Boolean whether or not to check components for correct pressure boundary conditions. limiter: Limits change from one iteration level to the next, if > 1.0, at limiter == 1.0 the change is unmodified. OUTPUT: None, edge properties 'htd' and 'conductance' are modified in-place. """ self._update_rbc_flow(limiter) self._update_conductance_and_LS(newGraph, assert_pBCs) #-------------------------------------------------------------------------- def solve(self, method, precision=None, maxIterations=1e4, limiter=0.5, **kwargs): """Solve for pressure, flow, hematocrit and conductance by iterating over linear and rheological analysis. Stop when either the desired accuracy has been achieved or the maximum number of iterations have been performed. INPUT: method: This can be either 'direct' or 'iterative' precision: Desired precision, measured in the maximum amount by which the pressure may change from one iteration level to the next. If the maximum change is below threshold, the iteration is aborted. maxIterations: The maximum number of iterations to perform. limiter: Limits change from one iteration level to the next, if > 1.0, at limiter == 1.0 the change is unmodified. **kwargs precisionLS: The accuracy to which the ls is to be solved. If not supplied, machine accuracy will be used. (This only applies to the iterative solver) OUTPUT: None, the vascular graph is modified in-place. """ G = self._G P = self._P invivo=self._invivo if precision is None: precision = self._eps iterationCount = 0 maxPDiff = 1e200 filename = 'iter_'+str(iterationCount)+'.vtp' g_output.write_vtp(G, filename, False) filenames = [filename] convergenceHistory = [] while iterationCount < maxIterations and maxPDiff > precision: stdout.write("\rITERATION %g \n" %iterationCount) self._rheological_analysis(None, False, limiter=0.5) maxPDiff, meanPDiff, medianPDiff = self._linear_analysis(method, **kwargs) #maxPDiff=self._maxPDiff #meanPDiff=self._meanPDiff #medianPDiff=self._medianPDiff log.info('Iteration %i of %i' % (iterationCount+1, maxIterations)) log.info('maximum pressure change: %.2e' % maxPDiff) log.info('mean pressure change: %.2e' % meanPDiff) log.info('median pressure change: %.2e\n' % medianPDiff) convergenceHistory.append((maxPDiff, meanPDiff, medianPDiff)) iterationCount += 1 G.es['htt'] = [P.discharge_to_tube_hematocrit(e['htd'], e['diameter'],invivo) for e in G.es] vrbc = P.rbc_volume(self._species) G.es['nMax'] = [np.pi * e['diameter']**2 / 4 * e['length'] / vrbc for e in G.es] G.es['minDist'] = [e['length'] / e['nMax'] for e in G.es] G.es['nRBC'] =[e['htt']*e['length']/e['minDist'] for e in G.es] filename = 'iter_'+str(iterationCount)+'.vtp' g_output.write_vtp(G, filename, False) filenames.append(filename) if iterationCount >= maxIterations: stdout.write("\rMaximum number of iterations reached\n") elif maxPDiff <= precision : stdout.write("\rPrecision limit is reached\n") self.integrity_check() G.es['htt'] = [P.discharge_to_tube_hematocrit(e['htd'], e['diameter'],invivo) for e in G.es] vrbc = P.rbc_volume(self._species) G.es['nMax'] = [np.pi * e['diameter']**2 / 4 * e['length'] / vrbc for e in G.es] G.es['minDist'] = [e['length'] / e['nMax'] for e in G.es] G.es['nRBC'] =[e['htt']*e['length']/e['minDist'] for e in G.es] G.es['v']=[4 * e['flow'] * P.velocity_factor(e['diameter'], invivo, tube_ht=e['htt']) / (np.pi * e['diameter']**2) if e['htt'] > 0 else 4 * e['flow'] / (np.pi * e['diameter']**2) for e in G.es] for v in G.vs: v['pressure']=v['pressure']/vgm.units.scaling_factor_du('mmHg',G['defaultUnits']) filename = 'iter_final.vtp' g_output.write_vtp(G, filename, False) filenames.append(filename) g_output.write_pvd_time_series('sequence.pvd', filenames) vgm.write_pkl(G, 'G_final.pkl') stdout.flush() return convergenceHistory return G #-------------------------------------------------------------------------- def integrity_check(self): """Asserts that mass conservation is honored by writing the sum of in- and outflow as well as the sum of the in- and outgoing discharge hematocrit to the graph vertices as 'flowSum' and 'htdSum' INPUT: None OUTPUT: None, flow and htd sums added as vertex properties in-place. """ G = self._G eps = self._eps G.es['plasmaFlow'] = [(1 - e['htd']) * e['flow'] for e in G.es] for v in G.vs: vertex = v.index vertexPressure = v['pressure'] outEdges = [] inEdges = [] for neighbor, edge in zip(G.neighbors(vertex, 'all'), G.adjacent(vertex, 'all')): if G.vs[neighbor]['pressure'] < vertexPressure: outEdges.append(edge) elif G.vs[neighbor]['pressure'] >= vertexPressure: inEdges.append(edge) v['flowSum'] = sum(G.es[outEdges]['flow']) - \ sum(G.es[inEdges]['flow']) v['rbcFlowSum'] = sum(G.es[outEdges]['rbcFlow']) - \ sum(G.es[inEdges]['rbcFlow']) v['plasmaFlowSum'] = sum(G.es[outEdges]['plasmaFlow']) - \ sum(G.es[inEdges]['plasmaFlow']) for e in G.es: e['errorHtd'] = abs(e['rbcFlow'] / e['flow'] - e['htd']) \ if e['flow'] > eps else 0.0 log.info('Max error htd: %.1g' % max(G.es['errorHtd']))
def __init__(self, G, withRBC=0, invivo=0, dMin_empirical=3.5, htdMax_empirical=0.6, verbose=True, **kwargs): """ Computes the flow and pressure field of a vascular graph without RBC tracking. It can be chosen between pure plasma flow, constant hematocrit or a given htt/htd distribution. The pressure boundary conditions (pBC) should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) invivo: boolean if the invivo or invitro empirical functions are used (default = 0) withRBC: = 0: no RBCs, pure plasma Flow (default) 0 < withRBC < 1 & 'htt' not in edgeAttributes: the given value is assigned as htt to all edges. 0 < withRBC < 1 & 'htt' in edgeAttributes: the given value is assigned as htt to all edges where htt = None. NOTE: If htd is not in the edge attributes, Htd will be computed from htt and used to compute the resistance. If htd is already in the edge attributes, it won't be recomputed but the current htd values will be used. dMin_empiricial: lower limit for the diameter that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 3.5). htdMax_empirical: upper limit for htd that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 0.6). Maximum has to be 1. verbose: Bool if WARNINGS and setup information is printed OUTPUT: None, the edge properties htt is assgined and the function update is executed (see description for more details) """ self._G = G self._eps = np.finfo(float).eps self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() self._withRBC = withRBC self._invivo = invivo self._verbose = verbose self._dMin_empirical = dMin_empirical self._htdMax_empirical = htdMax_empirical if self._verbose: print( 'INFO: The limits for the compuation of the effective viscosity are set to' ) print('Minimum diameter %.2f' % self._dMin_empirical) print('Maximum discharge %.2f' % self._htdMax_empirical) if self._withRBC != 0: if self._withRBC < 1.: if 'htt' not in G.es.attribute_names(): G.es['htt'] = [self._withRBC] * G.ecount() else: httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt'] = [self._withRBC] * len(httNone) else: if self._verbose: print('WARNING: htt is already an edge attribute. \n Existing values are not overwritten!'+\ '\n If new values should be assigned htt has to be deleted beforehand!') else: print('ERROR: 0 < withRBC < 1') if 'rBC' not in G.vs.attribute_names(): G.vs['rBC'] = [None] * G.vcount() if 'pBC' not in G.vs.attribute_names(): G.vs['pBC'] = [None] * G.vcount() self.update()
class LinearSystem(object): def __init__(self, G, withRBC=0, invivo=0, dMin_empirical=3.5, htdMax_empirical=0.6, verbose=True, **kwargs): """ Computes the flow and pressure field of a vascular graph without RBC tracking. It can be chosen between pure plasma flow, constant hematocrit or a given htt/htd distribution. The pressure boundary conditions (pBC) should be given in mmHG and pressure will be output in mmHg INPUT: G: Vascular graph in iGraph format.(the pBC should be given in mmHg) invivo: boolean if the invivo or invitro empirical functions are used (default = 0) withRBC: = 0: no RBCs, pure plasma Flow (default) 0 < withRBC < 1 & 'htt' not in edgeAttributes: the given value is assigned as htt to all edges. 0 < withRBC < 1 & 'htt' in edgeAttributes: the given value is assigned as htt to all edges where htt = None. NOTE: If htd is not in the edge attributes, Htd will be computed from htt and used to compute the resistance. If htd is already in the edge attributes, it won't be recomputed but the current htd values will be used. dMin_empiricial: lower limit for the diameter that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 3.5). htdMax_empirical: upper limit for htd that is used to compute nurel (effective viscosity). The aim of the limit is to avoid using the empirical equations in a range where no data exists (default = 0.6). Maximum has to be 1. verbose: Bool if WARNINGS and setup information is printed OUTPUT: None, the edge properties htt is assgined and the function update is executed (see description for more details) """ self._G = G self._eps = np.finfo(float).eps self._P = Physiology(G['defaultUnits']) self._muPlasma = self._P.dynamic_plasma_viscosity() self._withRBC = withRBC self._invivo = invivo self._verbose = verbose self._dMin_empirical = dMin_empirical self._htdMax_empirical = htdMax_empirical if self._verbose: print( 'INFO: The limits for the compuation of the effective viscosity are set to' ) print('Minimum diameter %.2f' % self._dMin_empirical) print('Maximum discharge %.2f' % self._htdMax_empirical) if self._withRBC != 0: if self._withRBC < 1.: if 'htt' not in G.es.attribute_names(): G.es['htt'] = [self._withRBC] * G.ecount() else: httNone = G.es(htt_eq=None).indices if len(httNone) > 0: G.es[httNone]['htt'] = [self._withRBC] * len(httNone) else: if self._verbose: print('WARNING: htt is already an edge attribute. \n Existing values are not overwritten!'+\ '\n If new values should be assigned htt has to be deleted beforehand!') else: print('ERROR: 0 < withRBC < 1') if 'rBC' not in G.vs.attribute_names(): G.vs['rBC'] = [None] * G.vcount() if 'pBC' not in G.vs.attribute_names(): G.vs['pBC'] = [None] * G.vcount() self.update() #-------------------------------------------------------------------------- def update(self): """Constructs the linear system A x = b where the matrix A contains the conductance information of the vascular graph, the vector b specifies the boundary conditions and the vector x holds the pressures at the vertices (for which the system needs to be solved). OUTPUT: matrix A and vector b """ htt2htd = self._P.tube_to_discharge_hematocrit nurel = self._P.relative_apparent_blood_viscosity G = self._G #Convert 'pBC' ['mmHG'] to default Units for v in G.vs(pBC_ne=None): v['pBC'] = v['pBC'] * vgm.units.scaling_factor_du( 'mmHg', G['defaultUnits']) nVertices = G.vcount() b = np.zeros(nVertices) A = lil_matrix((nVertices, nVertices), dtype=float) # Compute nominal and specific resistance: self._update_nominal_and_specific_resistance() #if with RBCs compute effective resistance if self._withRBC: if 'htd' not in G.es.attribute_names(): dischargeHt = [ min(htt2htd(htt, d, self._invivo), 1.0) for htt, d in zip(G.es['htt'], G.es['diameter']) ] G.es['htd'] = dischargeHt else: dischargeHt = G.es['htd'] if self._verbose: print('WARNING: htd is already an edge attribute. \n Existing values are not overwritten!'+\ '\n If new values should be assigned htd has to be deleted beforehand!') G.es['effResistance'] =[ res * nurel(max(self._dMin_empirical,d),min(dHt,self._htdMax_empirical),self._invivo) \ for res,dHt,d in zip(G.es['resistance'], dischargeHt,G.es['diameter'])] G.es['conductance'] = 1 / np.array(G.es['effResistance']) else: G.es['conductance'] = [1 / e['resistance'] for e in G.es] self._conductance = G.es['conductance'] for vertex in G.vs: i = vertex.index A.data[i] = [] A.rows[i] = [] b[i] = 0.0 if vertex['pBC'] is not None: A[i, i] = 1.0 b[i] = vertex['pBC'] else: aDummy = 0 k = 0 neighbors = [] for edge in G.incident(i, 'all'): if G.is_loop(edge): continue j = G.neighbors(i)[k] k += 1 conductance = G.es[edge]['conductance'] neighbor = G.vs[j] # +=, -= account for multiedges aDummy += conductance if neighbor['pBC'] is not None: b[i] = b[i] + neighbor['pBC'] * conductance #elif neighbor['rBC'] is not None: # b[i] = b[i] + neighbor['rBC'] else: if j not in neighbors: A[i, j] = -conductance else: A[i, j] = A[i, j] - conductance neighbors.append(j) if vertex['rBC'] is not None: b[i] += vertex['rBC'] A[i, i] = aDummy self._A = A self._b = b #-------------------------------------------------------------------------- def solve(self, method, **kwargs): """Solves the linear system A x = b for the vector of unknown pressures x, either using a direct solver (obsolete) or an iterative GMRES solver. From the pressures, the flow field is computed. INPUT: method: This can be either 'direct' or 'iterative2' OUTPUT: None - G is modified in place. G_final.pkl & G_final.vtp: are save as output sampledict.pkl: is saved as output """ b = self._b G = self._G htt2htd = self._P.tube_to_discharge_hematocrit A = self._A.tocsr() if method == 'direct': linalg.use_solver(useUmfpack=True) x = linalg.spsolve(A, b) elif method == 'iterative2': ml = rootnode_solver(A, smooth=('energy', { 'degree': 2 }), strength='evolution') M = ml.aspreconditioner(cycle='V') # Solve pressure system #x,info = gmres(A, self._b, tol=self._eps, maxiter=1000, M=M) x, info = gmres(A, self._b, tol=10 * self._eps, M=M) if info != 0: print('ERROR in Solving the Matrix') print(info) G.vs['pressure'] = x self._x = x conductance = self._conductance G.es['flow'] = [abs(G.vs[edge.source]['pressure'] - G.vs[edge.target]['pressure']) * \ conductance[i] for i, edge in enumerate(G.es)] #Default Units - mmHg for pressure for v in G.vs: v['pressure'] = v['pressure'] / vgm.units.scaling_factor_du( 'mmHg', G['defaultUnits']) if self._withRBC: G.es['v'] = [ e['htd'] / e['htt'] * e['flow'] / (0.25 * np.pi * e['diameter']**2) for e in G.es ] else: G.es['v'] = [ e['flow'] / (0.25 * np.pi * e['diameter']**2) for e in G.es ] #Convert 'pBC' from default Units to mmHg pBCneNone = G.vs(pBC_ne=None).indices G.vs[pBCneNone]['pBC'] = np.array(G.vs[pBCneNone]['pBC']) * ( 1 / vgm.units.scaling_factor_du('mmHg', G['defaultUnits'])) vgm.write_pkl(G, 'G_final.pkl') vgm.write_vtp(G, 'G_final.vtp', False) #Write Output sampledict = {} for eprop in ['flow', 'v']: if not eprop in sampledict.keys(): sampledict[eprop] = [] sampledict[eprop].append(G.es[eprop]) for vprop in ['pressure']: if not vprop in sampledict.keys(): sampledict[vprop] = [] sampledict[vprop].append(G.vs[vprop]) g_output.write_pkl(sampledict, 'sampledict.pkl') #-------------------------------------------------------------------------- def _update_nominal_and_specific_resistance(self, esequence=None): """Updates the nominal and specific resistance of a given edge sequence. INPUT: es: Sequence of edge indices as tuple. If not provided, all edges are updated. OUTPUT: None, the edge properties 'resistance' and 'specificResistance' are updated (or created). """ G = self._G if esequence is None: es = G.es else: es = G.es(esequence) G.es['specificResistance'] = [ 128 * self._muPlasma / (np.pi * d**4) for d in G.es['diameter'] ] G.es['resistance'] = [ l * sr for l, sr in zip(G.es['length'], G.es['specificResistance']) ]