From 015413e0d8f674029a9328f72a0f736ca209247f Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 11 Apr 2023 15:11:00 +0200 Subject: [PATCH 01/43] add function write_h5 --- tests/test_h5.py | 24 ++++++++ toughio/__init__.py | 2 + toughio/_io/__init__.py | 2 + toughio/_io/h5/__init__.py | 5 ++ toughio/_io/h5/_write.py | 111 +++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 tests/test_h5.py create mode 100644 toughio/_io/h5/__init__.py create mode 100644 toughio/_io/h5/_write.py diff --git a/tests/test_h5.py b/tests/test_h5.py new file mode 100644 index 00000000..4596a4dc --- /dev/null +++ b/tests/test_h5.py @@ -0,0 +1,24 @@ +import h5py +import helpers +import pathlib + +import toughio + + +def test_h5(): + this_dir = pathlib.Path(__file__).parent + path = this_dir / "support_files" / "outputs" + filename = helpers.tempdir("output.h5") + + toughio.write_h5( + filename=filename, + elements=path / "OUTPUT_ELEME.csv", + connections=path / "OUTPUT_CONNE.csv", + element_history={"A1912": path / "FOFT_A1912.csv"}, + connection_history={"A1912": path / "FOFT_A1912.csv"}, + generator_history={"A1162": path / "GOFT_A1162.csv"}, + rock_history={"ROCK": path / "ROFT.csv"}, + ) + + with h5py.File(filename, "r") as f: + assert list(f.keys()) == ["connection_history", "connections", "element_history", "elements", "generator_history", "rock_history"] diff --git a/toughio/__init__.py b/toughio/__init__.py index 0f0918ed..1fbef942 100644 --- a/toughio/__init__.py +++ b/toughio/__init__.py @@ -9,6 +9,7 @@ register_input, register_output, register_table, + write_h5, write_input, write_output, ) @@ -31,6 +32,7 @@ "read_input", "read_output", "read_table", + "write_h5", "write_input", "write_output", "from_meshio", diff --git a/toughio/_io/__init__.py b/toughio/_io/__init__.py index b4eb268b..cff65c2f 100644 --- a/toughio/_io/__init__.py +++ b/toughio/_io/__init__.py @@ -1,3 +1,4 @@ +from .h5 import write as write_h5 from .input import read as read_input from .input import register as register_input from .input import write as write_input @@ -13,6 +14,7 @@ "register_input", "register_output", "read_input", + "write_h5", "write_input", "read_output", "write_output", diff --git a/toughio/_io/h5/__init__.py b/toughio/_io/h5/__init__.py new file mode 100644 index 00000000..5a0e747a --- /dev/null +++ b/toughio/_io/h5/__init__.py @@ -0,0 +1,5 @@ +from ._write import write + +__all__ = [ + "write", +] diff --git a/toughio/_io/h5/_write.py b/toughio/_io/h5/_write.py new file mode 100644 index 00000000..9e13b35d --- /dev/null +++ b/toughio/_io/h5/_write.py @@ -0,0 +1,111 @@ +import h5py +import pathlib + +from ..output import Output +from ..output import read as read_output +from ..table import read as read_table + + +def write( + filename, + elements=None, + connections=None, + element_history=None, + connection_history=None, + generator_history=None, + rock_history=None, + labels_order=None, + compression_opts=4, +): + """ + Write TOUGH outputs to a HDF5 file. + + Parameters + ---------- + filename : str or pathlike + Output file name. + elements : namedtuple, list of namedtuple, str, pathlike or None, optional, default None + connections : namedtuple, list of namedtuple, str, pathlike or None, optional, default None + Connection outputs to export. + element_history : dict or None, optional, default None + Element history to export. + connection_history : dict or None, optional, default None + Connection history to export. + generator_history : dict or None, optional, default None + Generator history to export. + rock_history : dict or None, optional, default None + Rock history to export. + labels_order : list of array_like or None, optional, default None + List of labels. + compression_opts : int, optional, default 4 + Compression level for gzip compression. May be an integer from 0 to 9. + + """ + kwargs = { + "compression": "gzip", + "compression_opts": compression_opts, + } + + with h5py.File(filename, "w") as f: + if elements is not None: + group = f.create_group("elements") + _write_output(group, elements, labels_order, connection=False, **kwargs) + + if connections is not None: + group = f.create_group("connections") + _write_output(group, connections, labels_order, connection=True, **kwargs) + + if element_history is not None: + group = f.create_group("element_history") + _write_table(group, element_history, **kwargs) + + if connection_history is not None: + group = f.create_group("connection_history") + _write_table(group, connection_history, **kwargs) + + if generator_history is not None: + group = f.create_group("generator_history") + _write_table(group, generator_history, **kwargs) + + if rock_history is not None: + group = f.create_group("rock_history") + _write_table(group, rock_history, **kwargs) + + +def _write_output(f, outputs, labels_order, connection, **kwargs): + """Write TOUGH output to group.""" + if isinstance(outputs, (str, pathlib.Path)): + outputs = read_output(outputs, labels_order=labels_order, connection=connection) + + if isinstance(outputs, Output): + outputs = [outputs] + + elif isinstance(outputs, (list, tuple)): + for output in outputs: + if not isinstance(output, Output): + raise ValueError() + + else: + raise ValueError() + + for output in outputs: + group = f.create_group(f"time={output.time}") + group.create_dataset("labels", data=output.labels.astype("S"), **kwargs) + + for k, v in output.data.items(): + group.create_dataset(k, data=v, **kwargs) + + +def _write_table(f, tables, **kwargs): + """Write TOUGH table to group.""" + if not isinstance(tables, dict): + raise ValueError() + + for name, table in tables.items(): + group = f.create_group(name) + + if isinstance(table, (str, pathlib.Path)): + table = read_table(table) + + for k, v in table.items(): + group.create_dataset(k, data=v, **kwargs) From 3b4fdfdcfa97272179cc2cb59c40fb8f0d9dcaf1 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 13 Apr 2023 15:42:29 +0200 Subject: [PATCH 02/43] add option labels_order --- toughio/_io/output/_helpers.py | 2 +- toughio/_io/output/csv/_csv.py | 2 +- toughio/_io/output/petrasim/_petrasim.py | 2 +- toughio/_io/output/save/_save.py | 2 +- toughio/_io/output/tecplot/__init__.py | 1 + toughio/_io/output/tecplot/_tecplot.py | 2 +- toughio/_io/output/tough/_tough.py | 2 +- toughio/_mesh/_mesh.py | 12 ++++++++---- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/toughio/_io/output/_helpers.py b/toughio/_io/output/_helpers.py index d79fa215..b8361137 100644 --- a/toughio/_io/output/_helpers.py +++ b/toughio/_io/output/_helpers.py @@ -58,7 +58,7 @@ def read( file_format : str ('csv', 'petrasim', 'save', 'tecplot', 'tough') or None, optional, default None Input file format. labels_order : list of array_like or None, optional, default None - List of labels. + List of labels. If None, output will be assumed ordered. connection : bool, optional, default False Only for standard TOUGH output file. If `True`, return data related to connections. diff --git a/toughio/_io/output/csv/_csv.py b/toughio/_io/output/csv/_csv.py index b3cf773f..8c3fb094 100644 --- a/toughio/_io/output/csv/_csv.py +++ b/toughio/_io/output/csv/_csv.py @@ -45,7 +45,7 @@ def read(filename, file_type, file_format, labels_order): file_format : str Input file format. labels_order : list of array_like - List of labels. + List of labels. If None, output will be assumed ordered. Returns ------- diff --git a/toughio/_io/output/petrasim/_petrasim.py b/toughio/_io/output/petrasim/_petrasim.py index 79eb67b3..924ea6cc 100644 --- a/toughio/_io/output/petrasim/_petrasim.py +++ b/toughio/_io/output/petrasim/_petrasim.py @@ -22,7 +22,7 @@ def read(filename, file_type, file_format, labels_order): file_format : str Input file format. labels_order : list of array_like - List of labels. + List of labels. If None, output will be assumed ordered. Returns ------- diff --git a/toughio/_io/output/save/_save.py b/toughio/_io/output/save/_save.py index 7a4616b2..735fd805 100644 --- a/toughio/_io/output/save/_save.py +++ b/toughio/_io/output/save/_save.py @@ -21,7 +21,7 @@ def read(filename, file_type, file_format, labels_order): file_format : str Input file format. labels_order : list of array_like - List of labels. + List of labels. If None, output will be assumed ordered. Returns ------- diff --git a/toughio/_io/output/tecplot/__init__.py b/toughio/_io/output/tecplot/__init__.py index 1eb88d51..a0025b38 100644 --- a/toughio/_io/output/tecplot/__init__.py +++ b/toughio/_io/output/tecplot/__init__.py @@ -3,6 +3,7 @@ __all__ = [ "read", + "write", ] diff --git a/toughio/_io/output/tecplot/_tecplot.py b/toughio/_io/output/tecplot/_tecplot.py index f1133632..767034d3 100644 --- a/toughio/_io/output/tecplot/_tecplot.py +++ b/toughio/_io/output/tecplot/_tecplot.py @@ -41,7 +41,7 @@ def read(filename, file_type, file_format, labels_order): file_format : str Input file format. labels_order : list of array_like - List of labels. + List of labels. If None, output will be assumed ordered. Returns ------- diff --git a/toughio/_io/output/tough/_tough.py b/toughio/_io/output/tough/_tough.py index 4508b809..beae1b67 100644 --- a/toughio/_io/output/tough/_tough.py +++ b/toughio/_io/output/tough/_tough.py @@ -24,7 +24,7 @@ def read(filename, file_type, file_format, labels_order): file_format : str Input file format. labels_order : list of array_like - List of labels. + List of labels. If None, output will be assumed ordered. Returns ------- diff --git a/toughio/_mesh/_mesh.py b/toughio/_mesh/_mesh.py index 38f45dc8..f9a5d565 100644 --- a/toughio/_mesh/_mesh.py +++ b/toughio/_mesh/_mesh.py @@ -407,7 +407,7 @@ def write_incon(self, filename="INCON", eos=None): eos, ) - def read_output(self, file_or_output, time_step=-1, connection=False): + def read_output(self, file_or_output, time_step=-1, labels_order=None, connection=False): """ Import TOUGH results to the mesh. @@ -417,6 +417,8 @@ def read_output(self, file_or_output, time_step=-1, connection=False): Input file name or buffer, or output data. time_step : int, optional, default -1 Data for given time step to import. Default is last time step. + labels_order : list of array_like or None, optional, default None + List of labels. If None, output will be assumed ordered. connection : bool, optional, default False Only for standard TOUGH output file. If `True`, read data related to connections. @@ -429,20 +431,22 @@ def read_output(self, file_or_output, time_step=-1, connection=False): if isinstance(file_or_output, str): out = read_output(file_or_output, connection=connection) + else: out = file_or_output if not isinstance(out, Output): if not (-len(out) <= time_step < len(out)): raise ValueError() + out = out[time_step] if out.type == "element": - if len(out.labels) != self.n_cells: - raise ValueError() + if labels_order is not None: + out = reorder_labels(out, labels_order) - out = reorder_labels(out, self.labels) self.cell_data.update(out.data) + elif out.type == "connection": centers = self.centers labels_map = {k: v for v, k in enumerate(self.labels)} From 1e033390884b927252391b54d81f980fa12f755e Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 13 Apr 2023 16:02:20 +0200 Subject: [PATCH 03/43] fix output format guess --- toughio/_io/output/_helpers.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/toughio/_io/output/_helpers.py b/toughio/_io/output/_helpers.py index b8361137..6b049b60 100644 --- a/toughio/_io/output/_helpers.py +++ b/toughio/_io/output/_helpers.py @@ -74,15 +74,23 @@ def read( raise TypeError() if file_format is None: + # Guess type and format from content file_type, file_format = get_output_type(filename) - file_type = ( - "connection" if (file_format == "tough" and connection) else file_type + + # Otherwise, guess file format from extension + file_format = ( + file_format + if file_format + else filetype_from_filename(filename, _extension_to_filetype, "") ) else: if file_format not in _reader_map: raise ValueError() + + file_type = "element" # By default + if connection: file_type = "connection" if connection else "element" return _reader_map[file_format](filename, file_type, file_format, labels_order) @@ -158,4 +166,4 @@ def get_output_type(filename): return "connection", "csv" else: - raise ValueError() + return "element", None From e91749a3e6113504e9ca0521fa0d8ef56ffb16d3 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 13 Apr 2023 16:21:27 +0200 Subject: [PATCH 04/43] fix mesh reader registration --- toughio/_mesh/_helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/toughio/_mesh/_helpers.py b/toughio/_mesh/_helpers.py index 73826582..f0c57cf6 100644 --- a/toughio/_mesh/_helpers.py +++ b/toughio/_mesh/_helpers.py @@ -89,11 +89,14 @@ def read(filename, file_format=None, **kwargs): # Call custom readers if fmt in _reader_map: mesh = _reader_map[fmt](filename, **kwargs) - if fmt not in {"tough", "pickle"}: + + if fmt in {"avsucd", "flac3d"}: mesh.cell_data = {k: np.concatenate(v) for k, v in mesh.cell_data.items()} key = get_material_key(mesh.cell_data) + if key: mesh.cell_data["material"] = mesh.cell_data.pop(key) + else: mesh = meshio.read(filename, file_format) mesh = from_meshio(mesh) From 36185a149832847a3cdff2aa4ca23c24eb56fd9c Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 26 Apr 2023 10:03:43 +0200 Subject: [PATCH 05/43] remove file_format from output --- tests/helpers.py | 2 -- tests/test_output.py | 18 +++++++++--------- toughio/_cli/_export.py | 16 ++-------------- toughio/_cli/_extract.py | 2 -- toughio/_io/output/_common.py | 7 +++---- toughio/_io/output/_helpers.py | 2 +- toughio/_io/output/csv/_csv.py | 6 ++---- toughio/_io/output/petrasim/_petrasim.py | 6 ++---- toughio/_io/output/save/_save.py | 6 ++---- toughio/_io/output/tecplot/_tecplot.py | 6 ++---- toughio/_io/output/tough/_tough.py | 6 ++---- 11 files changed, 25 insertions(+), 52 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index c048b3bb..194d7793 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -74,7 +74,6 @@ output_eleme = [ toughio.Output( "element", - None, float(time), np.array([f"AAA0{i}" for i in range(10)]), { @@ -91,7 +90,6 @@ output_conne = [ toughio.Output( "connection", - None, float(time), np.array([[f"AAA0{i}", f"AAA0{i}"] for i in range(10)]), { diff --git a/tests/test_output.py b/tests/test_output.py index 47ff18fa..29217853 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -17,16 +17,16 @@ @pytest.mark.parametrize( - "filename, filename_ref", + "filename, filename_ref, file_format", [ - ("OUTPUT_ELEME.csv", "SAVE.out"), - ("OUTPUT_ELEME.tec", "SAVE.out"), - ("OUTPUT_ELEME_PETRASIM.csv", "SAVE.out"), - ("OUTPUT.out", "SAVE.out"), - ("OUTPUT_6.out", "SAVE_6.out"), + ("OUTPUT_ELEME.csv", "SAVE.out", "csv"), + ("OUTPUT_ELEME.tec", "SAVE.out", "tecplot"), + ("OUTPUT_ELEME_PETRASIM.csv", "SAVE.out", "petrasim"), + ("OUTPUT.out", "SAVE.out", "tough"), + ("OUTPUT_6.out", "SAVE_6.out", "tough"), ], ) -def test_output_eleme(filename, filename_ref): +def test_output_eleme(filename, filename_ref, file_format): this_dir = os.path.dirname(os.path.abspath(__file__)) filename = os.path.join(this_dir, "support_files", "outputs", filename) outputs = toughio.read_output(filename) @@ -48,10 +48,10 @@ def test_output_eleme(filename, filename_ref): assert time_ref == output.time assert ( save.labels.tolist() == output.labels.tolist() - if output.format in {"csv", "petrasim", "tough"} + if file_format in {"csv", "petrasim", "tough"} else len(output.labels) == 0 ) - if output.format != "tough": + if file_format != "tough": assert keys_ref == sorted(list(output.data)) assert helpers.allclose(save.data["X1"], outputs[-1].data["PRES"]) diff --git a/toughio/_cli/_export.py b/toughio/_cli/_export.py index 97c32561..6c5f861c 100644 --- a/toughio/_cli/_export.py +++ b/toughio/_cli/_export.py @@ -54,25 +54,13 @@ def export(argv=None): output = output[args.time_step] else: output = output[-1] - input_format = output.format labels = output.labels else: - input_format = output[-1].format labels = output[-1].labels print(" Done!") - # Display warning if mesh is provided but input file format is 'tecplot' - # Continue as if mesh was not provided - if input_format == "tecplot": - with_mesh = False - msg = ( - "Cannot use mesh file with Tecplot TOUGH output, inferring dimensionality" - if args.mesh - else "Inferring dimensionality" - ) - else: - with_mesh = bool(args.mesh) - msg = "Mesh file not specified, inferring dimensionality" + with_mesh = bool(args.mesh) + msg = "Mesh file not specified, inferring dimensionality" # Triangulate or voxelize if no mesh voxelized = False diff --git a/toughio/_cli/_extract.py b/toughio/_cli/_extract.py index 88e8fa3b..038de834 100644 --- a/toughio/_cli/_extract.py +++ b/toughio/_cli/_extract.py @@ -41,8 +41,6 @@ def extract(argv=None): # Read TOUGH output file output = read_output(args.infile, connection=args.connection) - if output[-1].format != "tough": - raise ValueError(f"Invalid TOUGH output file '{args.infile}'.") try: if not args.connection: diff --git a/toughio/_io/output/_common.py b/toughio/_io/output/_common.py index 98317fd2..877d6324 100644 --- a/toughio/_io/output/_common.py +++ b/toughio/_io/output/_common.py @@ -7,17 +7,16 @@ ] -Output = collections.namedtuple("Output", ["type", "format", "time", "labels", "data"]) +Output = collections.namedtuple("Output", ["type", "time", "labels", "data"]) -def to_output(file_type, file_format, labels_order, headers, times, labels, variables): +def to_output(file_type, labels_order, headers, times, labels, variables): """Create an Output namedtuple.""" outputs = [ Output( file_type, - file_format, time, - np.array(label), + np.array(label) if len(label) else np.array(label, dtype=" Date: Wed, 26 Apr 2023 10:16:08 +0200 Subject: [PATCH 06/43] fix timbc --- toughio/_io/input/tough/_read.py | 15 +++++++++------ toughio/_io/input/tough/_write.py | 6 ++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 42bea3f5..c2dd9eef 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -890,16 +890,19 @@ def _read_timbc(f): bcelm = f.next().strip() # Record 4 - line = f.next().strip() - data = [float(x) for x in line.split()] - if len(data) < 2 * nbcp: - raise ReadError() + times = [] + values = [] + for _ in range(nbcp): + line = f.next().strip() + data = [float(x) for x in line.split()] + times.append(data[0]) + values.append(data[1]) tmp = { "label": bcelm, "variable": nbcpv, - "times": data[::2], - "values": data[1::2], + "times": times, + "values": values, } timbc["boundary_conditions"].append(tmp) diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index 1acf78c6..b7ba4b96 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -1243,10 +1243,8 @@ def _write_timbc(parameters): out += write_ffrecord([data["label"]], end="\n") # Record 4 - tmp = np.zeros(2 * nbcp) - tmp[::2] = times - tmp[1::2] = values[:nbcp] - out += write_ffrecord(tmp, end="\n") + for time, value in zip(times, values): + out += write_ffrecord([time, value], end="\n") return out From cce4c10de3f45fc7702eb8db20dc8d8fa8c3b028 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 1 May 2023 20:08:33 +0200 Subject: [PATCH 07/43] support GENER in toughio-merge --- README.rst | 2 +- toughio/_cli/_merge.py | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index bdcdd808..23324c33 100644 --- a/README.rst +++ b/README.rst @@ -139,7 +139,7 @@ TOUGH simulation output can also be imported into Python as a list of *namedtupl - ``toughio-co2tab``: copy file *CO2TAB* to the target directory, - ``toughio-export``: export TOUGH simulation results to a file for visualization (VTK, VTU, Tecplot or XDMF), - ``toughio-extract``: extract results from TOUGH main output file and reformat as a TOUGH3 element or connection output file (mostly useful for TOUGH2 output *before* calling ``toughio-export``), -- ``toughio-merge``: merge input file, MESH and/or INCON into a single file (for storage or sharing), +- ``toughio-merge``: merge input file, GENER and/or MESH and/or INCON into a single file, - ``toughio-save2incon``: convert a *SAVE* file to an *INCON* file (mostly useful to automatically restart a simulation and reset the counters). Contributing diff --git a/toughio/_cli/_merge.py b/toughio/_cli/_merge.py index 82def338..15e9aea3 100644 --- a/toughio/_cli/_merge.py +++ b/toughio/_cli/_merge.py @@ -11,15 +11,20 @@ def merge(argv=None): # Check that input, MESH and INCON files exist head = os.path.split(args.infile)[0] + gener_filename = head + ("/" if head else "") + "GENER" mesh_filename = head + ("/" if head else "") + "MESH" incon_filename = head + ("/" if head else "") + "INCON" if not os.path.isfile(args.infile): raise ValueError(f"File '{args.infile}' not found.") - if not os.path.isfile(mesh_filename): - raise ValueError("MESH file not found.") + + gener_exists = os.path.isfile(gener_filename) + mesh_exists = os.path.isfile(mesh_filename) incon_exists = os.path.isfile(incon_filename) + if not (gener_exists or mesh_exists or incon_exists): + raise ValueError("GENER, MESH or INCON file(s) not found. Nothing to merge.") + # Buffer input file with open(args.infile, "r") as f: input_file = list(f) @@ -30,17 +35,28 @@ def merge(argv=None): count += int(line.upper()[:5] in {"ROCKS", "PARAM", "ENDFI", "ENDCY"}) if count < 3: raise ValueError(f"Invalid input file '{args.infile}'.") + + # Buffer GENER + if gener_exists: + with open(gener_filename, "r") as f: + gener_file = list(f) + + if not gener_file[0].startswith("GENER"): + raise ValueError("Invalid GENER file.") # Buffer MESH - with open(mesh_filename, "r") as f: - mesh_file = list(f) - if not mesh_file[0].startswith("ELEME"): - raise ValueError("Invalid MESH file.") + if mesh_exists: + with open(mesh_filename, "r") as f: + mesh_file = list(f) + + if not mesh_file[0].startswith("ELEME"): + raise ValueError("Invalid MESH file.") # Buffer INCON if exist if incon_exists: with open(incon_filename, "r") as f: incon_file = list(f) + if not incon_file[0].startswith("INCON"): raise ValueError("Invalid INCON file.") @@ -51,9 +67,16 @@ def merge(argv=None): # Buffer output file output_file = input_file[:i] - output_file += mesh_file + + if gener_exists: + output_file += gener_file + + if mesh_exists: + output_file += mesh_file + if incon_exists: output_file += incon_file + output_file += input_file[i:] # Write output file @@ -68,8 +91,8 @@ def _get_parser(): # Initialize parser parser = argparse.ArgumentParser( description=( - "Merge input file, MESH and/or INCON into a single file. " - "The files must be in the same directory." + "Merge input file, GENER and/or MESH and/or INCON into a single file. " + "The files must be in the same directory as input file." ), formatter_class=argparse.RawTextHelpFormatter, ) From 536fc62599cfa2de3ea43529622631c16693fb36 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 3 May 2023 11:52:01 +0200 Subject: [PATCH 08/43] fix writer when center is None --- toughio/_io/input/tough/_write.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index b7ba4b96..e1aca81b 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -1352,6 +1352,7 @@ def _write_eleme(parameters): if isinstance(data["material"], int) else data["material"] ) + center = data["center"] if data["center"] is not None else [None, None, None] values = [ k, data["nseq"], @@ -1360,9 +1361,9 @@ def _write_eleme(parameters): data["volume"], data["heat_exchange_area"], data["permeability_modifier"], - data["center"][0], - data["center"][1], - data["center"][2], + center[0], + center[1], + center[2], ] out += write_record(values, fmt) From f76c33de2940238aa38cec1efd813b19f14879ed Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 3 May 2023 11:52:25 +0200 Subject: [PATCH 09/43] fix reader when reading GENER file with +++ --- toughio/_io/input/tough/_read.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index c2dd9eef..f16e4712 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -179,9 +179,12 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): parameters.update(_read_roft(fiter)) elif line.startswith("GENER"): - gener, label_length = _read_gener(fiter, label_length, simulator) + gener, flag, label_length = _read_gener(fiter, label_length, simulator) parameters.update(gener) + if flag: + break + elif line.startswith("TIMBC"): parameters.update(_read_timbc(fiter)) @@ -816,8 +819,9 @@ def read_table(f, n, fmt): label_length = get_label_length(line[:9]) label_format = f"{{:>{label_length}}}" + flag = False while True: - if line.strip(): + if line.strip() and not line.startswith("+++"): data = read_record(line, fmt[label_length]) tmp = { "label": label_format.format(data[0]), @@ -859,13 +863,14 @@ def read_table(f, n, fmt): gener["generators"].append(tmp) else: + flag = line.startswith("+++") break line = f.next() return { "generators": [prune_values(generator) for generator in gener["generators"]] - }, label_length + }, flag, label_length def _read_timbc(f): From 2679b6d7eb8ce2f51abb9ff7f1b9d82c4d996cc9 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 5 May 2023 11:56:43 +0200 Subject: [PATCH 10/43] add function run --- toughio/__init__.py | 2 + toughio/_run.py | 99 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 toughio/_run.py diff --git a/toughio/__init__.py b/toughio/__init__.py index 1fbef942..27906915 100644 --- a/toughio/__init__.py +++ b/toughio/__init__.py @@ -19,6 +19,7 @@ from ._mesh import register as register_mesh from ._mesh import write as write_mesh from ._mesh import write_time_series +from ._run import run __all__ = [ "Mesh", @@ -44,6 +45,7 @@ "relative_permeability", "capillarity", "convert_labels", + "run", "_cli", "__version__", ] diff --git a/toughio/_run.py b/toughio/_run.py new file mode 100644 index 00000000..0cff5e88 --- /dev/null +++ b/toughio/_run.py @@ -0,0 +1,99 @@ +import os +import pathlib +import shutil +import subprocess +import sys + + +def run( + exec, + input_filename, + other_filenames=None, + command=None, + workers=None, + wsl=False, + working_dir=None, + silent=False +): + """ + Run TOUGH executable. + + Parameters + ---------- + exec : str or pathlike + Path to TOUGH executable. + input_filename : str or pathlike + TOUGH input file name. + other_filenames : list of str or None, optional, default None + Other simulation files to copy to working directory (e.g., MESH, INCON, GENER) if not already present. + command : callable or None, optional, default None + Command to execute TOUGH. Must be in the form ``f(exec, inp, out)``, where ``exec`` is the path to TOUGH executable, ``inp`` is the input file name, and ``out`` is the output file name. + workers : int or None, optional, default None + Number of MPI workers to invoke. + wsl : bool, optional, default False + Only for Windows. If `True`, run the final command as a Bash command. + working_dir : str, pathlike or None, optional, default None + Working directory. Input and output files will be generated in this directory. + silent : bool, optional, default False + If `True`, nothing will be printed to standard output. + + Returns + ------- + :class:`subprocess.CompletedProcess` + Subprocess completion status. + + """ + other_filenames = other_filenames if other_filenames else [] + if command is None: + command = lambda exec, inp, out: f"{exec} {inp} {out}" + + # Executable + exec = str(exec) + exec = f"{os.path.expanduser('~')}/{exec[2:]}" if exec.startswith("~/") else exec + + # Working directory + working_dir = os.getcwd() if working_dir is None else working_dir + working_dir = pathlib.Path(working_dir) + working_dir.mkdir(parents=True, exist_ok=True) + + # Check if input file is in working directory, otherwise copy + input_path = pathlib.Path(input_filename) + input_filename = working_dir / input_path.name + + if input_path.parent != working_dir: + shutil.copy(input_path, input_filename) + + # Copy other simulation files to working directory + for filename in other_filenames: + filename = pathlib.Path(filename) + + if filename.parent != working_dir: + shutil.copy(filename, working_dir) + + # Output filename + output_filename = f"{input_filename.stem}.out" + + # TOUGH command + cmd = command(exec, str(input_filename.name), str(output_filename)) + + # Use MPI + if workers is not None and workers > 1: + cmd = f"mpiexec -n {workers} {cmd}" + + # Use WSL + if wsl and sys.platform.startswith("win"): + cmd = f'bash -c "{cmd}"' + + kwargs = {} + if silent: + kwargs["stdout"] = subprocess.DEVNULL + kwargs["stderr"] = subprocess.STDOUT + + status = subprocess.run( + cmd, + shell=True, + cwd=str(working_dir), + **kwargs + ) + + return status From a7483d395624c3a80539ca1f7907333afd2a218a Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 5 May 2023 22:43:09 +0200 Subject: [PATCH 11/43] fix typo --- toughio/_cli/_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/_cli/_export.py b/toughio/_cli/_export.py index 6c5f861c..e2e845d7 100644 --- a/toughio/_cli/_export.py +++ b/toughio/_cli/_export.py @@ -213,7 +213,7 @@ def _get_parser(): parser.add_argument( "infile", type=str, - help="TOUGH output file", + help="TOUGH input file", ) # Mesh file From 080a3cafb05da4bf35b5faedfbf84fa2d8dabf6c Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 5 May 2023 22:43:27 +0200 Subject: [PATCH 12/43] fix file exists error --- toughio/_run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 0cff5e88..e6b68328 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -60,14 +60,14 @@ def run( input_path = pathlib.Path(input_filename) input_filename = working_dir / input_path.name - if input_path.parent != working_dir: + if input_path.parent.absolute() != working_dir.absolute(): shutil.copy(input_path, input_filename) # Copy other simulation files to working directory for filename in other_filenames: filename = pathlib.Path(filename) - if filename.parent != working_dir: + if filename.parent.absolute() != working_dir.absolute(): shutil.copy(filename, working_dir) # Output filename From db9ef457ee2d077d566e2e391fe483d5355645b4 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 15 May 2023 12:44:20 +0200 Subject: [PATCH 13/43] support docker and clean up --- toughio/_run.py | 70 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index e6b68328..8f89b389 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -1,3 +1,4 @@ +import glob import os import pathlib import shutil @@ -11,6 +12,7 @@ def run( other_filenames=None, command=None, workers=None, + docker=None, wsl=False, working_dir=None, silent=False @@ -24,14 +26,16 @@ def run( Path to TOUGH executable. input_filename : str or pathlike TOUGH input file name. - other_filenames : list of str or None, optional, default None - Other simulation files to copy to working directory (e.g., MESH, INCON, GENER) if not already present. + other_filenames : list, dict or None, optional, default None + Other simulation files to copy to working directory (e.g., MESH, INCON, GENER) if not already present. If ``other_filenames`` is a dict, must be in the for ``{old: new}``, where ``old`` is the current name of the file to copy, and ``new`` is the name of the file copied. command : callable or None, optional, default None - Command to execute TOUGH. Must be in the form ``f(exec, inp, out)``, where ``exec`` is the path to TOUGH executable, ``inp`` is the input file name, and ``out`` is the output file name. + Command to execute TOUGH. Must be in the form ``f(exec, inp, [out])``, where ``exec`` is the path to TOUGH executable, ``inp`` is the input file name, and ``out`` is the output file name (optional). workers : int or None, optional, default None Number of MPI workers to invoke. + docker : str, optional, default None + Name of Docker image. wsl : bool, optional, default False - Only for Windows. If `True`, run the final command as a Bash command. + Only for Windows. If `True`, run the final command as a Bash command. Ignored if `docker` is not None. working_dir : str, pathlike or None, optional, default None Working directory. Input and output files will be generated in this directory. silent : bool, optional, default False @@ -43,7 +47,16 @@ def run( Subprocess completion status. """ - other_filenames = other_filenames if other_filenames else [] + from . import write_input + + other_filenames = ( + {k: k for k in other_filenames} + if isinstance(other_filenames, (list, tuple)) + else other_filenames + if other_filenames + else {} + ) + if command is None: command = lambda exec, inp, out: f"{exec} {inp} {out}" @@ -57,18 +70,24 @@ def run( working_dir.mkdir(parents=True, exist_ok=True) # Check if input file is in working directory, otherwise copy - input_path = pathlib.Path(input_filename) - input_filename = working_dir / input_path.name + if not isinstance(input_filename, dict): + input_path = pathlib.Path(input_filename) + input_filename = working_dir / input_path.name + + if input_path.parent.absolute() != working_dir.absolute(): + shutil.copy(input_path, input_filename) - if input_path.parent.absolute() != working_dir.absolute(): - shutil.copy(input_path, input_filename) + else: + write_input(working_dir / "INFILE", input_filename) + input_filename = working_dir / "INFILE" # Copy other simulation files to working directory - for filename in other_filenames: - filename = pathlib.Path(filename) + for k, v in other_filenames.items(): + filename = pathlib.Path(k) + new_filename = pathlib.Path(v) - if filename.parent.absolute() != working_dir.absolute(): - shutil.copy(filename, working_dir) + if filename.parent.absolute() != working_dir.absolute() and filename.name != new_filename.name: + shutil.copy(filename, working_dir / v) # Output filename output_filename = f"{input_filename.stem}.out" @@ -80,8 +99,19 @@ def run( if workers is not None and workers > 1: cmd = f"mpiexec -n {workers} {cmd}" - # Use WSL - if wsl and sys.platform.startswith("win"): + # Use Docker or WSL + platform = sys.platform + + if docker: + if platform.startswith("win") and os.getenv("ComSpec").endswith("cmd.exe"): + cwd = "%cd%" + + else: + cwd = "${PWD}" + + cmd = f"docker run -it --rm -v {cwd}:/work -w /work {docker} {cmd}" + + elif wsl and platform.startswith("win"): cmd = f'bash -c "{cmd}"' kwargs = {} @@ -96,4 +126,14 @@ def run( **kwargs ) + # Clean up working directory + filenames = glob.glob(f"{str(working_dir)}/*") + glob.glob(f"{str(working_dir)}/.*") + + for filename in filenames: + for pattern in [".OUTPUT", "TABLE", "MESHA", "MESHB"]: + if pathlib.Path(filename).name.startswith(pattern): + os.remove(filename) + + break + return status From dd1205aca651005fae239d4f5c4026e9e96b47dc Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 15 May 2023 12:44:38 +0200 Subject: [PATCH 14/43] merge GENER file --- toughio/_cli/_merge.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/toughio/_cli/_merge.py b/toughio/_cli/_merge.py index 15e9aea3..c187f21b 100644 --- a/toughio/_cli/_merge.py +++ b/toughio/_cli/_merge.py @@ -38,24 +38,21 @@ def merge(argv=None): # Buffer GENER if gener_exists: - with open(gener_filename, "r") as f: - gener_file = list(f) + gener_file = _read_file(gener_filename, end="+++") if not gener_file[0].startswith("GENER"): raise ValueError("Invalid GENER file.") # Buffer MESH if mesh_exists: - with open(mesh_filename, "r") as f: - mesh_file = list(f) + mesh_file = _read_file(mesh_filename) if not mesh_file[0].startswith("ELEME"): raise ValueError("Invalid MESH file.") # Buffer INCON if exist if incon_exists: - with open(incon_filename, "r") as f: - incon_file = list(f) + incon_file = _read_file(incon_filename, end="+++") if not incon_file[0].startswith("INCON"): raise ValueError("Invalid INCON file.") @@ -108,3 +105,21 @@ def _get_parser(): parser.add_argument("outfile", type=str, help="merged TOUGH input file") return parser + + +def _read_file(filename, end=None): + with open(filename, "r") as f: + if end is None: + file = list(f) + + else: + file = [] + + for line in f: + if line.startswith(end): + file.append("\n") + break + + file.append(line) + + return file From 00e23123b83948fcf311e91a3208623bdb5b45ef Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 16 May 2023 12:08:57 +0200 Subject: [PATCH 15/43] fix issue with other_filename --- toughio/_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/_run.py b/toughio/_run.py index 8f89b389..a9e0f226 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -86,7 +86,7 @@ def run( filename = pathlib.Path(k) new_filename = pathlib.Path(v) - if filename.parent.absolute() != working_dir.absolute() and filename.name != new_filename.name: + if filename.parent.absolute() != working_dir.absolute() or filename.name != new_filename.name: shutil.copy(filename, working_dir / v) # Output filename From 0552691c5a1c30181b2c35e3a764e1477dde9144 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 23 May 2023 16:27:30 +0200 Subject: [PATCH 16/43] add userx(6) --- toughio/_common.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/toughio/_common.py b/toughio/_common.py index e4263039..53686b84 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -71,11 +71,11 @@ "INCON": { 0: ",".join(4 * ["20.13e"]), "default": { - 5: "5s,5d,5d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e", - 6: "6s,5d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e", - 7: "7s,4d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e", - 8: "8s,4d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e", - 9: "9s,3d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e", + 5: "5s,5d,5d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", + 6: "6s,5d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", + 7: "7s,4d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", + 8: "8s,4d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", + 9: "9s,3d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", }, "tmvoc": { 5: "5s,5d,5d,15.9e,2d", From 59cd09955be83120437ad4e4a0c4d6d6ff328786 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 2 Jun 2023 20:16:29 +0200 Subject: [PATCH 17/43] add option use_temp --- toughio/_run.py | 51 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index a9e0f226..585257ee 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -4,6 +4,7 @@ import shutil import subprocess import sys +import tempfile def run( @@ -15,6 +16,7 @@ def run( docker=None, wsl=False, working_dir=None, + use_temp=False, silent=False ): """ @@ -38,6 +40,8 @@ def run( Only for Windows. If `True`, run the final command as a Bash command. Ignored if `docker` is not None. working_dir : str, pathlike or None, optional, default None Working directory. Input and output files will be generated in this directory. + use_temp : bool, optional, default False + If `True`, run simulation in a temporary directory, and copy simulation files to `working_dir` at the end of the simulation. silent : bool, optional, default False If `True`, nothing will be printed to standard output. @@ -69,25 +73,36 @@ def run( working_dir = pathlib.Path(working_dir) working_dir.mkdir(parents=True, exist_ok=True) - # Check if input file is in working directory, otherwise copy + # Simulation directory + if use_temp: + temp_dir = tempfile.mkdtemp() + simulation_dir = pathlib.Path(temp_dir) + + with open(working_dir / "tempdir.txt", "w") as f: + f.write(temp_dir) + + else: + simulation_dir = working_dir + + # Check if input file is in simulation directory, otherwise copy if not isinstance(input_filename, dict): input_path = pathlib.Path(input_filename) - input_filename = working_dir / input_path.name + input_filename = simulation_dir / input_path.name - if input_path.parent.absolute() != working_dir.absolute(): + if input_path.parent.absolute() != simulation_dir.absolute(): shutil.copy(input_path, input_filename) else: - write_input(working_dir / "INFILE", input_filename) - input_filename = working_dir / "INFILE" + write_input(simulation_dir / "INFILE", input_filename) + input_filename = simulation_dir / "INFILE" # Copy other simulation files to working directory for k, v in other_filenames.items(): filename = pathlib.Path(k) new_filename = pathlib.Path(v) - if filename.parent.absolute() != working_dir.absolute() or filename.name != new_filename.name: - shutil.copy(filename, working_dir / v) + if filename.parent.absolute() != simulation_dir.absolute() or filename.name != new_filename.name: + shutil.copy(filename, simulation_dir / v) # Output filename output_filename = f"{input_filename.stem}.out" @@ -122,15 +137,33 @@ def run( status = subprocess.run( cmd, shell=True, - cwd=str(working_dir), + cwd=str(simulation_dir), **kwargs ) + # List of files to delete + patterns = [".OUTPUT", "TABLE", "MESHA", "MESHB"] + + # Copy files from temporary directory and delete it + if use_temp: + for filename in glob.glob(f"{str(simulation_dir)}/*"): + flag = True + + for pattern in patterns: + if pathlib.Path(filename).name.startswith(pattern): + flag = False + break + + if flag: + shutil.copy(filename, working_dir) + + shutil.rmtree(simulation_dir, ignore_errors=True) + # Clean up working directory filenames = glob.glob(f"{str(working_dir)}/*") + glob.glob(f"{str(working_dir)}/.*") for filename in filenames: - for pattern in [".OUTPUT", "TABLE", "MESHA", "MESHB"]: + for pattern in patterns + ["tempdir.txt"]: if pathlib.Path(filename).name.startswith(pattern): os.remove(filename) From 079d00dc39880b8b2650d67e5351bee35cb1e981 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 5 Jun 2023 10:35:42 +0200 Subject: [PATCH 18/43] add option ignore_patterns --- toughio/_run.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 585257ee..368ec364 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -17,6 +17,7 @@ def run( wsl=False, working_dir=None, use_temp=False, + ignore_patterns=None, silent=False ): """ @@ -41,7 +42,9 @@ def run( working_dir : str, pathlike or None, optional, default None Working directory. Input and output files will be generated in this directory. use_temp : bool, optional, default False - If `True`, run simulation in a temporary directory, and copy simulation files to `working_dir` at the end of the simulation. + If `True`, run simulation in a temporary directory, and copy simulation files to `working_dir` at the end of the simulation. This option may be required when running TOUGH through a Docker. + ignore_patterns : list or None, optional, default None + 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. @@ -64,6 +67,9 @@ def run( if command is None: command = lambda exec, inp, out: f"{exec} {inp} {out}" + ignore_patterns = list(ignore_patterns) if ignore_patterns else [] + ignore_patterns += [".OUTPUT*", "TABLE", "MESHA", "MESHB"] + # Executable exec = str(exec) exec = f"{os.path.expanduser('~')}/{exec[2:]}" if exec.startswith("~/") else exec @@ -141,32 +147,20 @@ def run( **kwargs ) - # List of files to delete - patterns = [".OUTPUT", "TABLE", "MESHA", "MESHB"] - # Copy files from temporary directory and delete it if use_temp: - for filename in glob.glob(f"{str(simulation_dir)}/*"): - flag = True - - for pattern in patterns: - if pathlib.Path(filename).name.startswith(pattern): - flag = False - break - - if flag: - shutil.copy(filename, working_dir) - + shutil.copytree(simulation_dir, working_dir, ignore=shutil.ignore_patterns(*ignore_patterns), dirs_exist_ok=True) shutil.rmtree(simulation_dir, ignore_errors=True) + os.remove(working_dir / "tempdir.txt") # Clean up working directory - filenames = glob.glob(f"{str(working_dir)}/*") + glob.glob(f"{str(working_dir)}/.*") - - for filename in filenames: - for pattern in patterns + ["tempdir.txt"]: - if pathlib.Path(filename).name.startswith(pattern): - os.remove(filename) - - break + patterns = [ + pathlib.Path(filename) + for pattern in ignore_patterns + for filename in glob.glob(f"{str(simulation_dir)}/{pattern}") + ] + + for pattern in patterns: + os.remove(pattern) return status From 7d305c4914b302a0acfec381f896455e958a5e31 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 14 Jun 2023 15:03:34 +0200 Subject: [PATCH 19/43] add task tar --- tasks.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tasks.py b/tasks.py index 07fc5dfc..07a6c1d3 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,7 @@ import glob import os import shutil +import tarfile from invoke import task @@ -70,3 +71,22 @@ def isort(c): @task def format(c): c.run("invoke isort black docstring") + + +@task +def tar(c): + patterns = [ + "__pycache__", + ] + + def filter(filename): + for pattern in patterns: + if filename.name.endswith(pattern): + return None + + return filename + + with tarfile.open("toughio.tar.gz", "w:gz") as tf: + tf.add("toughio", arcname="toughio/toughio", filter=filter) + tf.add("pyproject.toml", arcname="toughio/pyproject.toml") + tf.add("setup.cfg", arcname="toughio/setup.cfg") From 08b37906c19bb0123fdfd39c4dd1df1b4ea02796 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 21 Jun 2023 11:35:34 +0200 Subject: [PATCH 20/43] fix for gzip --- toughio/_common.py | 10 ++++++++-- toughio/_io/input/_helpers.py | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/toughio/_common.py b/toughio/_common.py index 53686b84..85c86ff8 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -168,9 +168,15 @@ def register_format( def filetype_from_filename(filename, ext_to_fmt, default=""): """Determine file type from its extension.""" - ext = os.path.splitext(filename)[1].lower() + from io import TextIOWrapper - return ext_to_fmt[ext] if ext in ext_to_fmt else default + if not isinstance(filename, TextIOWrapper): + ext = os.path.splitext(filename)[1].lower() + + return ext_to_fmt[ext] if ext in ext_to_fmt else default + + else: + return default @contextmanager diff --git a/toughio/_io/input/_helpers.py b/toughio/_io/input/_helpers.py index 23a55a4c..a755e9a0 100644 --- a/toughio/_io/input/_helpers.py +++ b/toughio/_io/input/_helpers.py @@ -152,7 +152,12 @@ def _get_file_format(filename, file_format, default): def _file_format_from_filename(filename): """Determine file format from its name.""" import pathlib + from io import TextIOWrapper - filename = pathlib.Path(filename).name + if not isinstance(filename, TextIOWrapper): + filename = pathlib.Path(filename).name - return _file_formats[filename] if filename in _file_formats else "" + return _file_formats[filename] if filename in _file_formats else "" + + else: + return "" From 330f868ce5499d7619fd952077d68136df60cb35 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 21 Jun 2023 12:25:48 +0200 Subject: [PATCH 21/43] fix isot --- toughio/_mesh/tough/_tough.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/_mesh/tough/_tough.py b/toughio/_mesh/tough/_tough.py index eb313a5b..ff9a2069 100644 --- a/toughio/_mesh/tough/_tough.py +++ b/toughio/_mesh/tough/_tough.py @@ -372,7 +372,7 @@ def _write_conne( # Calculate remaining variables lines = np.diff(centers, axis=1)[:, 0] - isot = _isot(lines) + isot = _isot(np.around(lines, decimals=4)) # Reduce sensitivity to floating point accuracy angles = np.dot(lines, gravity) / np.linalg.norm(lines, axis=1) if nodal_distance == "line": From 071d612ee9a67c3aaaafe4286c876d653f6cc508 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 21 Jun 2023 13:51:50 +0200 Subject: [PATCH 22/43] fix output reader --- toughio/_io/output/tough/_tough.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/toughio/_io/output/tough/_tough.py b/toughio/_io/output/tough/_tough.py index 55040c30..1a27f966 100644 --- a/toughio/_io/output/tough/_tough.py +++ b/toughio/_io/output/tough/_tough.py @@ -111,6 +111,10 @@ def _read_table(f, file_type): # Read headers headers = line.split() + # Read units + line = next(f) + nwsp = line.index(line.strip()[0]) # Index of first non whitespace character + # Look for next non-empty line while True: line = next(f) @@ -119,7 +123,7 @@ def _read_table(f, file_type): # Loop until end of output block while True: - if line[:15].strip() and not line.strip().startswith("ELEM"): + if line[:nwsp].strip() and not line.strip().startswith("ELEM"): if first: # Find first floating point for xf in line.split()[::-1]: From a8097d9ccbfb157b839f99bd5488c72bcecbc63d Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 22 Jun 2023 09:38:06 +0200 Subject: [PATCH 23/43] support minc in meshm block --- tests/test_input.py | 19 ++++++++++++ toughio/_common.py | 5 ++++ toughio/_io/input/tough/_common.py | 10 +++++++ toughio/_io/input/tough/_helpers.py | 10 +++++++ toughio/_io/input/tough/_read.py | 35 ++++++++++++++++++++-- toughio/_io/input/tough/_write.py | 46 +++++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 3 deletions(-) diff --git a/tests/test_input.py b/tests/test_input.py index e9ed2c84..0e2d0a34 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -800,6 +800,25 @@ def test_meshm_rz2d(layer): assert helpers.allclose(parameters_ref, parameters, atol=1.0e-4) +def test_minc(): + n_volume = np.random.randint(100) + 1 + + parameters_ref = { + "minc": { + "type": helpers.random_string(5), + "dual": helpers.random_string(5), + "n_minc": np.random.randint(100) + 1, + "n_volume": n_volume, + "where": helpers.random_string(4), + "parameters": np.random.rand(7), + "volumes": np.random.rand(n_volume), + } + } + parameters = write_read(parameters_ref) + + assert helpers.allclose(parameters_ref, parameters, atol=1.0e-4) + + def test_tmvoc(): parameters_ref = { "eos": "tmvoc", diff --git a/toughio/_common.py b/toughio/_common.py index 85c86ff8..0669e2b4 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -112,6 +112,11 @@ 2: ",".join(8 * ["10f"]), }, }, + "MINC": { + 1: "5s,5s,5s,5s", + 2: "3d,3d,4s,10f,10f,10f,10f,10f,10f,10f", + 3: ",".join(8 * ["10f"]), + }, }, "REACT": "25S", } diff --git a/toughio/_io/input/tough/_common.py b/toughio/_io/input/tough/_common.py index 33a577bc..6cfcd010 100644 --- a/toughio/_io/input/tough/_common.py +++ b/toughio/_io/input/tough/_common.py @@ -240,6 +240,16 @@ "thicknesses": [], } +minc = { + "type": None, + "dual": None, + "n_minc": None, + "n_volume": None, + "where": None, + "parameters": [], + "volumes": [], +} + default = { "density": None, "porosity": None, diff --git a/toughio/_io/input/tough/_helpers.py b/toughio/_io/input/tough/_helpers.py index 10fe1f91..7f1f0a8e 100644 --- a/toughio/_io/input/tough/_helpers.py +++ b/toughio/_io/input/tough/_helpers.py @@ -43,6 +43,7 @@ "connections": "dict", "initial_conditions": "dict", "meshmaker": "dict", + "minc": "dict", "poiseuille": "dict", "default": "dict", "array_dimensions": "dict", @@ -209,6 +210,15 @@ "permeability": "scalar_array_like", }, "MESHM": {"type": "str", "parameters": "array_like", "angle": "scalar"}, + "MINC": { + "type": "str", + "dual": "str", + "n_minc": "int", + "n_volume": "int", + "where": "str", + "parameters": "array_like", + "volumes": "array_like", + }, "POISE": { "start": "array_like", "end": "array_like", diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index f16e4712..3c0081e2 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -1137,7 +1137,6 @@ def _read_incon(f, label_length, n_variables, eos=None, simulator="tough"): def _read_meshm(f): """Read MESHM block data.""" - meshm = {"meshmaker": {}} fmt = block_to_format["MESHM"] # Mesh type @@ -1145,9 +1144,14 @@ def _read_meshm(f): data = read_record(line, fmt[1]) mesh_type = data[0].upper() + if mesh_type in {"XYZ", "RZ2D", "RZ2DL"}: + meshm = {"meshmaker": {"type": mesh_type.lower()}} + + else: + meshm = {"minc": {}} + # XYZ if mesh_type == "XYZ": - meshm["meshmaker"]["type"] = mesh_type.lower() fmt = fmt["XYZ"] # Record 1 @@ -1187,7 +1191,6 @@ def _read_meshm(f): # RZ2D elif mesh_type in {"RZ2D", "RZ2DL"}: - meshm["meshmaker"]["type"] = mesh_type.lower() fmt = fmt["RZ2D"] # Record 1 @@ -1259,6 +1262,32 @@ def _read_meshm(f): else: break + # MINC + elif mesh_type == "MINC": + fmt = fmt["MINC"] + + # Record 1 + line = f.next() + data = read_record(line, fmt[1]) + meshm["minc"]["type"] = data[1].lower() + meshm["minc"]["dual"] = data[3].lower() + + # Record 2 + line = f.next() + data = read_record(line, fmt[2]) + meshm["minc"]["n_minc"] = data[0] + meshm["minc"]["n_volume"] = data[1] + meshm["minc"]["where"] = data[2].lower() + meshm["minc"]["parameters"] = prune_values(data[3:]) + + # Record 3 + meshm["minc"]["volumes"] = [] + + while len(meshm["minc"]["volumes"]) < meshm["minc"]["n_volume"]: + line = f.next() + data = read_record(line, fmt[3]) + meshm["minc"]["volumes"] += prune_values(data) + return meshm diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index e1aca81b..69ca00e3 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -369,6 +369,9 @@ def write_buffer( if "MESHM" in blocks and parameters["meshmaker"]: out += _write_meshm(parameters) + if "MESHM" in blocks and parameters["minc"]: + out += _write_minc(parameters) + if "POISE" in blocks and poise and simulator == "toughreact": out += _write_poise(parameters) out += ["\n"] if space_between_blocks else [] @@ -1575,6 +1578,49 @@ def _write_meshm(parameters): return out +@check_parameters(dtypes["MINC"], keys="minc") +@block("MESHM", multi=True) +def _write_minc(parameters): + """Write MESHM block data (MINC).""" + from ._common import minc + + data = deepcopy(minc) + data.update(parameters["minc"]) + + # Format + fmt = block_to_format["MESHM"] + fmt0 = str2format(fmt[1]) + fmt1 = str2format(fmt["MINC"][1]) + fmt2 = str2format(fmt["MINC"][2]) + fmt3 = str2format(fmt["MINC"][3]) + + # Mesh type + out = write_record(["MINC"], fmt0) + + # Record 1 + values = [ + "PART", + data["type"].upper(), + None, + data["dual"].upper(), + ] + out += write_record(values, fmt1) + + # Record 2 + values = [ + data["n_minc"], + data["n_volume"], + f"{data['where'].upper():<4}", + *data["parameters"], + ] + out += write_record(values, fmt2) + + # Record 3 + out += write_record(data["volumes"], fmt3, multi=True) + + return out + + @check_parameters(dtypes["POISE"], keys=("react", "poiseuille")) @block("POISE") def _write_poise(parameters): From 7aa533ab97e82df902466d9c98619d0428bd1232 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 22 Jun 2023 10:32:24 +0200 Subject: [PATCH 24/43] remove key n_volume --- tests/test_input.py | 1 - toughio/_io/input/tough/_common.py | 2 +- toughio/_io/input/tough/_helpers.py | 1 - toughio/_io/input/tough/_read.py | 4 ++-- toughio/_io/input/tough/_write.py | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_input.py b/tests/test_input.py index 0e2d0a34..0db696e2 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -808,7 +808,6 @@ def test_minc(): "type": helpers.random_string(5), "dual": helpers.random_string(5), "n_minc": np.random.randint(100) + 1, - "n_volume": n_volume, "where": helpers.random_string(4), "parameters": np.random.rand(7), "volumes": np.random.rand(n_volume), diff --git a/toughio/_io/input/tough/_common.py b/toughio/_io/input/tough/_common.py index 6cfcd010..450f0600 100644 --- a/toughio/_io/input/tough/_common.py +++ b/toughio/_io/input/tough/_common.py @@ -71,6 +71,7 @@ "initial_conditions": {}, "boundary_conditions": {}, "meshmaker": {}, + "minc": {}, "default": {}, "array_dimensions": {}, "end_comments": "", @@ -244,7 +245,6 @@ "type": None, "dual": None, "n_minc": None, - "n_volume": None, "where": None, "parameters": [], "volumes": [], diff --git a/toughio/_io/input/tough/_helpers.py b/toughio/_io/input/tough/_helpers.py index 7f1f0a8e..98db4eeb 100644 --- a/toughio/_io/input/tough/_helpers.py +++ b/toughio/_io/input/tough/_helpers.py @@ -214,7 +214,6 @@ "type": "str", "dual": "str", "n_minc": "int", - "n_volume": "int", "where": "str", "parameters": "array_like", "volumes": "array_like", diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 3c0081e2..5b105622 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -1276,14 +1276,14 @@ def _read_meshm(f): line = f.next() data = read_record(line, fmt[2]) meshm["minc"]["n_minc"] = data[0] - meshm["minc"]["n_volume"] = data[1] + n_volume = data[1] meshm["minc"]["where"] = data[2].lower() meshm["minc"]["parameters"] = prune_values(data[3:]) # Record 3 meshm["minc"]["volumes"] = [] - while len(meshm["minc"]["volumes"]) < meshm["minc"]["n_volume"]: + while len(meshm["minc"]["volumes"]) < n_volume: line = f.next() data = read_record(line, fmt[3]) meshm["minc"]["volumes"] += prune_values(data) diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index 69ca00e3..6a5eba90 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -1609,7 +1609,7 @@ def _write_minc(parameters): # Record 2 values = [ data["n_minc"], - data["n_volume"], + len(data["volumes"]), f"{data['where'].upper():<4}", *data["parameters"], ] From 2dedfae4255e27afa91337258371a05c83fa0a28 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 23 Jun 2023 11:46:46 +0200 Subject: [PATCH 25/43] fix meshmaker block --- tests/test_input.py | 15 ++- toughio/_io/input/tough/_read.py | 66 +++++++---- toughio/_io/input/tough/_write.py | 187 +++++++++++++++--------------- 3 files changed, 151 insertions(+), 117 deletions(-) diff --git a/tests/test_input.py b/tests/test_input.py index 0db696e2..e82ab600 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -767,8 +767,8 @@ def test_meshm_xyz(): assert helpers.allclose(parameters_ref, parameters, atol=1.0e-4) -@pytest.mark.parametrize("layer", [True, False]) -def test_meshm_rz2d(layer): +@pytest.mark.parametrize("layer, minc", [(True, False), (False, False), (True, True), (False, True)]) +def test_meshm_rz2d(layer, minc): parameters_ref = { "meshmaker": { "type": "rz2dl" if layer else "rz2d", @@ -795,6 +795,17 @@ def test_meshm_rz2d(layer): ], } } + + if minc: + parameters_ref["minc"] = { + "type": helpers.random_string(5), + "dual": helpers.random_string(5), + "n_minc": np.random.randint(100) + 1, + "where": helpers.random_string(4), + "parameters": np.random.rand(7), + "volumes": np.random.rand(np.random.randint(100) + 1), + } + parameters = write_read(parameters_ref) assert helpers.allclose(parameters_ref, parameters, atol=1.0e-4) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 5b105622..c3997e5f 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -1137,7 +1137,37 @@ def _read_incon(f, label_length, n_variables, eos=None, simulator="tough"): def _read_meshm(f): """Read MESHM block data.""" + + def read_minc(f, data, fmt): + """Read MINC data.""" + minc = {} + + # Record 1 + line = f.next() + data = read_record(line, fmt[1]) + minc["type"] = data[1].lower() + minc["dual"] = data[3].lower() + + # Record 2 + line = f.next() + data = read_record(line, fmt[2]) + minc["n_minc"] = data[0] + n_volume = data[1] + minc["where"] = data[2].lower() + minc["parameters"] = prune_values(data[3:]) + + # Record 3 + minc["volumes"] = [] + + while len(minc["volumes"]) < n_volume: + line = f.next() + data = read_record(line, fmt[3]) + minc["volumes"] += prune_values(data) + + return minc + fmt = block_to_format["MESHM"] + fmt_minc = fmt["MINC"] # Mesh type line = f.next().strip() @@ -1259,35 +1289,23 @@ def _read_meshm(f): tmp = {"type": data_type.lower(), "thicknesses": thicknesses[:n]} meshm["meshmaker"]["parameters"].append(tmp) + # LAYER closes the block RZ2D + # RZ2D can be followed by MINC + line = f.next() + + if line.startswith("MINC"): + meshm["minc"] = read_minc(f, data, fmt_minc) + + else: + break + else: break # MINC elif mesh_type == "MINC": - fmt = fmt["MINC"] - - # Record 1 - line = f.next() - data = read_record(line, fmt[1]) - meshm["minc"]["type"] = data[1].lower() - meshm["minc"]["dual"] = data[3].lower() - - # Record 2 - line = f.next() - data = read_record(line, fmt[2]) - meshm["minc"]["n_minc"] = data[0] - n_volume = data[1] - meshm["minc"]["where"] = data[2].lower() - meshm["minc"]["parameters"] = prune_values(data[3:]) - - # Record 3 - meshm["minc"]["volumes"] = [] - - while len(meshm["minc"]["volumes"]) < n_volume: - line = f.next() - data = read_record(line, fmt[3]) - meshm["minc"]["volumes"] += prune_values(data) - + meshm["minc"] = read_minc(f, data, fmt_minc) + return meshm diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index 6a5eba90..a9cac28b 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -366,12 +366,9 @@ def write_buffer( if "INCON" in blocks and parameters["initial_conditions"]: out += _write_incon(parameters, eos_, simulator) - if "MESHM" in blocks and parameters["meshmaker"]: + if "MESHM" in blocks and (parameters["meshmaker"] or parameters["minc"]): out += _write_meshm(parameters) - if "MESHM" in blocks and parameters["minc"]: - out += _write_minc(parameters) - if "POISE" in blocks and poise and simulator == "toughreact": out += _write_poise(parameters) out += ["\n"] if space_between_blocks else [] @@ -1480,106 +1477,114 @@ def _write_meshm(parameters): """Write MESHM block data.""" from ._common import meshmaker, rz2d, xyz - data = deepcopy(meshmaker) - data.update(parameters["meshmaker"]) - - # Format - fmt = block_to_format["MESHM"] - fmt1 = str2format(fmt[1]) + out = [] - # Mesh type - mesh_type = data["type"].upper() if data["type"] else data["type"] - out = write_record([mesh_type], fmt1) + if parameters["meshmaker"]: + data = deepcopy(meshmaker) + data.update(parameters["meshmaker"]) - # XYZ - if mesh_type == "XYZ": - fmt = fmt["XYZ"] + # Format + fmt = block_to_format["MESHM"] fmt1 = str2format(fmt[1]) - fmt2 = str2format(fmt[2]) - fmt3 = str2format(fmt[3]) - - # Record 1 - out += write_record([data["angle"]], fmt1) - # Record 2 - for parameter_ in data["parameters"]: - parameter = deepcopy(xyz) - parameter.update(parameter_) - - values = [parameter["type"].upper()] - ndim = np.ndim(parameter["sizes"]) - - if ndim == 0: - values += [ - parameter["n_increment"], - parameter["sizes"], - ] - out += write_record(values, fmt2) - - elif ndim == 1: - values += [ - parameter["n_increment"] - if parameter["n_increment"] - else len(parameter["sizes"]) - ] - out += write_record(values, fmt2) - out += write_record(parameter["sizes"], fmt3, multi=True) - - else: - raise ValueError() + # Mesh type + mesh_type = data["type"].upper() if data["type"] else data["type"] + out += write_record([mesh_type], fmt1) - # RZ2D - elif mesh_type in {"RZ2D", "RZ2DL"}: - fmt = fmt["RZ2D"] - fmt0 = str2format(fmt[1]) + # XYZ + if mesh_type == "XYZ": + fmt = fmt["XYZ"] + fmt1 = str2format(fmt[1]) + fmt2 = str2format(fmt[2]) + fmt3 = str2format(fmt[3]) - for parameter_ in data["parameters"]: - parameter = deepcopy(rz2d) - parameter.update(parameter_) - - parameter_type = parameter["type"].upper() - out += write_record([parameter_type], fmt0) - - if parameter_type == "RADII": - fmt1 = str2format(fmt["RADII"][1]) - fmt2 = str2format(fmt["RADII"][2]) - - out += write_record([len(parameter["radii"])], fmt1) - out += write_record(parameter["radii"], fmt2, multi=True) - - elif parameter_type == "EQUID": - fmt1 = str2format(fmt["EQUID"]) - - values = [ - parameter["n_increment"], - None, - parameter["size"], - ] - out += write_record(values, fmt1) - - elif parameter_type == "LOGAR": - fmt1 = str2format(fmt["LOGAR"]) - - values = [ - parameter["n_increment"], - None, - parameter["radius"], - parameter["radius_ref"], - ] - out += write_record(values, fmt1) + # Record 1 + out += write_record([data["angle"]], fmt1) - elif parameter_type == "LAYER": - fmt1 = str2format(fmt["LAYER"][1]) - fmt2 = str2format(fmt["LAYER"][2]) + # Record 2 + for parameter_ in data["parameters"]: + parameter = deepcopy(xyz) + parameter.update(parameter_) + + values = [parameter["type"].upper()] + ndim = np.ndim(parameter["sizes"]) + + if ndim == 0: + values += [ + parameter["n_increment"], + parameter["sizes"], + ] + out += write_record(values, fmt2) + + elif ndim == 1: + values += [ + parameter["n_increment"] + if parameter["n_increment"] + else len(parameter["sizes"]) + ] + out += write_record(values, fmt2) + out += write_record(parameter["sizes"], fmt3, multi=True) - out += write_record([len(parameter["thicknesses"])], fmt1) - out += write_record(parameter["thicknesses"], fmt2, multi=True) + else: + raise ValueError() + + # Blank record + out += ["\n"] + + # RZ2D + elif mesh_type in {"RZ2D", "RZ2DL"}: + fmt = fmt["RZ2D"] + fmt0 = str2format(fmt[1]) + + for parameter_ in data["parameters"]: + parameter = deepcopy(rz2d) + parameter.update(parameter_) + + parameter_type = parameter["type"].upper() + out += write_record([parameter_type], fmt0) + + if parameter_type == "RADII": + fmt1 = str2format(fmt["RADII"][1]) + fmt2 = str2format(fmt["RADII"][2]) + + out += write_record([len(parameter["radii"])], fmt1) + out += write_record(parameter["radii"], fmt2, multi=True) + + elif parameter_type == "EQUID": + fmt1 = str2format(fmt["EQUID"]) + + values = [ + parameter["n_increment"], + None, + parameter["size"], + ] + out += write_record(values, fmt1) + + elif parameter_type == "LOGAR": + fmt1 = str2format(fmt["LOGAR"]) + + values = [ + parameter["n_increment"], + None, + parameter["radius"], + parameter["radius_ref"], + ] + out += write_record(values, fmt1) + + elif parameter_type == "LAYER": + fmt1 = str2format(fmt["LAYER"][1]) + fmt2 = str2format(fmt["LAYER"][2]) + + out += write_record([len(parameter["thicknesses"])], fmt1) + out += write_record(parameter["thicknesses"], fmt2, multi=True) + + if parameters["minc"]: + out += _write_minc(parameters) return out @check_parameters(dtypes["MINC"], keys="minc") -@block("MESHM", multi=True) def _write_minc(parameters): """Write MESHM block data (MINC).""" from ._common import minc From a62eec836face00536203811159c1ac3232646eb Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 23 Jun 2023 11:51:34 +0200 Subject: [PATCH 26/43] fix docstring --- toughio/_io/output/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/_io/output/_helpers.py b/toughio/_io/output/_helpers.py index fd9b3e2b..61c5f31c 100644 --- a/toughio/_io/output/_helpers.py +++ b/toughio/_io/output/_helpers.py @@ -106,7 +106,7 @@ def write(filename, output, file_format=None, **kwargs): Output file name or buffer. output : namedtuple or list of namedtuple namedtuple (type, format, time, labels, data) or list of namedtuple for each time step to export. - file_format : str ('csv', 'petrasim', 'save', 'tecplot', 'tough') or None, optional, default None + file_format : str ('csv', 'petrasim', 'tecplot') or None, optional, default None Output file format. Other Parameters From d80dd38876843f8e694fe2a488911757355f26c5 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 23 Jun 2023 12:00:45 +0200 Subject: [PATCH 27/43] update doc --- doc/source/guide/input.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/source/guide/input.rst b/doc/source/guide/input.rst index 6744dffa..e85f0ab3 100644 --- a/doc/source/guide/input.rst +++ b/doc/source/guide/input.rst @@ -78,6 +78,7 @@ A TOUGH input file is defined as follows: "connections": dict, "initial_conditions": dict, "meshmaker": dict, + "minc": dict, "chemical_properties": dict, "non_condensible_gas": list[str], "start": bool, @@ -365,6 +366,8 @@ The keyword ``"type"`` denotes the type of mesh to generate. The following value - ``"rz2d"`` - ``"rz2dl"`` +Note that the keyword ``"angle"`` is only used when ``"type"`` is ``"xyz"``. + If ``"type"`` is set to ``"xyz"``, each dictionary in ``"parameters"`` is defined as follows: .. code-block:: @@ -397,6 +400,23 @@ The keyword ``"type"`` denotes here the type of increments to generate. The foll - ``"layer"``: keyword ``"thicknesses"`` is required +MINC +**** + +MINC parameters are defined in a separate dictionary from ``"meshmaker"`` by the keyword ``"minc"``, and is organized as follows: + +.. code-block:: + + { + "type": str, + "dual": str, + "n_minc": int, + "where": str, + "parameters": list[float], + "volumes": list[float], + } + + TMVOC ***** From 2ce326d5c162e8f366125ff2f9b19e3df956f14b Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 28 Jun 2023 15:12:19 +0200 Subject: [PATCH 28/43] read and write records after +++ as ending comments --- toughio/_io/input/tough/_read.py | 11 +++++++++++ toughio/_io/input/tough/_write.py | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index c3997e5f..3094d408 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -56,6 +56,7 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): from ._common import blocks parameters = {} + flag = False # Title title = [] @@ -245,11 +246,21 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): elif line.startswith("ENDCY"): end_comments = read_end_comments(fiter) + if end_comments: parameters["end_comments"] = end_comments except: raise ReadError(f"failed to parse line {fiter.count}.") + + if flag: + end_comments = read_end_comments(fiter) + + if end_comments: + if isinstance(end_comments, str): + end_comments = [end_comments] + + parameters["end_comments"] = ["+++", *end_comments] return parameters diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index a9cac28b..607f9528 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -80,13 +80,13 @@ def write_buffer( blocks = blocks_.copy() elif block == "gener": - blocks = {"GENER"} + blocks = {"GENER", "END COMMENTS"} elif block == "mesh": - blocks = {"ELEME", "COORD", "CONNE"} + blocks = {"ELEME", "COORD", "CONNE", "END COMMENTS"} elif block == "incon": - blocks = {"INCON"} + blocks = {"INCON", "END COMMENTS"} else: raise ValueError() @@ -381,7 +381,13 @@ def write_buffer( out += _write_endcy() if "END COMMENTS" in blocks and parameters["end_comments"]: - out += ["\n"] if space_between_blocks else [] + if block in {"gener", "mesh", "incon"}: + while out[-1] == "\n": + out = out[:-1] + + else: + out += ["\n"] if space_between_blocks else [] + out += [f"{comment}\n" for comment in parameters["end_comments"]] return out From 8250e1d1e4ecfba5e57de21b214ca543dddab401 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 29 Jun 2023 17:46:25 +0200 Subject: [PATCH 29/43] use write_record --- toughio/_mesh/tough/_helpers.py | 85 +++++++++++---------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/toughio/_mesh/tough/_helpers.py b/toughio/_mesh/tough/_helpers.py index 8b155585..3b5e844d 100644 --- a/toughio/_mesh/tough/_helpers.py +++ b/toughio/_mesh/tough/_helpers.py @@ -50,16 +50,19 @@ def _write_eleme(labels, materials, volumes, nodes, material_name=None): ], fmt=fmt, ) + yield record[0] def _write_coord(nodes): """Return a generator that iterates over the records of block COORD.""" - fmt = "{:20.13e}{:20.13e}{:20.13e}\n" + fmt = block_to_format["COORD"] + fmt = str2format(fmt) for node in nodes: - record = fmt.format(*node) - yield record + record = write_record(node, fmt) + + yield record[0] def _write_conne(clabels, isot, d1, d2, areas, angles): @@ -85,6 +88,7 @@ def _write_conne(clabels, isot, d1, d2, areas, angles): ], fmt=fmt, ) + yield record[0] @@ -99,74 +103,41 @@ def _write_incon( ) label_length = len(labels[0]) fmt = block_to_format["INCON"] + fmt1 = str2format( + fmt[eos][label_length] + if eos in fmt + else fmt["default"][label_length] + ) + fmt2 = str2format(fmt[0]) iterables = zip(labels, values, porosity, userx, phase_composition) for label, value, phi, usrx, indicat0 in iterables: - cond1 = any(v > -1.0e-9 for v in value) + value = [v if v > -1.0e9 else None for v in value] + cond1 = any(v is not None for v in value) cond2 = phi is not None cond3 = usrx is not None + if cond1 or cond2 or cond3: # Record 1 - values = [label, "", ""] - ignore_types = [1, 2] - - if phi is not None: - values.append(phi) - else: - values.append("") - ignore_types.append(3) + values = [ + label, + None, + None, + phi, + ] if eos == "tmvoc": - if indicat0 is not None: - values.append(indicat0) - else: - values.append("") - ignore_types.append(4) + values += [indicat0] else: - if usrx is not None: - values += list(usrx) - else: - values += 3 * [""] - ignore_types += [4, 5, 6] - - fmt1 = str2format( - fmt[eos][label_length] if eos in fmt else fmt["default"][label_length], - ignore_types=ignore_types, - ) - fmt1 = f"{''.join(fmt1[: len(values)])}\n" - record = fmt1.format(*values) + values += list(usrx) if usrx is not None else [] + + record = write_record(values, fmt1)[0] # Record 2 - n = min(4, len(value)) - values = [] - ignore_types = [] - for i, v in enumerate(value[:n]): - if v > -1.0e9: - values.append(v) - else: - values.append("") - ignore_types.append(i) - - fmt2 = str2format(fmt[0], ignore_types=ignore_types) - fmt2 = f"{''.join(fmt2[: len(values)])}\n" - record += fmt2.format(*values) - - # Record 3 (EOS7R) - if len(value) > 4: - values = [] - ignore_types = [] - for i, v in enumerate(value[n:]): - if v > -1.0e9: - values.append(v) - else: - values.append("") - ignore_types.append(i) - - fmt2 = str2format(fmt[0], ignore_types=ignore_types) - fmt2 = f"{''.join(fmt2[: len(values)])}\n" - record += fmt2.format(*values) + record += write_record(value, fmt2, multi=True)[0] yield record + else: continue From f72593c0ca3ddce49d8086374051c6518561b3db Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 29 Jun 2023 18:23:53 +0200 Subject: [PATCH 30/43] optimize floating points --- toughio/_common.py | 44 +++++++++--------- toughio/_io/_common.py | 77 ++++++++++++++++++------------- toughio/_io/input/tough/_write.py | 4 -- 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/toughio/_common.py b/toughio/_common.py index 0669e2b4..edd1985a 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -29,7 +29,7 @@ "SOLVR": "1d,2s,2s,3s,2s,10f,10f", "PARAM": { 1: "2d,2d,4d,4d,4d,24S,10s,10f,10f", - 2: "10f,10f,10.1f,10f,5s,5s,10f,10f,10f", + 2: "10f,10f,10f,10f,5s,5s,10f,10f,10f", 3: ",".join(8 * ["10f"]), 4: "10f,10f,10s,10f,10f,10f", 5: ",".join(4 * ["20f"]), @@ -54,28 +54,28 @@ "DIFFU": ",".join(8 * ["10f"]), "OUTPU": {1: "20s", 2: "15s", 3: "20s,5d,5d"}, "ELEME": { - 5: "5s,5d,5d,5s,10.4e,10.4e,10.4e,10f,10f,10f", - 6: "6s,5d,4d,5s,10.4e,10.4e,10.4e,10f,10f,10f", - 7: "7s,4d,4d,5s,10.4e,10.4e,10.4e,10f,10f,10f", - 8: "8s,4d,3d,5s,10.4e,10.4e,10.4e,10f,10f,10f", - 9: "9s,3d,3d,5s,10.4e,10.4e,10.4e,10f,10f,10f", + 5: "5s,5d,5d,5s,10f,10f,10f,10f,10f,10f", + 6: "6s,5d,4d,5s,10f,10f,10f,10f,10f,10f", + 7: "7s,4d,4d,5s,10f,10f,10f,10f,10f,10f", + 8: "8s,4d,3d,5s,10f,10f,10f,10f,10f,10f", + 9: "9s,3d,3d,5s,10f,10f,10f,10f,10f,10f", }, "COORD": ",".join(3 * ["20f"]), "CONNE": { - 5: "10s,5d,5d,5d,5d,10.4e,10.4e,10.4e,10.3e,10.3e", - 6: "12s,5d,4d,4d,5d,10.4e,10.4e,10.4e,10.3e,10.3e", - 7: "14s,5d,3d,3d,5d,10.4e,10.4e,10.4e,10.3e,10.3e", - 8: "16s,3d,3d,3d,5d,10.4e,10.4e,10.4e,10.3e,10.3e", - 9: "18s,3d,2d,2d,5d,10.4e,10.4e,10.4e,10.3e,10.3e", + 5: "10s,5d,5d,5d,5d,10f,10f,10f,10f,10f", + 6: "12s,5d,4d,4d,5d,10f,10f,10f,10f,10f", + 7: "14s,5d,3d,3d,5d,10f,10f,10f,10f,10f", + 8: "16s,3d,3d,3d,5d,10f,10f,10f,10f,10f", + 9: "18s,3d,2d,2d,5d,10f,10f,10f,10f,10f", }, "INCON": { - 0: ",".join(4 * ["20.13e"]), + 0: ",".join(4 * ["20f"]), "default": { - 5: "5s,5d,5d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", - 6: "6s,5d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", - 7: "7s,4d,4d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", - 8: "8s,4d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", - 9: "9s,3d,3d,15.9e,10.3e,10.3e,10.3e,10.3e,10.3e,10.3e", + 5: "5s,5d,5d,15f,10f,10f,10f,10f,10f,10f", + 6: "6s,5d,4d,15f,10f,10f,10f,10f,10f,10f", + 7: "7s,4d,4d,15f,10f,10f,10f,10f,10f,10f", + 8: "8s,4d,3d,15f,10f,10f,10f,10f,10f,10f", + 9: "9s,3d,3d,15f,10f,10f,10f,10f,10f,10f", }, "tmvoc": { 5: "5s,5d,5d,15.9e,2d", @@ -85,11 +85,11 @@ 9: "9s,3d,3d,15.9e,2d", }, "toughreact": { - 5: "5s,5d,5d,15.9e,15.9e,15.9e,15.9e", - 6: "6s,5d,4d,15.9e,15.9e,15.9e,15.9e", - 7: "7s,4d,4d,15.9e,15.9e,15.9e,15.9e", - 8: "8s,4d,3d,15.9e,15.9e,15.9e,15.9e", - 9: "9s,3d,3d,15.9e,15.9e,15.9e,15.9e", + 5: "5s,5d,5d,15f,15f,15f,15f", + 6: "6s,5d,4d,15f,15f,15f,15f", + 7: "7s,4d,4d,15f,15f,15f,15f", + 8: "8s,4d,3d,15f,15f,15f,15f", + 9: "9s,3d,3d,15f,15f,15f,15f", }, }, "MESHM": { diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index 56163f81..c3cd91cc 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -53,6 +53,7 @@ def to_float(s): except ValueError: # It's probably something like "0.0001-001" significand, exponent = s[:-4], s[-4:] + return float(f"{significand}e{exponent}") @@ -61,49 +62,59 @@ def to_str(x, fmt): x = "" if x is None else x if not isinstance(x, str): - # Special handling for floating point numbers + # Let Python decides for floating points if "f" in fmt: - # Number of decimals is specified - if "." in fmt: - n = int(fmt[3:].split(".")[0]) - tmp = fmt.format(x) + tmp = str(float(x)) - if len(tmp) > n: - return fmt.replace("f", "e").format(x) + n = int(fmt[3:].split("f")[0]) + fmt = f"{{:>{n}}}" - else: - return tmp + if len(tmp) > n - 1 or "e" in tmp: + return fmt.format(scientific_notation(x, n - 1)) - # Let Python decides the format - # n - 1 to leave a whitespace between values + # Otherwise, display as is else: - n = int(fmt[3:].split("f")[0]) - tmp = str(float(x)) - - # If it's longer than n-1, use scientific notation - if len(tmp) > n - 1: - fmt = f"{{:>{n}.{n - 7}e}}" - - # Remove trailing zeros (e.g., "1.300e+07" -> " 1.3e+07") - left, right = fmt.format(x).split("e") - fmt = f"{{:>{len(left)}}}e{right}" - - return fmt.format(str(float(left))) - - # If it's a scientific notation, make sure there is a decimal point - elif "e" in tmp and "." not in tmp: - fmt = f" {{:>{n - 1}.1e}}" + fmt = f" {{:>{n - 1}}}" - return fmt.format(x) + return fmt.format(tmp) + + # Force scientific notation + elif "e" in fmt: + n = int(fmt[3:].split("e")[0]) + fmt = f"{{:>{n}}}" - # Otherwise, display as is - else: - fmt = f" {{:>{n - 1}}}" - - return fmt.format(tmp) + return fmt.format(scientific_notation(x, n - 1)) else: return fmt.format(x) else: return fmt.replace("g", "").replace("e", "").replace("f", "").format(x) + + +def scientific_notation(x, n): + """ + Scientific notation with fixed number of characters. + + Note + ---- + This function maximizes accuracy given a fixed number of characters. + + """ + tmp = np.format_float_scientific( + x, + unique=True, + trim="0", + exp_digits=1, + sign=False, + ) + tmp = tmp.replace("+", "") + + if len(tmp) > n: + significand, exponent = tmp.split("e") + significand = significand[:n - len(tmp)] + + return f"{significand}e{exponent}" + + else: + return tmp diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index 607f9528..ba328c46 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -839,10 +839,6 @@ def _write_param(parameters, eos_=None, simulator="tough"): if ndlt < 2: delten = t_steps[0] if ndlt else None - # Patch record format - fmt2 = block_to_format["PARAM"][2].replace("10.1f", "10.4e") - fmt2 = str2format(fmt2) - else: delten = -((ndlt - 1) // 8 + 1) From c44925389d27321c62627f74bd06517dda193198 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 29 Jun 2023 18:58:11 +0200 Subject: [PATCH 31/43] optimize input reader and writer --- toughio/_common.py | 19 +- toughio/_io/_common.py | 9 - toughio/_io/input/tough/_helpers.py | 287 +--------------------------- toughio/_io/input/tough/_write.py | 37 +--- 4 files changed, 9 insertions(+), 343 deletions(-) diff --git a/toughio/_common.py b/toughio/_common.py index edd1985a..77f01a8c 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -122,10 +122,8 @@ } -def str2format(fmt, ignore_types=None): +def str2format(fmt): """Convert a string to a list of formats.""" - ignore_types = ignore_types if ignore_types else () - token_to_format = { "s": "", "S": "", @@ -135,15 +133,12 @@ def str2format(fmt, ignore_types=None): } base_fmt = "{{:{}}}" - out = [] - for i, token in enumerate(fmt.split(",")): - n = token[:-1] - if i in ignore_types: - out.append(base_fmt.format(n.split(".")[0])) - elif token[-1].lower() == "s": - out.append(base_fmt.format(f"{n}.{n}")) - else: - out.append(base_fmt.format(f">{n}{token_to_format[token[-1]]}")) + out = [ + base_fmt.format(f"{token[:-1]}.{token[:-1]}") + if token[-1].lower() == "s" + else base_fmt.format(f">{token[:-1]}{token_to_format[token[-1]]}") + for token in fmt.split(",") + ] return out diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index c3cd91cc..76bebb6b 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -62,7 +62,6 @@ def to_str(x, fmt): x = "" if x is None else x if not isinstance(x, str): - # Let Python decides for floating points if "f" in fmt: tmp = str(float(x)) @@ -72,18 +71,10 @@ def to_str(x, fmt): if len(tmp) > n - 1 or "e" in tmp: return fmt.format(scientific_notation(x, n - 1)) - # Otherwise, display as is else: fmt = f" {{:>{n - 1}}}" return fmt.format(tmp) - - # Force scientific notation - elif "e" in fmt: - n = int(fmt[3:].split("e")[0]) - fmt = f"{{:>{n}}}" - - return fmt.format(scientific_notation(x, n - 1)) else: return fmt.format(x) diff --git a/toughio/_io/input/tough/_helpers.py b/toughio/_io/input/tough/_helpers.py index 98db4eeb..39d94a7e 100644 --- a/toughio/_io/input/tough/_helpers.py +++ b/toughio/_io/input/tough/_helpers.py @@ -1,5 +1,3 @@ -import logging -from copy import deepcopy from functools import wraps import numpy as np @@ -7,224 +5,6 @@ from ...._common import prune_values from ..._common import read_record, write_record -dtypes = { - "PARAMETERS": { - "title": "str_array_like", - "eos": "str", - "n_component": "int", - "n_phase": "int", - "do_diffusion": "bool", - "n_component_incon": "int", - "react": "dict", - "flac": "dict", - "chemical_properties": "dict", - "non_condensible_gas": "str_array_like", - "isothermal": "bool", - "start": "bool", - "nover": "bool", - "rocks": "dict", - "options": "dict", - "extra_options": "dict", - "more_options": "dict", - "selections": "dict", - "solver": "dict", - "generators": "array_like", - "boundary_conditions": "array_like", - "times": "array_like", - "hysteresis_options": "dict", - "element_history": "array_like", - "connection_history": "array_like", - "generator_history": "array_like", - "rock_history": "array_like", - "diffusion": "array_like", - "output": "dict", - "elements": "dict", - "coordinates": "bool", - "connections": "dict", - "initial_conditions": "dict", - "meshmaker": "dict", - "minc": "dict", - "poiseuille": "dict", - "default": "dict", - "array_dimensions": "dict", - "end_comments": "str_array_like", - }, - "DIMEN": { - "n_rocks": "int", - "n_times": "int", - "n_generators": "int", - "n_rates": "int", - "n_increment_x": "int", - "n_increment_y": "int", - "n_increment_z": "int", - "n_increment_rad": "int", - "n_properties": "int", - "n_properties_times": "int", - "n_regions": "int", - "n_regions_parameters": "int", - "n_ltab": "int", - "n_rpcap": "int", - "n_elements_timbc": "int", - "n_timbc": "int", - }, - "ROCKS": { - "density": "scalar", - "porosity": "scalar", - "permeability": "scalar_array_like", - "conductivity": "scalar", - "specific_heat": "scalar", - "compressibility": "scalar", - "expansivity": "scalar", - "conductivity_dry": "scalar", - "tortuosity": "scalar", - "klinkenberg_parameter": "scalar", - "distribution_coefficient_3": "scalar", - "distribution_coefficient_4": "scalar", - "tortuosity_exponent": "scalar", - "porosity_crit": "scalar", - "initial_condition": "array_like", - "relative_permeability": "dict", - "capillarity": "dict", - "react_tp": "dict", - "react_hcplaw": "dict", - "permeability_model": "dict", - "equivalent_pore_pressure": "dict", - "phase_composition": "int", - }, - "FLAC": {"creep": "bool", "porosity_model": "int", "version": "int"}, - "CHEMP": { - "temperature_crit": "scalar", - "pressure_crit": "scalar", - "compressibility_crit": "scalar", - "pitzer_factor": "scalar", - "dipole_moment": "scalar", - "boiling_point": "scalar", - "vapor_pressure_a": "scalar", - "vapor_pressure_b": "scalar", - "vapor_pressure_c": "scalar", - "vapor_pressure_d": "scalar", - "molecular_weight": "scalar", - "heat_capacity_a": "scalar", - "heat_capacity_b": "scalar", - "heat_capacity_c": "scalar", - "heat_capacity_d": "scalar", - "napl_density_ref": "scalar", - "napl_temperature_ref": "scalar", - "gas_diffusivity_ref": "scalar", - "gas_temperature_ref": "scalar", - "exponent": "scalar", - "napl_viscosity_a": "scalar", - "napl_viscosity_b": "scalar", - "napl_viscosity_c": "scalar", - "napl_viscosity_d": "scalar", - "volume_crit": "scalar", - "solubility_a": "scalar", - "solubility_b": "scalar", - "solubility_c": "scalar", - "solubility_d": "scalar", - "oc_coeff": "scalar", - "oc_fraction": "scalar", - "oc_decay": "scalar", - }, - "MODEL": {"id": "int", "parameters": "array_like"}, - "PARAM": { - "n_iteration": "int", - "n_cycle": "int", - "n_second": "int", - "n_cycle_print": "int", - "verbosity": "int", - "temperature_dependence_gas": "scalar", - "effective_strength_vapor": "scalar", - "t_ini": "scalar", - "t_max": "scalar", - "t_steps": "scalar_array_like", - "t_step_max": "scalar", - "t_reduce_factor": "scalar", - "gravity": "scalar", - "mesh_scale_factor": "scalar", - "eps1": "scalar", - "eps2": "scalar", - "w_upstream": "scalar", - "w_newton": "scalar", - "derivative_factor": "scalar", - "react_wdata": "array_like", - }, - "MOP": {i + 1: "int" for i in range(24)}, - "MOPR": {i + 1: "int" for i in range(25)}, - "MOMOP": {i + 1: "int" for i in range(50)}, - "HYSTE": {i + 1: "int" for i in range(3)}, - "SELEC": {"integers": "dict", "floats": "array_like"}, - "SOLVR": { - "method": "int", - "z_precond": "str", - "o_precond": "str", - "rel_iter_max": "scalar", - "eps": "scalar", - }, - "GENER": { - "label": "str", - "name": "str", - "nseq": "scalar", - "nadd": "scalar", - "nads": "scalar", - "type": "str", - "times": "scalar_array_like", - "rates": "scalar_array_like", - "specific_enthalpy": "scalar_array_like", - "layer_thickness": "scalar", - "n_layer": "int", - "conductivity_times": "array_like", - "conductivity_factors": "array_like", - }, - "TIMBC": { - "label": "str", - "variable": "int", - "times": "scalar_array_like", - "values": "scalar_array_like", - }, - "OUTPT": {"format": "int", "shape": "array_like"}, - "OUTPU": {"format": "str", "variables": "array_like"}, - "ELEME": { - "nseq": "int", - "nadd": "int", - "material": "str_int", - "volume": "scalar", - "heat_exchange_area": "scalar", - "permeability_modifier": "scalar", - "center": "array_like", - }, - "CONNE": { - "nseq": "int", - "nadd": "array_like", - "permeability_direction": "int", - "nodal_distances": "array_like", - "interface_area": "scalar", - "gravity_cosine_angle": "scalar", - "radiant_emittance_factor": "scalar", - }, - "INCON": { - "porosity": "scalar", - "userx": "array_like", - "values": "array_like", - "phase_composition": "int", - "permeability": "scalar_array_like", - }, - "MESHM": {"type": "str", "parameters": "array_like", "angle": "scalar"}, - "MINC": { - "type": "str", - "dual": "str", - "n_minc": "int", - "where": "str", - "parameters": "array_like", - "volumes": "array_like", - }, - "POISE": { - "start": "array_like", - "end": "array_like", - "aperture": "scalar", - }, -} - str_to_dtype = { "int": (int, np.int32, np.int64), @@ -270,72 +50,6 @@ def wrapper(*args, **kwargs): return decorator -def check_parameters(input_types, keys=None, is_list=False): - """Decorate function to check input parameters.""" - - def _check_parameters(params, keys=None): - for k, v in params.items(): - # Check whether parameters contain unknown keys - # Log error if it does and skip - if k not in input_types: - logging.warning( - f"Unknown key '{k}'{f' in {keys}' if keys else ''}. Skipping." - ) - continue - - # Check input types - input_type = str_to_dtype[input_types[k]] - if not (v is None or isinstance(v, input_type)): - raise TypeError( - f"Invalid type for parameter '{k}'{f' in {keys}' if keys else ''} (expected {input_types[k]})." - ) - - keys = [keys] if isinstance(keys, str) else keys - - def decorator(func, *args, **kwargs): - @wraps(func) - def wrapper(parameters, *args, **kwargs): - if not keys: - _check_parameters(parameters) - - else: - params = deepcopy(parameters[keys[0]]) - keys_str = f"['{keys[0]}']" - if is_list: - if isinstance(params, dict): - for k, v in params.items(): - tmp = keys_str - tmp += f"['{k}']" - - try: - for key in keys[1:]: - v = v[key] - tmp += f"['{key}']" - _check_parameters(v, tmp) - - except KeyError: - continue - else: - for i, param in enumerate(params): - tmp = f"{keys_str}[{i}]" - _check_parameters(param, tmp) - - else: - for key in keys[1:]: - params = params[key] - keys_str += f"['{key}']" - - _check_parameters(params, keys_str) - - out = func(parameters, *args, **kwargs) - - return out - - return wrapper - - return decorator - - def read_model_record(line, fmt, i=2): """Read model record defined by 'id' and 'parameters'.""" data = read_record(line, fmt) @@ -351,6 +65,7 @@ def write_model_record(data, key, fmt): if key in data: values = [data[key]["id"], None] values += list(data[key]["parameters"]) + return write_record(values, fmt) else: diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index ba328c46..d76b62b3 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -7,7 +7,7 @@ from ..._common import write_record from .._common import write_ffrecord from ._common import default -from ._helpers import block, check_parameters, dtypes, write_model_record +from ._helpers import block, write_model_record __all__ = [ "write", @@ -60,7 +60,6 @@ def write( f.write(record) -@check_parameters(dtypes["PARAMETERS"]) def write_buffer( params, block, @@ -393,7 +392,6 @@ def write_buffer( return out -@check_parameters(dtypes["DIMEN"], keys="array_dimensions") @block("DIMEN") def _write_dimen(parameters): """Write DIMEN block data.""" @@ -425,12 +423,6 @@ def _write_dimen(parameters): return out -@check_parameters(dtypes["ROCKS"], keys="default") -@check_parameters(dtypes["ROCKS"], keys="rocks", is_list=True) -@check_parameters( - dtypes["MODEL"], keys=("rocks", "relative_permeability"), is_list=True -) -@check_parameters(dtypes["MODEL"], keys=("rocks", "capillarity"), is_list=True) @block("ROCKS", multi=True) def _write_rocks(parameters, simulator="tough"): """Write ROCKS block data.""" @@ -517,8 +509,6 @@ def _write_rocks(parameters, simulator="tough"): return out -@check_parameters(dtypes["MODEL"], keys=("default", "relative_permeability")) -@check_parameters(dtypes["MODEL"], keys=("default", "capillarity")) @block("RPCAP") def _write_rpcap(parameters): """Write RPCAP block data.""" @@ -541,7 +531,6 @@ def _write_rpcap(parameters): return out -@check_parameters(dtypes["MOPR"], keys=("react", "options")) @block("REACT") def _write_react(parameters): """Write REACT block data.""" @@ -560,13 +549,6 @@ def _write_react(parameters): return out -@check_parameters(dtypes["FLAC"], keys="flac") -@check_parameters(dtypes["MODEL"], keys=("default", "permeability_model")) -@check_parameters(dtypes["MODEL"], keys=("default", "equivalent_pore_pressure")) -@check_parameters(dtypes["MODEL"], keys=("rocks", "permeability_model"), is_list=True) -@check_parameters( - dtypes["MODEL"], keys=("rocks", "equivalent_pore_pressure"), is_list=True -) @block("FLAC", multi=True) def _write_flac(parameters): """Write FLAC block data.""" @@ -610,7 +592,6 @@ def _write_flac(parameters): return out -@check_parameters(dtypes["CHEMP"], keys="chemical_properties", is_list=True) @block("CHEMP") def _write_chemp(parameters): """Write CHEMP block data.""" @@ -756,7 +737,6 @@ def _write_multi(parameters): return out -@check_parameters(dtypes["SOLVR"], keys="solver") @block("SOLVR") def _write_solvr(parameters): """Write SOLVR block data.""" @@ -789,8 +769,6 @@ def _write_start(): return [] -@check_parameters(dtypes["PARAM"], keys="options") -@check_parameters(dtypes["MOP"], keys="extra_options") def _write_param(parameters, eos_=None, simulator="tough"): """Write PARAM block data.""" # Load data @@ -890,7 +868,6 @@ def _write_param(parameters, eos_=None, simulator="tough"): return out -@check_parameters(dtypes["SELEC"], keys="selections") @block("SELEC") def _write_selec(parameters): """Write SELEC block data.""" @@ -983,7 +960,6 @@ def _write_indom(parameters, eos_): return out -@check_parameters(dtypes["MOMOP"], keys="more_options") @block("MOMOP") def _write_momop(parameters): """Write MOMOP block data.""" @@ -1022,7 +998,6 @@ def _write_times(parameters): return out -@check_parameters(dtypes["HYSTE"], keys="hysteresis_options") @block("HYSTE") def _write_hyste(parameters): """Write HYSTE block data.""" @@ -1094,7 +1069,6 @@ def _write_roft(parameters): return out -@check_parameters(dtypes["GENER"], keys="generators", is_list=True) @block("GENER", multi=True) def _write_gener(parameters, simulator="tough"): """Write GENER block data.""" @@ -1213,7 +1187,6 @@ def _write_gener(parameters, simulator="tough"): return out -@check_parameters(dtypes["TIMBC"], keys="boundary_conditions", is_list=True) @block("TIMBC") def _write_timbc(parameters): """Write TIMBC block data.""" @@ -1265,7 +1238,6 @@ def _write_diffu(parameters): return out -@check_parameters(dtypes["OUTPT"], keys=("react", "output")) @block("OUTPT") def _write_outpt(parameters): """Write OUTPT block data.""" @@ -1278,7 +1250,6 @@ def _write_outpt(parameters): return out -@check_parameters(dtypes["OUTPU"], keys="output") @block("OUTPU") def _write_outpu(parameters): """Write OUTPU block data.""" @@ -1333,7 +1304,6 @@ def _write_outpu(parameters): return out -@check_parameters(dtypes["ELEME"], keys="elements", is_list=True) @block("ELEME", multi=True) def _write_eleme(parameters): """Write ELEME block data.""" @@ -1387,7 +1357,6 @@ def _write_coord(parameters): return out -@check_parameters(dtypes["CONNE"], keys="connections", is_list=True) @block("CONNE", multi=True) def _write_conne(parameters): """Write CONNE block data.""" @@ -1420,7 +1389,6 @@ def _write_conne(parameters): return out -@check_parameters(dtypes["INCON"], keys="initial_conditions", is_list=True) @block("INCON", multi=True) def _write_incon(parameters, eos_=None, simulator="tough"): """Write INCON block data.""" @@ -1473,7 +1441,6 @@ def _write_incon(parameters, eos_=None, simulator="tough"): return out -@check_parameters(dtypes["MESHM"], keys="meshmaker") @block("MESHM", multi=True) def _write_meshm(parameters): """Write MESHM block data.""" @@ -1586,7 +1553,6 @@ def _write_meshm(parameters): return out -@check_parameters(dtypes["MINC"], keys="minc") def _write_minc(parameters): """Write MESHM block data (MINC).""" from ._common import minc @@ -1628,7 +1594,6 @@ def _write_minc(parameters): return out -@check_parameters(dtypes["POISE"], keys=("react", "poiseuille")) @block("POISE") def _write_poise(parameters): """Write POISE block data.""" From 75cd005f963a95e1cef8bbfa33d40633200b331e Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 30 Jun 2023 11:20:37 +0200 Subject: [PATCH 32/43] add option space_between_values --- toughio/_io/_common.py | 18 +- toughio/_io/input/_helpers.py | 2 + toughio/_io/input/tough/_helpers.py | 6 +- toughio/_io/input/tough/_write.py | 270 +++++++++++++++------------- 4 files changed, 155 insertions(+), 141 deletions(-) diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index 76bebb6b..00aafedd 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -23,10 +23,10 @@ def read_record(data, fmt): return out -def write_record(data, fmt, multi=False): +def write_record(data, fmt, space_between_values=False, multi=False): """Return a list of record strings given format.""" if not multi: - data = [to_str(d, f) for d, f in zip(data, fmt)] + data = [to_str(d, f, space_between_values) for d, f in zip(data, fmt)] out = [f"{''.join(data):80}\n"] else: @@ -39,7 +39,7 @@ def write_record(data, fmt, multi=False): out = [] for d in data: - d = [to_str(dd, f) for dd, f in zip(d, fmt)] + d = [to_str(dd, f, space_between_values) for dd, f in zip(d, fmt)] out += [f"{''.join(d):80}\n"] return out @@ -57,7 +57,7 @@ def to_float(s): return float(f"{significand}e{exponent}") -def to_str(x, fmt): +def to_str(x, fmt, space_between_values=False): """Convert variable to string.""" x = "" if x is None else x @@ -68,13 +68,13 @@ def to_str(x, fmt): n = int(fmt[3:].split("f")[0]) fmt = f"{{:>{n}}}" - if len(tmp) > n - 1 or "e" in tmp: - return fmt.format(scientific_notation(x, n - 1)) + if space_between_values: + n -= 1 - else: - fmt = f" {{:>{n - 1}}}" + if len(tmp) > n or "e" in tmp: + tmp = format(scientific_notation(x, n)) - return fmt.format(tmp) + return fmt.format(tmp) else: return fmt.format(x) diff --git a/toughio/_io/input/_helpers.py b/toughio/_io/input/_helpers.py index a755e9a0..bf064fa4 100644 --- a/toughio/_io/input/_helpers.py +++ b/toughio/_io/input/_helpers.py @@ -115,6 +115,8 @@ def write(filename, parameters, file_format=None, **kwargs): Only if ``file_format = "tough"`` and `block` is None. Blocks to ignore. space_between_blocks : bool, optional, default False Only if ``file_format = "tough"``. Add an empty record between blocks. + space_between_blocks : bool, optional, default True + Only if ``file_format = "tough"``. Add a white space between floating point values. eos : str or None, optional, default None Only if ``file_format = "tough"``. Equation of State. If `eos` is defined in `parameters`, this option will be ignored. diff --git a/toughio/_io/input/tough/_helpers.py b/toughio/_io/input/tough/_helpers.py index 39d94a7e..160b3878 100644 --- a/toughio/_io/input/tough/_helpers.py +++ b/toughio/_io/input/tough/_helpers.py @@ -60,16 +60,16 @@ def read_model_record(line, fmt, i=2): } -def write_model_record(data, key, fmt): +def write_model_record(data, key, fmt, space_between_values=False): """Write model record defined by 'id' and 'parameters'.""" if key in data: values = [data[key]["id"], None] values += list(data[key]["parameters"]) - return write_record(values, fmt) + return write_record(values, fmt, space_between_values) else: - return write_record([], []) + return write_record([], [], space_between_values) def read_primary_variables(f, fmt, n_variables): diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index d76b62b3..e9ca55f0 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -21,6 +21,7 @@ def write( ignore_blocks=None, eos=None, space_between_blocks=False, + space_between_values=True, simulator="tough", ): """ @@ -45,6 +46,8 @@ def write( Blocks to ignore. Only if `block` is None. space_between_blocks : bool, optional, default False Add an empty record between blocks. + space_between_blocks : bool, optional, default True + Add a white space between floating point values. eos : str or None, optional, default None Equation of State. If `eos` is defined in `parameters`, this option will be ignored. @@ -53,7 +56,7 @@ def write( raise ValueError() buffer = write_buffer( - parameters, block, ignore_blocks, space_between_blocks, eos, simulator + parameters, block, ignore_blocks, space_between_blocks, space_between_values, eos, simulator ) with open_file(filename, "w") as f: for record in buffer: @@ -65,6 +68,7 @@ def write_buffer( block, ignore_blocks=None, space_between_blocks=False, + space_between_values=True, eos_=None, simulator="tough", ): @@ -262,29 +266,29 @@ def write_buffer( out += ["\n"] if space_between_blocks else [] if "DIMEN" in blocks and parameters["array_dimensions"]: - out += _write_dimen(parameters) + out += _write_dimen(parameters, space_between_values) if "ROCKS" in blocks and parameters["rocks"]: - out += _write_rocks(parameters, simulator) + out += _write_rocks(parameters, space_between_values, simulator) if "RPCAP" in blocks and rpcap: - out += _write_rpcap(parameters) + out += _write_rpcap(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "REACT" in blocks and react and simulator == "toughreact": - out += _write_react(parameters) + out += _write_react(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "FLAC" in blocks and parameters["flac"]: - out += _write_flac(parameters) + out += _write_flac(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "CHEMP" in blocks and parameters["chemical_properties"]: - out += _write_chemp(parameters) + out += _write_chemp(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "NCGAS" in blocks and len(parameters["non_condensible_gas"]): - out += _write_ncgas(parameters) + out += _write_ncgas(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "MULTI" in blocks and multi: @@ -292,7 +296,7 @@ def write_buffer( out += ["\n"] if space_between_blocks else [] if "SOLVR" in blocks and parameters["solver"]: - out += _write_solvr(parameters) + out += _write_solvr(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "START" in blocks and parameters["start"]: @@ -300,53 +304,53 @@ def write_buffer( out += ["\n"] if space_between_blocks else [] if "PARAM" in blocks and param: - out += _write_param(parameters, eos_, simulator) + out += _write_param(parameters, space_between_values, eos_, simulator) out += ["\n"] if space_between_blocks else [] if "SELEC" in blocks and parameters["selections"]: - out += _write_selec(parameters) + out += _write_selec(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "INDOM" in blocks and indom: - out += _write_indom(parameters, eos_) + out += _write_indom(parameters, space_between_values, eos_) if "MOMOP" in blocks and parameters["more_options"]: out += _write_momop(parameters) out += ["\n"] if space_between_blocks else [] if "TIMES" in blocks and len(parameters["times"]): - out += _write_times(parameters) + out += _write_times(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "HYSTE" in blocks and parameters["hysteresis_options"]: - out += _write_hyste(parameters) + out += _write_hyste(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "FOFT" in blocks and len(parameters["element_history"]): - out += _write_foft(parameters) + out += _write_foft(parameters, space_between_values) if "COFT" in blocks and len(parameters["connection_history"]): - out += _write_coft(parameters) + out += _write_coft(parameters, space_between_values) if "GOFT" in blocks and len(parameters["generator_history"]): - out += _write_goft(parameters) + out += _write_goft(parameters, space_between_values) if "ROFT" in blocks and len(parameters["rock_history"]): out += _write_roft(parameters) if "GENER" in blocks and parameters["generators"]: - out += _write_gener(parameters, simulator) + out += _write_gener(parameters, space_between_values, simulator) if "TIMBC" in blocks and parameters["boundary_conditions"]: out += _write_timbc(parameters) out += ["\n"] if space_between_blocks else [] if "DIFFU" in blocks and len(parameters["diffusion"]): - out += _write_diffu(parameters) + out += _write_diffu(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "OUTPU" in blocks and output: - out += _write_outpu(parameters) + out += _write_outpu(parameters, space_between_values) out += ["\n"] if space_between_blocks else [] if "OUTPT" in blocks and outpt and simulator == "toughreact": @@ -354,19 +358,19 @@ def write_buffer( out += ["\n"] if space_between_blocks else [] if "ELEME" in blocks and parameters["elements"]: - out += _write_eleme(parameters) + out += _write_eleme(parameters, space_between_values) if "COORD" in blocks and parameters["coordinates"]: - out += _write_coord(parameters) + out += _write_coord(parameters, space_between_values) if "CONNE" in blocks and parameters["connections"]: - out += _write_conne(parameters) + out += _write_conne(parameters, space_between_values) if "INCON" in blocks and parameters["initial_conditions"]: - out += _write_incon(parameters, eos_, simulator) + out += _write_incon(parameters, space_between_values, eos_, simulator) if "MESHM" in blocks and (parameters["meshmaker"] or parameters["minc"]): - out += _write_meshm(parameters) + out += _write_meshm(parameters, space_between_values) if "POISE" in blocks and poise and simulator == "toughreact": out += _write_poise(parameters) @@ -393,7 +397,7 @@ def write_buffer( @block("DIMEN") -def _write_dimen(parameters): +def _write_dimen(parameters, space_between_values): """Write DIMEN block data.""" # Format fmt = block_to_format["DIMEN"] @@ -418,13 +422,13 @@ def _write_dimen(parameters): data["n_elements_timbc"], data["n_timbc"], ] - out = write_record(values, fmt1, multi=True) + out = write_record(values, fmt1, space_between_values, multi=True) return out @block("ROCKS", multi=True) -def _write_rocks(parameters, simulator="tough"): +def _write_rocks(parameters, space_between_values, simulator="tough"): """Write ROCKS block data.""" # Formats fmt = block_to_format["ROCKS"] @@ -477,7 +481,7 @@ def _write_rocks(parameters, simulator="tough"): v["conductivity"], v["specific_heat"], ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) # Record 2 if cond: @@ -492,25 +496,26 @@ def _write_rocks(parameters, simulator="tough"): v["tortuosity_exponent"], v["porosity_crit"], ] - out += write_record(values, fmt2) + out += write_record(values, fmt2, space_between_values) + else: - out += write_record([], []) if nad >= 2 else [] + out += write_record([], [], space_between_values) if nad >= 2 else [] # TOUGHREACT if nad >= 4: - out += write_model_record(v, "react_tp", fmt3) - out += write_model_record(v, "react_hcplaw", fmt4) + out += write_model_record(v, "react_tp", fmt3, space_between_values) + out += write_model_record(v, "react_hcplaw", fmt4, space_between_values) # Relative permeability / Capillary pressure if nad >= 2: - out += write_model_record(v, "relative_permeability", fmt5) - out += write_model_record(v, "capillarity", fmt5) + out += write_model_record(v, "relative_permeability", fmt5, space_between_values) + out += write_model_record(v, "capillarity", fmt5, space_between_values) return out @block("RPCAP") -def _write_rpcap(parameters): +def _write_rpcap(parameters, space_between_values): """Write RPCAP block data.""" data = deepcopy(default) data.update(parameters["default"]) @@ -524,15 +529,16 @@ def _write_rpcap(parameters): if key in data: values = [data[key]["id"], None] values += list(data[key]["parameters"]) - out += write_record(values, fmt) + out += write_record(values, fmt, space_between_values) + else: - out += write_record([], []) + out += write_record([], [], space_between_values) return out @block("REACT") -def _write_react(parameters): +def _write_react(parameters, space_between_values): """Write REACT block data.""" from ._common import react_options @@ -544,13 +550,13 @@ def _write_react(parameters): _react.update(parameters["react"]["options"]) tmp = [" " if _react[k] is None else str(_react[k]) for k in sorted(_react)] - out = write_record(["".join(tmp)], fmt) + out = write_record(["".join(tmp)], fmt, space_between_values) return out @block("FLAC", multi=True) -def _write_flac(parameters): +def _write_flac(parameters, space_between_values): """Write FLAC block data.""" # Load data from ._common import flac @@ -570,7 +576,7 @@ def _write_flac(parameters): data["porosity_model"], data["version"], ] - out = write_record(values, fmt1) + out = write_record(values, fmt1, space_between_values) # Additional records for v in parameters["rocks"].values(): @@ -582,18 +588,18 @@ def _write_flac(parameters): # Permeability model values = [data["permeability_model"]["id"]] values += list(data["permeability_model"]["parameters"]) - out += write_record(values, fmt2) + out += write_record(values, fmt2, space_between_values) # Equivalent pore pressure values = [data["equivalent_pore_pressure"]["id"], None] values += list(data["equivalent_pore_pressure"]["parameters"]) - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) return out @block("CHEMP") -def _write_chemp(parameters): +def _write_chemp(parameters, space_between_values): """Write CHEMP block data.""" # Load data from ._common import chemical_properties @@ -607,14 +613,14 @@ def _write_chemp(parameters): fmt3 = str2format(fmt[3]) # Record 1 - out = write_record([len(data)], fmt1) + out = write_record([len(data)], fmt1, space_between_values) # Record 2 for k, v in data.items(): vv = deepcopy(chemical_properties) vv.update(v) - out += write_record([k], fmt2) + out += write_record([k], fmt2, space_between_values) values = [ vv["temperature_crit"], @@ -623,7 +629,7 @@ def _write_chemp(parameters): vv["pitzer_factor"], vv["dipole_moment"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["boiling_point"], @@ -632,7 +638,7 @@ def _write_chemp(parameters): vv["vapor_pressure_c"], vv["vapor_pressure_d"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["molecular_weight"], @@ -641,7 +647,7 @@ def _write_chemp(parameters): vv["heat_capacity_c"], vv["heat_capacity_d"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["napl_density_ref"], @@ -650,7 +656,7 @@ def _write_chemp(parameters): vv["gas_temperature_ref"], vv["exponent"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["napl_viscosity_a"], @@ -659,7 +665,7 @@ def _write_chemp(parameters): vv["napl_viscosity_d"], vv["volume_crit"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["solubility_a"], @@ -667,20 +673,20 @@ def _write_chemp(parameters): vv["solubility_c"], vv["solubility_d"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) values = [ vv["oc_coeff"], vv["oc_fraction"], vv["oc_decay"], ] - out += write_record(values, fmt3) + out += write_record(values, fmt3, space_between_values) return out @block("NCGAS") -def _write_ncgas(parameters): +def _write_ncgas(parameters, space_between_values): """Write NCGAS block data.""" data = parameters["non_condensible_gas"] @@ -690,10 +696,10 @@ def _write_ncgas(parameters): fmt2 = str2format(fmt[2]) # Record 1 - out = write_record([len(data)], fmt1) + out = write_record([len(data)], fmt1, space_between_values) # Record 2 - out += write_record(data, fmt2, multi=True) + out += write_record(data, fmt2, space_between_values, multi=True) return out @@ -738,7 +744,7 @@ def _write_multi(parameters): @block("SOLVR") -def _write_solvr(parameters): +def _write_solvr(parameters, space_between_values): """Write SOLVR block data.""" from ._common import solver @@ -758,7 +764,7 @@ def _write_solvr(parameters): data["rel_iter_max"], data["eps"], ] - out = write_record(values, fmt) + out = write_record(values, fmt, space_between_values) return out @@ -769,7 +775,7 @@ def _write_start(): return [] -def _write_param(parameters, eos_=None, simulator="tough"): +def _write_param(parameters, space_between_values, eos_=None, simulator="tough"): """Write PARAM block data.""" # Load data from ._common import extra_options, header, options @@ -809,7 +815,7 @@ def _write_param(parameters, eos_=None, simulator="tough"): data["temperature_dependence_gas"], data["effective_strength_vapor"], ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) # Time steps t_steps = data["t_steps"] @@ -832,12 +838,12 @@ def _write_param(parameters, eos_=None, simulator="tough"): data["t_reduce_factor"], data["mesh_scale_factor"], ] - out += write_record(values, fmt2) + out += write_record(values, fmt2, space_between_values) # Record 2.1 if ndlt > 1: values = [x for x in t_steps] - out += write_record(values, fmt3, multi=True) + out += write_record(values, fmt3, space_between_values, multi=True) # TOUGHREACT if data["react_wdata"] and simulator == "toughreact": @@ -854,29 +860,31 @@ def _write_param(parameters, eos_=None, simulator="tough"): data["w_newton"], data["derivative_factor"], ] - out += write_record(values, fmt4) + out += write_record(values, fmt4, space_between_values) # Record 4 (TMVOC) if eos_ == "tmvoc": out += write_record( - [parameters["default"]["phase_composition"]], str2format("5d") + [parameters["default"]["phase_composition"]], str2format("5d"), space_between_values ) # Record 5 - out += write_record(parameters["default"]["initial_condition"], fmt5, multi=True) + out += write_record(parameters["default"]["initial_condition"], fmt5, space_between_values, multi=True) return out @block("SELEC") -def _write_selec(parameters): +def _write_selec(parameters, space_between_values): """Write SELEC block data.""" # Load data from ._common import selections data = deepcopy(selections) + if parameters["selections"]["integers"]: data["integers"].update(parameters["selections"]["integers"]) + if "floats" in parameters["selections"] and len(parameters["selections"]["floats"]): data["floats"] = parameters["selections"]["floats"] @@ -896,6 +904,7 @@ def _write_selec(parameters): data["integers"][1] = 1 ndim = 1 + else: ndim = None @@ -906,24 +915,24 @@ def _write_selec(parameters): # Record 1 values = [data["integers"][k] for k in sorted(data["integers"])] - out = write_record(values, fmt1) + out = write_record(values, fmt1, space_between_values) # Record 2 if ndim == 1: - out += write_record(data["floats"], fmt2) + out += write_record(data["floats"], fmt2, space_between_values) elif ndim == 2: for x in data["floats"]: - out += write_record(x, fmt2) + out += write_record(x, fmt2, space_between_values) else: - out += write_record([], fmt2) + out += write_record([], fmt2, space_between_values) return out @block("INDOM", multi=True) -def _write_indom(parameters, eos_): +def _write_indom(parameters, space_between_values, eos_): """Write INDOM block data.""" # Formats fmt = block_to_format["INDOM"] @@ -949,10 +958,10 @@ def _write_indom(parameters, eos_): else: values.append(None) - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) if cond1: - out += write_record(v["initial_condition"], fmt2, multi=True) + out += write_record(v["initial_condition"], fmt2, space_between_values, multi=True) else: out += ["\n"] @@ -979,7 +988,7 @@ def _write_momop(parameters): @block("TIMES") -def _write_times(parameters): +def _write_times(parameters, space_between_values): """Write TIMES block data.""" data = parameters["times"] data = data if np.ndim(data) else [data] @@ -990,16 +999,16 @@ def _write_times(parameters): fmt2 = str2format(fmt[2]) # Record 1 - out = write_record([len(data)], fmt1) + out = write_record([len(data)], fmt1, space_between_values) # Record 2 - out += write_record(data, fmt2, multi=True) + out += write_record(data, fmt2, space_between_values, multi=True) return out @block("HYSTE") -def _write_hyste(parameters): +def _write_hyste(parameters, space_between_values): """Write HYSTE block data.""" from ._common import hysteresis_options @@ -1011,46 +1020,46 @@ def _write_hyste(parameters): _hyste.update(parameters["hysteresis_options"]) values = [_hyste[k] for k in sorted(_hyste)] - out = write_record(values, fmt) + out = write_record(values, fmt, space_between_values) return out @block("FOFT", multi=True) -def _write_foft(parameters): +def _write_foft(parameters, space_between_values): """Write FOFT block data.""" # Formats fmt = block_to_format["FOFT"] fmt = str2format(fmt[5]) values = [x for x in parameters["element_history"]] - out = write_record(values, fmt, multi=True) + out = write_record(values, fmt, space_between_values, multi=True) return out @block("COFT", multi=True) -def _write_coft(parameters): +def _write_coft(parameters, space_between_values): """Write COFT block data.""" # Format fmt = block_to_format["COFT"] fmt = str2format(fmt[5]) values = [x for x in parameters["connection_history"]] - out = write_record(values, fmt, multi=True) + out = write_record(values, fmt, space_between_values, multi=True) return out @block("GOFT", multi=True) -def _write_goft(parameters): +def _write_goft(parameters, space_between_values): """Write GOFT block data.""" # Format fmt = block_to_format["GOFT"] fmt = str2format(fmt[5]) values = [x for x in parameters["generator_history"]] - out = write_record(values, fmt, multi=True) + out = write_record(values, fmt, space_between_values, multi=True) return out @@ -1070,7 +1079,7 @@ def _write_roft(parameters): @block("GENER", multi=True) -def _write_gener(parameters, simulator="tough"): +def _write_gener(parameters, space_between_values, simulator="tough"): """Write GENER block data.""" from ._common import generators @@ -1161,28 +1170,29 @@ def _write_gener(parameters, simulator="tough"): data["layer_thickness"], ktab, ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) if ltab > 1 and data["type"] != "DELV": # Record 2 - out += write_record(data["times"], fmt2, multi=True) + out += write_record(data["times"], fmt2, space_between_values, multi=True) # Record 3 - out += write_record(data["rates"], fmt2, multi=True) + out += write_record(data["rates"], fmt2, space_between_values, multi=True) # Record 4 if data["specific_enthalpy"] is not None: if isinstance(data["specific_enthalpy"], (list, tuple, np.ndarray)): specific_enthalpy = data["specific_enthalpy"] + else: specific_enthalpy = np.full(ltab, data["specific_enthalpy"]) - out += write_record(specific_enthalpy, fmt2, multi=True) + out += write_record(specific_enthalpy, fmt2, space_between_values, multi=True) # TOUGHREACT if ktab: - out += write_record(data["conductivity_times"], fmt2, multi=True) - out += write_record(data["conductivity_factors"], fmt2, multi=True) + out += write_record(data["conductivity_times"], fmt2, space_between_values, multi=True) + out += write_record(data["conductivity_factors"], fmt2, space_between_values, multi=True) return out @@ -1225,7 +1235,7 @@ def _write_timbc(parameters): @block("DIFFU") -def _write_diffu(parameters): +def _write_diffu(parameters, space_between_values): """Write DIFFU block data.""" # Format fmt = block_to_format["DIFFU"] @@ -1233,7 +1243,7 @@ def _write_diffu(parameters): out = [] for mass in parameters["diffusion"]: - out += write_record(mass, fmt, multi=True) + out += write_record(mass, fmt, space_between_values, multi=True) return out @@ -1251,7 +1261,7 @@ def _write_outpt(parameters): @block("OUTPU") -def _write_outpu(parameters): +def _write_outpu(parameters, space_between_values): """Write OUTPU block data.""" # Load data from ._common import output @@ -1268,7 +1278,7 @@ def _write_outpu(parameters): out = [] # Output format - out += write_record([data["format"].upper()], fmt1) if data["format"] else [] + out += write_record([data["format"].upper()], fmt1, space_between_values) if data["format"] else [] # Variables if data["variables"]: @@ -1283,29 +1293,29 @@ def _write_outpu(parameters): if np.ndim(v) == 0: values += [v] - buffer += write_record(values, fmt3) + buffer += write_record(values, fmt3, space_between_values) else: if np.ndim(v[0]) == 0: values += list(v) - buffer += write_record(values, fmt3) + buffer += write_record(values, fmt3, space_between_values) else: for vv in v: values_in = values + list(vv) - buffer += write_record(values_in, fmt3) + buffer += write_record(values_in, fmt3, space_between_values) else: - buffer += write_record(values, fmt3) + buffer += write_record(values, fmt3, space_between_values) - out += write_record([str(num_vars)], fmt2) + out += write_record([str(num_vars)], fmt2, space_between_values) out += buffer return out @block("ELEME", multi=True) -def _write_eleme(parameters): +def _write_eleme(parameters, space_between_values): """Write ELEME block data.""" from ._common import elements @@ -1337,13 +1347,13 @@ def _write_eleme(parameters): center[1], center[2], ] - out += write_record(values, fmt) + out += write_record(values, fmt, space_between_values) return out @block("COORD", multi=True) -def _write_coord(parameters): +def _write_coord(parameters, space_between_values): """Write COORD block data.""" # Format fmt = block_to_format["COORD"] @@ -1352,13 +1362,13 @@ def _write_coord(parameters): out = [] for v in parameters["elements"].values(): values = v["center"] - out += write_record(values, fmt) + out += write_record(values, fmt, space_between_values) return out @block("CONNE", multi=True) -def _write_conne(parameters): +def _write_conne(parameters, space_between_values): """Write CONNE block data.""" from ._common import connections @@ -1384,13 +1394,13 @@ def _write_conne(parameters): data["gravity_cosine_angle"], data["radiant_emittance_factor"], ] - out += write_record(values, fmt) + out += write_record(values, fmt, space_between_values) return out @block("INCON", multi=True) -def _write_incon(parameters, eos_=None, simulator="tough"): +def _write_incon(parameters, space_between_values, eos_=None, simulator="tough"): """Write INCON block data.""" from ._common import initial_conditions @@ -1423,8 +1433,10 @@ def _write_incon(parameters, eos_=None, simulator="tough"): if simulator == "toughreact": per = data["permeability"] per = [per] * 3 if not np.ndim(per) else per + if not (isinstance(per, (list, tuple, np.ndarray)) and len(per) == 3): raise TypeError() + values += [k for k in per] elif eos_ == "tmvoc": @@ -1433,16 +1445,16 @@ def _write_incon(parameters, eos_=None, simulator="tough"): else: values += list(data["userx"]) - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) # Record 2 - out += write_record(data["values"], fmt2, multi=True) + out += write_record(data["values"], fmt2, space_between_values, multi=True) return out @block("MESHM", multi=True) -def _write_meshm(parameters): +def _write_meshm(parameters, space_between_values): """Write MESHM block data.""" from ._common import meshmaker, rz2d, xyz @@ -1458,7 +1470,7 @@ def _write_meshm(parameters): # Mesh type mesh_type = data["type"].upper() if data["type"] else data["type"] - out += write_record([mesh_type], fmt1) + out += write_record([mesh_type], fmt1, space_between_values) # XYZ if mesh_type == "XYZ": @@ -1468,7 +1480,7 @@ def _write_meshm(parameters): fmt3 = str2format(fmt[3]) # Record 1 - out += write_record([data["angle"]], fmt1) + out += write_record([data["angle"]], fmt1, space_between_values) # Record 2 for parameter_ in data["parameters"]: @@ -1483,7 +1495,7 @@ def _write_meshm(parameters): parameter["n_increment"], parameter["sizes"], ] - out += write_record(values, fmt2) + out += write_record(values, fmt2, space_between_values) elif ndim == 1: values += [ @@ -1491,8 +1503,8 @@ def _write_meshm(parameters): if parameter["n_increment"] else len(parameter["sizes"]) ] - out += write_record(values, fmt2) - out += write_record(parameter["sizes"], fmt3, multi=True) + out += write_record(values, fmt2, space_between_values) + out += write_record(parameter["sizes"], fmt3, space_between_values, multi=True) else: raise ValueError() @@ -1510,14 +1522,14 @@ def _write_meshm(parameters): parameter.update(parameter_) parameter_type = parameter["type"].upper() - out += write_record([parameter_type], fmt0) + out += write_record([parameter_type], fmt0, space_between_values) if parameter_type == "RADII": fmt1 = str2format(fmt["RADII"][1]) fmt2 = str2format(fmt["RADII"][2]) - out += write_record([len(parameter["radii"])], fmt1) - out += write_record(parameter["radii"], fmt2, multi=True) + out += write_record([len(parameter["radii"])], fmt1, space_between_values) + out += write_record(parameter["radii"], fmt2, space_between_values, multi=True) elif parameter_type == "EQUID": fmt1 = str2format(fmt["EQUID"]) @@ -1527,7 +1539,7 @@ def _write_meshm(parameters): None, parameter["size"], ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) elif parameter_type == "LOGAR": fmt1 = str2format(fmt["LOGAR"]) @@ -1538,22 +1550,22 @@ def _write_meshm(parameters): parameter["radius"], parameter["radius_ref"], ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) elif parameter_type == "LAYER": fmt1 = str2format(fmt["LAYER"][1]) fmt2 = str2format(fmt["LAYER"][2]) - out += write_record([len(parameter["thicknesses"])], fmt1) - out += write_record(parameter["thicknesses"], fmt2, multi=True) + out += write_record([len(parameter["thicknesses"])], fmt1, space_between_values) + out += write_record(parameter["thicknesses"], fmt2, space_between_values, multi=True) if parameters["minc"]: - out += _write_minc(parameters) + out += _write_minc(parameters, space_between_values) return out -def _write_minc(parameters): +def _write_minc(parameters, space_between_values): """Write MESHM block data (MINC).""" from ._common import minc @@ -1568,7 +1580,7 @@ def _write_minc(parameters): fmt3 = str2format(fmt["MINC"][3]) # Mesh type - out = write_record(["MINC"], fmt0) + out = write_record(["MINC"], fmt0, space_between_values) # Record 1 values = [ @@ -1577,7 +1589,7 @@ def _write_minc(parameters): None, data["dual"].upper(), ] - out += write_record(values, fmt1) + out += write_record(values, fmt1, space_between_values) # Record 2 values = [ @@ -1586,10 +1598,10 @@ def _write_minc(parameters): f"{data['where'].upper():<4}", *data["parameters"], ] - out += write_record(values, fmt2) + out += write_record(values, fmt2, space_between_values) # Record 3 - out += write_record(data["volumes"], fmt3, multi=True) + out += write_record(data["volumes"], fmt3, space_between_values, multi=True) return out From 7d1b7e270b18b8d5f7b5870fe45c8835f356a9cd Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 30 Jun 2023 12:16:23 +0200 Subject: [PATCH 33/43] tidy up --- toughio/_common.py | 11 +++++------ toughio/_io/_common.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/toughio/_common.py b/toughio/_common.py index 77f01a8c..edf1f1dc 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -78,11 +78,11 @@ 9: "9s,3d,3d,15f,10f,10f,10f,10f,10f,10f", }, "tmvoc": { - 5: "5s,5d,5d,15.9e,2d", - 6: "6s,5d,4d,15.9e,2d", - 7: "7s,4d,4d,15.9e,2d", - 8: "8s,4d,3d,15.9e,2d", - 9: "9s,3d,3d,15.9e,2d", + 5: "5s,5d,5d,15f,2d", + 6: "6s,5d,4d,15f,2d", + 7: "7s,4d,4d,15f,2d", + 8: "8s,4d,3d,15f,2d", + 9: "9s,3d,3d,15f,2d", }, "toughreact": { 5: "5s,5d,5d,15f,15f,15f,15f", @@ -129,7 +129,6 @@ def str2format(fmt): "S": "", "d": "g", "f": "f", - "e": "e", } base_fmt = "{{:{}}}" diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index 00aafedd..a2d46238 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -80,7 +80,7 @@ def to_str(x, fmt, space_between_values=False): return fmt.format(x) else: - return fmt.replace("g", "").replace("e", "").replace("f", "").format(x) + return fmt.replace("g", "").replace("f", "").format(x) def scientific_notation(x, n): From 581cf1c6a3bc21677415fa7b90b6e2cb22ba19f5 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 3 Jul 2023 16:36:20 +0200 Subject: [PATCH 34/43] fix float format when 1<=abs(x)<10 --- toughio/_io/_common.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index a2d46238..6b4c78b7 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -71,8 +71,15 @@ def to_str(x, fmt, space_between_values=False): if space_between_values: n -= 1 - if len(tmp) > n or "e" in tmp: - tmp = format(scientific_notation(x, n)) + cond1 = len(tmp) > n + cond2 = "e" in tmp + + if cond1 or cond2: + tmp = ( + tmp[:n] + if not cond2 and tmp[-2] != "." + else format(scientific_notation(x, n)) + ) return fmt.format(tmp) From a59fce28fec57a9462d9b8a47c5d2d6ce028bd13 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Mon, 3 Jul 2023 18:19:44 +0200 Subject: [PATCH 35/43] more float format fixes --- toughio/_io/_common.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index 6b4c78b7..6cf7c54f 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -71,13 +71,10 @@ def to_str(x, fmt, space_between_values=False): if space_between_values: n -= 1 - cond1 = len(tmp) > n - cond2 = "e" in tmp - - if cond1 or cond2: + if len(tmp) > n or "e" in tmp: tmp = ( tmp[:n] - if not cond2 and tmp[-2] != "." + if 1.0 <= abs(x) < 10.0 else format(scientific_notation(x, n)) ) @@ -103,7 +100,7 @@ def scientific_notation(x, n): x, unique=True, trim="0", - exp_digits=1, + exp_digits=0, sign=False, ) tmp = tmp.replace("+", "") From 85df6c3f1d0b519a943c31e5d82761756d4d8839 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 6 Jul 2023 17:32:39 +0200 Subject: [PATCH 36/43] fix relative path issue --- toughio/_run.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 368ec364..65b36c4b 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -95,7 +95,7 @@ def run( input_path = pathlib.Path(input_filename) input_filename = simulation_dir / input_path.name - if input_path.parent.absolute() != simulation_dir.absolute(): + if input_path.parent.resolve() != simulation_dir.resolve(): shutil.copy(input_path, input_filename) else: @@ -107,8 +107,8 @@ def run( filename = pathlib.Path(k) new_filename = pathlib.Path(v) - if filename.parent.absolute() != simulation_dir.absolute() or filename.name != new_filename.name: - shutil.copy(filename, simulation_dir / v) + if filename.parent.resolve() != simulation_dir.resolve() or filename.name != new_filename.name: + shutil.copy(filename, simulation_dir / new_filename.name) # Output filename output_filename = f"{input_filename.stem}.out" @@ -130,7 +130,7 @@ def run( else: cwd = "${PWD}" - cmd = f"docker run -it --rm -v {cwd}:/work -w /work {docker} {cmd}" + cmd = f"docker run --rm -v {cwd}:/work -w /work {docker} {cmd}" elif wsl and platform.startswith("win"): cmd = f'bash -c "{cmd}"' From 010bf053397871e698b9305e7586d6eccc00362c Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Thu, 6 Jul 2023 18:02:55 +0200 Subject: [PATCH 37/43] pass kwargs to write_input --- toughio/_run.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 65b36c4b..eea34188 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -18,7 +18,8 @@ def run( working_dir=None, use_temp=False, ignore_patterns=None, - silent=False + silent=False, + **kwargs ): """ Run TOUGH executable. @@ -47,6 +48,33 @@ 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. + + Other Parameters + ---------------- + block : str {'all', 'gener', 'mesh', 'incon'} or None, optional, default None + Only if ``file_format = "tough"``. Blocks to be written: + + - 'all': write all blocks, + - 'gener': only write block GENER, + - 'mesh': only write blocks ELEME, COORD and CONNE, + - 'incon': only write block INCON, + - None: write all blocks except blocks defined in `ignore_blocks`. + + ignore_blocks : list of str or None, optional, default None + Only if ``file_format = "tough"`` and `block` is None. Blocks to ignore. + space_between_blocks : bool, optional, default False + Only if ``file_format = "tough"``. Add an empty record between blocks. + space_between_blocks : bool, optional, default True + Only if ``file_format = "tough"``. Add a white space between floating point values. + eos : str or None, optional, default None + Only if ``file_format = "tough"``. Equation of State. + If `eos` is defined in `parameters`, this option will be ignored. + mopr_10 : int, optional, default 0 + Only if ``file_format = "toughreact-solute"``. MOPR(10) value in file 'flow.inp'. + mopr_11 : int, optional, default 0 + Only if ``file_format = "toughreact-solute"``. MOPR(11) value in file 'flow.inp'. + verbose : bool, optional, default True + Only if ``file_format`` in {"toughreact-solute", "toughreact-chemical"}. If `True`, add comments to describe content of file. Returns ------- @@ -99,7 +127,7 @@ def run( shutil.copy(input_path, input_filename) else: - write_input(simulation_dir / "INFILE", input_filename) + write_input(simulation_dir / "INFILE", input_filename, **kwargs) input_filename = simulation_dir / "INFILE" # Copy other simulation files to working directory From eb69335d8fa352f19a6c9682b5aa968d1d5d07c7 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Wed, 19 Jul 2023 15:22:18 +0200 Subject: [PATCH 38/43] fix typo --- toughio/_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/_run.py b/toughio/_run.py index eea34188..ac2815c3 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -31,7 +31,7 @@ def run( input_filename : str or pathlike TOUGH input file name. other_filenames : list, dict or None, optional, default None - Other simulation files to copy to working directory (e.g., MESH, INCON, GENER) if not already present. If ``other_filenames`` is a dict, must be in the for ``{old: new}``, where ``old`` is the current name of the file to copy, and ``new`` is the name of the file copied. + Other simulation files to copy to working directory (e.g., MESH, INCON, GENER) if not already present. If ``other_filenames`` is a dict, must be in the form ``{old: new}``, where ``old`` is the current name of the file to copy, and ``new`` is the name of the file copied. command : callable or None, optional, default None Command to execute TOUGH. Must be in the form ``f(exec, inp, [out])``, where ``exec`` is the path to TOUGH executable, ``inp`` is the input file name, and ``out`` is the output file name (optional). workers : int or None, optional, default None From 5a92aaa75067adb0c89cd26b39f7d7edb5b6ce0f Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Fri, 4 Aug 2023 17:43:10 +0200 Subject: [PATCH 39/43] fix doc --- doc/source/guide/input.rst | 54 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/source/guide/input.rst b/doc/source/guide/input.rst index e85f0ab3..3b6f0c96 100644 --- a/doc/source/guide/input.rst +++ b/doc/source/guide/input.rst @@ -469,6 +469,33 @@ Non-condensible gases (block NCGAS) can be listed using keyword ``"non_condensib For TMVOC input files, the argument ``eos="tmvoc"`` **must** be provided to the functions :func:`toughio.read_input` and :func:`toughio.write_input`. +Array dimensions (iTOUGH2) +************************** + +iTOUGH2 allows users to provide array dimensions if an array is insufficiently dimensioned. An additional keyword ``"array_dimensions"`` can be used to specify such values, as follows: + +.. code-block:: + + "array_dimensions": { + "n_rocks": int, + "n_times": int, + "n_generators": int, + "n_rates": int, + "n_increment_x": int, + "n_increment_y": int, + "n_increment_z": int, + "n_increment_rad": int, + "n_properties": int, + "n_properties_times": int, + "n_regions": int, + "n_regions_parameters": int, + "n_ltab": int, + "n_rpcap": int, + "n_elements_timbc": int, + "n_timbc": int, + } + + TOUGHREACT (flow.inp) --------------------- @@ -557,33 +584,6 @@ The permeability of an element called ``"AAA00"`` is defined as follows: } -Array dimensions (iTOUGH2) -************************** - -iTOUGH2 allows users to provide array dimensions if an array is insufficiently dimensioned. An additional keyword ``"array_dimensions"`` can be used to specify such values, as follows: - -.. code-block:: - - "array_dimensions": { - "n_rocks": int, - "n_times": int, - "n_generators": int, - "n_rates": int, - "n_increment_x": int, - "n_increment_y": int, - "n_increment_z": int, - "n_increment_rad": int, - "n_properties": int, - "n_properties_times": int, - "n_regions": int, - "n_regions_parameters": int, - "n_ltab": int, - "n_rpcap": int, - "n_elements_timbc": int, - "n_timbc": int, - } - - TOUGHREACT (solute.inp) ----------------------- From f9b9470d729f1fb1f8a225299065e6358ff82bf5 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 8 Aug 2023 11:57:58 +0200 Subject: [PATCH 40/43] version bump --- toughio/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toughio/VERSION b/toughio/VERSION index 69669de6..f88cf52e 100644 --- a/toughio/VERSION +++ b/toughio/VERSION @@ -1 +1 @@ -1.12.2 \ No newline at end of file +1.13.0 \ No newline at end of file From 4ce9a51456365af7a618ea6929cb0cefa4c233c4 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 8 Aug 2023 11:58:54 +0200 Subject: [PATCH 41/43] invoke format --- tests/test_h5.py | 12 +++- tests/test_input.py | 4 +- toughio/_cli/_merge.py | 2 +- toughio/_common.py | 2 +- toughio/_io/_common.py | 8 +-- toughio/_io/h5/_write.py | 9 +-- toughio/_io/input/_helpers.py | 2 +- toughio/_io/input/tough/_helpers.py | 1 - toughio/_io/input/tough/_read.py | 14 +++-- toughio/_io/input/tough/_write.py | 73 ++++++++++++++++++------ toughio/_io/output/_helpers.py | 2 +- toughio/_io/output/csv/_csv.py | 4 +- toughio/_io/output/petrasim/_petrasim.py | 4 +- toughio/_io/output/tecplot/_tecplot.py | 4 +- toughio/_io/output/tough/_tough.py | 8 +-- toughio/_mesh/_mesh.py | 8 ++- toughio/_mesh/tough/_helpers.py | 4 +- toughio/_mesh/tough/_tough.py | 4 +- toughio/_run.py | 25 ++++---- 19 files changed, 120 insertions(+), 70 deletions(-) diff --git a/tests/test_h5.py b/tests/test_h5.py index 4596a4dc..d4825d92 100644 --- a/tests/test_h5.py +++ b/tests/test_h5.py @@ -1,6 +1,7 @@ +import pathlib + import h5py import helpers -import pathlib import toughio @@ -21,4 +22,11 @@ def test_h5(): ) with h5py.File(filename, "r") as f: - assert list(f.keys()) == ["connection_history", "connections", "element_history", "elements", "generator_history", "rock_history"] + assert list(f.keys()) == [ + "connection_history", + "connections", + "element_history", + "elements", + "generator_history", + "rock_history", + ] diff --git a/tests/test_input.py b/tests/test_input.py index e82ab600..7ef34e71 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -767,7 +767,9 @@ def test_meshm_xyz(): assert helpers.allclose(parameters_ref, parameters, atol=1.0e-4) -@pytest.mark.parametrize("layer, minc", [(True, False), (False, False), (True, True), (False, True)]) +@pytest.mark.parametrize( + "layer, minc", [(True, False), (False, False), (True, True), (False, True)] +) def test_meshm_rz2d(layer, minc): parameters_ref = { "meshmaker": { diff --git a/toughio/_cli/_merge.py b/toughio/_cli/_merge.py index c187f21b..aafdadff 100644 --- a/toughio/_cli/_merge.py +++ b/toughio/_cli/_merge.py @@ -35,7 +35,7 @@ def merge(argv=None): count += int(line.upper()[:5] in {"ROCKS", "PARAM", "ENDFI", "ENDCY"}) if count < 3: raise ValueError(f"Invalid input file '{args.infile}'.") - + # Buffer GENER if gener_exists: gener_file = _read_file(gener_filename, end="+++") diff --git a/toughio/_common.py b/toughio/_common.py index edf1f1dc..22cd3b95 100644 --- a/toughio/_common.py +++ b/toughio/_common.py @@ -173,7 +173,7 @@ def filetype_from_filename(filename, ext_to_fmt, default=""): ext = os.path.splitext(filename)[1].lower() return ext_to_fmt[ext] if ext in ext_to_fmt else default - + else: return default diff --git a/toughio/_io/_common.py b/toughio/_io/_common.py index 6cf7c54f..e88a9e2c 100644 --- a/toughio/_io/_common.py +++ b/toughio/_io/_common.py @@ -90,11 +90,11 @@ def to_str(x, fmt, space_between_values=False): def scientific_notation(x, n): """ Scientific notation with fixed number of characters. - + Note ---- This function maximizes accuracy given a fixed number of characters. - + """ tmp = np.format_float_scientific( x, @@ -107,9 +107,9 @@ def scientific_notation(x, n): if len(tmp) > n: significand, exponent = tmp.split("e") - significand = significand[:n - len(tmp)] + significand = significand[: n - len(tmp)] return f"{significand}e{exponent}" - + else: return tmp diff --git a/toughio/_io/h5/_write.py b/toughio/_io/h5/_write.py index 9e13b35d..949af5c0 100644 --- a/toughio/_io/h5/_write.py +++ b/toughio/_io/h5/_write.py @@ -1,6 +1,7 @@ -import h5py import pathlib +import h5py + from ..output import Output from ..output import read as read_output from ..table import read as read_table @@ -39,7 +40,7 @@ def write( List of labels. compression_opts : int, optional, default 4 Compression level for gzip compression. May be an integer from 0 to 9. - + """ kwargs = { "compression": "gzip", @@ -84,7 +85,7 @@ def _write_output(f, outputs, labels_order, connection, **kwargs): for output in outputs: if not isinstance(output, Output): raise ValueError() - + else: raise ValueError() @@ -103,7 +104,7 @@ def _write_table(f, tables, **kwargs): for name, table in tables.items(): group = f.create_group(name) - + if isinstance(table, (str, pathlib.Path)): table = read_table(table) diff --git a/toughio/_io/input/_helpers.py b/toughio/_io/input/_helpers.py index bf064fa4..9b4073f4 100644 --- a/toughio/_io/input/_helpers.py +++ b/toughio/_io/input/_helpers.py @@ -160,6 +160,6 @@ def _file_format_from_filename(filename): filename = pathlib.Path(filename).name return _file_formats[filename] if filename in _file_formats else "" - + else: return "" diff --git a/toughio/_io/input/tough/_helpers.py b/toughio/_io/input/tough/_helpers.py index 160b3878..93b7fdda 100644 --- a/toughio/_io/input/tough/_helpers.py +++ b/toughio/_io/input/tough/_helpers.py @@ -5,7 +5,6 @@ from ...._common import prune_values from ..._common import read_record, write_record - str_to_dtype = { "int": (int, np.int32, np.int64), "float": (float, np.float32, np.float64), diff --git a/toughio/_io/input/tough/_read.py b/toughio/_io/input/tough/_read.py index 3094d408..1a0b6ed7 100644 --- a/toughio/_io/input/tough/_read.py +++ b/toughio/_io/input/tough/_read.py @@ -252,7 +252,7 @@ def read_buffer(f, label_length, n_variables, eos, simulator="tough"): except: raise ReadError(f"failed to parse line {fiter.count}.") - + if flag: end_comments = read_end_comments(fiter) @@ -879,9 +879,11 @@ def read_table(f, n, fmt): line = f.next() - return { - "generators": [prune_values(generator) for generator in gener["generators"]] - }, flag, label_length + return ( + {"generators": [prune_values(generator) for generator in gener["generators"]]}, + flag, + label_length, + ) def _read_timbc(f): @@ -1176,7 +1178,7 @@ def read_minc(f, data, fmt): minc["volumes"] += prune_values(data) return minc - + fmt = block_to_format["MESHM"] fmt_minc = fmt["MINC"] @@ -1316,7 +1318,7 @@ def read_minc(f, data, fmt): # MINC elif mesh_type == "MINC": meshm["minc"] = read_minc(f, data, fmt_minc) - + return meshm diff --git a/toughio/_io/input/tough/_write.py b/toughio/_io/input/tough/_write.py index e9ca55f0..3d231a13 100644 --- a/toughio/_io/input/tough/_write.py +++ b/toughio/_io/input/tough/_write.py @@ -56,7 +56,13 @@ def write( raise ValueError() buffer = write_buffer( - parameters, block, ignore_blocks, space_between_blocks, space_between_values, eos, simulator + parameters, + block, + ignore_blocks, + space_between_blocks, + space_between_values, + eos, + simulator, ) with open_file(filename, "w") as f: for record in buffer: @@ -390,7 +396,7 @@ def write_buffer( else: out += ["\n"] if space_between_blocks else [] - + out += [f"{comment}\n" for comment in parameters["end_comments"]] return out @@ -508,7 +514,9 @@ def _write_rocks(parameters, space_between_values, simulator="tough"): # Relative permeability / Capillary pressure if nad >= 2: - out += write_model_record(v, "relative_permeability", fmt5, space_between_values) + out += write_model_record( + v, "relative_permeability", fmt5, space_between_values + ) out += write_model_record(v, "capillarity", fmt5, space_between_values) return out @@ -865,11 +873,18 @@ def _write_param(parameters, space_between_values, eos_=None, simulator="tough") # Record 4 (TMVOC) if eos_ == "tmvoc": out += write_record( - [parameters["default"]["phase_composition"]], str2format("5d"), space_between_values + [parameters["default"]["phase_composition"]], + str2format("5d"), + space_between_values, ) # Record 5 - out += write_record(parameters["default"]["initial_condition"], fmt5, space_between_values, multi=True) + out += write_record( + parameters["default"]["initial_condition"], + fmt5, + space_between_values, + multi=True, + ) return out @@ -961,7 +976,9 @@ def _write_indom(parameters, space_between_values, eos_): out += write_record(values, fmt1, space_between_values) if cond1: - out += write_record(v["initial_condition"], fmt2, space_between_values, multi=True) + out += write_record( + v["initial_condition"], fmt2, space_between_values, multi=True + ) else: out += ["\n"] @@ -1187,12 +1204,18 @@ def _write_gener(parameters, space_between_values, simulator="tough"): else: specific_enthalpy = np.full(ltab, data["specific_enthalpy"]) - out += write_record(specific_enthalpy, fmt2, space_between_values, multi=True) + out += write_record( + specific_enthalpy, fmt2, space_between_values, multi=True + ) # TOUGHREACT if ktab: - out += write_record(data["conductivity_times"], fmt2, space_between_values, multi=True) - out += write_record(data["conductivity_factors"], fmt2, space_between_values, multi=True) + out += write_record( + data["conductivity_times"], fmt2, space_between_values, multi=True + ) + out += write_record( + data["conductivity_factors"], fmt2, space_between_values, multi=True + ) return out @@ -1278,7 +1301,11 @@ def _write_outpu(parameters, space_between_values): out = [] # Output format - out += write_record([data["format"].upper()], fmt1, space_between_values) if data["format"] else [] + out += ( + write_record([data["format"].upper()], fmt1, space_between_values) + if data["format"] + else [] + ) # Variables if data["variables"]: @@ -1303,7 +1330,9 @@ def _write_outpu(parameters, space_between_values): else: for vv in v: values_in = values + list(vv) - buffer += write_record(values_in, fmt3, space_between_values) + buffer += write_record( + values_in, fmt3, space_between_values + ) else: buffer += write_record(values, fmt3, space_between_values) @@ -1504,11 +1533,13 @@ def _write_meshm(parameters, space_between_values): else len(parameter["sizes"]) ] out += write_record(values, fmt2, space_between_values) - out += write_record(parameter["sizes"], fmt3, space_between_values, multi=True) + out += write_record( + parameter["sizes"], fmt3, space_between_values, multi=True + ) else: raise ValueError() - + # Blank record out += ["\n"] @@ -1528,8 +1559,12 @@ def _write_meshm(parameters, space_between_values): fmt1 = str2format(fmt["RADII"][1]) fmt2 = str2format(fmt["RADII"][2]) - out += write_record([len(parameter["radii"])], fmt1, space_between_values) - out += write_record(parameter["radii"], fmt2, space_between_values, multi=True) + out += write_record( + [len(parameter["radii"])], fmt1, space_between_values + ) + out += write_record( + parameter["radii"], fmt2, space_between_values, multi=True + ) elif parameter_type == "EQUID": fmt1 = str2format(fmt["EQUID"]) @@ -1556,8 +1591,12 @@ def _write_meshm(parameters, space_between_values): fmt1 = str2format(fmt["LAYER"][1]) fmt2 = str2format(fmt["LAYER"][2]) - out += write_record([len(parameter["thicknesses"])], fmt1, space_between_values) - out += write_record(parameter["thicknesses"], fmt2, space_between_values, multi=True) + out += write_record( + [len(parameter["thicknesses"])], fmt1, space_between_values + ) + out += write_record( + parameter["thicknesses"], fmt2, space_between_values, multi=True + ) if parameters["minc"]: out += _write_minc(parameters, space_between_values) diff --git a/toughio/_io/output/_helpers.py b/toughio/_io/output/_helpers.py index 61c5f31c..41b9e9ce 100644 --- a/toughio/_io/output/_helpers.py +++ b/toughio/_io/output/_helpers.py @@ -87,7 +87,7 @@ def read( else: if file_format not in _reader_map: raise ValueError() - + file_type = "element" # By default if connection: diff --git a/toughio/_io/output/csv/_csv.py b/toughio/_io/output/csv/_csv.py index dd0f5339..db4ef5c6 100644 --- a/toughio/_io/output/csv/_csv.py +++ b/toughio/_io/output/csv/_csv.py @@ -64,9 +64,7 @@ def read(filename, file_type, labels_order=None): ) variables = np.array([[v[ilab:] for v in variable] for variable in variables]) - return to_output( - file_type, labels_order, headers, times, labels, variables - ) + return to_output(file_type, labels_order, headers, times, labels, variables) def _read_csv(f, file_type): diff --git a/toughio/_io/output/petrasim/_petrasim.py b/toughio/_io/output/petrasim/_petrasim.py index 5f79bd3d..45802522 100644 --- a/toughio/_io/output/petrasim/_petrasim.py +++ b/toughio/_io/output/petrasim/_petrasim.py @@ -57,9 +57,7 @@ def read(filename, file_type, labels_order=None): unique_times.append(time) variables.append(data[idx]) - return to_output( - file_type, labels_order, headers, unique_times, labels, variables - ) + return to_output(file_type, labels_order, headers, unique_times, labels, variables) def write(filename, output): diff --git a/toughio/_io/output/tecplot/_tecplot.py b/toughio/_io/output/tecplot/_tecplot.py index 16b12d10..54aa2953 100644 --- a/toughio/_io/output/tecplot/_tecplot.py +++ b/toughio/_io/output/tecplot/_tecplot.py @@ -58,9 +58,7 @@ def read(filename, file_type, labels_order=None): labels.append([]) variables.append(zone["data"]) - return to_output( - file_type, labels_order, headers, times, labels, variables - ) + return to_output(file_type, labels_order, headers, times, labels, variables) def read_buffer(f): diff --git a/toughio/_io/output/tough/_tough.py b/toughio/_io/output/tough/_tough.py index 1a27f966..e25a2836 100644 --- a/toughio/_io/output/tough/_tough.py +++ b/toughio/_io/output/tough/_tough.py @@ -77,9 +77,7 @@ def read(filename, file_type, labels_order=None): labels = [labels.copy() for _ in variables] variables = np.array([[v[2:] for v in variable] for variable in variables]) - return to_output( - file_type, labels_order, headers, times, labels, variables - ) + return to_output(file_type, labels_order, headers, times, labels, variables) def _read_table(f, file_type): @@ -113,7 +111,9 @@ def _read_table(f, file_type): # Read units line = next(f) - nwsp = line.index(line.strip()[0]) # Index of first non whitespace character + nwsp = line.index( + line.strip()[0] + ) # Index of first non whitespace character # Look for next non-empty line while True: diff --git a/toughio/_mesh/_mesh.py b/toughio/_mesh/_mesh.py index f9a5d565..1b557d10 100644 --- a/toughio/_mesh/_mesh.py +++ b/toughio/_mesh/_mesh.py @@ -407,7 +407,9 @@ def write_incon(self, filename="INCON", eos=None): eos, ) - def read_output(self, file_or_output, time_step=-1, labels_order=None, connection=False): + def read_output( + self, file_or_output, time_step=-1, labels_order=None, connection=False + ): """ Import TOUGH results to the mesh. @@ -438,7 +440,7 @@ def read_output(self, file_or_output, time_step=-1, labels_order=None, connectio if not isinstance(out, Output): if not (-len(out) <= time_step < len(out)): raise ValueError() - + out = out[time_step] if out.type == "element": @@ -446,7 +448,7 @@ def read_output(self, file_or_output, time_step=-1, labels_order=None, connectio out = reorder_labels(out, labels_order) self.cell_data.update(out.data) - + elif out.type == "connection": centers = self.centers labels_map = {k: v for v, k in enumerate(self.labels)} diff --git a/toughio/_mesh/tough/_helpers.py b/toughio/_mesh/tough/_helpers.py index 3b5e844d..50f93516 100644 --- a/toughio/_mesh/tough/_helpers.py +++ b/toughio/_mesh/tough/_helpers.py @@ -104,9 +104,7 @@ def _write_incon( label_length = len(labels[0]) fmt = block_to_format["INCON"] fmt1 = str2format( - fmt[eos][label_length] - if eos in fmt - else fmt["default"][label_length] + fmt[eos][label_length] if eos in fmt else fmt["default"][label_length] ) fmt2 = str2format(fmt[0]) diff --git a/toughio/_mesh/tough/_tough.py b/toughio/_mesh/tough/_tough.py index ff9a2069..f2e304ce 100644 --- a/toughio/_mesh/tough/_tough.py +++ b/toughio/_mesh/tough/_tough.py @@ -372,7 +372,9 @@ def _write_conne( # Calculate remaining variables lines = np.diff(centers, axis=1)[:, 0] - isot = _isot(np.around(lines, decimals=4)) # Reduce sensitivity to floating point accuracy + isot = _isot( + np.around(lines, decimals=4) + ) # Reduce sensitivity to floating point accuracy angles = np.dot(lines, gravity) / np.linalg.norm(lines, axis=1) if nodal_distance == "line": diff --git a/toughio/_run.py b/toughio/_run.py index ac2815c3..2d8314e9 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -19,7 +19,7 @@ def run( use_temp=False, ignore_patterns=None, silent=False, - **kwargs + **kwargs, ): """ Run TOUGH executable. @@ -48,7 +48,7 @@ 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. - + Other Parameters ---------------- block : str {'all', 'gener', 'mesh', 'incon'} or None, optional, default None @@ -80,7 +80,7 @@ def run( ------- :class:`subprocess.CompletedProcess` Subprocess completion status. - + """ from . import write_input @@ -135,7 +135,10 @@ def run( filename = pathlib.Path(k) new_filename = pathlib.Path(v) - if filename.parent.resolve() != simulation_dir.resolve() or filename.name != new_filename.name: + if ( + filename.parent.resolve() != simulation_dir.resolve() + or filename.name != new_filename.name + ): shutil.copy(filename, simulation_dir / new_filename.name) # Output filename @@ -168,16 +171,16 @@ def run( kwargs["stdout"] = subprocess.DEVNULL kwargs["stderr"] = subprocess.STDOUT - status = subprocess.run( - cmd, - shell=True, - cwd=str(simulation_dir), - **kwargs - ) + status = subprocess.run(cmd, shell=True, cwd=str(simulation_dir), **kwargs) # Copy files from temporary directory and delete it if use_temp: - shutil.copytree(simulation_dir, working_dir, ignore=shutil.ignore_patterns(*ignore_patterns), dirs_exist_ok=True) + shutil.copytree( + simulation_dir, + working_dir, + ignore=shutil.ignore_patterns(*ignore_patterns), + dirs_exist_ok=True, + ) shutil.rmtree(simulation_dir, ignore_errors=True) os.remove(working_dir / "tempdir.txt") From c43fc2472c935a5dabce243a29f9edba524e157e Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 8 Aug 2023 12:17:02 +0200 Subject: [PATCH 42/43] update run --- toughio/_run.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/toughio/_run.py b/toughio/_run.py index 2d8314e9..0fc8a01b 100644 --- a/toughio/_run.py +++ b/toughio/_run.py @@ -1,9 +1,9 @@ import glob import os import pathlib +import platform import shutil import subprocess -import sys import tempfile @@ -39,7 +39,7 @@ def run( docker : str, optional, default None Name of Docker image. wsl : bool, optional, default False - Only for Windows. If `True`, run the final command as a Bash command. Ignored if `docker` is not None. + Only for Windows. If `True`, run the final command as a Bash command. working_dir : str, pathlike or None, optional, default None Working directory. Input and output files will be generated in this directory. use_temp : bool, optional, default False @@ -151,11 +151,11 @@ def run( if workers is not None and workers > 1: cmd = f"mpiexec -n {workers} {cmd}" - # Use Docker or WSL - platform = sys.platform - + # Use Docker if docker: - if platform.startswith("win") and os.getenv("ComSpec").endswith("cmd.exe"): + if platform.system().startswith("Win") and os.getenv("ComSpec").endswith( + "cmd.exe" + ): cwd = "%cd%" else: @@ -163,7 +163,8 @@ def run( cmd = f"docker run --rm -v {cwd}:/work -w /work {docker} {cmd}" - elif wsl and platform.startswith("win"): + # Use WSL + if wsl and platform.system().startswith("Win"): cmd = f'bash -c "{cmd}"' kwargs = {} @@ -171,6 +172,10 @@ def run( kwargs["stdout"] = subprocess.DEVNULL kwargs["stderr"] = subprocess.STDOUT + else: + kwargs["stderr"] = subprocess.PIPE + kwargs["universal_newlines"] = True + status = subprocess.run(cmd, shell=True, cwd=str(simulation_dir), **kwargs) # Copy files from temporary directory and delete it From e987c4476f840c85113fc32d447943e1c3971f10 Mon Sep 17 00:00:00 2001 From: Keurfon Luu Date: Tue, 8 Aug 2023 13:36:14 +0200 Subject: [PATCH 43/43] fix doc --- doc/source/api/index.rst | 1 + doc/source/api/io.rst | 6 ++++-- doc/source/api/run.rst | 4 ++++ toughio/_io/h5/_write.py | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 doc/source/api/run.rst diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index e73930a1..fea20cd2 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -7,6 +7,7 @@ API Reference io mesh + run rpcap cli diff --git a/doc/source/api/io.rst b/doc/source/api/io.rst index 891ed1ed..c932d689 100644 --- a/doc/source/api/io.rst +++ b/doc/source/api/io.rst @@ -14,10 +14,12 @@ Input parameters Simulation outputs ------------------ -.. autofunction:: toughio.read_history - .. autofunction:: toughio.read_output +.. autofunction:: toughio.read_table + +.. autofunction:: toughio.write_h5 + .. autofunction:: toughio.write_output .. autofunction:: toughio.register_output diff --git a/doc/source/api/run.rst b/doc/source/api/run.rst new file mode 100644 index 00000000..81848bb5 --- /dev/null +++ b/doc/source/api/run.rst @@ -0,0 +1,4 @@ +Run +=== + +.. autofunction:: toughio.run diff --git a/toughio/_io/h5/_write.py b/toughio/_io/h5/_write.py index 949af5c0..6c89319b 100644 --- a/toughio/_io/h5/_write.py +++ b/toughio/_io/h5/_write.py @@ -26,6 +26,7 @@ def write( filename : str or pathlike Output file name. elements : namedtuple, list of namedtuple, str, pathlike or None, optional, default None + Element outputs to export. connections : namedtuple, list of namedtuple, str, pathlike or None, optional, default None Connection outputs to export. element_history : dict or None, optional, default None