• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GeoStat-Framework / ogs5py / 10690994871

03 Sep 2024 09:35PM UTC coverage: 54.194% (+1.5%) from 52.673%
10690994871

Pull #20

github

web-flow
Merge 8f51dbaaf into 455ffafa9
Pull Request #20: Bump actions/download-artifact from 2 to 4.1.7 in /.github/workflows

2804 of 5174 relevant lines covered (54.19%)

2.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.77
/src/ogs5py/ogs.py
1
# -*- coding: utf-8 -*-
2
"""Base Class for an OGS5 run."""
2✔
3
import glob
7✔
4
import os
7✔
5
import shutil
7✔
6
import sys
7✔
7
import time
7✔
8
import warnings
7✔
9
from copy import deepcopy as dcp
7✔
10

11
import pexpect
7✔
12
from pexpect.popen_spawn import PopenSpawn
7✔
13

14
from ogs5py.fileclasses import (
7✔
15
    ASC,
16
    BC,
17
    CCT,
18
    DDC,
19
    FCT,
20
    GEM,
21
    GLI,
22
    IC,
23
    KRC,
24
    MCP,
25
    MFP,
26
    MMP,
27
    MPD,
28
    MSH,
29
    MSP,
30
    NUM,
31
    OUT,
32
    PCS,
33
    PCT,
34
    PQC,
35
    REI,
36
    RFD,
37
    RFR,
38
    ST,
39
    TIM,
40
    GEMinit,
41
    GLIext,
42
    PQCdat,
43
)
44
from ogs5py.fileclasses.base import BOT_COM, CWD, TOP_COM, MultiFile
7✔
45
from ogs5py.tools.download import OGS5PY_CONFIG
7✔
46
from ogs5py.tools.script import gen_script
7✔
47
from ogs5py.tools.tools import Output, search_task_id
7✔
48
from ogs5py.tools.types import MULTI_FILES, OGS_EXT
7✔
49

50
# pexpect.spawn just runs on unix-like systems
51
if sys.platform == "win32":
7✔
52
    OGS_NAME = "ogs.exe"
5✔
53
    CmdRun = PopenSpawn
5✔
54
else:
55
    OGS_NAME = "ogs"
2✔
56
    CmdRun = pexpect.spawn
2✔
57

58

59
class OGS:
7✔
60
    """Class for an OGS5 model.
61

62
    In this class everything for an OGS5 model can be specified.
63

64
    Parameters
65
    ----------
66
    task_root : :class:`str`, optional
67
        Path to the destiny model folder.
68
        Default: cwd+"ogs5model"
69
    task_id : :class:`str`, optional
70
        Name for the ogs task.
71
        Default: "model"
72
    output_dir : :class:`str` or :class:`None`, optional
73
        Path to the output directory.
74
        Default: :class:`None`
75

76
    Notes
77
    -----
78
    The following Classes are present as attributes
79
        bc  : Boundary Condition
80
            Information of the Boundary Conditions for the model.
81
        cct : Communication Table
82
            Information of the Communication Table for the model.
83
        fct : Function
84
            Information of the Function definitions for the model.
85
        gem : geochemical thermodynamic modeling coupling
86
            Information of the geochemical thermodynamic modeling
87
            coupling for the model.
88
        gli : Geometry
89
            Information of the Geometry for the model.
90
        ic  : Initial Condition
91
            Information of the Initial Conditions for the model.
92
        krc : Kinetric Reaction
93
            Information of the Kinetric Reaction for the model.
94
        mcp : reactive components for modelling chemical processes
95
            Information of the reactive components for
96
            modelling chemical processes for the model.
97
        mfp : Fluid Properties
98
            Information of the Fluid Properties for the model.
99
        mmp : Medium Properties
100
            Information of the Medium Properties for the model.
101
        msh : Mesh
102
            Information of the Mesh for the model.
103
        msp : Solid Properties
104
            Information of the Solid Properties for the model.
105
        num : Settings for the numerical solver
106
            Information of the numerical solver for the model.
107
        out : Output Settings
108
            Information of the Output Settings for the model.
109
        pcs : Process settings
110
            Information of the Process settings for the model.
111
        pct : Particle Definition for Random walk
112
            Information of the Particles defined for Randomwalk setting.
113
        pqc : Phreqqc coupling (not supported yet)
114
            Information of the Boundary Conditions for the model.
115
        pqcdat : Phreqqc coupling (the phreeqc.dat file)
116
            phreeqc.dat file for the model.
117
            (just a line-wise file with no comfort)
118
        rei : Reaction Interface
119
            Information of the Reaction Interface for the model.
120
        rfd : definition of time-curves for variing BCs or STs
121
            Information of the time curves for the model.
122
        st  : Source Term
123
            Information of the Source Term for the model.
124
        tim : Time settings
125
            Information of the Time settings for the model.
126

127
    Additional
128
        mpd : Distributed Properties (list of files)
129
            Information of the Distributed Properties for the model.
130
        gli_ext : list for external Geometry definition
131
            External definition of surfaces (TIN) or polylines (POINT_VECTOR)
132
        rfr : list of restart files
133
            RESTART files as defined in the INITIAL_CONDITION
134
        gem_init : list of GEMS3K input files (lst file)
135
            given as GEMinit classes
136
        asc : list of ogs ASC files
137
            This file type comes either from .tim .pcs or .gem
138
        copy_files : list of path-strings
139
            Files that should be copied to the destiny folder.
140
    """
141

142
    def __init__(self, task_root=None, task_id="model", output_dir=None):
7✔
143
        if task_root is None:
7✔
144
            task_root = os.path.join(CWD, "ogs5model")
×
145
        self._task_root = os.path.normpath(task_root)
7✔
146
        self._task_id = task_id
7✔
147
        self._output_dir = None
7✔
148
        self.output_dir = output_dir
7✔
149
        self.exitstatus = None
7✔
150

151
        self.bc = BC(task_root=task_root, task_id=task_id)
7✔
152
        self.cct = CCT(task_root=task_root, task_id=task_id)
7✔
153
        self.ddc = DDC(task_root=task_root, task_id=task_id)
7✔
154
        self.fct = FCT(task_root=task_root, task_id=task_id)
7✔
155
        self.gem = GEM(task_root=task_root, task_id=task_id)
7✔
156
        self.gli = GLI(task_root=task_root, task_id=task_id)
7✔
157
        self.ic = IC(task_root=task_root, task_id=task_id)
7✔
158
        self.krc = KRC(task_root=task_root, task_id=task_id)
7✔
159
        self.mcp = MCP(task_root=task_root, task_id=task_id)
7✔
160
        self.mfp = MFP(task_root=task_root, task_id=task_id)
7✔
161
        self.mmp = MMP(task_root=task_root, task_id=task_id)
7✔
162
        self.msh = MSH(task_root=task_root, task_id=task_id)
7✔
163
        self.msp = MSP(task_root=task_root, task_id=task_id)
7✔
164
        self.num = NUM(task_root=task_root, task_id=task_id)
7✔
165
        self.out = OUT(task_root=task_root, task_id=task_id)
7✔
166
        self.pcs = PCS(task_root=task_root, task_id=task_id)
7✔
167
        self.pct = PCT(task_root=task_root, task_id=task_id)
7✔
168
        self.pqc = PQC(task_root=task_root, task_id=task_id)
7✔
169
        self.pqcdat = PQCdat(task_root=task_root, task_id=task_id)
7✔
170
        self.rei = REI(task_root=task_root, task_id=task_id)
7✔
171
        self.rfd = RFD(task_root=task_root, task_id=task_id)
7✔
172
        self.st = ST(task_root=task_root, task_id=task_id)
7✔
173
        self.tim = TIM(task_root=task_root, task_id=task_id)
7✔
174

175
        # create a list for mpd files
176
        self.mpd = MultiFile(MPD, task_root=task_root, task_id=task_id)
7✔
177
        # create a list for external Geometry definition (TIN and POINT_VECTOR)
178
        self.gli_ext = MultiFile(GLIext, task_root=task_root, task_id=task_id)
7✔
179
        # create a list for RESTART files in the INITIAL_CONDITION
180
        self.rfr = MultiFile(RFR, task_root=task_root, task_id=task_id)
7✔
181
        # create a list for GEMS3K input files (lst file)
182
        self.gem_init = MultiFile(
7✔
183
            GEMinit, task_root=task_root, task_id=task_id
184
        )
185
        # create a list for ASC files
186
        self.asc = MultiFile(ASC, task_root=task_root, task_id=task_id)
7✔
187
        # create a list of arbitrary files to be copied (names will be same)
188
        self.copy_files = []
7✔
189
        # store the Top Comment
190
        self._top_com = TOP_COM
7✔
191
        # store the Bottom Comment
192
        self._bot_com = BOT_COM
7✔
193

194
    @property
7✔
195
    def top_com(self):
5✔
196
        """Get and set the top comment for the ogs files."""
197
        return self._top_com
×
198

199
    @top_com.setter
7✔
200
    def top_com(self, value):
5✔
201
        self._top_com = value
4✔
202
        for ext in OGS_EXT:
4✔
203
            # workaround to get access to class-members by name
204
            getattr(self, ext[1:]).top_com = value
4✔
205

206
    @property
7✔
207
    def bot_com(self):
5✔
208
        """Get and set the bottom comment for the ogs files."""
209
        return self._bot_com
×
210

211
    @bot_com.setter
7✔
212
    def bot_com(self, value):
5✔
213
        self._bot_com = value
4✔
214
        for ext in OGS_EXT:
4✔
215
            # workaround to get access to class-members by name
216
            getattr(self, ext[1:]).bot_com = value
4✔
217

218
    @property
7✔
219
    def task_root(self):
5✔
220
        """Get and set the task_root path of the ogs model."""
221
        return self._task_root
7✔
222

223
    @task_root.setter
7✔
224
    def task_root(self, value):
5✔
225
        self._task_root = value
4✔
226
        for ext in OGS_EXT:
4✔
227
            # workaround to get access to class-members by name
228
            getattr(self, ext[1:]).task_root = value
4✔
229
        for ext in MULTI_FILES:
4✔
230
            getattr(self, ext).standard["task_root"] = value
4✔
231
            for i in range(len(getattr(self, ext))):
4✔
232
                getattr(self, ext)[i].task_root = value
×
233
        self.pqcdat.task_root = value
4✔
234

235
    @property
7✔
236
    def task_id(self):
5✔
237
        """:class:`str`: task_id (name) of the ogs model."""
238
        return self._task_id
4✔
239

240
    @task_id.setter
7✔
241
    def task_id(self, value):
5✔
242
        # workaround for asc
243
        for i in range(len(self.asc)):
4✔
244
            self.asc[i].name = value + self.asc[i].name[len(self._task_id) :]
×
245
        self._task_id = value
4✔
246
        for ext in OGS_EXT:
4✔
247
            getattr(self, ext[1:]).task_id = value
4✔
248
        for ext in MULTI_FILES:
4✔
249
            getattr(self, ext).standard["task_id"] = value
4✔
250

251
    @property
7✔
252
    def output_dir(self):
5✔
253
        """:class:`str`: output directory path of the ogs model."""
254
        return self._output_dir
4✔
255

256
    @output_dir.setter
7✔
257
    def output_dir(self, value):
5✔
258
        if value is None:
7✔
259
            self._output_dir = None
7✔
260
        else:
261
            self._output_dir = os.path.normpath(value)
7✔
262
            if not os.path.isabs(self._output_dir):
7✔
263
                # if not, put the outputfolder in the task_root
264
                self._output_dir = os.path.join(
7✔
265
                    os.path.abspath(self.task_root), self._output_dir
266
                )
267

268
    @property
7✔
269
    def has_output_dir(self):
5✔
270
        """:class:`bool`: State if the model has a output directory."""
271
        return self.output_dir is not None
4✔
272

273
    def add_copy_file(self, path):
7✔
274
        """
275
        Method to add an arbitrary file that should be copied.
276

277
        The base-name of the file will be keept and it will be copied to
278
        the task-root when the "write" routine is called.
279
        """
280
        if os.path.isfile(path):
×
281
            self.copy_files.append(os.path.abspath(path))
×
282
        else:
283
            print("OGS.add_copy_file: given file is not valid: " + str(path))
×
284

285
    def del_copy_file(self, index=None):
7✔
286
        """
287
        Method to delete a copy-file.
288

289
        Parameters
290
        ----------
291
        index : int or None, optional
292
            The index of the copy-file that should be deleted. If None, all
293
            copy-files are deleted. Default: None
294
        """
295
        if index is None:
×
296
            self.copy_files = []
×
297
        elif -len(self.copy_files) <= index < len(self.copy_files):
×
298
            del self.copy_files[index]
×
299
        else:
300
            print("OGS.del_copy_file: given index is not valid.")
×
301

302
    def add_mpd(self, mpd_file):
7✔
303
        """
304
        Method to add an ogs MEDIUM_PROPERTIES_DISTRIBUTED file to the model.
305

306
        This is used for disributed information in the MMP file.
307

308
        See ogs5py.MPD for further information
309
        """
310
        if isinstance(mpd_file, MPD):
×
311
            mpd_file.task_root = self.task_root
×
312
            self.mpd.append(mpd_file)
×
313

314
    def del_mpd(self, index=None):
7✔
315
        """
316
        Method to delete MEDIUM_PROPERTIES_DISTRIBUTED file.
317

318
        Parameters
319
        ----------
320
        index : int or None, optional
321
            The index of the mpd-file that should be deleted. If None, all
322
            mpd-files are deleted. Default: None
323
        """
324
        if index is None:
×
325
            self.mpd.reset_all()
×
326
        elif -len(self.mpd) <= index < len(self.mpd):
×
327
            del self.mpd[index]
×
328
        else:
329
            print("OGS.del_mpd: given index is not valid.")
×
330

331
    def add_gli_ext(self, gli_ext_file):
7✔
332
        """
333
        Method to add an external Geometry definition file to the model.
334

335
        This is used for TIN definition in SURFACE or POINT_VECTOR definition
336
        in POLYLINE in the GLI file.
337

338
        See ogs5py.GLI for further information
339
        """
340
        if isinstance(gli_ext_file, GLIext):
×
341
            gli_ext_file.task_root = self.task_root
×
342
            self.gli_ext.append(gli_ext_file)
×
343

344
    def del_gli_ext(self, index=None):
7✔
345
        """
346
        Method to delete external Geometry file.
347

348
        Parameters
349
        ----------
350
        index : int or None, optional
351
            The index of the external gli file that should be deleted.
352
            If None, all external gli files are deleted. Default: None
353
        """
354
        if index is None:
×
355
            self.gli_ext.reset_all()
×
356
        elif -len(self.gli_ext) <= index < len(self.gli_ext):
×
357
            del self.gli_ext[index]
×
358
        else:
359
            print("OGS.del_gli_ext: given index is not valid.")
×
360

361
    def add_rfr(self, rfr_file):
7✔
362
        """
363
        Method to add an ogs RESTART file to the model.
364

365
        This is used for disributed information in the IC file.
366

367
        See ogs5py.IC for further information
368
        """
369
        if isinstance(rfr_file, RFR):
×
370
            rfr_file.task_root = self.task_root
×
371
            self.rfr.append(rfr_file)
×
372

373
    def del_rfr(self, index=None):
7✔
374
        """
375
        Method to delete RESTART file.
376

377
        Parameters
378
        ----------
379
        index : int or None, optional
380
            The index of the RESTART file that should be deleted.
381
            If None, all RESTART files are deleted. Default: None
382
        """
383
        if index is None:
×
384
            self.rfr.reset_all()
×
385
        elif -len(self.rfr) <= index < len(self.rfr):
×
386
            del self.rfr[index]
×
387
        else:
388
            print("OGS.del_rfr: given index is not valid.")
×
389

390
    def add_gem_init(self, gem_init_file):
7✔
391
        """
392
        Method to add a GEMS3K input file.
393

394
        This is usually generated by GEM-SELEKTOR.
395

396
        See ogs5py.GEM and ogs5py.GEMinit for further information
397
        """
398
        if isinstance(gem_init_file, GEMinit):
×
399
            gem_init_file.task_root = self.task_root
×
400
            self.gem_init.append(gem_init_file)
×
401

402
    def del_gem_init(self, index=None):
7✔
403
        """
404
        Method to delete GEMS3K input file.
405

406
        Parameters
407
        ----------
408
        index : int or None, optional
409
            The index of the GEMS3K file that should be deleted.
410
            If None, all GEMS3K files are deleted. Default: None
411
        """
412
        if index is None:
×
413
            self.gem_init.reset_all()
×
414
        elif -len(self.gem_init) <= index < len(self.gem_init):
×
415
            del self.gem_init[index]
×
416
        else:
417
            print("OGS.del_rfr: given index is not valid.")
×
418

419
    def add_asc(self, asc_file):
7✔
420
        """
421
        Method to add a ASC file.
422

423
        See ogs5py.ASC for further information
424
        """
425
        if isinstance(asc_file, ASC):
×
426
            asc_file.task_root = self.task_root
×
427
            self.asc.append(asc_file)
×
428

429
    def del_asc(self, index=None):
7✔
430
        """
431
        Method to delete a ASC file.
432

433
        Parameters
434
        ----------
435
        index : int or None, optional
436
            The index of the ASC file that should be deleted.
437
            If None, all ASC files are deleted. Default: None
438
        """
439
        if index is None:
×
440
            self.asc.reset_all()
×
441
        elif -len(self.asc) <= index < len(self.asc):
×
442
            del self.asc[index]
×
443
        else:
444
            print("OGS.del_rfr: given index is not valid.")
×
445

446
    def reset(self):
7✔
447
        """Delete every content."""
448
        for ext in OGS_EXT:
4✔
449
            # workaround to get access to class-members by name
450
            getattr(self, ext[1:]).reset()
4✔
451
        for ext in MULTI_FILES:
4✔
452
            getattr(self, ext).reset_all()
4✔
453
        self.pqcdat.reset()
4✔
454
        self.copy_files = []
4✔
455

456
        # reset to initial attributes
457
        self.task_root = self._task_root
4✔
458
        self.task_id = self._task_id
4✔
459
        self.top_com = self._top_com
4✔
460
        self.bot_com = self._bot_com
4✔
461

462
    def write_input(self):
7✔
463
        """Method to call all write_file() methods that are initialized."""
464
        for ext in OGS_EXT:
4✔
465
            # workaround to get access to class-members by name
466
            ogs_file = getattr(self, ext[1:])
4✔
467
            if ogs_file.is_empty and ogs_file.force_writing:
4✔
468
                warnings.warn(
×
469
                    self.task_id
470
                    + ext
471
                    + ": file is empty, but forced to be written!"
472
                )
473
            getattr(self, ext[1:]).write_file()
4✔
474

475
        self.pqcdat.write_file()
4✔
476

477
        for mpd_file in self.mpd:
4✔
478
            mpd_file.write_file()
×
479

480
        for gli_ext_file in self.gli_ext:
4✔
481
            gli_ext_file.write_file()
×
482

483
        for rfr_file in self.rfr:
4✔
484
            rfr_file.write_file()
×
485

486
        for gem_init_file in self.gem_init:
4✔
487
            gem_init_file.write_file()
×
488

489
        for asc_file in self.asc:
4✔
490
            asc_file.write_file()
×
491

492
        for copy_file in self.copy_files:
4✔
493
            base = os.path.basename(copy_file)
×
494
            shutil.copyfile(copy_file, os.path.join(self.task_root, base))
×
495

496
    def gen_script(
7✔
497
        self,
498
        script_dir=os.path.join(os.getcwd(), "ogs_script"),
499
        script_name="model.py",
500
        ogs_cls_name="model",
501
        task_root=None,
502
        task_id=None,
503
        output_dir=None,
504
        separate_files=None,
505
    ):
506
        """
507
        Generate a python script for the given model.
508

509
        Parameters
510
        ----------
511
        script_dir : str
512
            target directory for the script
513
        script_name : str
514
            name for the script file (including .py ending)
515
        ogs_cls_name : str
516
            name of the model in the script
517
        task_root : str
518
            used task_root in the script
519
        task_id : str
520
            used task_id in the script
521
        output_dir : str
522
            used output_dir in the script
523
        separate_files : list of str or None
524
            list of files, that should be written to separate files and
525
            then loaded from the script
526

527
        Notes
528
        -----
529
        This will only create BlockFiles from the script. GLI and MSH files
530
        as well as every other file are stored separately.
531
        """
532
        gen_script(
4✔
533
            self,
534
            script_dir,
535
            script_name,
536
            ogs_cls_name,
537
            task_root,
538
            task_id,
539
            output_dir,
540
            separate_files,
541
        )
542

543
    def load_model(
7✔
544
        self,
545
        task_root,
546
        task_id=None,
547
        use_task_root=False,
548
        use_task_id=False,
549
        skip_files=None,
550
        skip_ext=None,
551
        encoding=None,
552
        verbose=False,
553
        search_ext=None,  # (".pcs"),
554
    ):
555
        """
556
        Load an existing OGS5 model.
557

558
        Parameters
559
        ----------
560
        task_root : str
561
            Path to the destiny folder.
562
        task_id : str or None, optional
563
            Task ID of the model to load. If None is given, it will be
564
            determind by the found files. If multiple possible task_ids were
565
            found, the first one in alphabetic order will be used.
566
            Default: None
567
        use_task_root : Bool, optional
568
            State if the given task_root should be used for this model.
569
            Default: False
570
        use_task_id : Bool, optional
571
            State if the given task_id should be used for this model.
572
            Default: False
573
        skip_files : list or None, optional
574
            List of file-names, that should not be read. Default: None
575
        skip_ext : list or None, optional
576
            List of file-extensions, that should not be read. Default: None
577
        encoding : str or None, optional
578
            encoding of the given files. If ``None`` is given, the system
579
            standard is used. Default: ``None``
580
        verbose : bool, optional
581
            Print information of the reading process. Default: False
582
        search_ext : str
583
            OGS extension that should be searched for. Default: ".pcs"
584

585
        Notes
586
        -----
587
        This method will search for all known OGS5 file-extensions in the
588
        given path (task_root).
589
        Additional files from:
590

591
            - GLI (POINT_VECTOR + TIN)
592
            - MMP (distributed media properties)
593
            - IC (RESTART)
594
            - GEM (GEM3SK init file)
595

596
        will be read automatically.
597

598
        If you get an ``UnicodeDecodeError`` try loading with:
599

600
            ``encoding="ISO-8859-15"``
601
        """
602
        self.reset()
4✔
603

604
        if skip_files is None:
4✔
605
            skip_files = []
4✔
606
        if skip_ext is None:
4✔
607
            skip_ext = []
4✔
608
        # search for possible task_ids in the directory
609
        found_ids = search_task_id(task_root, search_ext)
4✔
610
        if not found_ids:
4✔
611
            raise ValueError(
×
612
                "ogs5py.OGS.laod_model - nothing was found at: " + task_root
613
            )
614
        if verbose:
4✔
615
            print("ogs5py.OGS.laod_model - found ids: " + str(found_ids))
×
616
        # take the first found task_id
617
        if task_id is None:
4✔
618
            # take the first found task_id
619
            task_id = found_ids[0]
4✔
620
        # check if the given task_id is found
621
        elif task_id not in found_ids:
×
622
            raise ValueError(
×
623
                "ogs5py.OGS.load_model - didn't find given task_id ("
624
                + task_id
625
                + ") at: "
626
                + task_root
627
            )
628
        if verbose:
4✔
629
            print("ogs5py.OGS.laod_model - use id: " + task_id)
×
630
        # overwrite the task_root
631
        if use_task_root:
4✔
632
            self.task_root = task_root
×
633
        # overwrite the task_id
634
        if use_task_id:
4✔
635
            self.task_id = task_id
×
636

637
        # iterate over all ogs file-extensions
638
        for ext in OGS_EXT:
4✔
639
            if verbose:
4✔
640
                print(ext, end=" ")
×
641
            # skip certain file extensions if wanted
642
            if ext in skip_ext or ext[1:] in skip_ext:
4✔
643
                continue
×
644
            # search for files with given extension
645
            files = glob.glob(os.path.join(task_root, task_id + ext))
4✔
646
            # if nothing was found, skip
647
            if not files:
4✔
648
                continue
4✔
649
            # take the first found file if there are multiple
650
            fil = files[0]
4✔
651
            # skip file if wanted
652
            if os.path.basename(fil) in skip_files or fil in skip_files:
4✔
653
                continue
×
654
            # workaround to get access to class-members by name
655
            getattr(self, ext[1:]).read_file(
4✔
656
                fil, encoding=encoding, verbose=verbose
657
            )
658

659
            # append GEOMETRY defnitions
660
            if ext == ".gli":
4✔
661
                for ply in self.gli.POLYLINES:
4✔
662
                    # POINT_VECTOR definition of a POLYLINE
663
                    ext_name = ply["POINT_VECTOR"]
4✔
664
                    if ext_name is not None:
4✔
665
                        raw_name = os.path.basename(ext_name)
×
666
                        f_name, f_ext = os.path.splitext(raw_name)
×
667
                        ext_file = GLIext(
×
668
                            typ="POINT_VECTOR",
669
                            name=f_name,
670
                            file_ext=f_ext,
671
                            task_root=self.task_root,
672
                        )
673
                        path = os.path.join(task_root, ext_name)
×
674
                        ext_file.read_file(path, encoding=encoding)
×
675
                        self.gli_ext.append(dcp(ext_file))
×
676
                for srf in self.gli.SURFACES:
4✔
677
                    # Triangulation definition of a SURFACE
678
                    ext_name = srf["TIN"]
×
679
                    if ext_name is not None:
×
680
                        raw_name = os.path.basename(ext_name)
×
681
                        f_name, f_ext = os.path.splitext(raw_name)
×
682
                        ext_file = GLIext(
×
683
                            typ="TIN",
684
                            name=f_name,
685
                            file_ext=f_ext,
686
                            task_root=self.task_root,
687
                        )
688
                        path = os.path.join(task_root, ext_name)
×
689
                        ext_file.read_file(path, encoding=encoding)
×
690
                        self.gli_ext.append(dcp(ext_file))
×
691

692
            # append MEDIUM_PROPERTIES_DISTRIBUTED defnitions
693
            if ext == ".mmp":
4✔
694
                for i in range(len(self.mmp.mainkw)):
4✔
695
                    # external PERMEABILITY_DISTRIBUTION
696
                    if "PERMEABILITY_DISTRIBUTION" in self.mmp.subkw[i]:
4✔
697
                        index = self.mmp.subkw[i].index(
×
698
                            "PERMEABILITY_DISTRIBUTION"
699
                        )
700
                        ext_name = self.mmp.cont[i][index][0][0]
×
701
                        raw_name = os.path.basename(ext_name)
×
702
                        f_name, f_ext = os.path.splitext(raw_name)
×
703
                        ext_file = MPD(
×
704
                            name=f_name,
705
                            file_ext=f_ext,
706
                            task_root=self.task_root,
707
                        )
708
                        path = os.path.join(task_root, ext_name)
×
709
                        ext_file.read_file(path, encoding=encoding)
×
710
                        self.mpd.append(dcp(ext_file))
×
711
                    # external POROSITY_DISTRIBUTION
712
                    if "POROSITY_DISTRIBUTION" in self.mmp.subkw[i]:
4✔
713
                        index = self.mmp.subkw[i].index(
×
714
                            "POROSITY_DISTRIBUTION"
715
                        )
716
                        ext_name = self.mmp.cont[i][index][0][0]
×
717
                        raw_name = os.path.basename(ext_name)
×
718
                        f_name, f_ext = os.path.splitext(raw_name)
×
719
                        ext_file = MPD(
×
720
                            name=f_name,
721
                            file_ext=f_ext,
722
                            task_root=self.task_root,
723
                        )
724
                        path = os.path.join(task_root, ext_name)
×
725
                        ext_file.read_file(path, encoding=encoding)
×
726
                        self.mpd.append(dcp(ext_file))
×
727
                    # external GEOMETRY_AREA
728
                    if "GEOMETRY_AREA" in self.mmp.subkw[i]:
4✔
729
                        index = self.mmp.subkw[i].index("GEOMETRY_AREA")
×
730
                        if self.mmp.cont[i][index][0][0] == "FILE":
×
731
                            ext_name = self.mmp.cont[i][index][0][1]
×
732
                            raw_name = os.path.basename(ext_name)
×
733
                            f_name, f_ext = os.path.splitext(raw_name)
×
734
                            ext_file = MPD(
×
735
                                name=f_name,
736
                                file_ext=f_ext,
737
                                task_root=self.task_root,
738
                            )
739
                            path = os.path.join(task_root, ext_name)
×
740
                            ext_file.read_file(path, encoding=encoding)
×
741
                            self.mpd.append(dcp(ext_file))
×
742

743
            # append GEMS3K init file
744
            if ext == ".gem":
4✔
745
                for i in range(len(self.gem.mainkw)):
×
746
                    if "GEM_INIT_FILE" in self.gem.subkw[i]:
×
747
                        index = self.gem.subkw[i].index("GEM_INIT_FILE")
×
748
                        ext_name = self.gem.cont[i][index][0][0]
×
749
                        ext_file = GEMinit(
×
750
                            lst_name=ext_name, task_root=self.task_root
751
                        )
752
                        path = os.path.join(task_root, ext_name)
×
753
                        ext_file.read_file(path, encoding=encoding)
×
754
                        self.gem_init.append(dcp(ext_file))
×
755

756
            # append RESART defnitions
757
            if ext == ".ic":
4✔
758
                for i in range(len(self.ic.mainkw)):
4✔
759
                    if "DIS_TYPE" in self.ic.subkw[i]:
4✔
760
                        index = self.ic.subkw[i].index("DIS_TYPE")
4✔
761
                        if self.ic.cont[i][index][0][0] != "RESTART":
4✔
762
                            continue
4✔
763
                        ext_name = self.ic.cont[i][index][0][1]
×
764
                        raw_name = os.path.basename(ext_name)
×
765
                        f_name, f_ext = os.path.splitext(raw_name)
×
766
                        ext_file = RFR(
×
767
                            name=f_name,
768
                            file_ext=f_ext,
769
                            task_root=self.task_root,
770
                        )
771
                        path = os.path.join(task_root, ext_name)
×
772
                        ext_file.read_file(path, encoding=encoding)
×
773
                        self.rfr.append(dcp(ext_file))
×
774

775
            # read phreeqc.dat
776
            if ext == ".pqc":  # phreeqc.dat or Phreeqc.dat
4✔
777
                pqcfiles = glob.glob(os.path.join(task_root, "*hreeqc.dat"))
×
778
                self.pqcdat.read_file(
×
779
                    path=pqcfiles[0], encoding=encoding, verbose=verbose
780
                )
781

782
        # load ASC files
783
        files = glob.glob(os.path.join(task_root, task_id + "*.asc"))
4✔
784
        for fil in files:
4✔
785
            raw_name = os.path.basename(fil)
×
786
            f_name, f_ext = os.path.splitext(raw_name)
×
787
            ext_file = ASC(
×
788
                name=self.task_id + f_name[len(task_id) :],
789
                task_root=self.task_root,
790
            )
791
            path = os.path.join(task_root, fil)
×
792
            ext_file.read_file(path, encoding=encoding)
×
793
            self.asc.append(dcp(ext_file))
×
794

795
        return True
4✔
796

797
    def readvtk(self, pcs="ALL", output_dir=None):
7✔
798
        r"""
799
        Reader for vtk outputfiles of this OGS5 model.
800

801
        Parameters
802
        ----------
803
        pcs : string or None, optional
804
            specify the PCS type that should be collected
805
            Possible values are:
806

807
                - None/"" (no PCS_TYPE specified in \*.out)
808
                - "NO_PCS"
809
                - "GROUNDWATER_FLOW"
810
                - "LIQUID_FLOW"
811
                - "RICHARDS_FLOW"
812
                - "AIR_FLOW"
813
                - "MULTI_PHASE_FLOW"
814
                - "PS_GLOBAL"
815
                - "HEAT_TRANSPORT"
816
                - "DEFORMATION"
817
                - "MASS_TRANSPORT"
818
                - "OVERLAND_FLOW"
819
                - "FLUID_MOMENTUM"
820
                - "RANDOM_WALK"
821

822
            You can get a list with all known PCS-types by setting PCS="ALL"
823
            Default : None
824
        output_dir : :any:'None' or :class:'str', optional
825
            Sometimes OGS5 doesn't put the output in the right directory.
826
            You can specify a separate output directory here in this case.
827
            Default: :any:'None'
828

829
        Returns
830
        -------
831
        result : dict
832
            keys are the point names and the items are the data from the
833
            corresponding files
834
            if pcs="ALL", the output is a dictionary with the PCS-types as keys
835
        """
836
        from ogs5py.reader import readvtk as read
×
837

838
        if output_dir is not None:
×
839
            root = output_dir
×
840
        elif self.has_output_dir:
×
841
            root = self.output_dir
×
842
        else:
843
            root = self.task_root
×
844
        return read(task_root=root, task_id=self.task_id, pcs=pcs)
×
845

846
    def readpvd(self, pcs="ALL", output_dir=None):
7✔
847
        r"""
848
        Read the paraview pvd files of this OGS5 model.
849

850
        All concerned files are converted to a dictionary containing their data
851

852
        Parameters
853
        ----------
854
        pcs : string or None, optional
855
            specify the PCS type that should be collected
856
            Possible values are:
857

858
                - None/"" (no PCS_TYPE specified in \*.out)
859
                - "NO_PCS"
860
                - "GROUNDWATER_FLOW"
861
                - "LIQUID_FLOW"
862
                - "RICHARDS_FLOW"
863
                - "AIR_FLOW"
864
                - "MULTI_PHASE_FLOW"
865
                - "PS_GLOBAL"
866
                - "HEAT_TRANSPORT"
867
                - "DEFORMATION"
868
                - "MASS_TRANSPORT"
869
                - "OVERLAND_FLOW"
870
                - "FLUID_MOMENTUM"
871
                - "RANDOM_WALK"
872

873
            You can get a list with all known PCS-types by setting PCS="ALL"
874
            Default : "ALL"
875
        output_dir : :any:'None' or :class:'str', optional
876
            Sometimes OGS5 doesn't put the output in the right directory.
877
            You can specify a separate output directory here in this case.
878
            Default: :any:'None'
879

880
        Returns
881
        -------
882
        result : dict
883
            keys are the point names and the items are the data from the
884
            corresponding files
885
            if pcs="ALL", the output is a dictionary with the PCS-types as keys
886
        """
887
        from ogs5py.reader import readpvd as read
×
888

889
        if output_dir is not None:
×
890
            root = output_dir
×
891
        elif self.has_output_dir:
×
892
            root = self.output_dir
×
893
        else:
894
            root = self.task_root
×
895
        return read(task_root=root, task_id=self.task_id, pcs=pcs)
×
896

897
    def readtec_point(self, pcs="ALL", output_dir=None):
7✔
898
        r"""
899
        Collect TECPLOT point output from this OGS5 model.
900

901
        Parameters
902
        ----------
903
        pcs : string or None, optional
904
            specify the PCS type that should be collected
905
            Possible values are:
906

907
                - None/"" (no PCS_TYPE specified in \*.out)
908
                - "NO_PCS"
909
                - "GROUNDWATER_FLOW"
910
                - "LIQUID_FLOW"
911
                - "RICHARDS_FLOW"
912
                - "AIR_FLOW"
913
                - "MULTI_PHASE_FLOW"
914
                - "PS_GLOBAL"
915
                - "HEAT_TRANSPORT"
916
                - "DEFORMATION"
917
                - "MASS_TRANSPORT"
918
                - "OVERLAND_FLOW"
919
                - "FLUID_MOMENTUM"
920
                - "RANDOM_WALK"
921

922
            You can get a list with all known PCS-types by setting PCS="ALL"
923
            Default : "ALL"
924
        output_dir : :any:'None' or :class:'str', optional
925
            Sometimes OGS5 doesn't put the output in the right directory.
926
            You can specify a separate output directory here in this case.
927
            Default: :any:'None'
928

929
        Returns
930
        -------
931
        result : dict
932
            Keys are the point names and the items are the data from the
933
            corresponding files. If pcs="ALL",
934
            the output is a dictionary with the PCS-types as keys.
935
        """
936
        from ogs5py.reader import readtec_point as read
4✔
937

938
        if output_dir is not None:
4✔
939
            root = output_dir
×
940
        elif self.has_output_dir:
4✔
941
            root = self.output_dir
4✔
942
        else:
943
            root = self.task_root
×
944
        return read(task_root=root, task_id=self.task_id, pcs=pcs)
4✔
945

946
    def readtec_polyline(self, pcs="ALL", trim=True, output_dir=None):
7✔
947
        r"""
948
        Collect TECPLOT polyline output from this OGS5 model.
949

950
        Parameters
951
        ----------
952
        pcs : string or None, optional
953
            specify the PCS type that should be collected
954
            Possible values are:
955

956
                - None/"" (no PCS_TYPE specified in \*.out)
957
                - "NO_PCS"
958
                - "GROUNDWATER_FLOW"
959
                - "LIQUID_FLOW"
960
                - "RICHARDS_FLOW"
961
                - "AIR_FLOW"
962
                - "MULTI_PHASE_FLOW"
963
                - "PS_GLOBAL"
964
                - "HEAT_TRANSPORT"
965
                - "DEFORMATION"
966
                - "MASS_TRANSPORT"
967
                - "OVERLAND_FLOW"
968
                - "FLUID_MOMENTUM"
969
                - "RANDOM_WALK"
970

971
            You can get a list with all known PCS-types by setting pcs="ALL"
972
            Default : "ALL"
973
        output_dir : :any:'None' or :class:'str', optional
974
            Sometimes OGS5 doesn't put the output in the right directory.
975
            You can specify a separate output directory here in this case.
976
            Default: :any:'None'
977
        trim : Bool, optional
978
            if the ply_ids are not continuous, there will be "None" values in
979
            the output list. If trim is "True" these values will be eliminated.
980
            If there is just one output for a polyline, the list will
981
            be eliminated and the output will be the single dict.
982
            Default : True
983

984
        Returns
985
        -------
986
        result : dict
987
            keys are the Polyline names and the items are lists sorted by the
988
            ply_id (it is assumed, that the ply_ids are continuous, if not, the
989
            corresponding list entries are "None")
990
            if pcs="ALL", the output is a dictionary with the PCS-types as keys
991
        """
992
        from ogs5py.reader import readtec_polyline as read
×
993

994
        if output_dir is not None:
×
995
            root = output_dir
×
996
        elif self.has_output_dir:
×
997
            root = self.output_dir
×
998
        else:
999
            root = self.task_root
×
1000
        return read(task_root=root, task_id=self.task_id, pcs=pcs, trim=trim)
×
1001

1002
    def output_files(self, pcs=None, typ="VTK", element=None, output_dir=None):
7✔
1003
        r"""
1004
        Get a list of output file paths.
1005

1006
        Parameters
1007
        ----------
1008
        pcs : string or None, optional
1009
            specify the PCS type that should be collected
1010
            Possible values are:
1011

1012
                - None/"" (no PCS_TYPE specified in \*.out)
1013
                - "NO_PCS"
1014
                - "GROUNDWATER_FLOW"
1015
                - "LIQUID_FLOW"
1016
                - "RICHARDS_FLOW"
1017
                - "AIR_FLOW"
1018
                - "MULTI_PHASE_FLOW"
1019
                - "PS_GLOBAL"
1020
                - "HEAT_TRANSPORT"
1021
                - "DEFORMATION"
1022
                - "MASS_TRANSPORT"
1023
                - "OVERLAND_FLOW"
1024
                - "FLUID_MOMENTUM"
1025
                - "RANDOM_WALK"
1026

1027
            Default : None
1028
        typ : string, optional
1029
            Type of the output ("VTK", "PVD", "TEC_POINT" or "TEC_POLYLINE").
1030
            Default : "VTK"
1031
        element : string or None, optional
1032
            For tecplot output you can specify the name of the output element.
1033
            (Point-name of Line-name from GLI file)
1034
            Default: None
1035
        """
1036
        from ogs5py.tools.output import get_output_files as read
4✔
1037

1038
        if output_dir is not None:
4✔
1039
            root = output_dir
×
1040
        elif self.has_output_dir:
4✔
1041
            root = self.output_dir
4✔
1042
        else:
1043
            root = self.task_root
×
1044
        return read(root, self.task_id, pcs, typ, element)
4✔
1045

1046
    def run_model(
7✔
1047
        self,
1048
        ogs_exe=None,
1049
        ogs_name=OGS_NAME,
1050
        print_log=True,
1051
        save_log=True,
1052
        log_path=None,
1053
        log_name=None,
1054
        timeout=None,
1055
    ):
1056
        """
1057
        Run the defined OGS5 model.
1058

1059
        Parameters
1060
        ----------
1061
        ogs_exe : str or None, optional
1062
            path to the ogs executable. If ``None`` is given, the default sys
1063
            path will be searched with ``which``.
1064
            Can be a folder containing the exe with basename: ogs_name.
1065
            It will first look in the :any:`OGS5PY_CONFIG` folder.
1066
            Default: None
1067
        ogs_name : str or None, optional
1068
            Name of to the ogs executable to search for.
1069
            Just used if ,ogs_exe is ``None``. Default: ``"ogs"``
1070
        print_log : bool, optional
1071
            state if the ogs output should be displayed in the terminal.
1072
            Default: True
1073
        save_log : bool, optional
1074
            state if the ogs output should be saved to a file.
1075
            Default: True
1076
        log_path : str or None, optional
1077
            Path, where the log file should be saved. Default: None
1078
            (the defined output directory or the task_root directory)
1079
        log_name : str or None, optional
1080
            Name of the log file. Default: None
1081
            (task_id+time+"_log.txt")
1082
        timeout : int or None, optional
1083
            Time to wait for OGS5 to finish in seconds. Default: None
1084

1085
        Returns
1086
        -------
1087
        success : bool
1088
            State if OGS5 terminated 'normally'. (Allways true on Windows.)
1089
        """
1090
        # look for the standard ogs executable in the standard-path
1091
        if ogs_exe is None:
4✔
1092
            check_ogs = shutil.which(
×
1093
                ogs_name, path=OGS5PY_CONFIG
1094
            ) or shutil.which(ogs_name)
1095
            if check_ogs is None:
×
1096
                print(
×
1097
                    "Please put the ogs executable in the default sys path: "
1098
                    + str(os.defpath.split(os.pathsep))
1099
                )
1100
                print("Or provide the path to your executable")
×
1101
                return False
×
1102
            ogs_exe = check_ogs
×
1103
        else:
1104
            if sys.platform == "win32" and ogs_exe[-4:] == ".lnk":
4✔
1105
                print("Don't use file links under windows...")
×
1106
                return False
×
1107
            if os.path.islink(ogs_exe):
4✔
1108
                ogs_exe = os.path.realpath(ogs_exe)
×
1109
            if os.path.exists(ogs_exe):
4✔
1110
                if not os.path.isfile(ogs_exe):
4✔
1111
                    ogs_exe = os.path.join(ogs_exe, ogs_name)
×
1112
            else:
1113
                print("The given ogs_exe does not exist...")
×
1114
                return False
×
1115
        # use absolute path since we change the cwd in the ogs call
1116
        ogs_exe = os.path.abspath(ogs_exe)
4✔
1117

1118
        # create the command to call ogs
1119
        args = [ogs_exe, self.task_id]
4✔
1120
        # add optional output directory
1121
        # check if output directory is an absolute path with os.path.isabs
1122
        # otherwise set it in the task_root directory
1123
        if self.has_output_dir:
4✔
1124
            # format the outputdir
1125
            output_dir = os.path.normpath(self.output_dir)
4✔
1126
            # check if outputdir is given as absolut path
1127
            if not os.path.isabs(output_dir):
4✔
1128
                # if not, put the outputfolder in the task_root
1129
                output_dir = os.path.join(
×
1130
                    os.path.abspath(self.task_root), output_dir
1131
                )
1132
            # create the outputdir
1133
            if not os.path.exists(output_dir):
4✔
1134
                os.makedirs(output_dir)
4✔
1135
            # append the outputdir to the ogs-command
1136
            args.append("--output-directory")
4✔
1137
            args.append(output_dir)
4✔
1138
        else:
1139
            output_dir = os.path.abspath(self.task_root)
×
1140

1141
        # prevent eraising files...
1142
        if not save_log:
4✔
1143
            log_name = None
×
1144

1145
        # set standard log_name
1146
        if log_name is None:
4✔
1147
            log_name = (
4✔
1148
                self.task_id
1149
                + "_"
1150
                + time.strftime("%Y-%m-%d_%H-%M-%S")
1151
                + "_log.txt"
1152
            )
1153
        # put the logfile in the defined output-dir
1154
        if log_path is None:
4✔
1155
            log_path = output_dir
4✔
1156
        log = os.path.join(log_path, log_name)
4✔
1157

1158
        # create a splitted output stream (to file and stdout)
1159
        out = Output(log, print_log=print_log)
4✔
1160
        # call ogs with pexpect
1161
        child = CmdRun(
4✔
1162
            " ".join(args),
1163
            timeout=timeout,
1164
            logfile=out,
1165
            encoding=out.encoding,
1166
            cwd=os.path.abspath(self.task_root),
1167
        )
1168
        # wait for ogs to finish
1169
        child.expect(pexpect.EOF)
4✔
1170
        if sys.platform != "win32":
4✔
1171
            child.close()
2✔
1172
            self.exitstatus = child.exitstatus
2✔
1173
        else:
1174
            self.exitstatus = child.wait()
2✔
1175
        success = self.exitstatus == 0
4✔
1176
        # close the output stream
1177
        out.close()
4✔
1178

1179
        if not save_log:
4✔
1180
            os.remove(log)
×
1181

1182
        return success
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc