exovista 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exovista-1.0.0/.flake8 +31 -0
- exovista-1.0.0/.github/workflows/publish.yml +28 -0
- exovista-1.0.0/.gitignore +13 -0
- exovista-1.0.0/LICENSE +27 -0
- exovista-1.0.0/PKG-INFO +10 -0
- exovista-1.0.0/README.md +77 -0
- exovista-1.0.0/exovista/__init__.py +44 -0
- exovista-1.0.0/exovista/allclose.py +217 -0
- exovista-1.0.0/exovista/config.py +21 -0
- exovista-1.0.0/exovista/copy.py +197 -0
- exovista-1.0.0/exovista/element.py +918 -0
- exovista-1.0.0/exovista/ex_params.py +87 -0
- exovista-1.0.0/exovista/exodus_h.py +540 -0
- exovista-1.0.0/exovista/exoread.py +158 -0
- exovista-1.0.0/exovista/extension.py +307 -0
- exovista-1.0.0/exovista/file.py +4728 -0
- exovista-1.0.0/exovista/find_in_region.py +247 -0
- exovista-1.0.0/exovista/lineout.py +230 -0
- exovista-1.0.0/exovista/nc.py +131 -0
- exovista-1.0.0/exovista/netcdf.py +1036 -0
- exovista-1.0.0/exovista/parallel_file.py +1727 -0
- exovista-1.0.0/exovista/put_solution.py +54 -0
- exovista-1.0.0/exovista/region.py +370 -0
- exovista-1.0.0/exovista/similar.py +90 -0
- exovista-1.0.0/exovista/util.py +185 -0
- exovista-1.0.0/exovista/write_pyvista.py +116 -0
- exovista-1.0.0/exovista.egg-info/PKG-INFO +10 -0
- exovista-1.0.0/exovista.egg-info/SOURCES.txt +52 -0
- exovista-1.0.0/exovista.egg-info/dependency_links.txt +1 -0
- exovista-1.0.0/exovista.egg-info/entry_points.txt +2 -0
- exovista-1.0.0/exovista.egg-info/requires.txt +4 -0
- exovista-1.0.0/exovista.egg-info/top_level.txt +1 -0
- exovista-1.0.0/pyproject.toml +12 -0
- exovista-1.0.0/setup.cfg +4 -0
- exovista-1.0.0/test/conftest.py +8 -0
- exovista-1.0.0/test/data/edges.base.exo +0 -0
- exovista-1.0.0/test/data/edges.exo.4.0 +0 -0
- exovista-1.0.0/test/data/edges.exo.4.1 +0 -0
- exovista-1.0.0/test/data/edges.exo.4.2 +0 -0
- exovista-1.0.0/test/data/edges.exo.4.3 +0 -0
- exovista-1.0.0/test/data/mkmesh.gen +0 -0
- exovista-1.0.0/test/data/mkmesh.par.2.0 +0 -0
- exovista-1.0.0/test/data/mkmesh.par.2.1 +0 -0
- exovista-1.0.0/test/data/noh.exo +0 -0
- exovista-1.0.0/test/data/noh.exo.3.0 +0 -0
- exovista-1.0.0/test/data/noh.exo.3.1 +0 -0
- exovista-1.0.0/test/data/noh.exo.3.2 +0 -0
- exovista-1.0.0/test/parallel_read.py +559 -0
- exovista-1.0.0/test/parallel_write.py +18 -0
- exovista-1.0.0/test/pytest.ini +7 -0
- exovista-1.0.0/test/region.py +116 -0
- exovista-1.0.0/test/serial_read.py +558 -0
- exovista-1.0.0/test/serial_write.py +140 -0
- exovista-1.0.0/test/test.sh +2 -0
exovista-1.0.0/.flake8
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- conf -*-
|
|
2
|
+
# flake8 settings for Nevada core files.
|
|
3
|
+
#
|
|
4
|
+
# E1: Indentation
|
|
5
|
+
# - E129: visually indented line with same indent as next logical line
|
|
6
|
+
#
|
|
7
|
+
# E2: Whitespace
|
|
8
|
+
# - E203: space before :
|
|
9
|
+
# - E221: multiple spaces before operator
|
|
10
|
+
# - E241: multiple spaces after ','
|
|
11
|
+
# - E272: multiple spaces before keyword
|
|
12
|
+
#
|
|
13
|
+
# E7: Statement
|
|
14
|
+
# - E731: do not assign a lambda expression, use a def
|
|
15
|
+
#
|
|
16
|
+
# W5: Line break warning
|
|
17
|
+
# - W503: line break before binary operator
|
|
18
|
+
# - W504: line break after binary operator
|
|
19
|
+
#
|
|
20
|
+
# These are required to get the package.py files to test clean:
|
|
21
|
+
# - F999: syntax error in doctest
|
|
22
|
+
#
|
|
23
|
+
# N8: PEP8-naming
|
|
24
|
+
# - N801: class names should use CapWords convention
|
|
25
|
+
# - N813: camelcase imported as lowercase
|
|
26
|
+
# - N814: camelcase imported as constant
|
|
27
|
+
#
|
|
28
|
+
[flake8]
|
|
29
|
+
ignore = E129,E221,E241,E272,E731,W503,W504,F999,N801,N813,N814,E203,W605
|
|
30
|
+
max-line-length = 88
|
|
31
|
+
exclude = lib/nevada/external,test,__init__.py,.cache,.git,opt,third_party,lib/nevada/snl,var/nevada/py-packages,var/nevada/spack/repo/packages,docs/nevada,bin/hisread.py,bin/exoread.py,var/nevada/ci,var/nevada/distro,var/nevada/tmp,var/nevada/sandboxes,var/nevada/spack/experimental-repo,TestResults.*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Upload Python Package to PyPI when a Release is Created
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [created]
|
|
5
|
+
jobs:
|
|
6
|
+
pypi-publish:
|
|
7
|
+
name: Publish release to PyPI
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
environment:
|
|
10
|
+
name: pypi
|
|
11
|
+
url: https://pypi.org/p/exovista
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v4
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.x"
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install build
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: |
|
|
26
|
+
python -m build
|
|
27
|
+
- name: Publish package distributions to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
exovista-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright 2022 National Technology & Engineering Solutions of Sandia, LLC
|
|
2
|
+
(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
|
|
3
|
+
Government retains certain rights in this software.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
15
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
16
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
18
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
19
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
20
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
21
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
22
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
23
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
24
|
+
|
|
25
|
+
The views and conclusions contained in the software and documentation are those
|
|
26
|
+
of the authors and should not be interpreted as representing official policies,
|
|
27
|
+
either expressed or implied, of Sandia Corporation.
|
exovista-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exovista
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python wrappers to the ExodusII finite element database model
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Dist: netcdf4
|
|
7
|
+
Requires-Dist: pyvista
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: scipy
|
|
10
|
+
Dynamic: license-file
|
exovista-1.0.0/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ExoVista
|
|
2
|
+
|
|
3
|
+
An extension of the Sandia exodusii library for exporting PyVista meshes to Exodus format with user-defined element blocks and side sets. This project focuses on a simpler interface for PyVista users (e.g., meshes created via meshio or PyVista’s geometric utilities).
|
|
4
|
+
|
|
5
|
+
# Why ExoVista?
|
|
6
|
+
|
|
7
|
+
While PyVista can read and write Exodus files via meshio, this support is limited. For example, in the SIERRA toolset, element blocks are essential for defining material zones, and side sets are commonly used to assign boundary conditions. Currently, meshio cannot save side sets or user-defined element blocks.
|
|
8
|
+
|
|
9
|
+
ExoVista makes it easy to define side sets and element blocks by assigning a "region" cell array to the surface and volume meshes. Internally, the library automatically splits element blocks by cell type before exporting to an Exodus file.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
basic imports
|
|
13
|
+
```python
|
|
14
|
+
import exovista
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pyvista as pv
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Load the letter_a PyVista example (a tetrahedral volume mesh).
|
|
20
|
+
After centering the mesh, extract the surface and assign cell-wise region arrays that define element blocks and side sets in the exported Exodus file.
|
|
21
|
+
```python
|
|
22
|
+
def export_tetra_mesh():
|
|
23
|
+
volume = pv.examples.download_letter_a()
|
|
24
|
+
volume.points -= volume.center
|
|
25
|
+
surface = volume.extract_surface()
|
|
26
|
+
|
|
27
|
+
volume["region"] = 1 * (volume.cell_centers().points[:, 0] > 0)
|
|
28
|
+
volume.plot(show_edges=True, categories=True, parallel_projection=True, text="element blocks")
|
|
29
|
+
|
|
30
|
+
surface["region"] = 1 * (surface.cell_centers().points[:, 2] > 0)
|
|
31
|
+
surface.plot(show_edges=True, categories=True, parallel_projection=True, text="side sets")
|
|
32
|
+
|
|
33
|
+
exovista.write_exo("test.exo", volume, surface)
|
|
34
|
+
return None
|
|
35
|
+
```
|
|
36
|
+
<div style="display: flex; gap: 10px;">
|
|
37
|
+
<img src="https://github.com/user-attachments/assets/38b84cb6-7b45-43fa-82a4-1ae72bd81b14" width="48%">
|
|
38
|
+
<img src="https://github.com/user-attachments/assets/1140ffbf-c8b3-4e88-8db6-47c421d3fccc" width="48%">
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
This example constructs a structured cylindrical volume made of hexahedral cells.
|
|
42
|
+
As before, extract the surface and assign region arrays before exporting.
|
|
43
|
+
```python
|
|
44
|
+
def export_hex_mesh():
|
|
45
|
+
volume = pv.CylinderStructured(radius=np.linspace(1, 2, 5)).cast_to_unstructured_grid()
|
|
46
|
+
surface = volume.extract_surface()
|
|
47
|
+
|
|
48
|
+
volume["region"] = 1*(volume.cell_centers().points[:, 1] > 0) + 2*(volume.cell_centers().points[:, 2] > 0)
|
|
49
|
+
volume.plot(show_edges=True, categories=True, parallel_projection=True, text="element blocks")
|
|
50
|
+
|
|
51
|
+
surface["region"] = 1*(surface.cell_centers().points[:, 0] > 0)
|
|
52
|
+
surface.plot(show_edges=True, categories=True, parallel_projection=True, text="side sets")
|
|
53
|
+
|
|
54
|
+
exovista.write_exo("test.exo", volume, surface)
|
|
55
|
+
return None
|
|
56
|
+
```
|
|
57
|
+
<div style="display: flex; gap: 10px;">
|
|
58
|
+
<img src="https://github.com/user-attachments/assets/2cdf9a4a-fdc6-491b-9e3b-724d2d817c92" width="48%">
|
|
59
|
+
<img src="https://github.com/user-attachments/assets/7400879b-49a3-45d6-8d4f-3f4297fbf166" width="48%">
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
python -m pip install .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Copyright
|
|
69
|
+
This repo is a fork of https://github.com/sandialabs/exodusii and therefore inherits the below copyright:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Copyright 2022 National Technology & Engineering Solutions of Sandia, LLC
|
|
73
|
+
(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
|
|
74
|
+
Government retains certain rights in this software.
|
|
75
|
+
|
|
76
|
+
SCR# 2748
|
|
77
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .file import exodusii_file, ExodusIIFile, write_globals
|
|
2
|
+
from .parallel_file import parallel_exodusii_file, MFExodusIIFile
|
|
3
|
+
from .allclose import allclose
|
|
4
|
+
from .similar import similar
|
|
5
|
+
from .extension import * # noqa: F403
|
|
6
|
+
from .lineout import lineout
|
|
7
|
+
from .find_in_region import find_element_data_in_region, find_node_data_in_region
|
|
8
|
+
from .exoread import main as exoread
|
|
9
|
+
from .write_pyvista import write_exo
|
|
10
|
+
|
|
11
|
+
def File(filename, *files, mode="r"):
|
|
12
|
+
|
|
13
|
+
if mode not in "rw":
|
|
14
|
+
raise ValueError(f"Invalid Exodus file mode {mode!r}")
|
|
15
|
+
|
|
16
|
+
if mode == "r":
|
|
17
|
+
files = _find_files(filename, *files)
|
|
18
|
+
if len(files) > 1:
|
|
19
|
+
f = parallel_exodusii_file(*files)
|
|
20
|
+
elif len(files) == 1:
|
|
21
|
+
f = exodusii_file(files[0], mode="r")
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError("No files to open")
|
|
24
|
+
elif mode == "w":
|
|
25
|
+
if files:
|
|
26
|
+
raise TypeError(f"Exodus writer takes 1 file but {len(files)+1} were given")
|
|
27
|
+
f = exodusii_file(filename, mode="w")
|
|
28
|
+
|
|
29
|
+
return f
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
exo_file = File
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _find_files(*files):
|
|
36
|
+
import glob
|
|
37
|
+
|
|
38
|
+
found = []
|
|
39
|
+
for file in files:
|
|
40
|
+
globbed_files = glob.glob(file)
|
|
41
|
+
if not globbed_files:
|
|
42
|
+
raise FileNotFoundError(file)
|
|
43
|
+
found.extend(globbed_files)
|
|
44
|
+
return found
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from .util import string_kinds
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def allclose(
|
|
8
|
+
file1,
|
|
9
|
+
file2,
|
|
10
|
+
atol=1.0e-12,
|
|
11
|
+
rtol=1.0e-12,
|
|
12
|
+
dimensions=True,
|
|
13
|
+
variables=True,
|
|
14
|
+
verbose=False,
|
|
15
|
+
):
|
|
16
|
+
"""Returns True if two files are data-wise equal within a tolerance."""
|
|
17
|
+
|
|
18
|
+
from .file import ExodusIIFile
|
|
19
|
+
|
|
20
|
+
if not isinstance(file1, ExodusIIFile):
|
|
21
|
+
file1 = ExodusIIFile(file1)
|
|
22
|
+
if not isinstance(file2, ExodusIIFile):
|
|
23
|
+
file2 = ExodusIIFile(file2)
|
|
24
|
+
|
|
25
|
+
ach = allclose_helper(file1, file2, atol, rtol, verbose=verbose)
|
|
26
|
+
|
|
27
|
+
ach.set_dimensions(dimensions)
|
|
28
|
+
ach.set_variables(variables)
|
|
29
|
+
|
|
30
|
+
ach.compare_dimensions()
|
|
31
|
+
ach.compare_variables()
|
|
32
|
+
|
|
33
|
+
return True if not ach.errors else False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _allclose(a, b, atol=1.0e-08, rtol=1.0e-05):
|
|
37
|
+
"""Returns True if two arrays are element - wise equal within a tolerance."""
|
|
38
|
+
|
|
39
|
+
def compatible_dtypes(x, y):
|
|
40
|
+
if x.dtype.kind in string_kinds and y.dtype.kind not in string_kinds:
|
|
41
|
+
return False
|
|
42
|
+
if y.dtype.kind in string_kinds and x.dtype.kind not in string_kinds:
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def compatible_shape(x, y):
|
|
47
|
+
return x.shape == y.shape
|
|
48
|
+
|
|
49
|
+
if a is None or b is None:
|
|
50
|
+
if a != b:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"cannot compare type {type(a).__name__} to type {type(b).__name__}"
|
|
53
|
+
)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
x = np.asanyarray(a)
|
|
57
|
+
y = np.asanyarray(b)
|
|
58
|
+
|
|
59
|
+
if not compatible_dtypes(x, y):
|
|
60
|
+
raise TypeError(
|
|
61
|
+
f"adiff not supported for input dtypes {x.dtype.kind} and {y.dtype.kind}"
|
|
62
|
+
)
|
|
63
|
+
elif not compatible_shape(x, y):
|
|
64
|
+
raise ValueError("input arguments must have same shape")
|
|
65
|
+
|
|
66
|
+
if x.dtype.kind in string_kinds:
|
|
67
|
+
x = sorted(x.flatten())
|
|
68
|
+
y = sorted(y.flatten())
|
|
69
|
+
return all([x[i] == y[i] for i in range(len(x))])
|
|
70
|
+
|
|
71
|
+
return np.allclose(x, y, atol=atol, rtol=rtol)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class allclose_helper:
|
|
75
|
+
def __init__(self, file1, file2, atol, rtol, print_threshold=10, verbose=False):
|
|
76
|
+
self.file1 = file1
|
|
77
|
+
self.file2 = file2
|
|
78
|
+
self.atol = atol
|
|
79
|
+
self.rtol = rtol
|
|
80
|
+
self.print_threshold = print_threshold
|
|
81
|
+
self.verbose = verbose
|
|
82
|
+
|
|
83
|
+
self._dims_to_compare = None
|
|
84
|
+
self._vars_to_compare = None
|
|
85
|
+
|
|
86
|
+
self.errors = 0
|
|
87
|
+
|
|
88
|
+
def log_error(self, message, end="\n"):
|
|
89
|
+
if self.verbose:
|
|
90
|
+
sys.stderr.write(f"==> Error: {message}{end}")
|
|
91
|
+
self.errors += 1
|
|
92
|
+
|
|
93
|
+
def all_dimensions(self):
|
|
94
|
+
dims1 = self.file1.dimension_names()
|
|
95
|
+
dims2 = self.file2.dimension_names()
|
|
96
|
+
return sorted(set(dims1 + dims2))
|
|
97
|
+
|
|
98
|
+
def all_variables(self):
|
|
99
|
+
vars1 = self.file1.variable_names()
|
|
100
|
+
vars2 = self.file2.variable_names()
|
|
101
|
+
return sorted(set(vars1 + vars2))
|
|
102
|
+
|
|
103
|
+
def set_dimensions(self, dimensions):
|
|
104
|
+
if dimensions is True:
|
|
105
|
+
dimensions = self.all_dimensions()
|
|
106
|
+
elif dimensions is None or dimensions is False:
|
|
107
|
+
dimensions = []
|
|
108
|
+
elif isinstance(dimensions, str):
|
|
109
|
+
dimensions = [dimensions]
|
|
110
|
+
if dimensions[0].startswith("~"):
|
|
111
|
+
# Compare all - except those being negated
|
|
112
|
+
skip = dimensions[0][1:].split("|")
|
|
113
|
+
dimensions = [_ for _ in self.all_dimensions() if _ not in skip]
|
|
114
|
+
if not isinstance(dimensions, (list, tuple)):
|
|
115
|
+
raise ValueError("Expected list of dimensions to compare")
|
|
116
|
+
self.validate_dimensions(dimensions)
|
|
117
|
+
self._dims_to_compare = tuple(dimensions)
|
|
118
|
+
|
|
119
|
+
def set_variables(self, variables):
|
|
120
|
+
if variables is True:
|
|
121
|
+
variables = self.all_variables()
|
|
122
|
+
elif variables is None or variables is False:
|
|
123
|
+
variables = []
|
|
124
|
+
elif isinstance(variables, str):
|
|
125
|
+
variables = [variables]
|
|
126
|
+
if variables[0].startswith("~"):
|
|
127
|
+
# Compare all - except those being negated
|
|
128
|
+
skip = variables[0][1:].split("|")
|
|
129
|
+
variables = [_ for _ in self.all_variables() if _ not in skip]
|
|
130
|
+
if not isinstance(variables, (list, tuple)):
|
|
131
|
+
raise ValueError("Expected list of variables to compare")
|
|
132
|
+
self.validate_variables(variables)
|
|
133
|
+
self._vars_to_compare = tuple(variables)
|
|
134
|
+
|
|
135
|
+
def validate_dimensions(self, dimensions):
|
|
136
|
+
invalid = 0
|
|
137
|
+
valid_dimensions = self.all_dimensions()
|
|
138
|
+
for dimension in dimensions:
|
|
139
|
+
if dimension not in valid_dimensions:
|
|
140
|
+
self.log_error(f"{dimension} is not a valid dimension")
|
|
141
|
+
invalid += 1
|
|
142
|
+
if invalid:
|
|
143
|
+
raise ValueError("One or more invalid dimensions")
|
|
144
|
+
|
|
145
|
+
def validate_variables(self, variables):
|
|
146
|
+
invalid = 0
|
|
147
|
+
valid_variables = self.all_variables()
|
|
148
|
+
for variable in variables:
|
|
149
|
+
if variable not in valid_variables:
|
|
150
|
+
self.log_error(f"{variable} is not a valid variable")
|
|
151
|
+
invalid += 1
|
|
152
|
+
if invalid:
|
|
153
|
+
raise ValueError("One or more invalid variables")
|
|
154
|
+
|
|
155
|
+
def compare_dimensions(self):
|
|
156
|
+
if self._dims_to_compare is None:
|
|
157
|
+
raise ValueError("Dimensions to compare must first be set")
|
|
158
|
+
for dimension in self._dims_to_compare:
|
|
159
|
+
self.compare_dimension(dimension)
|
|
160
|
+
self._dims_to_compare = None
|
|
161
|
+
|
|
162
|
+
def compare_dimension(self, dim):
|
|
163
|
+
if dim not in self.file1.fh.dimensions:
|
|
164
|
+
self.log_error(f"dimension {dim} not found in {self.file1.filename}")
|
|
165
|
+
return
|
|
166
|
+
elif dim not in self.file2.fh.dimensions:
|
|
167
|
+
self.log_error(f"dimension {dim} not found in {self.file2.filename}")
|
|
168
|
+
return
|
|
169
|
+
elif dim in ("time_step",):
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
dim1 = self.file1.get_dimension(dim)
|
|
173
|
+
dim2 = self.file1.get_dimension(dim)
|
|
174
|
+
if not _allclose(dim1, dim2, atol=self.atol, rtol=self.rtol):
|
|
175
|
+
err = StringIO()
|
|
176
|
+
err.write(
|
|
177
|
+
f"{self.file1.filename}::{dim} != "
|
|
178
|
+
f"{self.file2.filename}::{dim} ({dim1} != {dim2})"
|
|
179
|
+
)
|
|
180
|
+
self.log_error(err.getvalue())
|
|
181
|
+
|
|
182
|
+
def compare_variables(self):
|
|
183
|
+
if self._vars_to_compare is None:
|
|
184
|
+
raise ValueError("Variables to compare must first be set")
|
|
185
|
+
for variable in self._vars_to_compare:
|
|
186
|
+
self.compare_variable(variable)
|
|
187
|
+
self._vars_to_compare = None
|
|
188
|
+
|
|
189
|
+
def compare_variable(self, var):
|
|
190
|
+
if var not in self.file1.fh.variables:
|
|
191
|
+
self.log_error(f"variable {var} not found in {self.file1.filename}")
|
|
192
|
+
return
|
|
193
|
+
elif var not in self.file2.fh.variables:
|
|
194
|
+
self.log_error(f"variable {var} not found in {self.file2.filename}")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
var1 = self.file1.get_variable(var)
|
|
198
|
+
var2 = self.file2.get_variable(var)
|
|
199
|
+
if var1.shape != var2.shape:
|
|
200
|
+
err = StringIO()
|
|
201
|
+
err.write(
|
|
202
|
+
f"{self.file1.filename}::{var}.shape != "
|
|
203
|
+
f"{self.file2.filename}::{var}.shape "
|
|
204
|
+
f"({var1.shape} != {var2.shape})"
|
|
205
|
+
)
|
|
206
|
+
self.log_error(err.getvalue())
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
if not _allclose(var1, var2, atol=self.atol, rtol=self.rtol):
|
|
210
|
+
err = StringIO()
|
|
211
|
+
s1 = np.array2string(var1, threshold=self.print_threshold)
|
|
212
|
+
s2 = np.array2string(var2, threshold=self.print_threshold)
|
|
213
|
+
err.write(
|
|
214
|
+
f"{self.file1.filename}::{var} != "
|
|
215
|
+
f"{self.file2.filename}::{var} ({s1} != {s2})"
|
|
216
|
+
)
|
|
217
|
+
self.log_error(err.getvalue())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def env_boolean(var, default=None):
|
|
6
|
+
value = os.getenv(var, default)
|
|
7
|
+
if value is None:
|
|
8
|
+
return default
|
|
9
|
+
if value.lower() in ("false", "0", "off", ""):
|
|
10
|
+
return False
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def initialize_config():
|
|
15
|
+
cfg = SimpleNamespace()
|
|
16
|
+
cfg.use_netcdf4_if_possible = env_boolean("EXODUSII_USE_NETCDF4", default="on")
|
|
17
|
+
cfg.debug = env_boolean("EXODUSII_DEBUG", default="off")
|
|
18
|
+
return cfg
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
config = initialize_config()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from .exodus_h import types
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
copy_extra_set_info = False
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def copy(source, target):
|
|
8
|
+
"""Copy the source ExodusII file to the target"""
|
|
9
|
+
target.put_init(
|
|
10
|
+
source.title(),
|
|
11
|
+
source.num_dimensions(),
|
|
12
|
+
source.num_nodes(),
|
|
13
|
+
source.num_elems(),
|
|
14
|
+
source.num_blks(),
|
|
15
|
+
source.num_node_sets(),
|
|
16
|
+
source.num_side_sets(),
|
|
17
|
+
num_edge=source.num_edges(),
|
|
18
|
+
num_edge_blk=source.num_edge_blk(),
|
|
19
|
+
num_face=source.num_faces(),
|
|
20
|
+
num_face_blk=source.num_face_blk(),
|
|
21
|
+
)
|
|
22
|
+
copy_mesh(source, target)
|
|
23
|
+
copy_variable_params(source, target)
|
|
24
|
+
copy_variable_histories(source, target)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def copy_mesh(source, target):
|
|
28
|
+
"""Copies ExodusII mesh information from source to target"""
|
|
29
|
+
|
|
30
|
+
target.put_coord_names(source.get_coord_names())
|
|
31
|
+
target.put_coords(source.get_coords())
|
|
32
|
+
|
|
33
|
+
for block in source.elem_blocks():
|
|
34
|
+
target.put_element_block(
|
|
35
|
+
block.id,
|
|
36
|
+
block.elem_type,
|
|
37
|
+
block.num_block_elems,
|
|
38
|
+
block.num_elem_nodes,
|
|
39
|
+
num_faces_per_elem=block.num_elem_faces,
|
|
40
|
+
num_edges_per_elem=block.num_elem_edges,
|
|
41
|
+
num_attr=block.num_elem_attrs,
|
|
42
|
+
)
|
|
43
|
+
for type in (types.node, types.edge, types.face):
|
|
44
|
+
conn = source.get_element_conn(block.id, type=type)
|
|
45
|
+
if conn is not None:
|
|
46
|
+
target.put_element_conn(block.id, conn, type=type)
|
|
47
|
+
|
|
48
|
+
if target.num_faces():
|
|
49
|
+
for block in source.face_blocks():
|
|
50
|
+
target.put_face_block(
|
|
51
|
+
block.id,
|
|
52
|
+
block.elem_type,
|
|
53
|
+
block.num_block_faces,
|
|
54
|
+
block.num_face_nodes,
|
|
55
|
+
block.num_face_attrs,
|
|
56
|
+
)
|
|
57
|
+
conn = source.get_face_block_conn(block.id)
|
|
58
|
+
target.put_face_conn(block.id, conn)
|
|
59
|
+
|
|
60
|
+
if target.num_edges():
|
|
61
|
+
for block in source.edge_blocks():
|
|
62
|
+
target.put_edge_block(
|
|
63
|
+
block.id,
|
|
64
|
+
block.elem_type,
|
|
65
|
+
block.num_block_edges,
|
|
66
|
+
block.num_edge_nodes,
|
|
67
|
+
block.num_edge_attrs,
|
|
68
|
+
)
|
|
69
|
+
conn = source.get_edge_block_conn(block.id)
|
|
70
|
+
target.put_edge_conn(block.id, conn)
|
|
71
|
+
|
|
72
|
+
for ns in source.node_sets():
|
|
73
|
+
target.put_node_set_param(ns.id, ns.num_nodes, ns.num_dist_facts)
|
|
74
|
+
target.put_node_set_name(ns.id, ns.name)
|
|
75
|
+
target.put_node_set_nodes(ns.id, ns.nodes)
|
|
76
|
+
if ns.num_dist_facts:
|
|
77
|
+
target.put_node_set_dist_fact(ns.id, ns.dist_facts)
|
|
78
|
+
|
|
79
|
+
for es in source.edge_sets():
|
|
80
|
+
target.put_edge_set_param(
|
|
81
|
+
es.id, es.num_edges, es.num_nodes_per_edge, es.num_dist_facts
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
for fs in source.face_sets():
|
|
85
|
+
target.put_face_set_param(
|
|
86
|
+
fs.id, fs.num_faces, fs.num_nodes_per_face, fs.num_dist_facts
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
for es in source.elem_sets():
|
|
90
|
+
target.put_element_set_param(es.id, es.num_elems, es.num_dist_facts)
|
|
91
|
+
|
|
92
|
+
for ss in source.side_sets():
|
|
93
|
+
target.put_side_set_param(ss.id, ss.num_sides, ss.num_dist_facts)
|
|
94
|
+
target.put_side_set_name(ss.id, ss.name)
|
|
95
|
+
target.put_side_set_sides(ss.id, ss.elems, ss.sides)
|
|
96
|
+
if ss.num_dist_facts:
|
|
97
|
+
target.put_side_set_dist_fact(ss.id, ss.dist_facts)
|
|
98
|
+
|
|
99
|
+
node_id_map = source.get_node_id_map()
|
|
100
|
+
target.put_node_id_map(node_id_map)
|
|
101
|
+
|
|
102
|
+
elem_id_map = source.get_element_id_map()
|
|
103
|
+
target.put_element_id_map(elem_id_map)
|
|
104
|
+
|
|
105
|
+
if source.num_edges():
|
|
106
|
+
edge_id_map = source.get_edge_id_map()
|
|
107
|
+
target.put_edge_id_map(edge_id_map)
|
|
108
|
+
|
|
109
|
+
if source.num_faces():
|
|
110
|
+
face_id_map = source.get_face_id_map()
|
|
111
|
+
target.put_face_id_map(face_id_map)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def copy_variable_params(source, target):
|
|
115
|
+
|
|
116
|
+
target.put_global_variable_params(source.get_global_variable_number())
|
|
117
|
+
target.put_global_variable_names(source.get_global_variable_names())
|
|
118
|
+
|
|
119
|
+
target.put_node_variable_params(source.get_node_variable_number())
|
|
120
|
+
target.put_node_variable_names(source.get_node_variable_names())
|
|
121
|
+
|
|
122
|
+
if source.get_element_variable_number() is not None:
|
|
123
|
+
target.put_element_variable_params(source.get_element_variable_number())
|
|
124
|
+
target.put_element_variable_names(source.get_element_variable_names())
|
|
125
|
+
table = source.get_element_variable_truth_table()
|
|
126
|
+
if table is not None:
|
|
127
|
+
target.put_element_variable_truth_table(table)
|
|
128
|
+
|
|
129
|
+
if source.get_face_variable_number() is not None:
|
|
130
|
+
target.put_face_variable_params(source.get_face_variable_number())
|
|
131
|
+
target.put_face_variable_names(source.get_face_variable_names())
|
|
132
|
+
table = source.get_face_variable_truth_table()
|
|
133
|
+
if table is not None:
|
|
134
|
+
target.put_face_variable_truth_table(table)
|
|
135
|
+
|
|
136
|
+
if source.get_edge_variable_number() is not None:
|
|
137
|
+
target.put_edge_variable_params(source.get_edge_variable_number())
|
|
138
|
+
target.put_edge_variable_names(source.get_edge_variable_names())
|
|
139
|
+
table = source.get_edge_variable_truth_table()
|
|
140
|
+
if table is not None:
|
|
141
|
+
target.put_edge_variable_truth_table(table)
|
|
142
|
+
|
|
143
|
+
target.put_node_set_variable_params(source.get_node_set_variable_number())
|
|
144
|
+
target.put_node_set_variable_names(source.get_node_set_variable_names())
|
|
145
|
+
|
|
146
|
+
if copy_extra_set_info:
|
|
147
|
+
|
|
148
|
+
target.put_edge_set_variable_params(source.get_edge_set_variable_number())
|
|
149
|
+
target.put_edge_set_variable_names(source.get_edge_set_variable_names())
|
|
150
|
+
|
|
151
|
+
target.put_face_set_variable_params(source.get_face_set_variable_number())
|
|
152
|
+
target.put_face_set_variable_names(source.get_face_set_variable_names())
|
|
153
|
+
|
|
154
|
+
target.put_element_set_variable_params(source.get_element_set_variable_number())
|
|
155
|
+
target.put_element_set_variable_names(source.get_element_set_variable_names())
|
|
156
|
+
|
|
157
|
+
target.put_side_set_variable_params(source.get_side_set_variable_number())
|
|
158
|
+
target.put_side_set_variable_names(source.get_side_set_variable_names())
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def copy_variable_histories(source, target):
|
|
162
|
+
|
|
163
|
+
for (time_step, time) in enumerate(source.get_times(), start=1):
|
|
164
|
+
target.put_time(time_step, time)
|
|
165
|
+
|
|
166
|
+
values = source.get_all_global_variable_values()
|
|
167
|
+
target.put_global_variable_values(None, values)
|
|
168
|
+
|
|
169
|
+
for name in source.get_node_variable_names():
|
|
170
|
+
values = source.get_node_variable_values(name)
|
|
171
|
+
target.put_node_variable_values(None, name, values)
|
|
172
|
+
|
|
173
|
+
for block_id in source.get_element_block_ids():
|
|
174
|
+
for name in source.get_element_variable_names():
|
|
175
|
+
values = source.get_element_variable_values(block_id, name)
|
|
176
|
+
target.put_element_variable_values(None, block_id, name, values)
|
|
177
|
+
|
|
178
|
+
for block_id in source.get_edge_block_ids():
|
|
179
|
+
for name in source.get_edge_variable_names():
|
|
180
|
+
values = source.get_edge_variable_values(block_id, name)
|
|
181
|
+
target.put_edge_variable_values(None, block_id, name, values)
|
|
182
|
+
|
|
183
|
+
for block_id in source.get_face_block_ids():
|
|
184
|
+
for name in source.get_face_variable_names():
|
|
185
|
+
values = source.get_face_variable_values(block_id, name)
|
|
186
|
+
target.put_face_variable_values(None, block_id, name, values)
|
|
187
|
+
|
|
188
|
+
if copy_extra_set_info:
|
|
189
|
+
for set_id in source.get_node_set_ids():
|
|
190
|
+
for name in source.get_node_set_variable_names():
|
|
191
|
+
values = source.get_node_set_variable_values(block_id, name)
|
|
192
|
+
target.put_node_set_variable_values(None, block_id, name, values)
|
|
193
|
+
|
|
194
|
+
for set_id in source.get_side_set_ids():
|
|
195
|
+
for name in source.get_side_set_variable_names():
|
|
196
|
+
values = source.get_side_set_variable_values(block_id, name)
|
|
197
|
+
target.put_side_set_variable_values(None, block_id, name, values)
|