Exemple #1
0
def test_general_dirichlet_bc():
    """
    Test setting arbitrary dirichlet boundary condition
    """
    network = cube_network(N=10)

    k_computer = ConductanceCalc(network)
    k_computer.compute()

    press_in = 121.1
    press_out = 0.901

    # Set dirichlet boundary conditions in one command
    LS = LinearSystemStandard(network)
    LS.fill_matrix(network.tubes.k_w)
    LS.set_dirichlet_pores([2, 92, 29],
                           np.array([press_in, press_in, press_out]))
    sol1 = LS.solve()

    # Set dirichlet boundary condition separately
    LS2 = LinearSystemStandard(network)
    LS2.fill_matrix(network.tubes.k_w)
    LS2.set_dirichlet_pores(2, press_in)
    LS2.set_dirichlet_pores(92, press_in)
    LS2.set_dirichlet_pores(29, press_out)
    sol2 = LS2.solve()

    assert np.allclose(sol1, sol2)
Exemple #2
0
def test_msrsb_unstructured():
    network = unstructured_network_delaunay(nr_pores=10000)
    k_computer = ConductanceCalc(network,
                                 sim_settings["fluid_properties"],
                                 pores_have_conductance=True)
    k_computer.compute()

    A = LaplacianMatrix(network)
    A.set_edge_weights(network.tubes.k_w + network.tubes.k_n)
    A = A.get_csr_matrix()
    rhs = np.zeros(network.nr_p)
    rhs[0] = 1e-10
    rhs[100] = -1e-10
    sol = solve_with_msrsb(A, rhs, tol=1e-12)

    A[1, :] = 0.0
    A[1, 1] = 1.0
    sol_exact = spsolve(A=A, b=rhs)

    sol -= np.min(sol)
    sol_exact -= np.min(sol_exact)
    error = sol - sol_exact

    Linf = np.linalg.norm(error, ord=np.inf) / np.linalg.norm(sol_exact,
                                                              ord=np.inf)
    L2_error = np.linalg.norm(error, ord=2) / np.linalg.norm(sol_exact, ord=2)
    assert (
        (Linf < 10e-2) &
        (L2_error < 10e-5)), "L1 error: %s  L2 error: %s" % (Linf, L2_error)
Exemple #3
0
    def __init__(self,
                 network,
                 fluid_properties,
                 pores_have_conductance=False):
        self.network = network
        x_max, x_min = np.max(network.pores.x), np.min(network.pores.x)
        y_max, y_min = np.max(network.pores.y), np.min(network.pores.y)
        z_max, z_min = np.max(network.pores.z), np.min(network.pores.z)
        self.network_vol = (x_max - x_min) * (y_max - y_min) * (z_max - z_min)

        self.total_flux_one_phase = None
        self.kr_n = np.zeros(3)
        self.kr_w = np.zeros(3)
        self.is_isotropic = True

        # Mask of tubes and pores not connected to the inlet, which makes the matrix singular
        p_conn_inlet, t_conn_inlet = graph_algs.get_pore_and_tube_connectivity_to_pore_list(
            self.network, self.network.pi_list_face[WEST])
        self.mask_ignored_tubes = (t_conn_inlet == 0)

        self.fluid_properties = fluid_properties

        self.cond_computer = ConductanceCalc(self.network, fluid_properties,
                                             pores_have_conductance)
        self.absolute_permeability()
Exemple #4
0
def test_symmetric_dirichlet_bc():
    """
    Test setting dirichlet boundary condition in a manner which leaves the matrix symmetric
    """
    network = cube_network(N=40)

    k_computer = ConductanceCalc(network)
    k_computer.compute()

    p_dirichlet = np.array([1., 23., 43.])
    pi_dirichlet = [2, 7200, 2300]

    # Set dirichlet boundary conditions using symmetric dirichlet boundary conditions
    LS_sym = LinearSystemStandard(network)
    LS_sym.fill_matrix(network.tubes.k_w)
    LS_sym.set_dirichlet_pores_symmetric(pi_list=pi_dirichlet,
                                         value=p_dirichlet)

    sol_sym = LS_sym.solve(solver="PETSC", tol=1e-10)

    # Set dirichlet boundary conditions using non-symmetric dirichlet boundary conditions
    LS = LinearSystemStandard(network)
    LS.fill_matrix(network.tubes.k_w)
    LS.set_dirichlet_pores(pi_list=pi_dirichlet, value=p_dirichlet)

    sol = LS.solve(solver="AMG", tol=1e-10)

    assert np.allclose(sol_sym, sol), np.linalg.norm(sol_sym - sol)
    def __init__(self, network, fluid_properties, explicit=True, delta_pc=0.01, ptol=1e-6):
        super(DynamicSimulation, self).__init__(network, fluid_properties)

        if np.any(network.pores.vol <= 0.0):
            raise ValueError("All pore volumes have to be positive")

        if np.any(network.tubes.vol != 0.0):
            raise ValueError("Network throats have to all have zero volume for dynamic flow solver")

        self.press_solve_tol = ptol
        self.explicit = explicit
        self.fluid_properties = fluid_properties
        self.SatComputer = DynamicSaturationComputer
        self.bool_accounted_pores = np.ones(network.nr_p, dtype=np.bool)  # Saturation is updated only in these pores
        self.sat_comp = self.SatComputer(network, self.bool_accounted_pores)
        self.time_stepper = DynamicTimeStepper(network, self.bool_accounted_pores, delta_pc=delta_pc)

        self.press_solver = PressureSolverDynamicDirichlet(self.network)
        self.press_solver_type = "petsc"

        self.pc_comp = DynamicCapillaryPressureComputer(network)
        self.k_comp = ConductanceCalc(network, self.fluid_properties)

        self.q_n = np.zeros(network.nr_p)  # nonwetting fluid source/sink
        self.q_w = np.zeros(network.nr_p)  # wetting fluid source/sink

        gamma = self.fluid_properties['gamma']
        self.snap_off_press = 1.001*JNModel.snap_off_pressure(gamma=gamma, r=network.tubes.r)
        self.piston_entry = JNModel.piston_entry_pressure(r=network.tubes.r, gamma=gamma, G=network.tubes.G)

        self.bc = SimulationBoundaryCondition()

        self.total_sink_nonwett = 0.0
        self.total_sink_wett = 0.0

        self.time = 0.0

        self.accumulated_saturation = 0.0
        self.saturation_wett_inflow = 0.0

        self.freeze_sink_nw = dict()

        self.stop_time = None
        self.flux_n = np.zeros(network.nr_p)  # Out-fluxes of the nonwetting phase from each pore
        self.flux_w = np.zeros(network.nr_p)  # Out-fluxes of the wetting phase from each pore

        self.sat_prev = np.copy(network.pores.sat)
        self.tube_invaded_prev = np.copy(network.tubes.invaded)
        self.pores_invaded_prev = np.copy(network.pores.invaded)

        self.ti_freeze_displacement = dict()

        self.cum_flux_tubes = np.zeros(network.nr_t)
Exemple #6
0
def test_msrsb_two_phase():
    nx, ny, nz = 3, 7, 11
    n_fine_per_cell = 5
    network = structured_network(Nx=n_fine_per_cell * nx,
                                 Ny=n_fine_per_cell * ny,
                                 Nz=n_fine_per_cell * nz)
    run_ip_algorithm(network, 0.7)
    p_c = network.pores.p_c
    assert np.sum(p_c) > 0.0

    k_computer = ConductanceCalc(network,
                                 sim_settings["fluid_properties"],
                                 pores_have_conductance=True)
    k_computer.compute()

    assert np.sum(network.tubes.k_n) > 0.0

    A = LaplacianMatrix(network)
    A.set_edge_weights(network.tubes.k_w + network.tubes.k_n)
    A = A.get_csr_matrix()

    An = LaplacianMatrix(network)
    An.set_edge_weights(network.tubes.k_n)
    An = An.get_csr_matrix()

    k_tot = network.tubes.k_n + network.tubes.k_w
    print "max/min total conductance ratio is ", max(k_tot) / min(k_tot)

    rhs = np.zeros(network.nr_p)
    rhs[0] = 1e-10
    rhs[100] = -1e-10
    rhs += -An * p_c

    sol = solve_with_msrsb(A, rhs, tol=1e-7)

    A[1, :] = 0.0
    A[1, 1] = 1.0

    sol_exact = spsolve(A=A, b=rhs)

    sol -= np.min(sol)
    sol_exact -= np.min(sol_exact)
    error = sol - sol_exact

    Linf = np.linalg.norm(error, ord=np.inf) / np.linalg.norm(sol_exact,
                                                              ord=np.inf)
    L2_error = np.linalg.norm(error, ord=2) / np.linalg.norm(sol_exact, ord=2)
    assert (
        (Linf < 10e-2) &
        (L2_error < 10e-5)), "L1 error: %s  L2 error: %s" % (Linf, L2_error)
Exemple #7
0
    def _update_inter_conductances(inter_edges, p_c):
        epetra_map = p_c.Map()
        inter_edgelist_local_1 = np.asarray(
            [epetra_map.LID(i) for i in inter_edges['edgelist'][0]],
            dtype=np.int32)
        inter_edgelist_local_2 = np.asarray(
            [epetra_map.LID(i) for i in inter_edges['edgelist'][1]],
            dtype=np.int32)

        assert np.all(inter_edgelist_local_1 >= 0)
        assert np.all(inter_edgelist_local_2 >= 0)

        inter_pc_edge = np.maximum(p_c[inter_edgelist_local_1],
                                   p_c[inter_edgelist_local_2])

        el_rad = inter_edges["r"]
        el_len = inter_edges["l"]
        el_G = inter_edges["G"]
        el_A_tot = inter_edges["A_tot"]
        invasion_status = inter_edges["invaded"]
        k_n, k_w = ConductanceCalc.compute_conductances(
            sim_settings['fluid_properties'], el_rad, el_len, el_G, el_A_tot,
            inter_pc_edge, invasion_status)

        inter_edges['k_n'] = k_n
        inter_edges['k_w'] = k_w

        return inter_edges
Exemple #8
0
def msrsb_two_phase():
    try:
        network = PoreNetwork.load("benchmark_network.pkl")

    except IOError:
        network = unstructured_network_delaunay(40000)
        run_ip_algorithm(network, 0.7)

        network.save("benchmark_network.pkl")

    p_c = network.pores.p_c
    assert np.sum(p_c) > 0.0

    k_computer = ConductanceCalc(network,
                                 sim_settings["fluid_properties"],
                                 pores_have_conductance=True)
    k_computer.compute()

    assert np.sum(network.tubes.k_n) > 0.0

    A = LaplacianMatrix(network)
    A.set_edge_weights(network.tubes.k_w + network.tubes.k_n)
    A = A.get_csr_matrix()

    An = LaplacianMatrix(network)
    An.set_edge_weights(network.tubes.k_n)
    An = An.get_csr_matrix()

    k_tot = network.tubes.k_n + network.tubes.k_w
    print "max/min total conductance ratio is %g " % (max(k_tot) / min(k_tot))

    rhs = np.zeros(network.nr_p)
    rhs[np.argmin(network.pores.x)] = 1e-10
    rhs[np.argmax(network.pores.x)] = -1e-10
    rhs += -An * p_c

    sol, history_gmres = solve_with_msrsb(A,
                                          rhs,
                                          tol=1e-6,
                                          smoother="gmres",
                                          v_per_subdomain=1000,
                                          conv_history=True,
                                          with_multiscale=False,
                                          max_iter=10000,
                                          tol_basis=1e-3,
                                          n_smooth=100,
                                          adapt_smoothing=False,
                                          verbose=True)

    sol, history_gmres_ms = solve_with_msrsb(A,
                                             rhs,
                                             tol=1e-6,
                                             smoother="gmres",
                                             v_per_subdomain=1000,
                                             conv_history=True,
                                             with_multiscale=True,
                                             max_iter=10000,
                                             tol_basis=1e-3,
                                             n_smooth=100,
                                             adapt_smoothing=False,
                                             verbose=True)

    sol, history_ilu_ms = solve_with_msrsb(A,
                                           rhs,
                                           tol=1e-6,
                                           smoother="ilu",
                                           v_per_subdomain=1000,
                                           conv_history=True,
                                           with_multiscale=True,
                                           max_iter=10000,
                                           tol_basis=1e-3,
                                           n_smooth=10,
                                           adapt_smoothing=False,
                                           verbose=True)

    A[1, :] = 0.0
    A[1, 1] = 1.0

    sol_exact = spsolve(A=A * 1e10, b=rhs * 1e10)

    sol -= np.min(sol)
    sol_exact -= np.min(sol_exact)
    error = sol - sol_exact

    Linf = np.linalg.norm(error, ord=np.inf) / np.linalg.norm(sol_exact,
                                                              ord=np.inf)
    L2_error = np.linalg.norm(error, ord=2) / np.linalg.norm(sol_exact, ord=2)

    plt.semilogy(history_gmres["residual"])
    plt.semilogy(history_gmres_ms["residual"])
    plt.semilogy(history_ilu_ms["residual"])

    plt.show()

    assert (
        (Linf < 10e-2) &
        (L2_error < 10e-5)), "L1 error: %s  L2 error: %s" % (Linf, L2_error)
class DynamicSimulation(Simulation):
    def __init__(self, network, fluid_properties, explicit=True, delta_pc=0.01, ptol=1e-6):
        super(DynamicSimulation, self).__init__(network, fluid_properties)

        if np.any(network.pores.vol <= 0.0):
            raise ValueError("All pore volumes have to be positive")

        if np.any(network.tubes.vol != 0.0):
            raise ValueError("Network throats have to all have zero volume for dynamic flow solver")

        self.press_solve_tol = ptol
        self.explicit = explicit
        self.fluid_properties = fluid_properties
        self.SatComputer = DynamicSaturationComputer
        self.bool_accounted_pores = np.ones(network.nr_p, dtype=np.bool)  # Saturation is updated only in these pores
        self.sat_comp = self.SatComputer(network, self.bool_accounted_pores)
        self.time_stepper = DynamicTimeStepper(network, self.bool_accounted_pores, delta_pc=delta_pc)

        self.press_solver = PressureSolverDynamicDirichlet(self.network)
        self.press_solver_type = "petsc"

        self.pc_comp = DynamicCapillaryPressureComputer(network)
        self.k_comp = ConductanceCalc(network, self.fluid_properties)

        self.q_n = np.zeros(network.nr_p)  # nonwetting fluid source/sink
        self.q_w = np.zeros(network.nr_p)  # wetting fluid source/sink

        gamma = self.fluid_properties['gamma']
        self.snap_off_press = 1.001*JNModel.snap_off_pressure(gamma=gamma, r=network.tubes.r)
        self.piston_entry = JNModel.piston_entry_pressure(r=network.tubes.r, gamma=gamma, G=network.tubes.G)

        self.bc = SimulationBoundaryCondition()

        self.total_sink_nonwett = 0.0
        self.total_sink_wett = 0.0

        self.time = 0.0

        self.accumulated_saturation = 0.0
        self.saturation_wett_inflow = 0.0

        self.freeze_sink_nw = dict()

        self.stop_time = None
        self.flux_n = np.zeros(network.nr_p)  # Out-fluxes of the nonwetting phase from each pore
        self.flux_w = np.zeros(network.nr_p)  # Out-fluxes of the wetting phase from each pore

        self.sat_prev = np.copy(network.pores.sat)
        self.tube_invaded_prev = np.copy(network.tubes.invaded)
        self.pores_invaded_prev = np.copy(network.pores.invaded)

        self.ti_freeze_displacement = dict()

        self.cum_flux_tubes = np.zeros(network.nr_t)

    def reset_status(self):
        """
        Resets the saturation in all pores as well as invasion state of all pores and tubes to those at the
        start of the previous call to advance_in_time.
        """
        self.network.pores.sat[:] = self.sat_prev
        self.network.tubes.invaded[:] = self.tube_invaded_prev
        self.network.pores.invaded[:] = self.pores_invaded_prev
        self.__update_capillary_pressure()
        self.set_boundary_conditions(self.bc_prev)

        self.network.pores.p_w[:] = 0.0
        self.network.pores.p_n[:] = 0.0
        self.time = self.time_start

    def set_boundary_conditions(self, bc):
        """
        Set boundary condition for simulation

        Parameters
        ----------
        bc: SimulationBoundaryCondition

        """
        self.bc = copy.deepcopy(bc)
        self.__set_rhs_source_arrays(self.bc)
        self.total_sink_nonwett = self.__compute_total_sink(NWETT)
        self.total_sink_wett = self.__compute_total_sink(WETT)
        self.total_source_nonwett = self.__compute_total_source(NWETT)
        self.total_source_wett = self.__compute_total_source(WETT)

        self.bool_accounted_pores = np.ones(self.network.nr_p, dtype=np.bool)
        self.bool_accounted_pores[self.bc.pi_list_inlet] = 0  # Ignore saturation for pressure boundary conditions
        self.bool_accounted_pores[self.bc.pi_list_outlet] = 0  # Ignore saturation for pressure boundary conditions

        self.sat_comp = DynamicSaturationComputer(self.network, self.bool_accounted_pores)

        assert len(self.bc.pi_list_w_sink) == len(np.unique(self.bc.pi_list_w_sink))

        self.pi_nghs_of_w_sinks_interior = {}
        for pi in self.bc.pi_list_w_sink:
            ngh_pores_interior = np.setdiff1d(self.network.ngh_pores[pi], self.bc.pi_list_w_sink, assume_unique=True)
            if len(ngh_pores_interior) == 0:
                ngh_pores_interior = self.network.ngh_pores[pi]

            self.pi_nghs_of_w_sinks_interior[pi] = ngh_pores_interior

    def advance_in_time(self, delta_t):
        """
        Advances the simulation to a specified time. Boundary conditions should be set before calling this function.

        Parameters
        ----------
        delta_t: float
            Time difference between initial state and final state of the simulation.

        """
        logger.debug("Starting simulation with time criterion")

        self.stop_time = self.time + delta_t

        def stop_criterion():
            return self.time >= self.stop_time

        self.sat_prev[:] = np.copy(self.network.pores.sat)
        self.tube_invaded_prev[:] = np.copy(self.network.tubes.invaded)
        self.pores_invaded_prev[:] = np.copy(self.network.pores.invaded)

        self.time_start = self.time
        self.bc_prev = copy.deepcopy(self.bc)

        return self.__advance(stop_criterion)

    def __set_rhs_source_arrays(self, bc):
        """
        Sets (or resets) source arrays from provided boundary conditions
        """
        self.q_n[:] = 0.0
        self.q_w[:] = 0.0

        self.q_n[bc.pi_list_nw_source] = bc.q_list_nw_source
        self.q_n[bc.pi_list_nw_sink] = bc.q_list_nw_sink

        self.q_w[bc.pi_list_w_source] = bc.q_list_w_source
        self.q_w[bc.pi_list_w_sink] = bc.q_list_w_sink

    def __compute_total_sink(self, FLUID):
        if FLUID == WETT:
            total_sink = np.sum(self.q_w[self.bc.pi_list_w_sink])
        if FLUID == NWETT:
            total_sink = np.sum(self.q_n[self.bc.pi_list_nw_sink])
        return total_sink

    def __compute_total_source(self, FLUID):
        if FLUID == WETT:
            total_source = np.sum(self.q_w[self.bc.pi_list_w_source])
        if FLUID == NWETT:
            total_source = np.sum(self.q_n[self.bc.pi_list_nw_source])
        return total_source

    def __solve_linear_system(self):
        network = self.network
        logger.debug("Start of __solve_linear_system")

        press_solver = self.press_solver

        logger.debug("Setting linear system")

        press_solver.setup_linear_system(k_n=self.network.tubes.k_n,
                                         k_w=self.network.tubes.k_w,
                                         p_c=self.network.pores.p_c)
        press_solver.add_source_rhs(self.q_n + self.q_w)

        logger.debug("Fixing boundary conditions")
        press_solver.set_dirichlet_pores(pi_list=self.bc.pi_list_inlet, value=self.bc.press_inlet_w)
        press_solver.set_dirichlet_pores(pi_list=self.bc.pi_list_outlet, value=self.bc.press_outlet_w)

        if self.bc.no_dirichlet:
            # Choose any pore to fix pressure to zero
            pi_dirichlet = ((self.q_n + self.q_w) == 0.0).nonzero()[0][0]
            press_solver.set_dirichlet_pores(pi_list=[pi_dirichlet], value=0.0)

        logger.debug("Solving Pressure with " + self.press_solver_type)
        network.pores.p_w[:] = press_solver.solve(self.press_solver_type, x0=network.pores.p_w,
                                                  tol=self.press_solve_tol)
        network.pores.p_n[:] = network.pores.p_w + network.pores.p_c

        logger.debug("Computing nonwetting and wetting fluxes")
        self.flux_n[:] = press_solver.compute_nonwetting_flux()
        self.flux_w[:] = press_solver.compute_wetting_flux()

    def __update_saturation_implicit(self, dt):
        eps_sat = 1.e-4
        logger.debug("Solving fully implicit")
        network = self.network
        print "solving fully implicit"

        def residual_saturation(p_w, p_c, sat, dt):
            p_n = p_w + p_c
            residual = (sat - network.pores.sat) + (A_n * p_n - self.q_n) * dt / network.pores.vol
            return residual

        def residual_pressure(p_w, p_c):
            rhs = -A_n * p_c + self.q_n + self.q_w
            rhs[pi_dirichlet] = 0.0
            ref_residual = norm(A * (rhs / A.diagonal()) - rhs, ord=np.inf)
            residual_normalized = (A*p_w - rhs)/ref_residual
            return residual_normalized

        pi_dirichlet = ((self.q_n + self.q_w) == 0.0).nonzero()[0][0]

        # Assume that the conductances do not change and are fixed
        A = laplacian_from_network(network, weights=network.tubes.k_n + network.tubes.k_w, ind_dirichlet=pi_dirichlet)
        A_n = laplacian_from_network(network, weights=network.tubes.k_n)

        p_w = np.copy(network.pores.p_w)
        sat = np.copy(network.pores.sat)

        ksp = get_petsc_ksp(A=A * 1e20, ksptype="minres", max_it=10000, tol=1e-9)

        logger.debug("Starting iteration")

        p_c = DynamicCapillaryPressureComputer.sat_to_pc_func(network.pores.sat, network.pores.r)

        while True:
            for iter in xrange(200):
                # Solve for pressure

                rhs = -A_n * p_c + self.q_n + self.q_w
                rhs[pi_dirichlet] = 0.0

                p_w[:] = petsc_solve_from_ksp(ksp, rhs*1e20, x=p_w, tol=1e-9)

                p_n = p_w + p_c
                # Solve for saturation
                if iter == 0:
                    damping = 1.
                if iter > 0:
                    damping = 0.4
                if iter > 10:
                    damping = 0.1
                if iter > 20:
                    damping = 0.055
                sat = (1-damping)*sat + damping * (network.pores.sat + (self.q_n - A_n * p_n) * dt / network.pores.vol)
                if np.min(sat) < 0.0:
                    print "saturation undershoot decreasing time-step slightly"
                    dt *= 0.5

                if np.max(sat) > 1.0:
                    print "saturation overshoot"

                sat = np.maximum(sat, 0.0)
                sat = np.minimum(sat, 0.99999999999)

                p_c = DynamicCapillaryPressureComputer.sat_to_pc_func(sat, network.pores.r)
                res_pw = residual_pressure(p_w, p_c)
                res_sat = residual_saturation(p_w, p_c, sat, dt)

                linf_res_pw = norm(res_pw, ord=np.inf)
                linf_res_sat = norm(res_sat, ord=np.inf)

                if iter % 10 == 0:
                    print "iteration %d \t sat res: %g \t press res %g"%(iter, linf_res_sat, linf_res_pw)

                if iter > 3 and linf_res_sat > 1000:
                    break

                if iter > 10 and linf_res_sat > 1.0:
                    break

                if linf_res_sat < eps_sat and linf_res_pw < 1e-5:
                    break

            if linf_res_sat < eps_sat and linf_res_pw < 1e-5:
                print "Iteration converged"
                print "iteration %d \t sat res: %g \t press res %g" % (iter, linf_res_sat, linf_res_pw)
                network.pores.sat[:] = sat
                network.pores.p_w[:] = p_w
                network.pores.p_n[:] = p_n
                network.pores.p_c[:] = p_c
                assert np.all(sat <= 1.0)
                print "Leaving implicit loop"
                break
            else:
                p_w = np.copy(network.pores.p_w)
                sat = np.copy(network.pores.sat)
                p_c = DynamicCapillaryPressureComputer.sat_to_pc_func(sat, network.pores.r)
                dt *= 0.5
                print "decreasing timestep to", dt

        print "timestep is", dt
        return dt

    def __check_mass_conservation(self):
        if self.bc.no_dirichlet:
            total_flux = np.sum(self.q_w) + np.sum(self.q_n)
            source_max = np.max(np.abs(self.q_n)) + np.max(np.abs(self.q_w))
            assert np.abs(total_flux) <= source_max * 1.e-4, "total flux is %e. Maximum source is %e" % (
            total_flux, source_max)

    def __adjust_magnitude_wetting_sink_pores(self):
        """
        Adjusts the magnitude of the sink pores when their status changes so that the total sink remains constant.
        If no more sink pores are available, the source pores are adjusted.
        """
        network = self.network
        p_c = network.pores.p_c
        sat = network.pores.sat
        pi_list_sink = self.bc.pi_list_w_sink
        pi_list_source = self.bc.pi_list_w_source
        rhs_source_wett = self.q_w
        pi_nghs_of_w_sinks_interior = self.pi_nghs_of_w_sinks_interior

        # TODO: Above a certain threshold block a wetting sink using self.rhs_source_wett[pi] = 0.0
        for pi in pi_list_sink:
            pi_nghs_interior = pi_nghs_of_w_sinks_interior[pi]
            pc_max_ngh = max(p_c.take(pi_nghs_interior))

            if (sat[pi] > 0.9) and (p_c[pi] > 1.5 * pc_max_ngh):  # Heuristic that can be improved
                rhs_source_wett[pi] = 0.0
                logger.debug("freezing W sink %d. p_c: %g. Max pc_ngh: %g. sat: %g", pi, p_c[pi], pc_max_ngh,  sat[pi])

        total_after_blockage = np.sum(self.q_w[pi_list_sink])
        assert total_after_blockage <= 0.0

        # Scale wetting sinks so that their total is equal to self.q_w_tot_sink
        if total_after_blockage < 0.0:
            self.q_w[pi_list_sink] = self.q_w[pi_list_sink] * self.q_w_tot_sink / total_after_blockage
            assert np.all(self.q_w[pi_list_sink] <= 0.0)

        elif total_after_blockage == 0.0 and self.q_w_tot_sink < 0.0:
            # If there is net imbibition and the simulation boundary conditions are defined
            # by sources then scale the wetting sources.

            if ((self.q_w_tot_source + self.q_w_tot_sink) > 0.0) and self.bc.no_dirichlet:
                self.q_w[pi_list_source] = (self.q_w[pi_list_source] *
                                            (self.q_w_tot_source + self.q_w_tot_sink) / self.q_w_tot_source)
            # Otherwise exit simulation
            else:
                logger.warning("Total sink before blockage is %e")
                logger.warning("Cannot adjust wetting sink")
                return -1

        assert np.all(self.q_w[pi_list_sink] <= 0.0)

        self.__check_mass_conservation()

    def __adjust_magnitude_nonwetting_sink_pores(self):
        """
        Adjusts the magnitude of the sink pores when their status changes so that the total sink remains constant.
        If no more sink pores are available, the source pores are adjusted.
        """
        network = self.network
        sat = network.pores.sat
        pi_list_sink = self.bc.pi_list_nw_sink
        pi_list_source = self.bc.pi_list_nw_source

        rhs_source = self.q_n

        if np.sum(self.q_n_tot_sink) == 0.0:
            return

        freeze_sink_nw = self.freeze_sink_nw

        for pi in pi_list_sink:

            # Set a nonwetting sink to be frozen if it is completely filled with the wetting fluid.
            if network.pores.invaded[pi] == WETT:
                rhs_source[pi] = 0.0
                logger.debug("freezing NW sink %d", pi)
                freeze_sink_nw[pi] = 1

            # Set a nonwetting sink to be unfrozen if it surpasses a certain nonwetting threshold
            if (freeze_sink_nw.setdefault(pi, 0) == 1) & (sat[pi] > 0.5):
                logger.debug("Unfreezing NW sink %d", pi)
                freeze_sink_nw[pi] = 0

            # Freeze a nonwetting sink.
            if freeze_sink_nw.setdefault(pi, 0) == 1:
                logger.debug("Setting NW sink to zero since it is marked as frozen. Its Saturation is %e" % sat[pi])
                rhs_source[pi] = 0.0

        total_after_blockage = np.sum(self.q_n[pi_list_sink])

        assert total_after_blockage <= 0.0

        if total_after_blockage < 0.0:
            rhs_source[pi_list_sink] = rhs_source[pi_list_sink] * self.q_n_tot_sink / total_after_blockage
            assert np.all(rhs_source[pi_list_sink] <= 0.0)

        elif total_after_blockage == 0.0 and self.q_n_tot_sink < 0.0:

            # If drainage and the simulation is defined solely by sources then scale the nonwetting sources
            if ((self.q_n_tot_source + self.q_n_tot_sink) > 0.0) and self.bc.no_dirichlet:
                rhs_source[pi_list_source] = rhs_source[pi_list_source] * (self.q_n_tot_source + self.q_n_tot_sink) / self.q_n_tot_source
            else:
                logger.warning("Cannot adjust nonwetting sink")
                return -1

        self.__check_mass_conservation()

    def __adjust_magnitude_sink_pores(self, FLUID):
        """
        Adjusts the magnitude of the sink pores when their status changes so that the total sink remains constant.
        If no more sink pores are available, the source pores are adjusted.
        """

        if FLUID == WETT:
            return self.__adjust_magnitude_wetting_sink_pores()

        if FLUID == NWETT:
            return self.__adjust_magnitude_nonwetting_sink_pores()

    def __invade_nonwetting_source_pores(self):
        self.network.pores.invaded[self.bc.pi_list_nw_source] = NWETT
        self.network.pores.invaded[self.bc.pi_list_inlet] = NWETT

    def __compute_time_step(self):
        assert np.all(self.network.pores.invaded[self.q_n > 0.0] == 1)
        dt, dt_details = self.time_stepper.timestep(flux_n=self.flux_n, source_nonwett=self.q_n)

        STOP_FLAG = False

        iter = 0
        while (dt == 0) and (iter < 4):
            logger.debug("Time step is zero. Attempting correction")
            logger.debug("Starting inner iteration" + str(iter))

            self.__invade_nonwetting_source_pores()

            update_pore_status(self.network, flux_n=self.flux_n, flux_w=self.flux_w, source_nonwett=self.q_n,
                               source_wett=self.q_w, bool_accounted_pores=self.bool_accounted_pores)

            self.__set_rhs_source_arrays(self.bc)
            ierr = self.__adjust_magnitude_sink_pores(WETT)
            if ierr == -1:
                STOP_FLAG = True
                break

            ierr = self.__adjust_magnitude_sink_pores(NWETT)
            if ierr == -1:
                STOP_FLAG = True
                break

            self.__update_capillary_pressure()

            self.k_comp.compute()  # Side effect- Computes network.tubes.k_n and k_w

            if np.sum(self.q_w) == 0 and np.sum(self.q_n) == 0:
                logger.info("Exiting loop because sources are zero")
                STOP_FLAG = True
                break

            logger.debug("Solving linear system")
            self.__solve_linear_system()

            iter += 1

        if STOP_FLAG is True:
            dt = 0.0
        else:
            dt, dt_details = self.time_stepper.timestep(flux_n=self.flux_n, source_nonwett=self.q_n)

        assert np.all(self.network.pores.invaded[self.q_n > 0.0] == 1)

        if dt == 0.0:
            logger.debug("Time step is zero after attempting correction")

        # Compute time-step for a stop_time criteria
        if self.stop_time is not None:
            assert self.stop_time >= self.time
            dt = min(self.stop_time - self.time, dt)

        return dt, dt_details

    def __solve_pressure_and_pore_status(self):
        network = self.network
        k_comp = self.k_comp
        pe_comp = self.pe_comp

        def interior_loop():
            self.__invade_nonwetting_source_pores()
            self.__update_capillary_pressure()

            logger.debug("Snapping off Tubes")
            snapoff_all_tubes(network, pe_comp)

            logger.debug("Computing Conductances")
            k_comp.compute()  # Side effect - Computes network.tubes.k_n and k_w
            # Set source and sink arrays
            self.__set_rhs_source_arrays(self.bc)

            ierr = self.__adjust_magnitude_sink_pores(WETT)
            if ierr == -1:
                print "cannot adjust wetting sinks"
                return ierr

            ierr = self.__adjust_magnitude_sink_pores(NWETT)
            if ierr == -1:
                print "cannot adjust nonwetting sinks"
                return ierr

            self.__solve_linear_system()
            self.__invade_nonwetting_source_pores()
            self.__update_capillary_pressure()

            logger.debug("Done with interior loop")

            return 0

        ierr = interior_loop()

        if ierr == -1:
            return ierr

        update_pore_status(self.network, flux_n=self.flux_n, flux_w=self.flux_w, source_nonwett=self.q_n,
                           source_wett=self.q_w, bool_accounted_pores=self.bool_accounted_pores)

        ierr = interior_loop()

        if ierr == -1:
            return ierr

        for ti in self.ti_freeze_displacement:
            self.ti_freeze_displacement[ti] += 1

        for key in list(self.ti_freeze_displacement.keys()):
            if self.ti_freeze_displacement[key] > 20:
                del self.ti_freeze_displacement[key]

        logger.debug("frozen tubes are currently %s", self.ti_freeze_displacement.keys())

        for iter in xrange(10000):
            is_event = update_tube_piston_w(network, self.piston_entry, self.flux_w, self.q_w)
            ierr = interior_loop()
            if ierr == -1:
                return -1
            if not is_event:
                break

        ti_piston_nonwett = get_piston_disp_tubes_nonwett(network, self.piston_entry, self.flux_n, self.q_n)
        ti_piston_nonwett = set(ti_piston_nonwett) - set(self.ti_freeze_displacement.keys())

        if len(ti_piston_nonwett) == 0:
            ti_piston_nonwett = get_piston_disp_tubes_nonwett(network, self.piston_entry, self.flux_n, self.q_n)

        for ti_nonwett in ti_piston_nonwett:
            invade_tube_nw(network, ti_nonwett)
            ierr = interior_loop()

            if ierr == -1:
                return -1

            ti_piston_wetting = get_piston_disp_tubes_wett(network, self.piston_entry, self.flux_w, self.q_w)

            for ti_wett in ti_piston_wetting:
                if ti_wett == ti_nonwett:
                    self.ti_freeze_displacement[ti_nonwett] = 1
                invade_tube_w(network, ti_wett)
                ierr = interior_loop()
                if ierr == -1:
                    return ierr

        if ierr == -1:
            return ierr

        update_pore_status(self.network, flux_n=self.flux_n, flux_w=self.flux_w, source_nonwett=self.q_n,
                           source_wett=self.q_w, bool_accounted_pores=self.bool_accounted_pores)

        ierr = interior_loop()
        if ierr == -1:
            return -1

        return 0

    def __update_capillary_pressure(self):
        self.pc_comp.compute()

        if len(self.bc.pi_list_inlet) > 0:
            self.network.pores.p_c[self.bc.pi_list_inlet] = self.bc.press_inlet_nw - self.bc.press_inlet_w

        if len(self.bc.pi_list_outlet) > 0:
            self.network.pores.p_c[self.bc.pi_list_outlet] = 0.001

    def __advance(self, stop_criterion):
        self.network.pores.p_w[:] = 0.0
        self.network.pores.p_n[:] = 0.0

        network = self.network

        # Reset dictionary used to track frozen sink pores
        self.freeze_sink_nw = dict()

        self.q_w_tot_source = np.sum(self.q_w[self.bc.pi_list_w_source])
        self.q_w_tot_sink = np.sum(self.q_w[self.bc.pi_list_w_sink])
        self.q_w_tot = self.q_w_tot_source + self.q_w_tot_sink

        self.q_n_tot_source = np.sum(self.q_n[self.bc.pi_list_nw_source])
        self.q_n_tot_sink = np.sum(self.q_n[self.bc.pi_list_nw_sink])
        self.q_n_tot = self.q_n_tot_source + self.q_n_tot_sink

        logger.debug("Simulation Status. Time: %f , Saturation: %f", self.time, self.sat_comp.sat_nw())

        network.pores.invaded[self.bc.pi_list_nw_source] = NWETT

        #  Notes: During loop, self.q_n_tot and self.q_w_tot are NOT modified. But self.rhs_source_* are updated
        counter = 0
        time_init = self.time
        while True:
            _plist = self.bc.pi_list_inlet
            if len(_plist) > 0:
                self.network.pores.p_c[_plist] = self.bc.press_inlet_nw - self.bc.press_inlet_w
                self.network.pores.sat[_plist] = self.pc_comp.pc_to_sat_func(self.network.pores.r[_plist], self.network.pores.p_c[_plist])
            logger.debug("Simulation Status. Time: %f , Saturation: %f", self.time, self.sat_comp.sat_nw())

            assert np.all(network.pores.invaded[self.q_n > 0.0] == 1)

            logger.debug("Solving Pressure")
            self.__update_capillary_pressure()

            ierr = self.__solve_pressure_and_pore_status()
            self.__update_capillary_pressure()

            if ierr == -1:
                logger.warning("Exiting solver because nonwetting sinks or wetting sinks cannot be adjusted anymore")
                logger.warning("Time elapsed  Time elapsed is %e", self.time - time_init)
                logger.warning("Initial nonwetting source was  %e", self.q_n_tot_source)
                logger.warning("Initial nonwetting sink was  %e", self.q_n_tot_sink)

                break

            if self.bc.no_dirichlet:
                assert np.isclose(self.q_n_tot, np.sum(self.q_n), atol=max(abs(self.q_n_tot) * 1e-5, 1e-14)), "%g %g %d" % (self.q_n_tot, np.sum(self.q_n), counter)

            if self.bc.no_dirichlet:
                assert np.isclose(self.q_w_tot, np.sum(self.q_w), atol=max(abs(self.q_w_tot) * 1e-5, 1e-14)), "%g %g %d" % (self.q_w_tot, np.sum(self.q_w), counter)

            logger.debug("Computing time step")
            dt, dt_details = self.__compute_time_step()

            dt_ratio = dt_details["pc_crit_drain"]/dt_details["sat_n_double"]

            logger.debug("Updating saturation")
            if self.explicit:
                self.sat_comp.update_saturation(flux_n=self.flux_n, dt=dt, source_nonwett=self.q_n)
            else:
                dt = self.__update_saturation_implicit(dt=dt)

            pi_1 = network.edgelist[:,0]
            pi_2 = network.edgelist[:,1]
            self.cum_flux_tubes += (network.pores.p_n[pi_1] - network.pores.p_n[pi_2]) * network.tubes.k_n * dt

            _plist = self.bc.pi_list_inlet
            if len(_plist) > 0:
                self.network.pores.p_c[_plist] = self.bc.press_inlet_nw - self.bc.press_inlet_w
                self.network.pores.sat[_plist] = self.pc_comp.pc_to_sat_func(self.network.pores.r[_plist], self.network.pores.p_c[_plist])

            pi_sink_all = np.union1d(self.bc.pi_list_w_sink, self.bc.pi_list_nw_sink).astype(np.int)

            flux_nw_inlet = np.sum(self.q_n[self.bc.pi_list_nw_source])
            flux_nw_outlet = np.sum(self.q_n[pi_sink_all])

            flux_w_inlet = np.sum(self.q_w[self.bc.pi_list_w_source])
            flux_w_outlet = np.sum(self.q_w[pi_sink_all])

            self.accumulated_saturation += (flux_nw_inlet + flux_nw_outlet)*dt/network.total_pore_vol
            self.time += dt

            pn_max = np.max(network.pores.p_n)
            pn_min = np.min(network.pores.p_n)

            pw_max = np.max(network.pores.p_w)
            pw_min = np.min(network.pores.p_w)

            logger.debug("")
            logger.debug("="*80)
            logger.debug("Nonwetting influx: %e,  outflux: %e", flux_nw_inlet, flux_nw_outlet)
            logger.debug("Wetting influx: %e,  outflux: %e", flux_w_inlet, flux_w_outlet)

            logger.debug("Max Pressures. NW: %e, W: %e", pn_max, pw_max)
            logger.debug("Min Pressures. NW: %e, W: %e", pn_min, pw_min)
            logger.debug("Max PC %e", np.max(network.pores.p_n-network.pores.p_w))

            logger.debug("Accumulated Nonwetting Saturation %e", self.accumulated_saturation)
            logger.debug("Time %e after timestep %e", self.time, dt)
            logger.debug("Time ratio %e", dt_ratio)
            logger.debug("="*80)
            logger.debug("")

            if dt_ratio < 1e-4 and counter > 1000:
                logger.warning("Exiting solver because time step too slow.")
                break

            if stop_criterion():
                logger.debug("Exiting solver because stopping criterion has been successfully reached")
                break

            # If either of the initial nonwetting sink and source is nonzero and they are now, then stop the simulation
            if (self.q_n_tot_source != 0.0) or (self.q_n_tot_sink != 0.0):

                if (flux_nw_inlet == 0.0) and (flux_nw_outlet == 0.0):
                    logger.warning("Exiting solver because both flux_nw_inlet and outlet are zero. Time elapsed is %e", self.time - time_init)
                    logger.warning("Initial nonwetting source was  %e", self.q_n_tot_source)
                    logger.warning("Initial nonwetting sink was  %e", self.q_n_tot_sink)
                    break

            counter += 1

        return self.time - time_init
    def __init__(self, network,  fluid_properties, num_subnetworks, comm=None, mpicomm=None, subgraph_ids=None,
                 delta_s_max=0.01, delta_pc=0.01, ptol_ms=1.e-6, ptol_fs=1.e-6, btol=1.e-2):
        self.network = network

        if comm is None:
            comm = Epetra.PyComm()

        if mpicomm is None:
            mpicomm = MPI.COMM_WORLD

        self.comm, self.mpicomm = comm, mpicomm
        self.fluid_properties = fluid_properties
        self.my_id = comm.MyPID()
        my_id = self.my_id
        self.num_proc = comm.NumProc()

        self.num_subnetworks = num_subnetworks

        # On the master cpu do the following:
        # 1) Create graph corresponding to pore network.
        # 2) Partition the graph into subgraphs
        # 3) Create a coarse graph with the subgraphs as the nodes
        # 4) Use the coarse graph to assign each subgraph to a processor
        if my_id == 0:
            self.graph = network_to_igraph(network, edge_attributes=["l", "A_tot", "r", "G"])

            # create global_id attributes before creating subgraphs.
            self.graph.vs["global_id"] = np.arange(self.graph.vcount())
            self.graph.es["global_id"] = np.arange(self.graph.ecount())

            if subgraph_ids is None:
                _, subgraph_ids = pymetis.part_graph(num_subnetworks, self.graph.get_adjlist())

            subgraph_ids = np.asarray(subgraph_ids)
            self.graph.vs["subgraph_id"] = subgraph_ids

            # Assign a processor id to each subgraph
            coarse_graph = coarse_graph_from_partition(self.graph, subgraph_ids)
            _, proc_ids = pymetis.part_graph(self.num_proc, coarse_graph.get_adjlist())
            coarse_graph.vs['proc_id'] = proc_ids
            coarse_graph.vs["subgraph_id"] = np.arange(coarse_graph.vcount())

            # Assign a processor id to each pore
            subgraph_id_to_proc_id = {v["subgraph_id"]: v['proc_id'] for v in coarse_graph.vs}
            self.graph.vs["proc_id"] = [subgraph_id_to_proc_id[v["subgraph_id"]] for v in self.graph.vs]

        if my_id != 0:
            coarse_graph = None
            self.graph = None
            network = None
            subgraph_ids = None

        self.coarse_graph = self.mpicomm.bcast(coarse_graph, root=0)
        self.graph = self.distribute_graph(self.graph, self.coarse_graph, self.mpicomm)

        self.my_subnetworks = _create_subnetworks(network, subgraph_ids, self.coarse_graph, self.mpicomm)

        self.my_subgraph_ids = self.my_subnetworks.keys()
        self.my_subgraph_ids_with_ghost = list(set().union(*self.coarse_graph.neighborhood(self.my_subgraph_ids)))

        self.inter_subgraph_edges = _create_inter_subgraph_edgelist(self.graph, self.my_subgraph_ids, self.mpicomm)
        self.inter_processor_edges = self.create_inter_processor_edgelist(self.graph, self.my_subgraph_ids, self.mpicomm)

        self.subgraph_id_to_v_center_id = self.subgraph_central_vertices(self.graph, self.my_subgraph_ids_with_ghost)

        # Epetra maps to facilitate data transfer between processors
        self.unique_map, self.nonunique_map, self.subgraph_ids_vec = self.create_maps(self.graph, self.comm)

        self.epetra_importer = Epetra.Import(self.nonunique_map, self.unique_map)
        assert self.epetra_importer.NumPermuteIDs() == 0
        assert self.epetra_importer.NumSameIDs() == self.unique_map.NumMyElements()

        # subgraph_id to support vertices map. Stores the support vertex ids (global ids) of both
        # subnetworks belonging to this processor as well as those belonging to ghost subnetworks.
        # Only support vertex ids belonging to this processor are stored.
        self.my_basis_support = dict()
        self.my_subgraph_support = dict()

        my_global_elements = self.unique_map.MyGlobalElements()


        self.graph["global_to_local"] = dict((v["global_id"], v.index) for v in self.graph.vs)
        self.graph["local_to_global"] = dict((v.index, v["global_id"]) for v in self.graph.vs)

        # support region for each subgraph
        for i in self.my_subgraph_ids:
            self.my_subgraph_support[i] = self.my_subnetworks[i].pi_local_to_global

        # support region for each subgraph
        self.my_subgraph_support_with_ghosts = dict()
        for i in self.my_subgraph_ids_with_ghost:
            self.my_subgraph_support_with_ghosts[i] = np.asarray(self.graph.vs.select(subgraph_id=i)["global_id"])

        for i in self.my_subgraph_ids:
            assert np.all(np.sort(self.my_subgraph_support[i]) == np.sort(self.my_subgraph_support_with_ghosts[i]))

        for i in self.my_subgraph_ids:
            if num_subnetworks == 1:
                support_vertices = self.my_subnetworks[0].pi_local_to_global
            else:
                support_vertices = support_of_basis_function(i, self.graph, self.coarse_graph,
                                                             self.subgraph_id_to_v_center_id, self.my_subgraph_support_with_ghosts)

            self.my_basis_support[i] = np.intersect1d(support_vertices, my_global_elements).astype(np.int32)

        # Create distributed arrays - Note: Memory wasted here by allocating extra arrays which include ghost cells.
        # This can be optimized but the python interface for PyTrilinos is not documented well enough.
        # Better would be to create only the arrays which include ghost cells.
        unique_map = self.unique_map
        nonunique_map = self.nonunique_map
        self.p_c = Epetra.Vector(unique_map)
        self.p_w = Epetra.Vector(unique_map)
        self.sat = Epetra.Vector(unique_map)
        self.global_source_wett = Epetra.Vector(unique_map)
        self.global_source_nonwett = Epetra.Vector(unique_map)
        self.out_flux_w = Epetra.Vector(unique_map)
        self.out_flux_n = Epetra.Vector(unique_map)

        self.p_c_with_ghost = Epetra.Vector(nonunique_map)
        self.p_w_with_ghost = Epetra.Vector(nonunique_map)
        self.sat_with_ghost = Epetra.Vector(nonunique_map)
        self.global_source_nonwett_with_ghost = Epetra.Vector(nonunique_map)
        self.out_flux_n_with_ghost = Epetra.Vector(nonunique_map)

        # Simulation parameters
        self.delta_s_max = delta_s_max
        self.ptol_ms = ptol_ms
        self.ptol_fs = ptol_fs
        self.delta_pc = delta_pc
        self.btol = btol

        # Crate dynamic simulations
        self.simulations = dict()

        for i in self.my_subgraph_ids:
            self.simulations[i] = DynamicSimulation(self.my_subnetworks[i], self.fluid_properties, delta_pc=self.delta_pc,
                                                    ptol=self.ptol_fs)

            self.simulations[i].solver_type = "lu"

            k_comp = ConductanceCalc(self.my_subnetworks[i], self.fluid_properties)
            k_comp.compute()
            pc_comp = DynamicCapillaryPressureComputer(self.my_subnetworks[i])
            pc_comp.compute()

        self.time = 0.0
        self.stop_time = None

        self.pi_list_press_inlet = []
        self.press_inlet_w = None
        self.press_inlet_nw = None

        self.pi_list_press_outlet = []
        self.press_outlet_w = None
        self.press_outlet_nw = None
Exemple #11
0
class SimpleRelPermComputer(object):
    def __init__(self,
                 network,
                 fluid_properties,
                 pores_have_conductance=False):
        self.network = network
        x_max, x_min = np.max(network.pores.x), np.min(network.pores.x)
        y_max, y_min = np.max(network.pores.y), np.min(network.pores.y)
        z_max, z_min = np.max(network.pores.z), np.min(network.pores.z)
        self.network_vol = (x_max - x_min) * (y_max - y_min) * (z_max - z_min)

        self.total_flux_one_phase = None
        self.kr_n = np.zeros(3)
        self.kr_w = np.zeros(3)
        self.is_isotropic = True

        # Mask of tubes and pores not connected to the inlet, which makes the matrix singular
        p_conn_inlet, t_conn_inlet = graph_algs.get_pore_and_tube_connectivity_to_pore_list(
            self.network, self.network.pi_list_face[WEST])
        self.mask_ignored_tubes = (t_conn_inlet == 0)

        self.fluid_properties = fluid_properties

        self.cond_computer = ConductanceCalc(self.network, fluid_properties,
                                             pores_have_conductance)
        self.absolute_permeability()

    def _general_absolute_permeability(self, coord, pi_inlet, pi_outlet):
        network = self.network
        tube_k_w = self.cond_computer.conductances_fully_wetting()

        pi_dirichlet = np.union1d(pi_inlet, pi_outlet)
        A = laplacian_from_network(network,
                                   weights=tube_k_w,
                                   ind_dirichlet=pi_dirichlet)
        rhs = np.zeros(network.nr_p)
        rhs[pi_inlet] = 1.0

        pressure = petsc_solve(A * 1e20, rhs * 1e20, tol=1e-12)

        mu_w = self.fluid_properties["mu_w"]
        self.total_flux_one_phase = flux_into_pores(network, pressure,
                                                    tube_k_w, pi_inlet)

        coord_max = np.min(coord)
        coord_min = np.max(coord)

        return mu_w * np.abs(self.total_flux_one_phase) * (
            coord_max - coord_min)**2 / self.network_vol

    def _general_effective_nonwetting_permeability(self, coord, pi_inlet,
                                                   pi_outlet):
        network = self.network
        tube_k_n, tube_k_w = self.cond_computer.conductances_two_phase()

        p_conn_inlet, t_conn_inlet = graph_algs.get_pore_and_tube_nonwetting_connectivity_to_pore_list(
            network, pi_inlet)
        is_nwett_pore_outlet_conn_to_inlet = np.any(
            p_conn_inlet[pi_outlet] == 1)

        if not is_nwett_pore_outlet_conn_to_inlet:
            kr_n_eff = 0.0
        else:
            pi_dirichlet = reduce(np.union1d,
                                  (pi_inlet, pi_outlet,
                                   (p_conn_inlet == 0).nonzero()[0]))
            A = laplacian_from_network(network,
                                       weights=tube_k_n,
                                       ind_dirichlet=pi_dirichlet)

            rhs = np.zeros(network.nr_p)
            rhs[pi_inlet] = 1.0

            pressure = petsc_solve(A * 1e20, rhs * 1e20, tol=1e-12)

            mu_n = self.fluid_properties["mu_n"]
            total_flux_nwett = flux_into_pores(network, pressure, tube_k_n,
                                               pi_inlet)

            coord_max = np.min(coord)
            coord_min = np.max(coord)

            kr_n_eff = mu_n * np.abs(total_flux_nwett) * (
                coord_max - coord_min)**2 / self.network_vol

        return kr_n_eff

    def effective_nonwetting_permeability(self):
        network = self.network
        K_n = [0] * 3

        K_n[0] = self._general_effective_nonwetting_permeability(
            network.pores.x, network.pi_list_face[WEST],
            network.pi_list_face[EAST])

        K_n[1] = self._general_effective_nonwetting_permeability(
            network.pores.y, network.pi_list_face[SOUTH],
            network.pi_list_face[NORTH])

        K_n[2] = self._general_effective_nonwetting_permeability(
            network.pores.z, network.pi_list_face[BOTTOM],
            network.pi_list_face[TOP])

        return np.asarray(K_n)

    def absolute_permeability(self):
        network = self.network
        K = [0] * 3

        K[0] = self._general_absolute_permeability(network.pores.x,
                                                   network.pi_list_face[WEST],
                                                   network.pi_list_face[EAST])

        K[1] = self._general_absolute_permeability(network.pores.y,
                                                   network.pi_list_face[SOUTH],
                                                   network.pi_list_face[NORTH])

        K[2] = self._general_absolute_permeability(
            network.pores.z, network.pi_list_face[BOTTOM],
            network.pi_list_face[TOP])

        assert np.all(K > 0.0), K

        return np.asarray(K)