Esempio n. 1
0
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')
Esempio n. 2
0
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'])
        ]