From 84e03ffa0d3f4371788a781b2b55a644c6fa91ea Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 6 Oct 2023 18:37:39 +0200 Subject: [PATCH 01/11] add option docker_args --- toughio/_run.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/toughio/_run.py b/toughio/_run.py index cc90fe0e..2d1d254e 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -19,6 +19,7 @@ def run( use_temp=False, ignore_patterns=None, silent=False, + docker_args=None, **kwargs, ): """ @@ -48,6 +49,8 @@ def run( If provided, output files that match the glob-style patterns will be discarded. silent : bool, optional, default False If `True`, nothing will be printed to standard output. + docker_args : list or None, optional, default None + List of arguments passed to `docker run` command. Other Parameters ---------------- @@ -167,7 +170,16 @@ def run( except AttributeError: uid = "" - cmd = f"docker run --rm {uid} -v {cwd}:/shared -w /shared {docker} {cmd}" + docker_args = docker_args if docker_args else [] + docker_args += [ + "--rm", + uid, + "-v", + f"{cwd}:/shared", + "-w", + "/shared", + ] + cmd = f"docker run {' '.join(docker_args)} {docker} {cmd}" # Use WSL if wsl and is_windows: From 865b6e08d46bb73a22d3eeacfb4ab68c8e608a13 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 6 Oct 2023 18:37:51 +0200 Subject: [PATCH 02/11] version bump --- toughio/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/VERSION b/toughio/VERSION index f0df1f7d..d9ee6574 100644 --- a/toughio/VERSION +++ b/toughio/VERSION @@ -1 +1 @@ -1.13.2 \ No newline at end of file +1.13.3 \ No newline at end of file From a5b22886a6b56dbb1d0591157b040d9ae61804b2 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 20 Oct 2023 14:51:47 +0200 Subject: [PATCH 03/11] add method __get_item__ --- toughio/_mesh/_mesh.py | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/toughio/_mesh/_mesh.py b/toughio/_mesh/_mesh.py index 1b557d10..51f994d6 100644 --- a/toughio/_mesh/_mesh.py +++ b/toughio/_mesh/_mesh.py @@ -93,6 +93,67 @@ def __repr__(self): lines.append(f" Cell data: {', '.join(self.cell_data)}") return "\n".join(lines) + + def __getitem__(self, islice): + """Slice mesh.""" + if np.ndim(islice) == 0: + islice = [islice] + + if np.ndim(islice) != 1: + raise ValueError() + + if isinstance(islice[0], (bool, np.bool_)): + if len(islice) != self.n_cells: + raise ValueError() + + elif isinstance(islice[0], (int, np.int8, np.int16, np.int32, np.int64)): + if np.max(islice) >= self.n_cells: + raise ValueError() + + tmp = islice + islice = np.zeros(self.n_cells, dtype=bool) + islice[tmp] = True + + else: + raise TypeError() + + count = 0 + cell_idx = [] + nodes_to_keep = set() + + for (_, cell_data) in self.cells: + cell_idx.append([]) + + for j, cell in enumerate(cell_data): + if islice[count]: + cell_idx[-1].append(j) + + for node in cell: + nodes_to_keep.add(node) + + count += 1 + + # Prune orphaned nodes + node_map = {} + point_idx = [] + count = 0 + + for i in range(self.n_points): + if i in nodes_to_keep: + node_map[i] = count + point_idx.append(i) + count += 1 + + return Mesh( + points=self.points[point_idx], + cells=[ + (cell_type, np.array([[node_map[i] for i in cell] for cell in cell_data[idx]])) + for idx, (cell_type, cell_data) in zip(cell_idx, self.cells) + ], + point_data={k: v[point_idx] for k, v in self.point_data.items()}, + cell_data={k: v[islice] for k, v in self.cell_data.items()}, + field_data={k: v for k, v in self.field_data.items()} + ) def extrude_to_3d(self, height=1.0, axis=2, inplace=True): """ From fb695f69b2892eaf88406d64a02e5005860ce9bd Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 27 Oct 2023 10:29:46 +0200 Subject: [PATCH 04/11] add option petsc_args --- toughio/_run.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/toughio/_run.py b/toughio/_run.py index 2d1d254e..ad3a44bb 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -19,6 +19,7 @@ def run( use_temp=False, ignore_patterns=None, silent=False, + petsc_args=None, docker_args=None, **kwargs, ): @@ -49,6 +50,8 @@ def run( If provided, output files that match the glob-style patterns will be discarded. silent : bool, optional, default False If `True`, nothing will be printed to standard output. + petsc_args : list or None, optional, default None + List of arguments passed to PETSc solver (written to `.petscrc`). docker_args : list or None, optional, default None List of arguments passed to `docker run` command. @@ -144,6 +147,18 @@ def run( ): shutil.copy(filename, simulation_dir / new_filename.name) + # PETSc arguments + petsc_args = petsc_args if petsc_args else [] + + if petsc_args: + with open(simulation_dir / ".petscrc", "w") as f: + for arg in petsc_args: + if arg.startswith("-"): + f.write(f"{arg} ") + + else: + f.write(f"{arg}\n") + # Output filename output_filename = f"{input_filename.stem}.out" From 609043946326b29fa079b9a2a7b4c40787620fb3 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Sun, 5 Nov 2023 11:06:56 +0100 Subject: [PATCH 05/11] check executable command before running TOUGH --- toughio/_run.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/toughio/_run.py b/toughio/_run.py index ad3a44bb..9b503291 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -1,4 +1,5 @@ import glob +import json import os import pathlib import platform @@ -108,6 +109,48 @@ def run( exec = str(exec) exec = f"{os.path.expanduser('~')}/{exec[2:]}" if exec.startswith("~/") else exec + if not docker: + if shutil.which(exec) is None: + raise RuntimeError(f"executable '{exec}' not found.") + + else: + # Check if Docker is in the PATH + if shutil.which("docker") is None: + raise RuntimeError("Docker executable not found.") + + # Check if Docker daemon is running + status = subprocess.run( + ["docker", "version", "-f", "'{{json .}}'"], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + ibeg = status.stdout.index("{") + iend = status.stdout[::-1].index("}") + data = json.loads(status.stdout[ibeg:-iend]) + + if not data["Server"]: + raise RuntimeError("cannot connect to the Docker daemon. Is the Docker daemon running?") + + # Check if Docker image exists + status = subprocess.run( + ["docker", "images", "-q", docker], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + if not status.stdout: + raise RuntimeError(f"image '{docker}' not found.") + + # Check if the executable exists inside the image + status = subprocess.run( + ["docker", "run", "--rm", docker, "which", exec], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + if not status.stdout: + raise RuntimeError(f"executable '{exec}' not found in Docker image '{docker}'.") + # Working directory working_dir = os.getcwd() if working_dir is None else working_dir working_dir = pathlib.Path(working_dir) From 3fb18ee5752e47a6fc123bda068a97500a302d06 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Sun, 5 Nov 2023 11:07:17 +0100 Subject: [PATCH 06/11] add option blocks --- toughio/_io/input/_helpers.py | 2 + toughio/_io/input/tough/_read.py | 109 ++++++++++++++++--------------- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/toughio/_io/input/_helpers.py b/toughio/_io/input/_helpers.py index 9b4073f4..78230a4b 100644 --- a/toughio/_io/input/_helpers.py +++ b/toughio/_io/input/_helpers.py @@ -61,6 +61,8 @@ def read(filename, file_format=None, **kwargs): Other Parameters ---------------- + blocks : list of str or None, optional, default None + Only if ``file_format = "tough"``. Blocks to read. If None, all blocks are read. label_length : int or None, optional, default None Only if ``file_format = "tough"``. Number of characters in cell labels. n_variables : int or None, optional, default None diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index f09d88d5..584bf21d 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -17,7 +17,7 @@ } -def read(filename, label_length=None, n_variables=None, eos=None, simulator="tough"): +def read(filename, blocks=None, label_length=None, n_variables=None, eos=None, simulator="tough"): """ Read TOUGH input file. @@ -25,6 +25,8 @@ def read(filename, label_length=None, n_variables=None, eos=None, simulator="tou ---------- filename : str, pathlike or buffer Input file name or buffer. + blocks : list of str or None, optional, default None + Blocks to read. If None, all blocks are read. label_length : int or None, optional, default None Number of characters in cell labels. n_variables : int or None, optional, default None @@ -46,39 +48,44 @@ def read(filename, label_length=None, n_variables=None, eos=None, simulator="tou raise ValueError() with open_file(filename, "r") as f: - out = read_buffer(f, label_length, n_variables, eos, simulator) + out = read_buffer(f, blocks, label_length, n_variables, eos, simulator) return out -def read_buffer(f, label_length, n_variables, eos, simulator="tough"): +def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): """Read TOUGH input file.""" from ._common import blocks + # Block filters + blocks_ = blocks_ if blocks_ is not None else blocks + blocks_ = set(blocks_) + parameters = {} flag = False # Title - title = [] - while True: - if len(title) >= 100: - raise ValueError() + if "TITLE" in blocks_: + title = [] + while True: + if len(title) >= 100: + raise ValueError() - line = f.readline().strip() + line = f.readline().strip() - if line[:5].rstrip().upper() not in blocks: - title.append(line) + if line[:5].rstrip().upper() not in blocks: + title.append(line) - else: - break + else: + break - if title: - title = title[0] if len(title) == 1 else title + if title: + title = title[0] if len(title) == 1 else title - if title: - parameters["title"] = title + if title: + parameters["title"] = title - f.seek(0) + f.seek(0) # Loop over blocks # Some blocks (INCON, INDOM, PARAM) need to rewind to previous line but tell and seek are disabled by next @@ -87,13 +94,13 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): try: for line in fiter: - if line.startswith("DIMEN"): + if line.startswith("DIMEN") and "DIMEN" in blocks_: parameters.update(_read_dimen(fiter)) - elif line.startswith("ROCKS"): + elif line.startswith("ROCKS") and "ROCKS" in blocks_: parameters.update(_read_rocks(fiter, simulator)) - elif line.startswith("RPCAP"): + elif line.startswith("RPCAP") and "RPCAP" in blocks_: rpcap = _read_rpcap(fiter) if "default" in parameters: @@ -102,7 +109,7 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): else: parameters["default"] = rpcap - elif line.startswith("REACT"): + elif line.startswith("REACT") and "REACT" in blocks_: react = _read_react(fiter) if "react" in parameters: parameters["react"].update(react["react"]) @@ -110,35 +117,35 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): else: parameters.update(react) - elif line.startswith("FLAC"): + elif line.startswith("FLAC") and "FLAC" in blocks_: flac = _read_flac(fiter, parameters["rocks"]) parameters["flac"] = flac["flac"] for k, v in flac["rocks"].items(): parameters["rocks"][k].update(v) - elif line.startswith("CHEMP"): + elif line.startswith("CHEMP") and "CHEMP" in blocks_: parameters.update(_read_chemp(fiter)) - elif line.startswith("NCGAS"): + elif line.startswith("NCGAS") and "NCGAS" in blocks_: parameters.update(_read_ncgas(fiter)) - elif line.startswith("MULTI"): + elif line.startswith("MULTI") and "MULTI" in blocks_: parameters.update(_read_multi(fiter)) if not n_variables: n_variables = parameters["n_component"] + 1 - elif line.startswith("SOLVR"): + elif line.startswith("SOLVR") and "SOLVR" in blocks_: parameters.update(_read_solvr(fiter)) - elif line.startswith("INDEX"): + elif line.startswith("INDEX") and "INDEX" in blocks_: parameters["index"] = True - elif line.startswith("START"): + elif line.startswith("START") and "START" in blocks_: parameters["start"] = True - elif line.startswith("PARAM"): + elif line.startswith("PARAM") and "PARAM" in blocks_: param, n_variables = _read_param(fiter, n_variables, eos) parameters["options"] = param["options"] parameters["extra_options"] = param["extra_options"] @@ -149,68 +156,68 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): else: parameters["default"] = param["default"] - elif line.startswith("SELEC"): + elif line.startswith("SELEC") and "SELEC" in blocks_: parameters.update(_read_selec(fiter)) - elif line.startswith("INDOM"): + elif line.startswith("INDOM") and "INDOM" in blocks_: indom, n_variables = _read_indom(fiter, n_variables, eos) for k, v in indom["rocks"].items(): parameters["rocks"][k].update(v) - elif line.startswith("MOMOP"): + elif line.startswith("MOMOP") and "MOMOP" in blocks_: parameters.update(_read_momop(fiter)) - elif line.startswith("TIMES"): + elif line.startswith("TIMES") and "TIMES" in blocks_: parameters.update(_read_times(fiter)) - elif line.startswith("HYSTE"): + elif line.startswith("HYSTE") and "HYSTE" in blocks_: parameters.update(_read_hyste(fiter)) - elif line.startswith("FOFT"): + elif line.startswith("FOFT") and "FOFT" in blocks_: oft, label_length = _read_oft(fiter, "FOFT", label_length) parameters.update(oft) - elif line.startswith("COFT"): + elif line.startswith("COFT") and "COFT" in blocks_: oft, label_length = _read_oft(fiter, "COFT", label_length) parameters.update(oft) - elif line.startswith("GOFT"): + elif line.startswith("GOFT") and "GOFT" in blocks_: oft, label_length = _read_oft(fiter, "GOFT", label_length) parameters.update(oft) - elif line.startswith("ROFT"): + elif line.startswith("ROFT") and "ROFT" in blocks_: parameters.update(_read_roft(fiter)) - elif line.startswith("GENER"): + elif line.startswith("GENER") and "GENER" in blocks_: gener, flag, label_length = _read_gener(fiter, label_length, simulator) parameters.update(gener) if flag: break - elif line.startswith("TIMBC"): + elif line.startswith("TIMBC") and "TIMBC" in blocks_: parameters.update(_read_timbc(fiter)) - elif line.startswith("DIFFU"): + elif line.startswith("DIFFU") and "DIFFU" in blocks_: parameters.update(_read_diffu(fiter)) - elif line.startswith("OUTPT"): + elif line.startswith("OUTPT") and "OUTPT" in blocks_: outpt = _read_outpt(fiter) if "react" in parameters: parameters["react"].update(outpt["react"]) else: parameters.update(outpt) - elif line.startswith("OUTPU"): + elif line.startswith("OUTPU") and "OUTPU" in blocks_: parameters.update(_read_outpu(fiter)) - elif line.startswith("ELEME"): + elif line.startswith("ELEME") and "ELEME" in blocks_: eleme, label_length = _read_eleme(fiter, label_length) parameters.update(eleme) parameters["coordinates"] = False - elif line.startswith("COORD"): + elif line.startswith("COORD") and "COORD" in blocks_: coord = _read_coord(fiter) for k, v in zip(parameters["elements"], coord): @@ -218,14 +225,14 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): parameters["coordinates"] = True - elif line.startswith("CONNE"): + elif line.startswith("CONNE") and "CONNE" in blocks_: conne, flag, label_length = _read_conne(fiter, label_length) parameters.update(conne) if flag: break - elif line.startswith("INCON"): + elif line.startswith("INCON") and "INCON" in blocks_: incon, flag, label_length, n_variables = _read_incon( fiter, label_length, n_variables, eos, simulator ) @@ -234,20 +241,20 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): if flag: break - elif line.startswith("MESHM"): + elif line.startswith("MESHM") and "MESHM" in blocks_: parameters.update(_read_meshm(fiter)) - elif line.startswith("POISE"): + elif line.startswith("POISE") and "POISE" in blocks_: poise = _read_poise(fiter) if "react" in parameters: parameters["react"].update(poise["react"]) else: parameters.update(poise) - elif line.startswith("NOVER"): + elif line.startswith("NOVER") and "NOVER" in blocks_: parameters["nover"] = True - elif line.startswith("ENDCY"): + elif line.startswith("ENDCY") and "ENDCY" in blocks_: end_comments = read_end_comments(fiter) if end_comments: From 2047cc11b4deafdbf386613351f8dfa9f7ae2587 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 6 Nov 2023 10:04:48 +0100 Subject: [PATCH 07/11] stop reader when all blocks have been read --- toughio/_io/input/tough/_read.py | 121 +++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 38 deletions(-) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 584bf21d..53735870 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -53,19 +53,21 @@ def read(filename, blocks=None, label_length=None, n_variables=None, eos=None, s return out -def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): +def read_buffer(f, block_stack, label_length, n_variables, eos, simulator="tough"): """Read TOUGH input file.""" from ._common import blocks # Block filters - blocks_ = blocks_ if blocks_ is not None else blocks - blocks_ = set(blocks_) + block_stack = block_stack if block_stack is not None else blocks + block_stack = set(block_stack) parameters = {} flag = False # Title - if "TITLE" in blocks_: + if "TITLE" in block_stack: + block_stack.remove("TITLE") + title = [] while True: if len(title) >= 100: @@ -94,13 +96,16 @@ def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): try: for line in fiter: - if line.startswith("DIMEN") and "DIMEN" in blocks_: + if line.startswith("DIMEN") and "DIMEN" in block_stack: + block_stack.remove("DIMEN") parameters.update(_read_dimen(fiter)) - elif line.startswith("ROCKS") and "ROCKS" in blocks_: + elif line.startswith("ROCKS") and "ROCKS" in block_stack: + block_stack.remove("ROCKS") parameters.update(_read_rocks(fiter, simulator)) - elif line.startswith("RPCAP") and "RPCAP" in blocks_: + elif line.startswith("RPCAP") and "RPCAP" in block_stack: + block_stack.remove("RPCAP") rpcap = _read_rpcap(fiter) if "default" in parameters: @@ -109,43 +114,53 @@ def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): else: parameters["default"] = rpcap - elif line.startswith("REACT") and "REACT" in blocks_: + elif line.startswith("REACT") and "REACT" in block_stack: + block_stack.remove("REACT") react = _read_react(fiter) + if "react" in parameters: parameters["react"].update(react["react"]) else: parameters.update(react) - elif line.startswith("FLAC") and "FLAC" in blocks_: + elif line.startswith("FLAC") and "FLAC" in block_stack: + block_stack.remove("FLAC") flac = _read_flac(fiter, parameters["rocks"]) parameters["flac"] = flac["flac"] for k, v in flac["rocks"].items(): parameters["rocks"][k].update(v) - elif line.startswith("CHEMP") and "CHEMP" in blocks_: + elif line.startswith("CHEMP") and "CHEMP" in block_stack: + block_stack.remove("CHEMP") parameters.update(_read_chemp(fiter)) - elif line.startswith("NCGAS") and "NCGAS" in blocks_: + elif line.startswith("NCGAS") and "NCGAS" in block_stack: + block_stack.remove("NCGAS") parameters.update(_read_ncgas(fiter)) - elif line.startswith("MULTI") and "MULTI" in blocks_: + elif line.startswith("MULTI") and "MULTI" in block_stack: + block_stack.remove("MULTI") parameters.update(_read_multi(fiter)) if not n_variables: n_variables = parameters["n_component"] + 1 - elif line.startswith("SOLVR") and "SOLVR" in blocks_: + elif line.startswith("SOLVR") and "SOLVR" in block_stack: + block_stack.remove("SOLVR") parameters.update(_read_solvr(fiter)) - elif line.startswith("INDEX") and "INDEX" in blocks_: + elif line.startswith("INDEX") and "INDEX" in block_stack: + block_stack.remove("INDEX") parameters["index"] = True - elif line.startswith("START") and "START" in blocks_: + elif line.startswith("START") and "START" in block_stack: + block_stack.remove("START") parameters["start"] = True - elif line.startswith("PARAM") and "PARAM" in blocks_: + elif line.startswith("PARAM") and "PARAM" in block_stack: + block_stack.remove("PARAM") param, n_variables = _read_param(fiter, n_variables, eos) parameters["options"] = param["options"] parameters["extra_options"] = param["extra_options"] @@ -156,68 +171,86 @@ def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): else: parameters["default"] = param["default"] - elif line.startswith("SELEC") and "SELEC" in blocks_: + elif line.startswith("SELEC") and "SELEC" in block_stack: + block_stack.remove("SELEC") parameters.update(_read_selec(fiter)) - elif line.startswith("INDOM") and "INDOM" in blocks_: + elif line.startswith("INDOM") and "INDOM" in block_stack: + block_stack.remove("INDOM") indom, n_variables = _read_indom(fiter, n_variables, eos) for k, v in indom["rocks"].items(): parameters["rocks"][k].update(v) - elif line.startswith("MOMOP") and "MOMOP" in blocks_: + elif line.startswith("MOMOP") and "MOMOP" in block_stack: + block_stack.remove("MOMOP") parameters.update(_read_momop(fiter)) - elif line.startswith("TIMES") and "TIMES" in blocks_: + elif line.startswith("TIMES") and "TIMES" in block_stack: + block_stack.remove("TIMES") parameters.update(_read_times(fiter)) - elif line.startswith("HYSTE") and "HYSTE" in blocks_: + elif line.startswith("HYSTE") and "HYSTE" in block_stack: + block_stack.remove("HYSTE") parameters.update(_read_hyste(fiter)) - elif line.startswith("FOFT") and "FOFT" in blocks_: + elif line.startswith("FOFT") and "FOFT" in block_stack: + block_stack.remove("FOFT") oft, label_length = _read_oft(fiter, "FOFT", label_length) parameters.update(oft) - elif line.startswith("COFT") and "COFT" in blocks_: + elif line.startswith("COFT") and "COFT" in block_stack: + block_stack.remove("COFT") oft, label_length = _read_oft(fiter, "COFT", label_length) parameters.update(oft) - elif line.startswith("GOFT") and "GOFT" in blocks_: + elif line.startswith("GOFT") and "GOFT" in block_stack: + block_stack.remove("GOFT") oft, label_length = _read_oft(fiter, "GOFT", label_length) parameters.update(oft) - elif line.startswith("ROFT") and "ROFT" in blocks_: + elif line.startswith("ROFT") and "ROFT" in block_stack: + block_stack.remove("ROFT") parameters.update(_read_roft(fiter)) - elif line.startswith("GENER") and "GENER" in blocks_: + elif line.startswith("GENER") and "GENER" in block_stack: + block_stack.remove("GENER") gener, flag, label_length = _read_gener(fiter, label_length, simulator) parameters.update(gener) if flag: break - elif line.startswith("TIMBC") and "TIMBC" in blocks_: + elif line.startswith("TIMBC") and "TIMBC" in block_stack: + block_stack.remove("TIMBC") parameters.update(_read_timbc(fiter)) - elif line.startswith("DIFFU") and "DIFFU" in blocks_: + elif line.startswith("DIFFU") and "DIFFU" in block_stack: + block_stack.remove("DIFFU") parameters.update(_read_diffu(fiter)) - elif line.startswith("OUTPT") and "OUTPT" in blocks_: + elif line.startswith("OUTPT") and "OUTPT" in block_stack: + block_stack.remove("OUTPT") outpt = _read_outpt(fiter) + if "react" in parameters: parameters["react"].update(outpt["react"]) + else: parameters.update(outpt) - elif line.startswith("OUTPU") and "OUTPU" in blocks_: + elif line.startswith("OUTPU") and "OUTPU" in block_stack: + block_stack.remove("OUTPU") parameters.update(_read_outpu(fiter)) - elif line.startswith("ELEME") and "ELEME" in blocks_: + elif line.startswith("ELEME") and "ELEME" in block_stack: + block_stack.remove("ELEME") eleme, label_length = _read_eleme(fiter, label_length) parameters.update(eleme) parameters["coordinates"] = False - elif line.startswith("COORD") and "COORD" in blocks_: + elif line.startswith("COORD") and "COORD" in block_stack: + block_stack.remove("COORD") coord = _read_coord(fiter) for k, v in zip(parameters["elements"], coord): @@ -225,14 +258,16 @@ def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): parameters["coordinates"] = True - elif line.startswith("CONNE") and "CONNE" in blocks_: + elif line.startswith("CONNE") and "CONNE" in block_stack: + block_stack.remove("CONNE") conne, flag, label_length = _read_conne(fiter, label_length) parameters.update(conne) if flag: break - elif line.startswith("INCON") and "INCON" in blocks_: + elif line.startswith("INCON") and "INCON" in block_stack: + block_stack.remove("INCON") incon, flag, label_length, n_variables = _read_incon( fiter, label_length, n_variables, eos, simulator ) @@ -241,25 +276,35 @@ def read_buffer(f, blocks_, label_length, n_variables, eos, simulator="tough"): if flag: break - elif line.startswith("MESHM") and "MESHM" in blocks_: + elif line.startswith("MESHM") and "MESHM" in block_stack: + block_stack.remove("MESHM") parameters.update(_read_meshm(fiter)) - elif line.startswith("POISE") and "POISE" in blocks_: + elif line.startswith("POISE") and "POISE" in block_stack: + block_stack.remove("POISE") poise = _read_poise(fiter) + if "react" in parameters: parameters["react"].update(poise["react"]) + else: parameters.update(poise) - elif line.startswith("NOVER") and "NOVER" in blocks_: + elif line.startswith("NOVER") and "NOVER" in block_stack: + block_stack.remove("NOVER") parameters["nover"] = True - elif line.startswith("ENDCY") and "ENDCY" in blocks_: + elif line.startswith("ENDCY") and "ENDCY" in block_stack: + block_stack.remove("ENDCY") end_comments = read_end_comments(fiter) if end_comments: parameters["end_comments"] = end_comments + # Stop reading if block stack is empty + if not block_stack: + break + except: raise ReadError(f"failed to parse line {fiter.count}.") From e716ce9bb9cab59fd19a14ebbc6f7089cb1893f6 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 6 Nov 2023 10:05:26 +0100 Subject: [PATCH 08/11] check executable in run --- tests/test_run.py | 1 + toughio/_run.py | 84 +++++++++++++++++++++++++---------------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/tests/test_run.py b/tests/test_run.py index 8f9586c1..186308f5 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -16,6 +16,7 @@ ], ) def test_run(exec, workers, docker, wsl, cmd): + toughio._run._check_exec = False status = toughio.run( exec, {}, diff --git a/toughio/_run.py b/toughio/_run.py index 9b503291..5ee64464 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -8,6 +8,9 @@ import tempfile +_check_exec = True # Bool to be monkeypatched in tests + + def run( exec, input_filename, @@ -109,47 +112,48 @@ def run( exec = str(exec) exec = f"{os.path.expanduser('~')}/{exec[2:]}" if exec.startswith("~/") else exec - if not docker: - if shutil.which(exec) is None: - raise RuntimeError(f"executable '{exec}' not found.") - - else: - # Check if Docker is in the PATH - if shutil.which("docker") is None: - raise RuntimeError("Docker executable not found.") - - # Check if Docker daemon is running - status = subprocess.run( - ["docker", "version", "-f", "'{{json .}}'"], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - ibeg = status.stdout.index("{") - iend = status.stdout[::-1].index("}") - data = json.loads(status.stdout[ibeg:-iend]) - - if not data["Server"]: - raise RuntimeError("cannot connect to the Docker daemon. Is the Docker daemon running?") - - # Check if Docker image exists - status = subprocess.run( - ["docker", "images", "-q", docker], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - - if not status.stdout: - raise RuntimeError(f"image '{docker}' not found.") + if _check_exec: + if not docker: + if shutil.which(exec) is None: + raise RuntimeError(f"executable '{exec}' not found.") - # Check if the executable exists inside the image - status = subprocess.run( - ["docker", "run", "--rm", docker, "which", exec], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - - if not status.stdout: - raise RuntimeError(f"executable '{exec}' not found in Docker image '{docker}'.") + else: + # Check if Docker is in the PATH + if shutil.which("docker") is None: + raise RuntimeError("Docker executable not found.") + + # Check if Docker daemon is running + status = subprocess.run( + ["docker", "version", "-f", "'{{json .}}'"], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + ibeg = status.stdout.index("{") + iend = status.stdout[::-1].index("}") + data = json.loads(status.stdout[ibeg:-iend]) + + if not data["Server"]: + raise RuntimeError("cannot connect to the Docker daemon. Is the Docker daemon running?") + + # Check if Docker image exists + status = subprocess.run( + ["docker", "images", "-q", docker], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + if not status.stdout: + raise RuntimeError(f"image '{docker}' not found.") + + # Check if the executable exists inside the image + status = subprocess.run( + ["docker", "run", "--rm", docker, "which", exec], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + if not status.stdout: + raise RuntimeError(f"executable '{exec}' not found in Docker image '{docker}'.") # Working directory working_dir = os.getcwd() if working_dir is None else working_dir From c3120773b9f0c8e5d032e9e854527f772fda4ced Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 6 Nov 2023 14:31:31 +0100 Subject: [PATCH 09/11] capture stdout in ipython --- toughio/_run.py | 64 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 5ee64464..4b079b22 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -1,5 +1,4 @@ import glob -import json import os import pathlib import platform @@ -7,7 +6,6 @@ import subprocess import tempfile - _check_exec = True # Bool to be monkeypatched in tests @@ -124,16 +122,15 @@ def run( # Check if Docker daemon is running status = subprocess.run( - ["docker", "version", "-f", "'{{json .}}'"], + ["docker", "version"], stdout=subprocess.PIPE, universal_newlines=True, ) - ibeg = status.stdout.index("{") - iend = status.stdout[::-1].index("}") - data = json.loads(status.stdout[ibeg:-iend]) - if not data["Server"]: - raise RuntimeError("cannot connect to the Docker daemon. Is the Docker daemon running?") + if "Server" not in status.stdout: + raise RuntimeError( + "cannot connect to the Docker daemon. Is the Docker daemon running?" + ) # Check if Docker image exists status = subprocess.run( @@ -153,7 +150,9 @@ def run( ) if not status.stdout: - raise RuntimeError(f"executable '{exec}' not found in Docker image '{docker}'.") + raise RuntimeError( + f"executable '{exec}' not found in Docker image '{docker}'." + ) # Working directory working_dir = os.getcwd() if working_dir is None else working_dir @@ -196,7 +195,7 @@ def run( # PETSc arguments petsc_args = petsc_args if petsc_args else [] - + if petsc_args: with open(simulation_dir / ".petscrc", "w") as f: for arg in petsc_args: @@ -247,16 +246,45 @@ def run( if wsl and is_windows: cmd = f"bash -c '{cmd}'" - kwargs = {} - if silent: - kwargs["stdout"] = subprocess.DEVNULL - kwargs["stderr"] = subprocess.STDOUT + # Run simulation + if not silent: + # See + p = subprocess.Popen( + cmd, + shell=True, + cwd=str(simulation_dir), + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + universal_newlines=False, + ) - else: - kwargs["stderr"] = subprocess.PIPE - kwargs["universal_newlines"] = True + stdout = [] + cr = False + for line in open(os.dup(p.stdout.fileno()), newline=""): + # Handle carriage return + # newline in open is converting \r\n as \r moving \r at the end of the previous string + # This is only an issue for Spyder + line = f"\r{line}" if cr else line + cr = line.endswith("\r") + + line = line[:-2] if cr else line + print(line, end="", flush=True) + stdout.append(line) + + status = subprocess.CompletedProcess( + args=p.args, + returncode=0, + stdout="".join(stdout), + ) - status = subprocess.run(cmd, shell=True, cwd=str(simulation_dir), **kwargs) + else: + status = subprocess.run( + cmd, + shell=True, + cwd=str(simulation_dir), + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) # Copy files from temporary directory and delete it if use_temp: From 33627022368e34cb3647db26812ed6c684bc546d Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 6 Nov 2023 14:31:41 +0100 Subject: [PATCH 10/11] invoke format --- toughio/_io/input/tough/_read.py | 11 +++++++++-- toughio/_mesh/_mesh.py | 21 ++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 53735870..10362be5 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -17,7 +17,14 @@ } -def read(filename, blocks=None, label_length=None, n_variables=None, eos=None, simulator="tough"): +def read( + filename, + blocks=None, + label_length=None, + n_variables=None, + eos=None, + simulator="tough", +): """ Read TOUGH input file. @@ -67,7 +74,7 @@ def read_buffer(f, block_stack, label_length, n_variables, eos, simulator="tough # Title if "TITLE" in block_stack: block_stack.remove("TITLE") - + title = [] while True: if len(title) >= 100: diff --git a/toughio/_mesh/_mesh.py b/toughio/_mesh/_mesh.py index 51f994d6..e755d4ee 100644 --- a/toughio/_mesh/_mesh.py +++ b/toughio/_mesh/_mesh.py @@ -93,7 +93,7 @@ def __repr__(self): lines.append(f" Cell data: {', '.join(self.cell_data)}") return "\n".join(lines) - + def __getitem__(self, islice): """Slice mesh.""" if np.ndim(islice) == 0: @@ -101,27 +101,27 @@ def __getitem__(self, islice): if np.ndim(islice) != 1: raise ValueError() - + if isinstance(islice[0], (bool, np.bool_)): if len(islice) != self.n_cells: raise ValueError() - + elif isinstance(islice[0], (int, np.int8, np.int16, np.int32, np.int64)): if np.max(islice) >= self.n_cells: raise ValueError() - + tmp = islice islice = np.zeros(self.n_cells, dtype=bool) islice[tmp] = True - + else: raise TypeError() - + count = 0 cell_idx = [] nodes_to_keep = set() - for (_, cell_data) in self.cells: + for _, cell_data in self.cells: cell_idx.append([]) for j, cell in enumerate(cell_data): @@ -147,12 +147,15 @@ def __getitem__(self, islice): return Mesh( points=self.points[point_idx], cells=[ - (cell_type, np.array([[node_map[i] for i in cell] for cell in cell_data[idx]])) + ( + cell_type, + np.array([[node_map[i] for i in cell] for cell in cell_data[idx]]), + ) for idx, (cell_type, cell_data) in zip(cell_idx, self.cells) ], point_data={k: v[point_idx] for k, v in self.point_data.items()}, cell_data={k: v[islice] for k, v in self.cell_data.items()}, - field_data={k: v for k, v in self.field_data.items()} + field_data={k: v for k, v in self.field_data.items()}, ) def extrude_to_3d(self, height=1.0, axis=2, inplace=True): From 68442605bc5b423ae249a4c0a3c4cca2355a09ff Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 6 Nov 2023 14:31:47 +0100 Subject: [PATCH 11/11] version bump --- toughio/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/VERSION b/toughio/VERSION index d9ee6574..cd99d386 100644 --- a/toughio/VERSION +++ b/toughio/VERSION @@ -1 +1 @@ -1.13.3 \ No newline at end of file +1.14.0 \ No newline at end of file