def test_mechanical_boundary_conditions():
    """ Test that mechanical boundary conditions are correctly set
    """

    this_method = test_mechanical_boundary_conditions.__name__
    now_as_YYMMDD = pendulum.now().format("YYMMDD")

    # Create setup with no fractures
    params = {'shearzone_names': None}
    setup = test_util.prepare_setup(
        model=gts.ContactMechanicsISC,
        path_head=f"{this_method}/{now_as_YYMMDD}/test_1",
        params=params,
    )

    gb: pp.GridBucket = setup.gb
    g: pp.Grid = gb.grids_of_dimension(3)[0]
    data = gb.node_props(g)

    mech_params: dict = data[pp.PARAMETERS][setup.mechanics_parameter_key]
    # Get the mechanical bc values
    mech_bc = mech_params['bc_values'].reshape((3, -1), order='F')
    mech_bc_type: pp.BoundaryConditionVectorial = mech_params['bc']

    all_bf, east, west, north, south, top, bottom = setup.domain_boundary_sides(
        g)
    # --- TESTS ---

    # 1. Test that mechanical BCs have the right type
    #   We want neumann on all faces except setup.faces_to_fix(),
    #   which should be on the bottom of the grid.
    dir_faces = setup.faces_to_fix(g)
    neu_faces = np.setdiff1d(all_bf, dir_faces)
    assert np.all(mech_bc_type.is_dir[:, dir_faces])
    assert np.all(mech_bc_type.is_neu[:, neu_faces])

    # 2.a. Test that Neumann BCs all point into the domain.
    #   The physical conditions at Grimsel Test Site implies
    #   a compressive boundary traction on all faces.

    # We expect the dirichlet faces to be on bottom. Remove them
    # for the sake of testing neumann conditions
    btm = np.setdiff1d(np.where(bottom)[0], dir_faces)

    # West
    assert np.all(mech_bc[0, west] > 0)
    # East
    assert np.all(mech_bc[0, east] < 0)
    # North
    assert np.all(mech_bc[1, north] < 0)
    # South
    assert np.all(mech_bc[1, south] > 0)
    # Top
    assert np.all(mech_bc[2, top] < 0)
    # Bottom
    assert np.all(mech_bc[2, btm] > 0)

    # 2.b. Test that Dirichlet BCs are zero
    np.allclose(mech_bc[:, dir_faces], 0)
def test_compare_run_mech_and_run_mech_by_filter_term():
    """ This test runs pure mechanics by running the test
    'test_mechanics_class_methods.test_decomposition_of_stress()'
    for the hydrostatic case. Then, it runs the test
    'test_run_mechanics_term_by_filter()'.

    The goal is to compare the output of these two methods and ensure they
    are the same.
    """
    # 1. Prepare parameters
    stress = gts.stress_tensor()
    # We set up hydrostatic stress
    hydrostatic = np.mean(np.diag(stress)) * np.ones(stress.shape[0])
    stress = np.diag(hydrostatic)
    no_shearzones = None
    gravity = False  # No gravity effects
    params = {
        "stress": stress,
        "shearzone_names": no_shearzones,
        "_gravity_bc": gravity,
        "_gravity_src": gravity,
    }

    # Storage folder
    this_method_name = test_compare_run_mech_and_run_mech_by_filter_term.__name__
    now_as_YYMMDD = pendulum.now().format("YYMMDD")
    _folder_root = f"{this_method_name}/{now_as_YYMMDD}"

    # 1. --- Setup ContactMechanicsISC ---
    setup_mech = test_util.prepare_setup(
        model=gts.ContactMechanicsISC,
        path_head=f"{_folder_root}/test_mech",
        params=params,
        prepare_simulation=False,
        setup_loggers=True,
    )
    setup_mech.create_grid()

    # 2. --- Setup BiotReduceToMechanics ---
    params2 = test_util.prepare_params(
        path_head=f"{_folder_root}/test_biot_reduce_to_mech",
        params=params,
        setup_loggers=False,
    )
    setup_biot = BiotReduceToMechanics(params=params2)

    # Recreate the same mesh as for the above setup
    path_to_gb_msh = f"{setup_mech.viz_folder_name}/gmsh_frac_file.msh"
    gb2 = pp.fracture_importer.dfm_from_gmsh(path_to_gb_msh,
                                             dim=3,
                                             network=setup_mech._network)
    setup_biot.set_grid(gb2)

    # 3. --- Run simulations ---
    nl_params = {}

    # Run ContactMechanicsISC
    pp.run_stationary_model(setup_mech, nl_params)

    # Run BiotReduceToMechanics
    pp.run_time_dependent_model(setup_biot, nl_params)

    # --- Compare results ---
    def get_u(_setup):
        gb = _setup.gb
        g = gb.grids_of_dimension(3)[0]
        d = gb.node_props(g)
        u = d["state"]["u"].reshape((3, -1), order="F")
        return u

    u_mech = get_u(setup_mech)
    u_biot = get_u(setup_biot)
    assert np.isclose(
        np.sum(np.abs(u_mech - u_biot)),
        0.0), ("Running mechanics or biot (only discretize mechanics "
               "term should return same result.")
    return setup_mech, setup_biot
def test_decomposition_of_stress(setup='normal_shear'):
    """ Test the solutions acquired when decomposing stress to
    purely compressive and purely rotational components.

    --- Setup ---
    A: 1. Get the stress tensor and split in diagonal and off-diagonal components.
    B: 1. Get the stress tensor and split in hydrostatic and deviatoric components.
    --
    2. Acquire solutions for each split tensor and the full tensor separately (3 cases).
    3. Compare solutions quantitatively (linearity of solution) and qualitatively (Paraview)

    Parameters
    ----------
    setup : str : {'normal_shear', 'hydrostatic'}
        Type of setup.
        'normal_shear' simply separates the normal and shear components
        'hydrostatic' separates hydrostatic and deviatoric stresses
    """

    # Import stress tensor
    stress = gts.isc_modelling.stress_tensor()

    if setup == 'normal_shear':
        # Get normal and shear stresses
        normal_stress = np.diag(np.diag(stress).copy())
        shear_stress = stress - normal_stress
        fname_n = 'normal_stress'
        fname_s = 'shear_stress'
    elif setup == 'hydrostatic':
        # Get hydrostatic and deviatoric stresses
        hydrostatic = np.mean(np.diag(stress)) * np.ones(stress.shape[0])
        normal_stress = np.diag(hydrostatic)
        shear_stress = stress - normal_stress
        fname_n = 'hydrostatic_stress'
        fname_s = 'deviatoric_stress'
    else:
        raise ValueError(f"Did not recognise input setup={setup}")

    # 1. Full stress tensor
    this_method_name = test_decomposition_of_stress.__name__
    now_as_YYMMDD = pendulum.now().format("YYMMDD")
    _folder_root = f"{this_method_name}/{now_as_YYMMDD}/no_gravity/{setup}"
    gravity = False
    no_shearzones = None
    params = {
        "shearzone_names": no_shearzones,
        "stress": stress,
        "_gravity_bc": gravity,
        "_gravity_src": gravity,
    }
    setup = test_util.prepare_setup(
        model=gts.ContactMechanicsISC,
        path_head=f"{_folder_root}",
        params=params,
        prepare_simulation=False,
        setup_loggers=True,
    )
    setup.create_grid()

    # -- Safely copy the grid generated above --
    # Ensures the implicit in-place variable changes across grid_buckets (gb.copy() is insufficient)
    # Get the grid_bucket .msh path
    path_to_gb_msh = f"{setup.viz_folder_name}/gmsh_frac_file.msh"
    # Re-create the grid buckets
    gb_n = pp.fracture_importer.dfm_from_gmsh(path_to_gb_msh,
                                              dim=3,
                                              network=setup._network)
    gb_s = pp.fracture_importer.dfm_from_gmsh(path_to_gb_msh,
                                              dim=3,
                                              network=setup._network)

    # 1. Pure normal stress / hydrostatic stress
    params_n = params.update({'stress': normal_stress})
    setup_n = test_util.prepare_setup(
        model=gts.ContactMechanicsISC,
        path_head=f"{_folder_root}/{fname_n}",
        params=params_n,
        prepare_simulation=False,
        setup_loggers=False,
    )
    setup_n.set_grid(gb_n)

    # 2. Pure shear stress / deviatoric stress
    params_s = params.update({"stress": shear_stress})
    setup_s = test_util.prepare_setup(
        model=gts.ContactMechanicsISC,
        path_head=f"{_folder_root}/{fname_s}",
        params=params_s,
        prepare_simulation=False,
        setup_loggers=False,
    )
    setup_s.set_grid(gb_s)

    # --- Run simulations ---
    params_nl = {}  # Use default Newton solver parameters

    # 1. Full stress tensor
    pp.run_stationary_model(setup, params_nl)

    # 2. Pure normal stress
    pp.run_stationary_model(setup_n, params_nl)

    # 3. Pure shear stress
    pp.run_stationary_model(setup_s, params_nl)

    # --- Compare results ---
    def get_u(_setup):
        gb = _setup.gb
        g = gb.grids_of_dimension(3)[0]
        d = gb.node_props(g)
        u = d['state']['u'].reshape((3, -1), order='F')
        return u

    return get_u(setup), get_u(setup_n), get_u(setup_s), [
        setup, setup_n, setup_s
    ]