コード例 #1
0
class FEMNavierStokesModel2d_channel:
    def __init__(self, pde, mesh, p, dt, T):
        self.p = p
        self.mesh = mesh
        self.dt = dt
        self.T = T
        self.itype = self.mesh.itype
        self.ftype = self.mesh.ftype
        self.pde = pde
        self.vspace = LagrangeFiniteElementSpace(mesh, p + 1)
        self.pspace = LagrangeFiniteElementSpace(mesh, p)
        self.vdof = self.vspace.dof
        self.pdof = self.pspace.dof
        self.cellmeasure = mesh.entity_measure('cell')
        self.integralalg = FEMeshIntegralAlg(self.mesh,
                                             p + 4,
                                             cellmeasure=self.cellmeasure)
        self.uh0 = self.vspace.function()
        self.uh1 = self.vspace.function()
        self.ph = self.pspace.function()

    @timer
    def NS_VC_Solver(self):
        """
        The Navier-Stokes Velocity-Correction scheme solver.
        """
        pde = self.pde
        dt = self.dt
        uh0 = self.uh0
        uh1 = self.uh1
        ph = self.ph
        vspace = self.vspace
        pspace = self.pspace
        vdof = self.vdof
        pdof = self.pdof
        pface2dof = pdof.face_to_dof()  # (NE,fldof)
        pcell2dof = pdof.cell_to_dof()  # (NC,cldof)
        ucell2dof = vspace.cell_to_dof()  # (NC,cldof)

        idxDirEdge = self.set_Dirichlet_edge()
        cellidxDir = self.mesh.ds.edge2cell[idxDirEdge, 0]
        localidxDir = self.mesh.ds.edge2cell[idxDirEdge, 2]

        Dir_face2dof = pface2dof[idxDirEdge, :]  # (NDir,flodf)
        Dir_cell2dof = pcell2dof[cellidxDir, :]  # (NDir,cldof)
        n_Dir = self.mesh.face_unit_normal(index=idxDirEdge)  # (NDir,2)
        dir_face_measure = self.mesh.entity_measure(
            'face', index=idxDirEdge)  # (NDir,2)
        cell_measure = self.mesh.cell_area()
        dir_cell_measure = self.mesh.cell_area(cellidxDir)

        f_q = self.integralalg.faceintegrator
        f_bcs, f_ws = f_q.get_quadrature_points_and_weights(
        )  # f_bcs.shape: (NQ,(GD-1)+1)
        f_pp = self.mesh.bc_to_point(
            f_bcs, index=idxDirEdge
        )  # f_pp.shape: (NQ,NDir,GD) the physical Gauss points

        c_q = self.integralalg.cellintegrator
        c_bcs, c_ws = c_q.get_quadrature_points_and_weights(
        )  # c_bcs.shape: (NQ,GD+1)
        c_pp = self.mesh.bc_to_point(
            c_bcs)  # c_pp.shape: (NQ_cell,NC,GD) the physical Gauss points

        last_uh0 = vspace.function()
        last_uh1 = vspace.function()
        next_ph = pspace.function()

        # # t^{n+1}: Pressure-Left-StiffMatrix
        plsm = self.pspace.stiff_matrix()
        basis_int = pspace.integral_basis()
        p_phi = pspace.face_basis(
            f_bcs)  # (NQ,1,fldof). 实际上这里可以直接用 pspace.basis(f_bcs), 两个函数的代码是相同的
        p_gphi_f = pspace.edge_grad_basis(f_bcs, cellidxDir,
                                          localidxDir)  # (NDir,NQ,cldof,GD)
        p_gphi_c = pspace.grad_basis(c_bcs)  # (NQ_cell,NC,ldof,GD)

        # # t^{n+1}: Velocity-Left-MassMatrix and -StiffMatrix
        ulmm = self.vspace.mass_matrix()
        ulsm = self.vspace.stiff_matrix()
        u_phi = vspace.basis(c_bcs)  # (NQ,1,cldof)

        next_t = 0
        dofLocalIdxInflow, localIdxInflow = self.set_velocity_inflow_dof()
        idxOutFlowEdge = self.set_outflow_edge()
        for nt in range(int(self.T / dt)):
            curr_t = nt * dt
            next_t = curr_t + dt

            # ---------------------------------------
            # 1st-step: get the p^{n+1}
            # ---------------------------------------
            # # Pressure-Right-Matrix
            if curr_t == 0.:
                # for Dirichlet-face-integration
                last_gu_val0 = pde.grad_velocity0(f_pp,
                                                  0)  # grad_u0: (NQ,NDir,GD)
                last_gu_val1 = pde.grad_velocity1(f_pp,
                                                  0)  # grad_u1: (NQ,NDir,GD)

                # for cell-integration
                last_u_val = pde.velocityInitialValue(c_pp)  # (NQ,NC,GD)
                last_u_val0 = last_u_val[..., 0]  # (NQ,NC)
                last_u_val1 = last_u_val[..., 1]  # (NQ,NC)

                last_nolinear_val0 = pde.NS_nolinearTerm_0(c_pp, 0)  # (NQ,NC)
                last_nolinear_val1 = pde.NS_nolinearTerm_1(c_pp, 0)  # (NQ,NC)
            else:
                # for Dirichlet-face-integration
                # last_gu_val0 = vspace.grad_value(last_uh0, f_bcs)  # grad_u0: (NQ,NDir,GD)
                # last_gu_val1 = vspace.grad_value(last_uh1, f_bcs)  # grad_u1: (NQ,NDir,GD)
                last_gu_val0 = self.uh_grad_value_at_faces(
                    last_uh0, f_bcs, cellidxDir,
                    localidxDir)  # grad_u0: (NQ,NDir,GD)
                last_gu_val1 = self.uh_grad_value_at_faces(
                    last_uh1, f_bcs, cellidxDir,
                    localidxDir)  # grad_u0: (NQ,NDir,GD)

                # for cell-integration
                last_u_val0 = vspace.value(last_uh0, c_bcs)  # (NQ,NC)
                last_u_val1 = vspace.value(last_uh1, c_bcs)

                last_nolinear_val = self.NSNolinearTerm(
                    last_uh0, last_uh1,
                    c_bcs)  # last_nolinear_val.shape: (NQ,NC,GD)
                last_nolinear_val0 = last_nolinear_val[..., 0]  # (NQ,NC)
                last_nolinear_val1 = last_nolinear_val[..., 1]  # (NQ,NC)

            uDir_val = pde.dirichlet(
                f_pp, next_t, bd_threshold=localIdxInflow)  # (NQ,NDir,GD)
            f_val = pde.source(c_pp, next_t)  # (NQ,NC,GD)

            # # --- to update the pressure value --- # #
            # # to get the Pressure's Right-hand Vector
            prv = np.zeros((pdof.number_of_global_dofs(), ),
                           dtype=self.ftype)  # (Npdof,)

            # for Dirichlet faces integration
            dir_int0 = -1 / dt * np.einsum('i, ijk, jk, ijn, j->jn', f_ws,
                                           uDir_val, n_Dir, p_phi,
                                           dir_face_measure)  # (NDir,fldof)
            dir_int1 = -pde.nu * (
                np.einsum('i, j, ij, jin, j->jn', f_ws, n_Dir[:, 1],
                          last_gu_val1[..., 0] - last_gu_val0[..., 1],
                          p_gphi_f[..., 0], dir_face_measure) +
                np.einsum('i, j, ij, jin, j->jn', f_ws, -n_Dir[:, 0],
                          last_gu_val1[..., 0] - last_gu_val0[..., 1],
                          p_gphi_f[..., 1], dir_face_measure))  # (NDir,cldof)
            # for cell integration
            cell_int0 = 1 / dt * (
                np.einsum('i, ij, ijk, j->jk', c_ws, last_u_val0,
                          p_gphi_c[..., 0], cell_measure) +
                np.einsum('i, ij, ijk, j->jk', c_ws, last_u_val1,
                          p_gphi_c[..., 1], cell_measure))  # (NC,cldof)
            cell_int1 = -(
                np.einsum('i, ij, ijk, j->jk', c_ws, last_nolinear_val0,
                          p_gphi_c[..., 0], cell_measure) +
                np.einsum('i, ij, ijk, j->jk', c_ws, last_nolinear_val1,
                          p_gphi_c[..., 1], cell_measure))  # (NC,cldof)
            cell_int2 = (np.einsum('i, ij, ijk, j->jk', c_ws, f_val[..., 0],
                                   p_gphi_c[..., 0], cell_measure) +
                         np.einsum('i, ij, ijk, j->jk', c_ws, f_val[..., 1],
                                   p_gphi_c[..., 1], cell_measure)
                         )  # (NC,cldof)

            np.add.at(prv, Dir_face2dof, dir_int0)
            np.add.at(prv, Dir_cell2dof, dir_int1)
            np.add.at(prv, pcell2dof, cell_int0 + cell_int1 + cell_int2)

            # # Method I: The following code is right! Pressure satisfies \int_\Omega p = 0
            # plsm_temp = bmat([[plsm, basis_int.reshape(-1, 1)], [basis_int, None]], format='csr')
            # prv = np.r_[prv, 0]
            # next_ph[:] = spsolve(plsm_temp, prv)[:-1]  # we have added one addtional dof

            # # Method II: Using the Dirichlet boundary of pressure
            def dir_pressure(p):
                return pde.pressure_dirichlet(p, next_t)

            bc = DirichletBC(pspace, dir_pressure, threshold=idxOutFlowEdge)
            plsm_temp, prv = bc.apply(plsm.copy(), prv)
            next_ph[:] = spsolve(plsm_temp, prv).reshape(-1)

            # # --- to update the velocity value --- # #
            next_gradph = pspace.grad_value(next_ph, c_bcs)  # (NQ,NC,2)

            # the velocity u's Left-Matrix
            ulm0 = 1 / dt * ulmm + pde.nu * ulsm
            ulm1 = 1 / dt * ulmm + pde.nu * ulsm

            # # to get the u's Right-hand Vector
            def dir_u0(p):
                return pde.dirichlet(p, next_t,
                                     bd_threshold=dofLocalIdxInflow)[..., 0]

            def dir_u1(p):
                return pde.dirichlet(p, next_t,
                                     bd_threshold=dofLocalIdxInflow)[..., 1]

            # for the first-component of velocity
            urv0 = np.zeros((vdof.number_of_global_dofs(), ),
                            dtype=self.ftype)  # (Nvdof,)
            urv0_temp = np.einsum('i, ij, ijk, j->jk', c_ws,
                                  last_u_val0 / dt - next_gradph[..., 0] -
                                  last_nolinear_val0 + f_val[..., 0], u_phi,
                                  cell_measure)  # (NC,clodf)
            np.add.at(urv0, ucell2dof, urv0_temp)
            u0_bc = DirichletBC(vspace, dir_u0, threshold=idxDirEdge)
            ulm0, urv0 = u0_bc.apply(ulm0, urv0)
            last_uh0[:] = spsolve(ulm0, urv0).reshape(-1)

            # for the second-component of velocity
            urv1 = np.zeros((vdof.number_of_global_dofs(), ),
                            dtype=self.ftype)  # (Nvdof,)
            urv1_temp = np.einsum('i, ij, ijk, j->jk', c_ws,
                                  last_u_val1 / dt - next_gradph[..., 1] -
                                  last_nolinear_val1 + f_val[..., 1], u_phi,
                                  cell_measure)  # (NC,clodf)
            np.add.at(urv1, ucell2dof, urv1_temp)
            u1_bc = DirichletBC(vspace, dir_u1, threshold=idxDirEdge)
            ulm1, urv1 = u1_bc.apply(ulm1, urv1)
            last_uh1[:] = spsolve(ulm1, urv1).reshape(-1)

            if nt % 100 == 0:
                print('# ------------ logging the circle info ------------ #')
                print('current t = ', curr_t)
                p_l2err, u0_l2err, u1_l2err = self.currt_error(
                    next_ph, last_uh0, last_uh1, next_t)
                print('p_l2err = %e,  u0_l2err = %e,  u1_l2err = %e' %
                      (p_l2err, u0_l2err, u1_l2err))
                print(
                    '# ------------------------------------------------- # \n')
                if np.isnan(p_l2err) | np.isnan(u0_l2err) | np.isnan(u1_l2err):
                    print('Some error is nan: breaking the program')
                    break

            # print('end of current time')

        # print('# ------------ the end error ------------ #')
        # p_l2err, u0_l2err, u1_l2err = self.currt_error(next_ph, last_uh0, last_uh1, next_t)
        # print('p_l2err = %e,  u0_l2err = %e,  u1_l2err = %e' % (p_l2err, u0_l2err, u1_l2err))

        return last_uh0, last_uh1, next_ph

    def currt_error(self, ph, uh0, uh1, t):
        pde = self.pde

        def currt_pressure(p):
            return pde.pressure(p, t)

        p_l2err = self.pspace.integralalg.L2_error(currt_pressure, ph)

        # print('p_l2err = %e' % p_l2err)

        def currt_u0(p):
            return pde.velocity(p, t)[..., 0]

        u0_l2err = self.vspace.integralalg.L2_error(currt_u0, uh0)

        # print('u0_l2err = %e' % u0_l2err)

        def currt_u1(p):
            return pde.velocity(p, t)[..., 1]

        u1_l2err = self.vspace.integralalg.L2_error(currt_u1, uh1)
        # print('u1_l2err = %e' % u1_l2err)

        return p_l2err, u0_l2err, u1_l2err

    def uh_grad_value_at_faces(self, vh, f_bcs, cellidx, localidx):
        cell2dof = self.vdof.cell2dof
        f_gphi = self.vspace.edge_grad_basis(f_bcs, cellidx,
                                             localidx)  # (NE,NQ,cldof,GD)

        # val.shape: (NQ,NE,GD)
        # vh.shape: (v_gdof,)
        # vh[cell2dof[cellidx]].shape: (Ncellidx,cldof)
        val = np.einsum('ik, ijkm->jim', vh[cell2dof[cellidx]], f_gphi)
        return val

    def NSNolinearTerm(self, uh0, uh1, bcs):
        vspace = self.vspace
        val0 = vspace.value(uh0, bcs)  # val0.shape: (NQ,NC)
        val1 = vspace.value(uh1, bcs)  # val1.shape: (NQ,NC)
        gval0 = vspace.grad_value(uh0, bcs)  # gval0.shape: (NQ,NC,2)
        gval1 = vspace.grad_value(uh1, bcs)

        NSNolinear = np.empty(gval0.shape,
                              dtype=self.ftype)  # NSNolinear.shape: (NQ,NC,2)

        NSNolinear[..., 0] = val0 * gval0[..., 0] + val1 * gval0[..., 1]
        NSNolinear[..., 1] = val0 * gval1[..., 0] + val1 * gval1[..., 1]

        return NSNolinear

    def set_Dirichlet_edge(self, idxDirEdge=None):
        if idxDirEdge is not None:
            return idxDirEdge

        mesh = self.mesh
        node = mesh.node  # (NV,2)
        edge2cell = mesh.ds.edge_to_cell()
        isBdEdge = (edge2cell[:, 0] == edge2cell[:, 1]
                    )  # (NE,), the bool vars, to get the boundary edges
        idxBdEdge, = np.nonzero(isBdEdge)

        edge2node = mesh.ds.edge_to_node()  # (NE,2)
        mid_coor = (node[edge2node[:, 0], :] +
                    node[edge2node[:, 1], :]) / 2  # (NE,2)
        bd_mid = mid_coor[isBdEdge, :]

        isOutflow = np.abs(bd_mid[:, 0] - 1.0) < 1e-6

        isDirEdge = ~isOutflow  # here, we set all the boundary but the right edges are Dir edges
        idxDirEdge = idxBdEdge[isDirEdge]  # (NE_Dir,)

        return idxDirEdge

    def set_outflow_edge(self, idxOutEdge=None):
        if idxOutEdge is not None:
            return idxOutEdge

        mesh = self.mesh
        node = mesh.node  # (NV,2)
        edge2cell = mesh.ds.edge_to_cell()
        isBdEdge = (edge2cell[:, 0] == edge2cell[:, 1]
                    )  # (NE,), the bool vars, to get the boundary edges
        idxBdEdge, = np.nonzero(isBdEdge)

        edge2node = mesh.ds.edge_to_node()  # (NE,2)
        mid_coor = (node[edge2node[:, 0], :] +
                    node[edge2node[:, 1], :]) / 2  # (NE,2)
        bd_mid = mid_coor[isBdEdge, :]

        isOutflow = np.abs(bd_mid[:, 0] - 1.0) < 1e-6
        idxOutEdge = idxBdEdge[isOutflow]  # (NE_Dir,)

        return idxOutEdge

    def set_velocity_inflow_dof(self):
        mesh = self.mesh
        node = mesh.node  # (NV,2)
        edge2node = mesh.ds.edge_to_node()  # (NE,2)

        idxDirEdge = self.set_Dirichlet_edge()
        dirIndicator = np.full(idxDirEdge.shape, False)

        mid_coor = (node[edge2node[:, 0], :] +
                    node[edge2node[:, 1], :]) / 2  # (NE,2)
        bd_mid = mid_coor[idxDirEdge, :]

        # --- set inflow edges --- #
        inflow_x = 0.0
        isInflow = np.abs(bd_mid[:, 0] - inflow_x) < 1e-6
        localIdxInflow = np.nonzero(isInflow)
        idxInflow = idxDirEdge[isInflow]

        # --- set inflow dof --- #
        edge2dof = self.vdof.edge_to_dof()
        dir_dof = np.unique(edge2dof[idxDirEdge].flatten())
        inflow_dof = np.unique(edge2dof[idxInflow].flatten())

        dofLocalIdxInflow = np.searchsorted(dir_dof, inflow_dof)
        return dofLocalIdxInflow, localIdxInflow

    def set_Neumann_edge(self, idxNeuEdge=None):
        if idxNeuEdge is not None:
            return idxNeuEdge

        mesh = self.mesh
        edge2cell = mesh.ds.edge_to_cell()
        bdEdge = (edge2cell[:, 0] == edge2cell[:, 1]
                  )  # the bool vars, to get the boundary edges

        isNeuEdge = bdEdge  # here, we first set all the boundary edges are Neu edges

        issetNeuEdge = 'no'
        if issetNeuEdge == 'no':
            isNeuEdge = None

        idxNeuEdge, = np.nonzero(isNeuEdge)  # (NE_Dir,)

        return idxNeuEdge
コード例 #2
0
class FEMCahnHilliardModel2d:
    def __init__(self, pde, mesh, p, dt):
        self.pde = pde
        self.p = p
        self.mesh = mesh
        self.timemesh, self.dt = self.pde.time_mesh(dt)
        self.itype = self.mesh.itype
        self.ftype = self.mesh.ftype
        self.pde = pde
        self.space = LagrangeFiniteElementSpace(mesh, p)
        self.dof = self.space.dof
        self.cellmeasure = mesh.entity_measure('cell')
        self.integralalg = FEMeshIntegralAlg(self.mesh, p + 4, cellmeasure=self.cellmeasure)
        self.uh = self.space.function()
        self.wh = self.space.function()
        self.StiffMatrix = self.space.stiff_matrix()
        self.MassMatrix = self.space.mass_matrix()

    def setCoefficient_T1stOrder(self, dt_minimum=None):
        pde = self.pde
        dt_min = self.dt if dt_minimum is None else dt_minimum
        m = pde.m
        epsilon = pde.epsilon

        s = np.sqrt(4 * epsilon / (m * dt_min))
        alpha = 1. / (2 * epsilon) * (-s + np.sqrt(abs(s ** 2 - 4 * epsilon / (m * self.dt))))
        return s, alpha

    def setCoefficient_T2ndOrder(self, dt_minimum=None):
        pde = self.pde
        dt_min = self.dt if dt_minimum is None else dt_minimum
        m = pde.m
        epsilon = pde.epsilon

        s = np.sqrt(4 * (3/2) * epsilon / (m * dt_min))
        alpha = 1. / (2 * epsilon) * (-s + np.sqrt(abs(s ** 2 - 4 * (3/2) * epsilon / (m * self.dt))))
        return s, alpha

    def uh_grad_value_at_faces(self, vh, f_bcs, cellidx, localidx):
        cell2dof = self.dof.cell2dof
        f_gphi = self.space.edge_grad_basis(f_bcs, cellidx, localidx)  # (NE,NQ,cldof,GD)
        val = np.einsum('ik, ijkm->jim', vh[cell2dof[cellidx]], f_gphi)  # (NQ,NE,GD)
        return val

    def grad_free_energy_at_faces(self, uh, f_bcs, idxBdEdge, cellidx, localidx):
        """
        1. Compute the grad of free energy at FACE Gauss-integration points (barycentric coordinates).
        2. In this function, the free energy has NO coefficients.
        -------
        :param uh:
        :param f_bcs: f_bcs.shape: (NQ,(GD-1)+1)
        :return:
        """

        uh_val = self.space.value(uh, f_bcs)[..., idxBdEdge]  # (NQ,NBE)
        guh_val = self.uh_grad_value_at_faces(uh, f_bcs, cellidx, localidx)  # (NQ,NBE,GD)

        guh_val[..., 0] = 3 * uh_val ** 2 * guh_val[..., 0] - guh_val[..., 0]
        guh_val[..., 1] = 3 * uh_val ** 2 * guh_val[..., 1] - guh_val[..., 1]
        return guh_val  # (NQ,NBE,2)

    def grad_free_energy_at_cells(self, uh, c_bcs):
        """
        1. Compute the grad of free energy at CELL Gauss-integration points (barycentric coordinates).
        2. In this function, the free energy has NO coefficients.
        -------
        :param uh:
        :param c_bcs: c_bcs.shape: (NQ,GD+1)
        :return:
        """

        uh_val = self.space.value(uh, c_bcs)  # (NQ,NC)
        guh_val = self.space.grad_value(uh, c_bcs)  # (NQ,NC,2)

        guh_val[..., 0] = 3 * uh_val ** 2 * guh_val[..., 0] - guh_val[..., 0]
        guh_val[..., 1] = 3 * uh_val ** 2 * guh_val[..., 1] - guh_val[..., 1]
        return guh_val  # (NQ,NC,2)

    @timer
    def CH_Solver_T1stOrder(self):
        pde = self.pde
        timemesh = self.timemesh
        NT = len(timemesh)
        dt = self.dt
        uh = self.uh
        wh = self.wh
        sm = self.StiffMatrix
        mm = self.MassMatrix
        space = self.space
        dof = self.dof
        face2dof = dof.face_to_dof()  # (NE,fldof)
        cell2dof = dof.cell_to_dof()  # (NC,cldof)

        dt_min = pde.dt_min if hasattr(pde, 'dt_min') else dt
        s, alpha = self.setCoefficient_T1stOrder(dt_minimum=dt_min)
        m = pde.m
        epsilon = pde.epsilon
        eta = pde.eta

        print('    # #################################### #')
        print('      Time 1st-order scheme')
        print('    # #################################### #')

        print('    # ------------ parameters ------------ #')
        print('    s = %.4e,  alpha = %.4e,  m = %.4e,  epsilon = %.4e,  eta = %.4e' % (s, alpha, m, epsilon, eta))
        print('    t0 = %.4e,  T = %.4e, dt = %.4e' % (timemesh[0], timemesh[-1], dt))
        print(' ')

        idxNeuEdge = self.set_Neumann_edge()
        nBd = self.mesh.face_unit_normal(index=idxNeuEdge)  # (NBE,2)
        NeuCellIdx = self.mesh.ds.edge2cell[idxNeuEdge, 0]
        NeuLocalIdx = self.mesh.ds.edge2cell[idxNeuEdge, 2]
        neu_face_measure = self.mesh.entity_measure('face', index=idxNeuEdge)  # (Nneu,2)
        cell_measure = self.mesh.cell_area()

        f_q = self.integralalg.faceintegrator
        f_bcs, f_ws = f_q.get_quadrature_points_and_weights()  # f_bcs.shape: (NQ,(GD-1)+1)
        f_pp = self.mesh.bc_to_point(f_bcs, index=idxNeuEdge)  # f_pp.shape: (NQ,NBE,GD) the physical Gauss points
        c_q = self.integralalg.cellintegrator
        c_bcs, c_ws = c_q.get_quadrature_points_and_weights()  # c_bcs.shape: (NQ,GD+1)
        c_pp = self.mesh.bc_to_point(c_bcs)  # c_pp.shape: (NQ_cell,NC,GD) the physical Gauss points

        phi_f = space.face_basis(f_bcs)  # (NQ,1,fldof). 实际上这里可以直接用 pspace.basis(f_bcs), 两个函数的代码是相同的
        phi_c = space.basis(c_bcs)  # (NQ,NC,clodf)
        gphi_c = space.grad_basis(c_bcs)  # (NQ,NC,cldof,GD)

        # # time-looping
        print('    # ------------ begin the time-looping ------------ #')
        for nt in range(NT-1):
            currt_t = timemesh[nt]
            next_t = currt_t + dt

            if nt % max([int(NT / 10), 1]) == 0:
                print('    currt_t = %.4e' % currt_t)
            if nt == 0:
                # the initial value setting
                u_c = pde.solution(c_pp, pde.t0)  # (NQC,NC)
                gu_c = pde.gradient(c_pp, pde.t0)  # (NQC,NC,2)
                u_f = pde.solution(f_pp, pde.t0)  # (NQF,NBE)
                gu_f = pde.gradient(f_pp, pde.t0)  # (NQF,NBE,2)

                grad_free_energy_c = epsilon / eta ** 2 * (3 * np.repeat(u_c[..., np.newaxis], 2, axis=2) ** 2 * gu_c - gu_c)
                grad_free_energy_f = epsilon / eta ** 2 * (3 * np.repeat(u_f[..., np.newaxis], 2, axis=2) ** 2 * gu_f - gu_f)

                uh_val = u_c  # (NQ,NC)
                guh_val_c = gu_c  # (NQ,NC,2)
                guh_val_f = gu_f  # (NQ,NE,2)

                # gu_c_test = gu_c.copy()
                # gu_c_test[..., 0] = epsilon / eta ** 2 * (3 * u_c ** 2 * gu_c_test[..., 0] - gu_c_test[..., 0])
                # gu_c_test[..., 1] = epsilon / eta ** 2 * (3 * u_c ** 2 * gu_c_test[..., 1] - gu_c_test[..., 1])
                # gu_f_test = gu_f.copy()
                # gu_f_test[..., 0] = epsilon / eta ** 2 * (3 * u_f ** 2 * gu_f_test[..., 0] - gu_f_test[..., 0])
                # gu_f_test[..., 1] = epsilon / eta ** 2 * (3 * u_f ** 2 * gu_f_test[..., 1] - gu_f_test[..., 1])
                # print(np.allclose(gu_c_test, grad_free_energy_c))
                # print(np.allclose(gu_f_test, grad_free_energy_f))
            else:
                grad_free_energy_c = epsilon / eta ** 2 * self.grad_free_energy_at_cells(uh, c_bcs)  # (NQ,NC,2)
                grad_free_energy_f = epsilon / eta ** 2 * self.grad_free_energy_at_faces(uh, f_bcs, idxNeuEdge, NeuCellIdx,
                                                                                         NeuLocalIdx)  # (NQ,NE,2)
                uh_val = space.value(uh, c_bcs)  # (NQ,NC)
                guh_val_c = space.grad_value(uh, c_bcs)  # (NQ,NC,2)
                guh_val_f = self.uh_grad_value_at_faces(uh, f_bcs, NeuCellIdx, NeuLocalIdx)  # (NQ,NE,2)

            # # # ---------------------------------------- yc test --------------------------------------------------- # #
            # if nt == 0:
            #     def init_solution(p):
            #         return pde.solution(p, 0)
            #     uh[:] = space.interpolation(init_solution)
            # grad_free_energy_c = epsilon / eta ** 2 * self.grad_free_energy_at_cells(uh, c_bcs)  # (NQ,NC,2)
            # grad_free_energy_f = epsilon / eta ** 2 * self.grad_free_energy_at_faces(uh, f_bcs, idxNeuEdge, NeuCellIdx,
            #                                                                          NeuLocalIdx)  # (NQ,NE,2)
            # uh_val = space.value(uh, c_bcs)  # (NQ,NC)
            # guh_val_c = space.grad_value(uh, c_bcs)  # (NQ,NC,2)
            # guh_val_f = self.uh_grad_value_at_faces(uh, f_bcs, NeuCellIdx, NeuLocalIdx)  # (NQ,NE,2)
            # # # --------------------------------------- end test ---------------------------------------------------- # #

            Neumann = pde.neumann(f_pp, next_t, nBd)  # (NQ,NE)
            LaplaceNeumann = pde.laplace_neumann(f_pp, next_t, nBd)  # (NQ,NE)
            f_val = pde.source(c_pp, next_t, m, epsilon, eta)  # (NQ,NC)

            # # get the auxiliary equation Right-hand-side-Vector
            aux_rv = np.zeros((dof.number_of_global_dofs(),), dtype=self.ftype)  # (Ndof,)

            # # aux_rhs_c_0:  -1. / (epsilon * m * dt) * (uh^n,phi)_\Omega
            aux_rhs_c_0 = -1. / (epsilon * m) * (1/dt * np.einsum('i, ij, ijk, j->jk', c_ws, uh_val, phi_c, cell_measure) +
                                                 np.einsum('i, ij, ijk, j->jk', c_ws, f_val, phi_c, cell_measure))  # (NC,cldof)
            # # aux_rhs_c_1: -s / epsilon * (\nabla uh^n, \nabla phi)_\Omega
            aux_rhs_c_1 = -s / epsilon * (
                    np.einsum('i, ij, ijk, j->jk', c_ws, guh_val_c[..., 0], gphi_c[..., 0], cell_measure)
                    + np.einsum('i, ij, ijk, j->jk', c_ws, guh_val_c[..., 1], gphi_c[..., 1], cell_measure))  # (NC,cldof)
            # # aux_rhs_c_2: 1 / epsilon * (\nabla h(uh^n), \nabla phi)_\Omega
            aux_rhs_c_2 = 1. / epsilon * (
                    np.einsum('i, ij, ijk, j->jk', c_ws, grad_free_energy_c[..., 0], gphi_c[..., 0], cell_measure)
                    + np.einsum('i, ij, ijk, j->jk', c_ws, grad_free_energy_c[..., 1], gphi_c[..., 1], cell_measure))  # (NC,cldof)

            # # aux_rhs_f_0: (\nabla wh^{n+1}\cdot n, phi)_\Gamma, wh is the solution of auxiliary equation
            aux_rhs_f_0 = alpha * np.einsum('i, ij, ijn, j->jn', f_ws, Neumann, phi_f, neu_face_measure) \
                          + np.einsum('i, ij, ijn, j->jn', f_ws, LaplaceNeumann, phi_f, neu_face_measure)  # (Nneu,fldof)
            # # aux_rhs_f_1: s / epsilon * (\nabla uh^n \cdot n, phi)_\Gamma
            aux_rhs_f_1 = s / epsilon * np.einsum('i, ijk, jk, ijn, j->jn', f_ws, guh_val_f, nBd, phi_f,
                                                  neu_face_measure)  # (Nneu,fldof)
            # # aux_rhs_f_2: -1 / epsilon * (\nabla h(uh^n) \cdot n, phi)_\Gamma
            aux_rhs_f_2 = -1. / epsilon * np.einsum('i, ijk, jk, ijn, j->jn', f_ws, grad_free_energy_f, nBd, phi_f,
                                                    neu_face_measure)  # (Nneu,fldof)

            np.add.at(aux_rv, cell2dof, aux_rhs_c_0 + aux_rhs_c_1 + aux_rhs_c_2)
            np.add.at(aux_rv, face2dof[idxNeuEdge, :], aux_rhs_f_0 + aux_rhs_f_1 + aux_rhs_f_2)

            # # update the solution of auxiliary equation
            wh[:] = spsolve(sm + (alpha + s / epsilon) * mm, aux_rv)

            # # update the original solution
            orig_rv = np.zeros((dof.number_of_global_dofs(),), dtype=self.ftype)  # (Ndof,)
            wh_val = space.value(wh, c_bcs)  # (NQ,NC)
            orig_rhs_c = - np.einsum('i, ij, ijk, j->jk', c_ws, wh_val, phi_c, cell_measure)  # (NC,cldof)
            orig_rhs_f = np.einsum('i, ij, ijn, j->jn', f_ws, Neumann, phi_f, neu_face_measure)
            np.add.at(orig_rv, cell2dof, orig_rhs_c)
            np.add.at(orig_rv, face2dof[idxNeuEdge, :], orig_rhs_f)
            uh[:] = spsolve(sm - alpha * mm, orig_rv)
            # currt_l2err = self.currt_error(uh, next_t)
            # print('max(uh) = ', max(uh))
            # if max(uh[:]) > 1e5:
            #     break
        print('    # ------------ end the time-looping ------------ #\n')

        l2err, h1err = self.currt_error(uh, timemesh[-1])
        print('    # ------------ the last errors ------------ #')
        print('    l2err = %.4e, h1err = %.4e' % (l2err, h1err))
        return l2err, h1err

    def CH_Solver_T2ndOrder(self):
        pde = self.pde
        timemesh = self.timemesh
        NT = len(timemesh)
        space = self.space
        dt = self.dt
        uh = self.uh
        last_uh = space.function()
        wh = self.wh
        sm = self.StiffMatrix
        mm = self.MassMatrix
        dof = self.dof
        face2dof = dof.face_to_dof()  # (NE,fldof)
        cell2dof = dof.cell_to_dof()  # (NC,cldof)

        dt_min = pde.dt_min if hasattr(pde, 'dt_min') else dt
        s, alpha = self.setCoefficient_T2ndOrder(dt_minimum=dt_min)
        m = pde.m
        epsilon = pde.epsilon
        eta = pde.eta

        print('    # #################################### #')
        print('      Time 2nd-order scheme')
        print('    # #################################### #')

        print('    # ------------ parameters ------------ #')
        print('    s = %.4e,  alpha = %.4e,  m = %.4e,  epsilon = %.4e,  eta = %.4e' % (s, alpha, m, epsilon, eta))
        print('    t0 = %.4e,  T = %.4e, dt = %.4e' % (timemesh[0], timemesh[-1], dt))
        print(' ')

        idxNeuEdge = self.set_Neumann_edge()
        nBd = self.mesh.face_unit_normal(index=idxNeuEdge)  # (NBE,2)
        NeuCellIdx = self.mesh.ds.edge2cell[idxNeuEdge, 0]
        NeuLocalIdx = self.mesh.ds.edge2cell[idxNeuEdge, 2]
        neu_face_measure = self.mesh.entity_measure('face', index=idxNeuEdge)  # (Nneu,2)
        cell_measure = self.mesh.cell_area()

        f_q = self.integralalg.faceintegrator
        f_bcs, f_ws = f_q.get_quadrature_points_and_weights()  # f_bcs.shape: (NQ,(GD-1)+1)
        f_pp = self.mesh.bc_to_point(f_bcs, index=idxNeuEdge)  # f_pp.shape: (NQ,NBE,GD) the physical Gauss points
        c_q = self.integralalg.cellintegrator
        c_bcs, c_ws = c_q.get_quadrature_points_and_weights()  # c_bcs.shape: (NQ,GD+1)
        c_pp = self.mesh.bc_to_point(c_bcs)  # c_pp.shape: (NQ_cell,NC,GD) the physical Gauss points

        phi_f = space.face_basis(f_bcs)  # (NQ,1,fldof). 实际上这里可以直接用 pspace.basis(f_bcs), 两个函数的代码是相同的
        phi_c = space.basis(c_bcs)  # (NQ,NC,clodf)
        gphi_c = space.grad_basis(c_bcs)  # (NQ,NC,cldof,GD)

        # # time-looping
        print('    # ------------ begin the time-looping ------------ #')

        def init_solution(p):
            return pde.solution(p, 0)
        last_uh[:] = space.interpolation(init_solution)
        for nt in range(NT-1):
            currt_t = timemesh[nt]
            next_t = currt_t + dt
            if nt % max([int(NT/10), 1]) == 0:
                print('    currt_t = %.4e' % currt_t)
            if nt == 0:
                # the initial value setting
                s, alpha = self.setCoefficient_T1stOrder(dt_minimum=dt_min)

                u_c = pde.solution(c_pp, pde.t0)  # (NQC,NC)
                gu_c = pde.gradient(c_pp, pde.t0)  # (NQC,NC,2)
                u_f = pde.solution(f_pp, pde.t0)  # (NQF,NBE)
                gu_f = pde.gradient(f_pp, pde.t0)  # (NQF,NBE,2)

                grad_free_energy_c = epsilon / eta ** 2 * (3 * np.repeat(u_c[..., np.newaxis], 2, axis=2) ** 2 * gu_c - gu_c)
                grad_free_energy_f = epsilon / eta ** 2 * (3 * np.repeat(u_f[..., np.newaxis], 2, axis=2) ** 2 * gu_f - gu_f)

                uh_val = u_c  # (NQ,NC)
                guh_val_c = gu_c  # (NQ,NC,2)
                guh_val_f = gu_f  # (NQ,NE,2)
            else:
                s, alpha = self.setCoefficient_T2ndOrder(dt_minimum=dt_min)
                grad_free_energy_c = epsilon / eta ** 2 * self.grad_free_energy_at_cells(2*uh - last_uh, c_bcs)  # (NQ,NC,2)
                grad_free_energy_f = epsilon / eta ** 2 * self.grad_free_energy_at_faces(2*uh - last_uh, f_bcs, idxNeuEdge, NeuCellIdx, NeuLocalIdx)  # (NQ,NE,2)
                uh_val = space.value(2 * uh - 1/2 * last_uh, c_bcs)  # (NQ,NC)
                guh_val_c = space.grad_value(2*uh - last_uh, c_bcs)  # (NQ,NC,2)
                guh_val_f = self.uh_grad_value_at_faces(2*uh - last_uh, f_bcs, NeuCellIdx, NeuLocalIdx)  # (NQ,NE,2)

                last_uh[:] = uh[:]  # update the last_uh

            Neumann = pde.neumann(f_pp, next_t, nBd)  # (NQ,NE)
            LaplaceNeumann = pde.laplace_neumann(f_pp, next_t, nBd)  # (NQ,NE)
            f_val = pde.source(c_pp, next_t, m, epsilon, eta)  # (NQ,NC)

            # # get the auxiliary equation Right-hand-side-Vector
            aux_rv = np.zeros((dof.number_of_global_dofs(),), dtype=self.ftype)  # (Ndof,)
            aux_rv_temp = np.zeros((dof.number_of_global_dofs(),), dtype=self.ftype)

            # # aux_rhs_c_0:  -1. / (epsilon * m * dt) * (uh^n,phi)_\Omega
            aux_rhs_c_0 = -1. / (epsilon * m) * (1/dt * np.einsum('i, ij, ijk, j->jk', c_ws, uh_val, phi_c, cell_measure) +
                                                 np.einsum('i, ij, ijk, j->jk', c_ws, f_val, phi_c, cell_measure))  # (NC,cldof)
            # # aux_rhs_c_1: -s / epsilon * (\nabla uh^n, \nabla phi)_\Omega
            aux_rhs_c_1 = -s / epsilon * (
                    np.einsum('i, ij, ijk, j->jk', c_ws, guh_val_c[..., 0], gphi_c[..., 0], cell_measure)
                    + np.einsum('i, ij, ijk, j->jk', c_ws, guh_val_c[..., 1], gphi_c[..., 1], cell_measure))  # (NC,cldof)
            # # aux_rhs_c_2: 1 / epsilon * (\nabla h(uh^n), \nabla phi)_\Omega
            aux_rhs_c_2 = 1. / epsilon * (
                    np.einsum('i, ij, ijk, j->jk', c_ws, grad_free_energy_c[..., 0], gphi_c[..., 0], cell_measure)
                    + np.einsum('i, ij, ijk, j->jk', c_ws, grad_free_energy_c[..., 1], gphi_c[..., 1], cell_measure))  # (NC,cldof)

            # # aux_rhs_f_0: (\nabla wh^{n+1}\cdot n, phi)_\Gamma, wh is the solution of auxiliary equation
            aux_rhs_f_0 = alpha * np.einsum('i, ij, ijn, j->jn', f_ws, Neumann, phi_f, neu_face_measure) \
                          + np.einsum('i, ij, ijn, j->jn', f_ws, LaplaceNeumann, phi_f, neu_face_measure)  # (Nneu,fldof)
            # # aux_rhs_f_1: s / epsilon * (\nabla uh^n \cdot n, phi)_\Gamma
            aux_rhs_f_1 = s / epsilon * np.einsum('i, ijk, jk, ijn, j->jn', f_ws, guh_val_f, nBd, phi_f,
                                                  neu_face_measure)  # (Nneu,fldof)
            # # aux_rhs_f_2: -1 / epsilon * (\nabla h(uh^n) \cdot n, phi)_\Gamma
            aux_rhs_f_2 = -1. / epsilon * np.einsum('i, ijk, jk, ijn, j->jn', f_ws, grad_free_energy_f, nBd, phi_f,
                                                    neu_face_measure)  # (Nneu,fldof)

            np.add.at(aux_rv_temp, cell2dof, aux_rhs_c_0)
            np.add.at(aux_rv, cell2dof, aux_rhs_c_0 + aux_rhs_c_1 + aux_rhs_c_2)
            np.add.at(aux_rv, face2dof[idxNeuEdge, :], aux_rhs_f_0 + aux_rhs_f_1 + aux_rhs_f_2)

            # # update the solution of auxiliary equation
            wh[:] = spsolve(sm + (alpha + s / epsilon) * mm, aux_rv)

            # # update the original solution
            orig_rv = np.zeros((dof.number_of_global_dofs(),), dtype=self.ftype)  # (Ndof,)
            wh_val = space.value(wh, c_bcs)  # (NQ,NC)
            orig_rhs_c = - np.einsum('i, ij, ijk, j->jk', c_ws, wh_val, phi_c, cell_measure)  # (NC,cldof)
            orig_rhs_f = np.einsum('i, ij, ijn, j->jn', f_ws, Neumann, phi_f, neu_face_measure)
            np.add.at(orig_rv, cell2dof, orig_rhs_c)
            np.add.at(orig_rv, face2dof[idxNeuEdge, :], orig_rhs_f)
            uh[:] = spsolve(sm - alpha * mm, orig_rv)
        print('    # ------------ end the time-looping ------------ #\n')

        l2err, h1err = self.currt_error(uh, timemesh[-1])
        print('    # ------------ the last errors ------------ #')
        print('    l2err = %.4e, h1err = %.4e' % (l2err, h1err))
        return l2err, h1err

    def currt_error(self, uh, t):
        pde = self.pde

        def currt_solution(p):
            return pde.solution(p, t)
        l2err = self.space.integralalg.L2_error(currt_solution, uh)

        def currt_grad_solution(p):
            return pde.gradient(p, t)
        h1err = self.space.integralalg.L2_error(currt_grad_solution, uh.grad_value)

        return l2err, h1err

    def set_Dirichlet_edge(self, idxDirEdge=None):
        if idxDirEdge is not None:
            return idxDirEdge

        mesh = self.mesh
        edge2cell = mesh.ds.edge_to_cell()
        isBdEdge = (edge2cell[:, 0] == edge2cell[:, 1])  # (NE,), the bool vars, to get the boundary edges

        isDirEdge = isBdEdge  # here, we set all the boundary edges are Dir edges
        idxDirEdge, = np.nonzero(isDirEdge)  # (NE_Dir,)

        return idxDirEdge

    def set_Neumann_edge(self, idxNeuEdge=None):
        if idxNeuEdge is not None:
            return idxNeuEdge

        mesh = self.mesh
        edge2cell = mesh.ds.edge_to_cell()
        bdEdge = (edge2cell[:, 0] == edge2cell[:, 1])  # the bool vars, to get the boundary edges

        isNeuEdge = bdEdge  # here, we first set all the boundary edges are Neu edges
        idxNeuEdge, = np.nonzero(isNeuEdge)  # (NE_Dir,)
        return idxNeuEdge