Source code for simcardems.cli

import json
import typing
from pathlib import Path

import click
import typing_extensions

from . import models
from . import postprocess as post
from . import utils
from .config import Config
from .runner import Runner
from .version import __version__

# from . import land_model

logger = utils.getLogger(__name__)


@click.group()
@click.version_option(__version__)
def cli():
    pass


@click.command("run")
@click.argument("geometry-path", type=click.Path(exists=True, resolve_path=True))
@click.option(
    "-s",
    "--geometry-schema-path",
    default=None,
    help=(
        "Schema for the geometry. If not provided it will assume that this file has the "
        "same file name as `geometry-path` but with the suffix `.json`."
    ),
)
@click.option(
    "-o",
    "--outdir",
    default=Config.outdir,
    type=click.Path(writable=True, resolve_path=True),
    help="Output directory",
)
@click.option("--dt", default=Config.dt, type=float, help="Time step")
@click.option(
    "-T",
    "--end-time",
    "T",
    default=Config.T,
    type=float,
    help="End-time of simulation",
)
@click.option(
    "-n",
    "--num_refinements",
    default=Config.num_refinements,
    type=int,
    help="Number of refinements of for the mesh using in the EP model",
)
@click.option(
    "--save_freq",
    default=Config.save_freq,
    type=int,
    help="Set frequency of saving results to file",
)
@click.option(
    "--set_material",
    default=Config.set_material,
    type=str,
    help="Choose material properties for mechanics model (default is HolzapfelOgden, option is Guccione",
)
@click.option(
    "--bnd_rigid",
    is_flag=True,
    default=Config.bnd_rigid,
    help="Flag to set boundary conditions for the mechanics problem to rigid motion condition",
)
@click.option(
    "--load_state",
    is_flag=True,
    default=Config.load_state,
    help="If load existing state if exists, otherwise create a new state",
)
@click.option(
    "-IC",
    "--cell_init_file",
    default=Config.cell_init_file,
    type=click.Path(),
    help=(
        "Path to file containing initial conditions (json or h5 file). "
        "If none is provided then the default initial conditions will be used"
    ),
)
@click.option(
    "--loglevel",
    default=Config.loglevel,
    type=int,
    help="How much printing. DEBUG: 10, INFO:20 (default), WARNING: 30",
)
@click.option(
    "--debug-mode",
    default=Config.debug_mode,
    type=bool,
    help="Run in debug mode. Save more output",
)
@click.option(
    "--show_progress_bar/--hide_progress_bar",
    default=Config.show_progress_bar,
    help="Shows or hide the progress bar.",
)
@click.option(
    "--drug_factors_file",
    default=Config.drug_factors_file,
    type=click.Path(),
    help="Path to drugs scaling factors file (json)",
)
@click.option(
    "--popu_factors_file",
    default=Config.popu_factors_file,
    type=click.Path(),
    help="Path to population scaling factors file (json)",
)
@click.option(
    "--disease_state",
    default=Config.disease_state,
    type=str,
    help="Indicate disease state. Default is healthy. ",
)
@click.option(
    "--mechanics-use-continuation",
    default=Config.mechanics_use_continuation,
    type=bool,
    help="Use continuation based mechanics solver",
)
@click.option(
    "--mechanics-use-custom-newton-solver",
    default=Config.mechanics_use_custom_newton_solver,
    type=bool,
    help="Use custom newton solver and solve ODEs at each Newton iteration",
)
@click.option(
    "--pcl",
    default=Config.PCL,
    type=float,
    help="Pacing cycle length (ms)",
)
@click.option(
    "--spring",
    default=Config.spring,
    type=float,
    help="Set value of spring for Robin boundary condition",
)
@click.option(
    "--traction",
    default=Config.traction,
    type=float,
    help="Set value of traction for Neumann boundary condition",
)
@click.option(
    "--fix_right_plane",
    is_flag=True,
    default=Config.fix_right_plane,
    help="Fix right plane in fiber direction (only usable for slab)",
)
@click.option(
    "--coupling-type",
    default=Config.coupling_type,
    type=click.Choice(models.list_coupling_types()),
    help="Type of coupled model",
)
def run(
    geometry_path: utils.PathLike,
    geometry_schema_path: typing.Optional[utils.PathLike],
    outdir: utils.PathLike,
    T: float,
    dt: float,
    bnd_rigid: bool,
    load_state: bool,
    cell_init_file: utils.PathLike,
    show_progress_bar: bool,
    save_freq: int,
    loglevel: int,
    debug_mode: bool,
    num_refinements: int,
    set_material: str,
    drug_factors_file: utils.PathLike,
    popu_factors_file: utils.PathLike,
    disease_state: str,
    mechanics_use_continuation: bool,
    mechanics_use_custom_newton_solver: bool,
    pcl: float,
    spring: float,
    traction: float,
    fix_right_plane: bool,
    coupling_type: typing_extensions.Literal[
        "fully_coupled_ORdmm_Land",
        "fully_coupled_Tor_Land",
        "explicit_ORdmm_Land",
        "pureEP_ORdmm_Land",
    ],
):
    config = Config(
        geometry_path=geometry_path,
        geometry_schema_path=geometry_schema_path,
        outdir=outdir,
        T=T,
        dt=dt,
        bnd_rigid=bnd_rigid,
        spring=spring,
        traction=traction,
        fix_right_plane=fix_right_plane,
        load_state=load_state,
        cell_init_file=cell_init_file,
        show_progress_bar=show_progress_bar,
        save_freq=save_freq,
        loglevel=loglevel,
        debug_mode=debug_mode,
        num_refinements=num_refinements,
        set_material=set_material,
        drug_factors_file=drug_factors_file,
        popu_factors_file=popu_factors_file,
        disease_state=disease_state,
        mechanics_use_continuation=mechanics_use_continuation,
        mechanics_use_custom_newton_solver=mechanics_use_custom_newton_solver,
        PCL=pcl,
        coupling_type=coupling_type,
    )
    main(config=config)


@click.command("run-json")
@click.argument("path", required=True, type=click.Path(exists=True))
def run_json(path):
    with open(path, "r") as json_file:
        data = json.load(json_file)

    main(Config(**data))


[docs] def main(config: typing.Optional[Config]): if config is None: config = Config() geometry_path = Path(config.geometry_path) if not geometry_path.is_file(): msg = f"Unable to to find geometry path {geometry_path}" raise IOError(msg) if config.geometry_schema_path is None: config.geometry_schema_path = geometry_path.with_suffix(".json") # Get all arguments and dump them to a json file info_dict = config.__dict__ outdir = Path(config.outdir) outdir.mkdir(exist_ok=True) with open(outdir.joinpath("parameters.json"), "w") as f: json.dump(info_dict, f, default=post.json_serial) runner = Runner(config=config) runner.solve( T=config.T, save_freq=config.save_freq, show_progress_bar=config.show_progress_bar, )
@click.command("postprocess") @click.argument("folder", required=True, type=click.Path(exists=True)) @click.option( "--plot-state-traces", is_flag=True, default=False, help="Plot state traces", ) @click.option( "--make-xdmf", is_flag=True, default=False, help="Make xdmf files", ) @click.option( "--population", is_flag=True, default=False, help="Plot population", ) @click.option( "--reduction", default="average", help="What type of reduction to perform when plotting state traces, by default 'average'.", ) @click.option("--num_models", default=5, help="Number of models to be analyzed") def postprocess( folder, num_models, plot_state_traces, make_xdmf, population, reduction, ): folder = Path(folder) if plot_state_traces: post.plot_state_traces(folder.joinpath("results.h5"), reduction=reduction) if make_xdmf: post.make_xdmffiles(folder.joinpath("results.h5")) if population: logger.info("Execute postprocess for population") post.save_popu_json(folder, num_models) @click.command("gui") def gui(): # Make sure we can import the required packages from . import gui # noqa: F401 gui_path = Path(__file__).parent.joinpath("gui.py") import subprocess as sp sp.run(["streamlit", "run", gui_path.as_posix()]) @click.command("run-benchmark") @click.argument( "outdir", required=True, type=click.Path(resolve_path=True, writable=True), ) @click.option( "--overwrite/--no-overwrite", default=True, type=bool, help="If True overwrite results in output directory", ) def run_benchmark(outdir, overwrite): # Make sure we can import the required packages from . import benchmark # noqa: F401 benchmark_path = Path(__file__).parent.joinpath("benchmark.py") import subprocess as sp path = Path(outdir) if path.exists(): if overwrite: import shutil shutil.rmtree(path) path.mkdir(parents=True, exist_ok=True) sp.run(["python3", benchmark_path, path.as_posix()]) cli.add_command(run) cli.add_command(run_json) cli.add_command(postprocess) cli.add_command(gui) cli.add_command(run_benchmark)