def test_create_grid():
    """ Create a standard grid.

    Returns the ContactMechanicsBiot class with an overwritten create_grid method.
    """
    _this_file = Path(os.path.abspath(__file__)).parent
    _results_path = _this_file / "results"
    _results_path.mkdir(parents=True,
                        exist_ok=True)  # Create path if not exists
    __setup_logging(_results_path)
    logger.info(f"Path to results: {_results_path}")

    # --- DOMAIN ARGUMENTS ---
    params = {
        'mesh_args': {
            'mesh_size_frac': 10,
            'mesh_size_min': 10,
            'mesh_size_bound': 10
        },
        'bounding_box': {
            'xmin': -6,
            'xmax': 80,
            'ymin': 55,
            'ymax': 150,
            'zmin': 0,
            'zmax': 50
        },
        'shearzone_names': ["S1_1", "S1_2", "S1_3", "S3_1", "S3_2"]
    }

    gb = gts.isc_modelling.create_isc_domain(_results_path,
                                             **params,
                                             n_refinements=0)[0]

    # Standard variables for ContactMechanicsISC class
    params['viz_folder_name'] = _results_path
    params['solver'] = 'direct'
    params['stress'] = gts.isc_modelling.stress_tensor()
    params['length_scale'] = 1
    params['scalar_scale'] = 1

    cm = gts.isc_modelling.ContactMechanicsISCWithGrid(params, gb)

    return cm
Example #2
0
def prepare_params(
    path_head: str,
    params: dict,
    setup_loggers: bool,
) -> dict:
    """
    Method to prepare default set of parameters for model runs.

    Parameters
    ----------
    path_head : str
        folder structure to store results in.
        Computed relative to:
            '...GTS/test/{test_method_name}/{path_head}'
    params : dict
        Update or pass additional parameters to params
    setup_loggers: bool
        Whether to setup loggers
        (usually false for subsequent calls to this method in one test run)

    Returns
    -------
    default_params : dict
        model parameters
    """

    if params is None:
        params = {}

    _this_file = Path(os.path.abspath(__file__)).parent
    _results_path = _this_file / f"results/{path_head}"
    _results_path.mkdir(parents=True,
                        exist_ok=True)  # Create path if not exists
    if setup_loggers:
        __setup_logging(_results_path)
        logger.info(f"Set up logger at {pendulum.now().to_atom_string()}")
    logger.info(f"Path to results: {_results_path}")

    # --- DOMAIN ARGUMENTS ---
    default_params = {
        'mesh_args': {
            'mesh_size_frac': 10,
            'mesh_size_min': .1 * 10,
            'mesh_size_bound': 6 * 10
        },
        'bounding_box': {
            'xmin': -20,
            'xmax': 80,
            'ymin': 50,
            'ymax': 150,
            'zmin': -25,
            'zmax': 75
        },
        'shearzone_names': ["S1_1", "S1_2", "S1_3", "S3_1", "S3_2"],
        'folder_name': _results_path,
        'solver': 'direct',
        'source_scalar_borehole_shearzone':  # Only relevant for biot
        {
            "shearzone": "S1_2",
            "borehole": "INJ1"
        },
        'stress': gts.isc_modelling.stress_tensor(),
        'length_scale': 1,
        'scalar_scale': 1,
    }
    default_params.update(params)

    return default_params
Example #3
0
def convergence_study():
    """ Perform a convergence study of a given problem setup.
    """

    # 1. Step: Create n grids by uniform refinement.
    # 2. Step: for grid i in list of n grids:
    # 2. a. Step: Set up the mechanics model.
    # 2. b. Step: Solve the mechanics problem.
    # 2. c. Step: Keep the grid (with solution data)
    # 3. Step: Let the finest grid be the reference solution.
    # 4. Step: For every other grid:
    # 4. a. Step: Map the solution to the fine grid, and compute error.
    # 5. Step: Compute order of convergence, etc.

    # -----------------
    # --- ARGUMENTS ---
    # -----------------
    viz_folder_name = Path(
        os.path.abspath(__file__)).parent / "results/mech_convergence_2test"
    if not os.path.exists(viz_folder_name):
        os.makedirs(viz_folder_name, exist_ok=True)

    shearzone_names = ["S1_1", "S1_2", "S1_3", "S3_1", "S3_2"]

    mesh_size = 10
    mesh_args = {  # A very coarse grid
        "mesh_size_frac": mesh_size,
        "mesh_size_min": mesh_size,
        "mesh_size_bound": mesh_size,
    }

    bounding_box = {
        "xmin": -6,
        "xmax": 80,
        "ymin": 55,
        "ymax": 150,
        "zmin": 0,
        "zmax": 50,
    }

    # 1. Step: Create n grids by uniform refinement.
    gb_list = create_isc_domain(
        viz_folder_name=viz_folder_name,
        shearzone_names=shearzone_names,
        bounding_box=bounding_box,
        mesh_args=mesh_args,
        n_refinements=1,
    )

    scales = {
        'scalar_scale': 1,
        'length_scale': 1,
    }
    solver = 'direct'

    # ---------------------------
    # --- PHYSICAL PARAMETERS ---
    # ---------------------------

    stress = stress_tensor()

    # ----------------------
    # --- SET UP LOGGING ---
    # ----------------------
    print(viz_folder_name / "results.log")
    logger = __setup_logging(viz_folder_name)

    logger.info(
        f"Preparing setup for mechanics convergence study on {pendulum.now().to_atom_string()}"
    )
    logger.info(f"Reporting on {len(gb_list)} grid buckets.")
    logger.info(f"Visualization folder path: \n {viz_folder_name}")
    logger.info(f"Mesh arguments for coarsest grid: \n {mesh_args}")
    logger.info(f"Bounding box: \n {bounding_box}")
    logger.info(f"Variable scaling: \n {scales}")
    logger.info(f"Solver type: {solver}")
    logger.info(f"Stress tensor: \n {stress}")

    # -----------------------
    # --- SETUP AND SOLVE ---
    # -----------------------

    newton_options = {  # Parameters for Newton solver.
        "max_iterations": 10,
        "convergence_tol": 1e-10,
        "divergence_tol": 1e5,
    }
    logger.info(f"Options for Newton solver: \n {newton_options}")

    from GTS.isc_modelling.mechanics import ContactMechanicsISCWithGrid
    for gb in gb_list:
        setup = ContactMechanicsISCWithGrid(
            viz_folder_name,
            'main_run',
            'linux',
            mesh_args,
            bounding_box,
            shearzone_names,
            scales,
            stress,
            solver,
            gb,
        )

        logger.info("Setup complete. Starting simulation")
        pp.run_stationary_model(setup, params=newton_options)
        logger.info("Simulation complete. Exporting solution.")

    return gb_list
Example #4
0
def _abstract_model_setup(
    model: Type[ContactMechanics],
    params: dict = None,
):
    """ Helper method to assemble model setup for biot and mechanics.

    Parameters
    ----------
    model : Type[ContactMechanics]
        Which model to run
        Accepted: 'ContactMechanicsISC', 'ContactMechanicsBiotISC'
    params : dict (Default: None)
        Custom parameters to pass to model
        See below of default values
    """
    # ------------------------------------------
    # --- FOLDER AND FILE RELATED PARAMETERS ---
    # ------------------------------------------
    _this_file = Path(os.path.abspath(__file__)).parent
    path_head = "default/default_1"
    _results_path = _this_file / f"results/{path_head}"

    # ------------------------------------
    # --- MODELLING RELATED PARAMETERS ---
    # ------------------------------------
    sz = 10
    mesh_args = {
        'mesh_size_frac': sz,
        'mesh_size_min': 0.1 * sz,
        'mesh_size_bound': 6 * sz,
    }

    bounding_box = {
        'xmin': -20,
        'xmax': 80,
        'ymin': 50,
        'ymax': 150,
        'zmin': -25,
        'zmax': 75
    }

    # Set which borehole / shearzone to inject fluid to
    # This corresponds to setup in HS2 from Doetsch et al 2018
    source_scalar_borehole_shearzone = {
        "shearzone": "S1_2",
        "borehole": "INJ1",
    }  # (If ContactMechanicsISC is run, this is ignored)

    # ---------------------------
    # --- PHYSICAL PARAMETERS ---
    # ---------------------------

    stress = stress_tensor()

    # -----------------------------
    # --- INITIALIZE PARAMETERS ---
    # -----------------------------

    in_params = {
        "folder_name": _results_path,
        "mesh_args": mesh_args,
        "bounding_box": bounding_box,
        "shearzone_names": None,
        "length_scale": 1,
        "scalar_scale": 1,
        "solver": "direct",
        "source_scalar_borehole_shearzone": source_scalar_borehole_shearzone,
        "stress": stress,
    }

    # Update default parameter set with input parameters
    in_params.update(params)

    # ---------------------
    # --- BACKEND SETUP ---
    # ---------------------

    # Create viz folder path if it does not already exist
    viz_folder_name = in_params["folder_name"]
    Path(viz_folder_name).mkdir(parents=True, exist_ok=True)

    # Set up logging
    __setup_logging(viz_folder_name)
    logger.info(
        f"Preparing setup for mechanics simulation on {pendulum.now().to_atom_string()}"
    )
    logger.info(f"Simulation parameters:\n {pformat(in_params)}")

    # -------------------
    # --- SETUP MODEL ---
    # -------------------

    setup = model(params=params)
    return setup
def test_biot_parameter_scaling(**kw):
    """ Test scaling of parameters in Biot
    """
    _this_file = Path(os.path.abspath(__file__)).parent
    _results_path = _this_file / "results/test_biot_parameter_scaling/default"
    _results_path.mkdir(parents=True,
                        exist_ok=True)  # Create path if not exists
    __setup_logging(_results_path)
    logger.info(f"Path to results: {_results_path}")

    # --- DOMAIN ARGUMENTS ---
    params = {
        'mesh_args': {
            'mesh_size_frac': 10,
            'mesh_size_min': .1 * 10,
            'mesh_size_bound': 6 * 10
        },
        'bounding_box': {
            'xmin': -20,
            'xmax': 80,
            'ymin': 50,
            'ymax': 150,
            'zmin': -25,
            'zmax': 75
        },
        'shearzone_names': ["S1_1", "S1_2", "S1_3", "S3_1", "S3_2"],
        'folder_name': _results_path,
        'solver': 'direct',
        'stress': gts.isc_modelling.stress_tensor(),
        'source_scalar_borehole_shearzone': {
            'borehole': 'INJ1',
            'shearzone': 'S1_1'
        },
        # Initially: We calculate unscaled variables.
        'length_scale': 1,
        'scalar_scale': 1,
    }
    setup = gts.ContactMechanicsBiotISC(params)
    setup.create_grid()
    setup.well_cells()
    setup.set_parameters()

    # Save copies of the original data on the 3D grid
    gb = setup.gb
    g = gb.grids_of_dimension(3)[0]
    data = gb.node_props(g)
    mech_params = data['parameters']['mechanics']
    flow_params = data['parameters']['flow']
    mech = {
        'bc_values': mech_params['bc_values'].copy(),
        'source': mech_params['source'].copy(),
        'fourth_order_tensor': mech_params['fourth_order_tensor'].copy(),
        'biot_alpha': mech_params['biot_alpha'],  # Float
    }

    flow = {
        'bc_values': flow_params['bc_values'].copy(),
        'mass_weight': flow_params['mass_weight'],  # Float
        'source': flow_params['source'].copy(),
        'second_order_tensor': flow_params['second_order_tensor'].copy(),
        'biot_alpha': flow_params['biot_alpha'],  # Float
    }

    # Scale grid:
    setup.scalar_scale = kw.get('ss', 1 * pp.GIGA)
    setup.length_scale = kw.get('ls', 100)
    setup.create_grid(overwrite_grid=True)
    ss = setup.scalar_scale
    ls = setup.length_scale

    # Recompute parameters
    setup.prepare_simulation()

    # Mimic NewtonSolver:
    setup.before_newton_iteration()

    # Check size of entries in matrix A.
    A, b = setup.assembler.assemble_matrix_rhs()
    logger.info("------------------------------------------")
    logger.info(f"Max element in A {np.max(np.abs(A)):.2e}")
    logger.info(
        f"Max {np.max(np.sum(np.abs(A), axis=1)):.2e} and min {np.min(np.sum(np.abs(A), axis=1)):.2e} A sum."
    )

    # Find the new parameter dictionaries
    dim = 3
    gb = setup.gb
    g = gb.grids_of_dimension(dim)[0]
    data = gb.node_props(g)
    scaled_mech = data['parameters']['mechanics']
    scaled_flow = data['parameters']['flow']
    all_bf, east, west, north, south, top, bottom = setup.domain_boundary_sides(
        g)

    # --- Do the comparisons: ---
    test_cell = 0
    logger.info(
        f"scalar_scale={setup.scalar_scale:.2e}. length_scale={setup.length_scale:.2e}"
    )
    # - FLOW -
    # Permeability [m2]
    #   k_scl = k * scalar_scale / length_scale ** 2
    k = flow['second_order_tensor'].values[0, 0, :]
    k_scl = scaled_flow['second_order_tensor'].values[0, 0, :]
    logger.info(f"unscaled k/mu={k[test_cell]:.2e}")
    logger.info(f"Scaled k/mu={k_scl[test_cell]:.2e}")
    assert np.allclose(k * ss / ls**2,
                       k_scl), "k_scl = k * scalar_scale / length_scale ** 2"

    # Mass weight / Effective Storage term (possibly aperture scaled) [1/Pa]
    #   mw_scl = mw * scalar_scale
    mw = flow['mass_weight']
    mw_scl = scaled_flow['mass_weight']
    logger.info(f'mass_weight={mw:.2e}')
    logger.info(f'mass_weight scaled={mw_scl:.2e}')
    assert np.allclose(mw * ss, mw_scl), "mw_scl = mw * scalar_scale"

    # Source [m ** dim / s]
    #   fs_scl = fs * length_scale ** dim
    fs = flow['source'][test_cell]
    fs_scl = scaled_flow['source'][test_cell]
    logger.info(f"Unscaled flow source={fs:.2e}")
    logger.info(f"Scaled flow source={fs_scl:.2e}")
    assert np.allclose(fs * ls**dim, fs), "fs_scl = fs * length_scale ** dim"

    # Boundary conditions (FLOW)
    #   Dirchlet [Pa]
    #   fd_scl = fd / scalar_scale
    fd = flow['bc_values']
    fd_scl = scaled_flow['bc_values']
    logger.info(f"bc flow={fd[all_bf][0]:.2e}")
    logger.info(f"bc flow scaled={fd_scl[all_bf][0]:.2e}")
    assert np.allclose(fd / ss, fd_scl), "fd_scl = fd / scalar_scale"
    #   Neumann [m2 / s] (integrated across 2D surfaces)

    # Biot alpha should remain unchanged
    assert flow['biot_alpha'] == scaled_flow['biot_alpha']

    # - MECHANICS -
    # Mu and Lambda [Pa]
    #   m_scl = m / scalar_scale AND lm_scl = lm / scalar_scale
    mu = mech['fourth_order_tensor'].mu
    mu_scl = scaled_mech['fourth_order_tensor'].mu
    lmbda = mech['fourth_order_tensor'].lmbda
    lmbda_scl = scaled_mech['fourth_order_tensor'].lmbda

    logger.info(f"mu={mu[test_cell]:.2e}. mu scaled = {mu_scl[test_cell]:.2e}")
    logger.info(
        f"lambda={lmbda[test_cell]:.2e}. lambda scaled={lmbda_scl[test_cell]:.2e}"
    )
    assert np.allclose(mu / ss, mu_scl)
    assert np.allclose(lmbda / ss, lmbda_scl)

    # Mechanics source [Pa m2] (integrated over 3D volume)
    #   In the code: ms_scl = ms * length_scale / scalar_scale (assuming integration is scaled)
    #   ms_scl = ms / (scalar_scale * length_scale ** 2)
    ms = mech['source'].reshape((3, -1), order='F')
    ms_scl = scaled_mech['source'].reshape((3, -1), order='F')
    logger.info(f"Mechanics source={ms[:, test_cell]}")
    logger.info(f"Mechanics source scaled={ms_scl[:, test_cell]}")
    assert np.allclose(ms / (ss * ls**2), ms_scl)

    # Boundary conditions (MECHANICS)
    #   Neumann [Pa m2] (integrated across 2D surfaces)
    #   Note: In the code, we divide by scalar_scale. length_scale**2 is incorporated by pre-scaled grid.
    #   mn_scl = mn / ( scalar_scale * length_scale**(dim-1) )
    mn = mech['bc_values'].reshape((3, -1), order='F')
    mn_scl = scaled_mech['bc_values'].reshape((3, -1), order='F')
    logger.info(f"mech neumann (3 faces on east) =\n{mn[:, east][:, :3]}")
    logger.info(
        f"mech neumann scaled (3 faces on east) =\n{mn_scl[:, east][:, :3]}")
    assert np.allclose(mn[:, all_bf] / (ss * ls**(dim - 1)), mn_scl[:, all_bf])

    return setup, mech, flow, scaled_mech, scaled_flow
def test_biot_condition_number(**kw):
    """ Test scaling of parameters in Biot
    """
    _this_file = Path(os.path.abspath(__file__)).parent
    _results_path = _this_file / "results/test_biot_condition_number/default"
    _results_path.mkdir(parents=True,
                        exist_ok=True)  # Create path if not exists
    __setup_logging(_results_path)
    # path = str(_results_path)
    # # GTS logger
    # gts_logger = logging.getLogger('GTS')
    # gts_logger.setLevel(logging.INFO)
    #
    # # PorePy logger
    # pp_logger = logging.getLogger('porepy')
    # pp_logger.setLevel(logging.DEBUG)
    #
    # # Add handler for logging debug messages to file.
    # fh = logging.FileHandler(path + "/" + "results.log")
    # fh.setLevel(logging.DEBUG)
    # fh.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
    #
    # gts_logger.addHandler(fh)
    # pp_logger.addHandler(fh)

    logger.info(f"Path to results: {_results_path}")

    # --- DOMAIN ARGUMENTS ---
    params = {
        'mesh_args': {
            'mesh_size_frac': 10,
            'mesh_size_min': .1 * 10,
            'mesh_size_bound': 6 * 10
        },
        'bounding_box': {
            'xmin': -20,
            'xmax': 80,
            'ymin': 50,
            'ymax': 150,
            'zmin': -25,
            'zmax': 75
        },
        'shearzone_names': ["S1_1", "S1_2", "S1_3", "S3_1", "S3_2"],
        'folder_name': _results_path,
        'solver': 'direct',
        'stress': gts.isc_modelling.stress_tensor(),
        'source_scalar_borehole_shearzone': {
            'borehole': 'INJ1',
            'shearzone': 'S1_1'
        },
        'length_scale': kw.get('ls', 100),
        'scalar_scale': kw.get('ss', 1 * pp.GIGA),
    }
    logger.info(f"input parameters: \n {params}")

    setup = gts.ContactMechanicsBiotISC(params)

    setup.create_grid(overwrite_grid=True)
    ss = setup.scalar_scale
    ls = setup.length_scale
    print(f'ss={ss}')
    print(f'ls={ls}')

    # Recompute parameters
    setup.prepare_simulation()

    # Mimic NewtonSolver:
    setup.before_newton_iteration()

    # Check size of entries in matrix A.
    A, b = setup.assembler.assemble_matrix_rhs()
    logger.info("------------------------------------------")
    logger.info(f"Max element in A {np.max(np.abs(A)):.2e}")
    logger.info(
        f"Max {np.max(np.sum(np.abs(A), axis=1)):.2e} and min {np.min(np.sum(np.abs(A), axis=1)):.2e} A sum."
    )

    return setup