tecio-python 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl
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.
- tecio/__init__.py +1 -1
- tecio/cli/tec2mat.py +2 -2
- tecio/cli/tecdump.py +1 -1
- tecio/cli/tecfix.py +2 -2
- tecio/cli/tecmerge.py +4 -3
- tecio/cli/teconvert.py +2 -2
- tecio/cli/tecscale.py +1 -1
- tecio/cli/tecslice.py +10 -9
- tecio/cli/tecstats.py +2 -2
- tecio/dat/_read.py +277 -35
- tecio/plt/_read.py +1 -1
- tecio/plugins/paraview/TecplotTecioReader.py +1070 -0
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/METADATA +2 -2
- tecio_python-0.2.4.dist-info/RECORD +34 -0
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/WHEEL +1 -1
- tecio_python-0.2.2.dist-info/RECORD +0 -33
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/entry_points.txt +0 -0
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/licenses/NOTICE +0 -0
- {tecio_python-0.2.2.dist-info → tecio_python-0.2.4.dist-info}/top_level.txt +0 -0
tecio/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ from importlib import metadata
|
|
|
9
9
|
try:
|
|
10
10
|
__version__ = metadata.version("tecio")
|
|
11
11
|
except metadata.PackageNotFoundError:
|
|
12
|
-
__version__ = "0.
|
|
12
|
+
__version__ = "0.2.4"
|
|
13
13
|
|
|
14
14
|
from . import cli, dat, libtecio, plt, szl
|
|
15
15
|
from ._containers import VariableList, ZoneList
|
tecio/cli/tec2mat.py
CHANGED
|
@@ -136,9 +136,9 @@ Examples:
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
See Also:
|
|
139
|
-
* :mod:`tecio.cli.teconvert
|
|
139
|
+
* :mod:`tecio.cli.teconvert` - Convert between Tecplot file formats (``.szplt``,
|
|
140
140
|
``.plt``, ``.dat``) without leaving the Tecplot ecosystem.
|
|
141
|
-
* :mod:`tecio.cli.tecdump
|
|
141
|
+
* :mod:`tecio.cli.tecdump` - Inspect the full contents and metadata of a file before
|
|
142
142
|
converting it.
|
|
143
143
|
|
|
144
144
|
Note:
|
tecio/cli/tecdump.py
CHANGED
|
@@ -70,7 +70,7 @@ Examples:
|
|
|
70
70
|
See Also:
|
|
71
71
|
* :mod:`tecio.cli.tecstats` - Compute per-zone min, max, and mean statistics without
|
|
72
72
|
printing raw values.
|
|
73
|
-
* :mod:`tecio.cli.tecfix
|
|
73
|
+
* :mod:`tecio.cli.tecfix` - Rewrite a file with invalid variable
|
|
74
74
|
arrays set to passive once bad values have been identified.
|
|
75
75
|
"""
|
|
76
76
|
|
tecio/cli/tecfix.py
CHANGED
|
@@ -66,9 +66,9 @@ Examples:
|
|
|
66
66
|
tecfix(["flow.szplt", "--output", "clean.szplt"])
|
|
67
67
|
|
|
68
68
|
See Also:
|
|
69
|
-
* :mod:`tecio.cli.tecdump
|
|
69
|
+
* :mod:`tecio.cli.tecdump` - Inspect file contents and metadata to
|
|
70
70
|
identify zones or variables that may require fixing.
|
|
71
|
-
* :mod:`tecio.cli.tecstats
|
|
71
|
+
* :mod:`tecio.cli.tecstats` - Compute per-zone min, max, and mean
|
|
72
72
|
statistics without printing raw values.
|
|
73
73
|
|
|
74
74
|
Note:
|
tecio/cli/tecmerge.py
CHANGED
|
@@ -83,10 +83,10 @@ Examples:
|
|
|
83
83
|
tecmerge(["part1.szplt", "part2.szplt", "--output", "combined.szplt"])
|
|
84
84
|
|
|
85
85
|
See Also:
|
|
86
|
-
* :mod:`tecio.cli.tecextract
|
|
86
|
+
* :mod:`tecio.cli.tecextract` - Extract a zone/variable subset from a single file —
|
|
87
87
|
the inverse of merging.
|
|
88
|
-
* :mod:`tecio.cli.tecsplit
|
|
89
|
-
* :mod:`tecio.cli.tecslice
|
|
88
|
+
* :mod:`tecio.cli.tecsplit` - Split a file into separate grid and solution files.
|
|
89
|
+
* :mod:`tecio.cli.tecslice` - Extract planar slices from volumetric zone data.
|
|
90
90
|
|
|
91
91
|
"""
|
|
92
92
|
|
|
@@ -155,6 +155,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
155
155
|
),
|
|
156
156
|
)
|
|
157
157
|
parser.add_argument(
|
|
158
|
+
"-f",
|
|
158
159
|
"--force",
|
|
159
160
|
action="store_true",
|
|
160
161
|
default=False,
|
tecio/cli/teconvert.py
CHANGED
|
@@ -71,7 +71,7 @@ Examples:
|
|
|
71
71
|
teconvert(["-dat", "-o", "flow.dat", "flow.szplt"])
|
|
72
72
|
|
|
73
73
|
See Also:
|
|
74
|
-
:mod:`tecio.cli.tecextract
|
|
74
|
+
:mod:`tecio.cli.tecextract` - Extract a zone/variable subset while simultaneously
|
|
75
75
|
converting format via the output file extension.
|
|
76
76
|
"""
|
|
77
77
|
|
|
@@ -132,7 +132,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
132
132
|
" Explicit output path\n"
|
|
133
133
|
" $ teconvert -plt -o /tmp/out.plt flow.dat\n"
|
|
134
134
|
" Overwrite existing\n"
|
|
135
|
-
"
|
|
135
|
+
" $ teconvert --force -dat flow.szplt\n"
|
|
136
136
|
),
|
|
137
137
|
formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter(
|
|
138
138
|
prog, width=70, max_help_position=24
|
tecio/cli/tecscale.py
CHANGED
|
@@ -80,7 +80,7 @@ Examples:
|
|
|
80
80
|
tecscale(["-variable", "Pressure", "-scale", "1e-3", "flow.szplt"])
|
|
81
81
|
|
|
82
82
|
See Also:
|
|
83
|
-
:mod:`tecio.cli.teconvert
|
|
83
|
+
:mod:`tecio.cli.teconvert` - Convert between Tecplot file formats without applying
|
|
84
84
|
any variable transformation.
|
|
85
85
|
|
|
86
86
|
"""
|
tecio/cli/tecslice.py
CHANGED
|
@@ -114,10 +114,10 @@ Examples:
|
|
|
114
114
|
tecslice(["-i", "::2", "-o", "thinned.szplt", "flow.szplt"])
|
|
115
115
|
|
|
116
116
|
See Also:
|
|
117
|
-
* :mod:`tecio.cli.tecextract
|
|
117
|
+
* :mod:`tecio.cli.tecextract` - Extract a subset of zones or variables by index
|
|
118
118
|
rather than by positional slice.
|
|
119
|
-
* :mod:`tecio.cli.tecsplit
|
|
120
|
-
* :mod:`tecio.cli.tecmerge
|
|
119
|
+
* :mod:`tecio.cli.tecsplit` - Split a file into separate grid and solution files.
|
|
120
|
+
* :mod:`tecio.cli.tecmerge` - Merge zones from multiple files into a single output.
|
|
121
121
|
"""
|
|
122
122
|
|
|
123
123
|
from __future__ import annotations
|
|
@@ -277,7 +277,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
277
277
|
description=(
|
|
278
278
|
# -|--------------------|---------------------------------------------|
|
|
279
279
|
"Slice a Tecplot file along IJK indices (ordered zones) and/or\n"
|
|
280
|
-
"solution time (all zone types).\n"
|
|
280
|
+
"solution time (all zone types).\n\n"
|
|
281
281
|
"Slice notation: start:end:skip (any component may be omitted)."
|
|
282
282
|
),
|
|
283
283
|
epilog=(
|
|
@@ -329,9 +329,9 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
329
329
|
ijk = parser.add_argument_group(
|
|
330
330
|
"IJK slicing",
|
|
331
331
|
(
|
|
332
|
-
|
|
333
|
-
"Indices are 1-based and
|
|
334
|
-
"Applied only to ordered (structured) zones."
|
|
332
|
+
# -|--------------------|---------------------------------------------|
|
|
333
|
+
"Colon-notation slices for structured axes. Indices are 1-based and\n"
|
|
334
|
+
"inclusive. Applied only to ordered (structured) zones."
|
|
335
335
|
),
|
|
336
336
|
)
|
|
337
337
|
for axis in ("i", "j", "k"):
|
|
@@ -351,8 +351,9 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
351
351
|
tslice = parser.add_argument_group(
|
|
352
352
|
"solution-time slicing",
|
|
353
353
|
(
|
|
354
|
-
|
|
355
|
-
"Strand-0 zones are always
|
|
354
|
+
# -|--------------------|---------------------------------------------|
|
|
355
|
+
"Applied per strand to all zone types. Strand-0 zones are always\n"
|
|
356
|
+
"written unchanged."
|
|
356
357
|
),
|
|
357
358
|
)
|
|
358
359
|
tslice.add_argument(
|
tecio/cli/tecstats.py
CHANGED
|
@@ -85,9 +85,9 @@ Examples:
|
|
|
85
85
|
tecstats(["-zone", "2", "-variable", "3", "flow.szplt"])
|
|
86
86
|
|
|
87
87
|
See Also:
|
|
88
|
-
* :mod:`tecio.cli.tecdump
|
|
88
|
+
* :mod:`tecio.cli.tecdump` - Inspect the full contents and metadata of a file,
|
|
89
89
|
including auxiliary data and raw variable arrays.
|
|
90
|
-
* :mod:`tecio.cli.tecfix
|
|
90
|
+
* :mod:`tecio.cli.tecfix` - Rewrite a file with invalid variable arrays set to
|
|
91
91
|
passive once bad values have been identified via statistics.
|
|
92
92
|
|
|
93
93
|
"""
|
tecio/dat/_read.py
CHANGED
|
@@ -137,6 +137,10 @@ _STR_TO_DATATYPE: dict[str, DataType] = {
|
|
|
137
137
|
#: Values per line for Write data blocks.
|
|
138
138
|
_VALUES_PER_LINE: int = 5
|
|
139
139
|
|
|
140
|
+
# Safety cap on the number of grow-and-reparse iterations the vectorized block readers
|
|
141
|
+
# will attempt before giving up and falling back to the tolerant token-by-token parser
|
|
142
|
+
_MAX_FAST_BLOCK_ITERATIONS: int = 64
|
|
143
|
+
|
|
140
144
|
|
|
141
145
|
# ======================================================================================
|
|
142
146
|
# Shared internal helpers
|
|
@@ -174,6 +178,42 @@ def _strip_comment(line: str) -> str:
|
|
|
174
178
|
return line[:idx].rstrip() if idx >= 0 else line.rstrip()
|
|
175
179
|
|
|
176
180
|
|
|
181
|
+
def _is_float_block_boundary(line: str) -> bool:
|
|
182
|
+
r"""Return ``True`` if a stripped *line* ends a float data block early.
|
|
183
|
+
|
|
184
|
+
Shared by :meth:`Read._read_float_block_slow` (line-by-line) and
|
|
185
|
+
:meth:`Read._read_float_block_fast` (bulk, on parse failure) so the two readers
|
|
186
|
+
agree on exactly where a block stops.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> _is_float_block_boundary("ZONE T=\\"next\\"")
|
|
190
|
+
True
|
|
191
|
+
"""
|
|
192
|
+
upper = line.lstrip().upper()
|
|
193
|
+
if not upper:
|
|
194
|
+
return False
|
|
195
|
+
if upper.split("=")[0].split()[0] == "ZONE":
|
|
196
|
+
return True
|
|
197
|
+
return (
|
|
198
|
+
upper.startswith(("DATASETAUXDATA", "VARAUXDATA"))
|
|
199
|
+
or upper.startswith("TITLE")
|
|
200
|
+
and "=" in upper
|
|
201
|
+
or upper.startswith("VARIABLES")
|
|
202
|
+
and "=" in upper
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _is_int_block_boundary(line: str) -> bool:
|
|
207
|
+
r"""Return ``True`` if a stripped *line* ends an int (connectivity) block.
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
>>> _is_int_block_boundary("DATASETAUXDATA foo=\\"bar\\"")
|
|
211
|
+
True
|
|
212
|
+
"""
|
|
213
|
+
upper = line.lstrip().upper()
|
|
214
|
+
return upper.startswith(("ZONE", "DATASETAUXDATA"))
|
|
215
|
+
|
|
216
|
+
|
|
177
217
|
def _infer_data_type(arr: npt.NDArray) -> DataType:
|
|
178
218
|
"""Return the most appropriate :class:`DataType` for *arr*'s dtype.
|
|
179
219
|
|
|
@@ -569,38 +609,99 @@ def _apply_varauxdata(line: str, var_auxdata_list: list) -> None:
|
|
|
569
609
|
class _LineBuffer:
|
|
570
610
|
"""Peekable iterator over stripped, comment-free text lines.
|
|
571
611
|
|
|
612
|
+
Also exposes a small *raw* interface (:meth:`take_raw`, :meth:`position`,
|
|
613
|
+
:meth:`seek`) used only by the vectorized numeric-block readers
|
|
614
|
+
(:meth:`Read._read_float_block_fast`/:meth:`Read._read_int_block_fast`). Those
|
|
615
|
+
readers bypass per-line comment-stripping for speed and instead detect the rare line
|
|
616
|
+
that *isn't* plain numeric data (a comment, a header keyword) by letting NumPy's
|
|
617
|
+
parser fail on it, then falling back to this class's ordinary stripped-line
|
|
618
|
+
interface. Keeping both interfaces on one object means the fast path can hand back
|
|
619
|
+
an exact resume position for that fallback via :meth:`position`/:meth:`seek`.
|
|
620
|
+
|
|
572
621
|
Example:
|
|
573
622
|
>>> buf = _LineBuffer(lines)
|
|
574
623
|
"""
|
|
575
624
|
|
|
625
|
+
__slots__ = ("_lines", "_peeked", "_peeked_pos", "_pos")
|
|
626
|
+
|
|
576
627
|
def __init__(self, lines: list[str]) -> None:
|
|
577
628
|
self._lines: list[str] = lines
|
|
578
629
|
self._pos: int = 0
|
|
630
|
+
# One-line lookahead cache so a peek_stripped() immediately followed by
|
|
631
|
+
# next_stripped() strips comments/whitespace only once instead of twice.
|
|
632
|
+
self._peeked: str | None = None
|
|
633
|
+
self._peeked_pos: int = -1
|
|
579
634
|
|
|
580
635
|
def has_more(self) -> bool:
|
|
581
636
|
"""Return ``True`` if there are unconsumed lines."""
|
|
582
|
-
return self._pos < len(self._lines)
|
|
637
|
+
return self._peeked is not None or self._pos < len(self._lines)
|
|
583
638
|
|
|
584
639
|
def peek_stripped(self) -> str:
|
|
585
640
|
"""Return the next non-blank stripped line without consuming it."""
|
|
641
|
+
if self._peeked is not None:
|
|
642
|
+
return self._peeked
|
|
586
643
|
pos = self._pos
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
stripped =
|
|
644
|
+
n = len(self._lines)
|
|
645
|
+
while pos < n:
|
|
646
|
+
stripped = _strip_comment(self._lines[pos]).strip()
|
|
647
|
+
pos += 1
|
|
590
648
|
if stripped:
|
|
649
|
+
self._peeked = stripped
|
|
650
|
+
self._peeked_pos = pos
|
|
591
651
|
return stripped
|
|
592
|
-
|
|
652
|
+
self._peeked = ""
|
|
653
|
+
self._peeked_pos = pos
|
|
593
654
|
return ""
|
|
594
655
|
|
|
595
656
|
def next_stripped(self) -> str:
|
|
596
657
|
"""Consume and return the next non-blank stripped line."""
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return
|
|
658
|
+
if self._peeked is None:
|
|
659
|
+
self.peek_stripped()
|
|
660
|
+
value = self._peeked or ""
|
|
661
|
+
self._pos = self._peeked_pos
|
|
662
|
+
self._peeked = None
|
|
663
|
+
self._peeked_pos = -1
|
|
664
|
+
return value
|
|
665
|
+
|
|
666
|
+
def take_raw(self, count: int) -> list[str]:
|
|
667
|
+
"""Consume and return up to *count* **unprocessed** source lines.
|
|
668
|
+
|
|
669
|
+
Unlike :meth:`next_stripped`, this does not strip comments, skip blank lines, or
|
|
670
|
+
strip whitespace -- it is a raw slice of the underlying line list, advancing the
|
|
671
|
+
position by exactly how many lines were returned (fewer than *count* at end of
|
|
672
|
+
file). Any pending :meth:`peek_stripped` lookahead is discarded first, since
|
|
673
|
+
this call always resumes from the true unconsumed position.
|
|
674
|
+
|
|
675
|
+
Intended only for the numeric fast-path readers, which feed the result straight
|
|
676
|
+
to a whitespace-tolerant NumPy parser and don't need per-line preprocessing.
|
|
677
|
+
|
|
678
|
+
Example:
|
|
679
|
+
>>> lines = buf.take_raw(64)
|
|
680
|
+
"""
|
|
681
|
+
self._peeked = None
|
|
682
|
+
self._peeked_pos = -1
|
|
683
|
+
end = min(self._pos + count, len(self._lines))
|
|
684
|
+
out = self._lines[self._pos : end]
|
|
685
|
+
self._pos = end
|
|
686
|
+
return out
|
|
687
|
+
|
|
688
|
+
def position(self) -> int:
|
|
689
|
+
"""Return an opaque marker for the current position.
|
|
690
|
+
|
|
691
|
+
Example:
|
|
692
|
+
>>> marker = buf.position()
|
|
693
|
+
"""
|
|
694
|
+
return self._pos
|
|
695
|
+
|
|
696
|
+
def seek(self, marker: int) -> None:
|
|
697
|
+
"""Restore the position to a marker previously returned by :meth:`position`.
|
|
698
|
+
|
|
699
|
+
Example:
|
|
700
|
+
>>> buf.seek(marker)
|
|
701
|
+
"""
|
|
702
|
+
self._pos = marker
|
|
703
|
+
self._peeked = None
|
|
704
|
+
self._peeked_pos = -1
|
|
604
705
|
|
|
605
706
|
|
|
606
707
|
# ======================================================================================
|
|
@@ -706,20 +807,23 @@ class ReadAuxData:
|
|
|
706
807
|
class ReadVariable:
|
|
707
808
|
"""Variable metadata and data for one zone parsed from an ASCII DAT file.
|
|
708
809
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
>>> var = ReadVariable(name, data, value_location, ...)
|
|
810
|
+
Args:
|
|
811
|
+
zone_index: 1-based index of the zone this variable belongs to.
|
|
812
|
+
var_index: 1-based dataset variable index.
|
|
713
813
|
"""
|
|
714
814
|
|
|
715
815
|
def __init__(
|
|
716
816
|
self,
|
|
817
|
+
zone_index: int,
|
|
818
|
+
var_index: int,
|
|
717
819
|
name: str,
|
|
718
820
|
data: npt.NDArray | None,
|
|
719
821
|
value_location: ValueLocation = ValueLocation.NODAL,
|
|
720
822
|
is_passive: bool = False,
|
|
721
823
|
shared_zone: int | None = None,
|
|
722
824
|
) -> None:
|
|
825
|
+
self.zone_index: int = zone_index
|
|
826
|
+
self.var_index: int = var_index
|
|
723
827
|
self._name: str = name
|
|
724
828
|
self._data: npt.NDArray | None = data
|
|
725
829
|
self._value_location: ValueLocation = value_location
|
|
@@ -844,6 +948,7 @@ class ReadZone:
|
|
|
844
948
|
"""Zone data parsed from a Tecplot ASCII DAT file.
|
|
845
949
|
|
|
846
950
|
Attributes:
|
|
951
|
+
zone_index: 1-based dataset zone index.
|
|
847
952
|
datapacking: :class:`~tecio.libtecio.DataPacking` member reflecting the
|
|
848
953
|
``DATAPACKING`` keyword found in the zone header (``BLOCK`` or
|
|
849
954
|
``POINT``). The data arrays are identical either way; this attribute
|
|
@@ -853,14 +958,15 @@ class ReadZone:
|
|
|
853
958
|
automatically resolves to the source zone's array.
|
|
854
959
|
|
|
855
960
|
Example:
|
|
856
|
-
>>> zone = ReadZone(title, zone_type, I, J, K, ...)
|
|
961
|
+
>>> zone = ReadZone(zone_index, title, zone_type, I, J, K, ...)
|
|
857
962
|
"""
|
|
858
963
|
|
|
859
964
|
def __init__(
|
|
860
965
|
self,
|
|
966
|
+
zone_index: int,
|
|
861
967
|
title: str,
|
|
862
968
|
zone_type: ZoneType,
|
|
863
|
-
I: int, # noqa
|
|
969
|
+
I: int, # noqa E741
|
|
864
970
|
J: int,
|
|
865
971
|
K: int,
|
|
866
972
|
solution_time: float,
|
|
@@ -871,6 +977,7 @@ class ReadZone:
|
|
|
871
977
|
datapacking: DataPacking = DataPacking.BLOCK,
|
|
872
978
|
shared_connectivity: int | None = None,
|
|
873
979
|
) -> None:
|
|
980
|
+
self.zone_index: int = zone_index
|
|
874
981
|
self.title: str = title
|
|
875
982
|
self.zone_type: ZoneType = zone_type
|
|
876
983
|
self.I: int = I
|
|
@@ -1052,7 +1159,6 @@ class Read:
|
|
|
1052
1159
|
release on exit. Provided for API consistency with the Write classes and to
|
|
1053
1160
|
support the ``with tecio.open(...) as r:`` pattern.
|
|
1054
1161
|
"""
|
|
1055
|
-
pass
|
|
1056
1162
|
|
|
1057
1163
|
# -- Properties --------------------------------------------------------------------
|
|
1058
1164
|
|
|
@@ -1264,9 +1370,7 @@ class Read:
|
|
|
1264
1370
|
# Stop at a new zone, top-level keyword, or the first data line.
|
|
1265
1371
|
if nxt_upper.split("=")[0].split()[0] == "ZONE":
|
|
1266
1372
|
break
|
|
1267
|
-
if nxt_upper.startswith("DATASETAUXDATA")
|
|
1268
|
-
"VARAUXDATA"
|
|
1269
|
-
):
|
|
1373
|
+
if nxt_upper.startswith(("DATASETAUXDATA", "VARAUXDATA")):
|
|
1270
1374
|
break
|
|
1271
1375
|
first_ch = nxt.lstrip()[0] if nxt.lstrip() else ""
|
|
1272
1376
|
# A data line begins with a numeric token (covers leading-dot values
|
|
@@ -1332,7 +1436,7 @@ class Read:
|
|
|
1332
1436
|
)
|
|
1333
1437
|
|
|
1334
1438
|
if zone_type == ZoneType.ORDERED:
|
|
1335
|
-
I = int(kv.get("I", "1") or "1") # noqa
|
|
1439
|
+
I = int(kv.get("I", "1") or "1") # noqa E741
|
|
1336
1440
|
J = int(kv.get("J", "1") or "1")
|
|
1337
1441
|
K = int(kv.get("K", "1") or "1")
|
|
1338
1442
|
num_nodes = I * J * K
|
|
@@ -1342,7 +1446,7 @@ class Read:
|
|
|
1342
1446
|
# spellings for the node and element counts.
|
|
1343
1447
|
num_nodes = int(kv.get("NODES", kv.get("N", "0")) or "0")
|
|
1344
1448
|
num_cells = int(kv.get("ELEMENTS", kv.get("E", "0")) or "0")
|
|
1345
|
-
I, J, K = num_nodes, num_cells, 0 # noqa
|
|
1449
|
+
I, J, K = num_nodes, num_cells, 0 # noqa E741
|
|
1346
1450
|
|
|
1347
1451
|
# Packing: DATAPACKING wins, then legacy F, else BLOCK (Tecplot default).
|
|
1348
1452
|
if "DATAPACKING" in kv:
|
|
@@ -1459,8 +1563,13 @@ class Read:
|
|
|
1459
1563
|
self._zones[src_zone_1based - 1].variable[var_idx].values
|
|
1460
1564
|
)
|
|
1461
1565
|
|
|
1566
|
+
# This zone's own 1-based index
|
|
1567
|
+
zone_index = len(self._zones) + 1
|
|
1568
|
+
|
|
1462
1569
|
read_vars = [
|
|
1463
1570
|
ReadVariable(
|
|
1571
|
+
zone_index=zone_index,
|
|
1572
|
+
var_index=idx + 1,
|
|
1464
1573
|
name=name,
|
|
1465
1574
|
data=_shaped(
|
|
1466
1575
|
var_arrays[idx],
|
|
@@ -1475,6 +1584,7 @@ class Read:
|
|
|
1475
1584
|
|
|
1476
1585
|
self._zones.append(
|
|
1477
1586
|
ReadZone(
|
|
1587
|
+
zone_index=zone_index,
|
|
1478
1588
|
title=zone_title,
|
|
1479
1589
|
zone_type=zone_type,
|
|
1480
1590
|
I=I,
|
|
@@ -1497,26 +1607,98 @@ class Read:
|
|
|
1497
1607
|
"""Read *n_values* floats from *tokens* into an intermediate float64 array.
|
|
1498
1608
|
|
|
1499
1609
|
This is a pure text-parsing primitive. Callers cast the result to each
|
|
1500
|
-
variable's real dtype
|
|
1610
|
+
variable's real dtype afterward, once individual per-variable arrays have been
|
|
1501
1611
|
split out.
|
|
1502
1612
|
|
|
1613
|
+
Dispatches to :meth:`_read_float_block_fast`, a vectorized NumPy parser that
|
|
1614
|
+
handles the overwhelming majority of (well-formed) input in a handful of C-level
|
|
1615
|
+
calls regardless of block size, and falls back to :meth:`_read_float_block_slow`
|
|
1616
|
+
-- a tolerant, token-by-token parser -- only if the fast path can't make sense
|
|
1617
|
+
of what it read (a stray comment or non-numeric token inside the block, or a
|
|
1618
|
+
boundary reached before *n_values* values were collected). The fallback re-reads
|
|
1619
|
+
from *tokens* at the position the fast path started from, so the two never
|
|
1620
|
+
disagree about what was consumed.
|
|
1621
|
+
|
|
1503
1622
|
See Also:
|
|
1504
1623
|
:meth:`_read_block_var_data`/:meth:`_read_point_var_data`.
|
|
1505
1624
|
|
|
1506
1625
|
Examples:
|
|
1507
1626
|
>>> arr = Read._read_float_block(tokens, 100)
|
|
1508
1627
|
"""
|
|
1628
|
+
if n_values <= 0:
|
|
1629
|
+
return np.empty(0, dtype=np.float64)
|
|
1630
|
+
|
|
1631
|
+
marker = tokens.position()
|
|
1632
|
+
try:
|
|
1633
|
+
arr = Read._read_float_block_fast(tokens, n_values)
|
|
1634
|
+
except ValueError:
|
|
1635
|
+
arr = None
|
|
1636
|
+
if arr is not None:
|
|
1637
|
+
return arr
|
|
1638
|
+
|
|
1639
|
+
tokens.seek(marker)
|
|
1640
|
+
return Read._read_float_block_slow(tokens, n_values)
|
|
1641
|
+
|
|
1642
|
+
@staticmethod
|
|
1643
|
+
def _read_float_block_fast(
|
|
1644
|
+
tokens: _LineBuffer, n_values: int
|
|
1645
|
+
) -> npt.NDArray | None:
|
|
1646
|
+
"""Vectorized fast path for :meth:`_read_float_block`.
|
|
1647
|
+
|
|
1648
|
+
Reads raw (unprocessed) lines in a handful of growing bulk grabs (sized from a
|
|
1649
|
+
one-line width probe, so a multi-million-value block costs a small constant
|
|
1650
|
+
number of Python-level calls). Passing ``sep`` here is the (fully supported)
|
|
1651
|
+
text-parsing mode of that function, not its deprecated binary-decoding mode.
|
|
1652
|
+
|
|
1653
|
+
Returns ``None`` (never a short array) if a block boundary is hit before
|
|
1654
|
+
*n_values* values are collected, so the caller can unambiguously fall back to
|
|
1655
|
+
the slow, boundary-aware parser. A ``ValueError`` from ``numpy.fromstring`` is
|
|
1656
|
+
allowed to propagate to the caller for the same reason.
|
|
1657
|
+
|
|
1658
|
+
Example:
|
|
1659
|
+
>>> arr = Read._read_float_block_fast(tokens, 100)
|
|
1660
|
+
|
|
1661
|
+
"""
|
|
1662
|
+
pieces: list[npt.NDArray] = []
|
|
1663
|
+
have = 0
|
|
1664
|
+
per_line: int | None = None
|
|
1665
|
+
for _ in range(_MAX_FAST_BLOCK_ITERATIONS):
|
|
1666
|
+
remaining = n_values - have
|
|
1667
|
+
if remaining <= 0:
|
|
1668
|
+
break
|
|
1669
|
+
# First grab is a single-line probe to measure the block's (typically
|
|
1670
|
+
# constant) values-per-line width
|
|
1671
|
+
n_lines = 1 if per_line is None else -(-remaining // per_line)
|
|
1672
|
+
batch = tokens.take_raw(n_lines)
|
|
1673
|
+
if not batch:
|
|
1674
|
+
return None # ran out of input before reaching n_values
|
|
1675
|
+
piece = np.fromstring("".join(batch), dtype=np.float64, sep=" ")
|
|
1676
|
+
if per_line is None:
|
|
1677
|
+
per_line = max(piece.size, 1)
|
|
1678
|
+
pieces.append(piece)
|
|
1679
|
+
have += piece.size
|
|
1680
|
+
else:
|
|
1681
|
+
return None # didn't converge; let the slow path sort it out
|
|
1682
|
+
arr = pieces[0] if len(pieces) == 1 else np.concatenate(pieces)
|
|
1683
|
+
return arr[:n_values] if arr.size >= n_values else None
|
|
1684
|
+
|
|
1685
|
+
@staticmethod
|
|
1686
|
+
def _read_float_block_slow(tokens: _LineBuffer, n_values: int) -> npt.NDArray:
|
|
1687
|
+
"""Tolerant, token-by-token fallback for :meth:`_read_float_block`.
|
|
1688
|
+
|
|
1689
|
+
Reads one stripped line at a time, converting tokens one at a time and silently
|
|
1690
|
+
skipping any that aren't valid floats, stopping at a block boundary (see
|
|
1691
|
+
:func:`_is_float_block_boundary`) even if *n_values* hasn't been reached
|
|
1692
|
+
yet. Slow by design, but guarantees a result either way.
|
|
1693
|
+
|
|
1694
|
+
Example:
|
|
1695
|
+
>>> arr = Read._read_float_block_slow(tokens, 100)
|
|
1696
|
+
|
|
1697
|
+
"""
|
|
1509
1698
|
values: list[float] = []
|
|
1510
1699
|
while len(values) < n_values and tokens.has_more():
|
|
1511
1700
|
nxt = tokens.peek_stripped()
|
|
1512
|
-
|
|
1513
|
-
if (
|
|
1514
|
-
upper.split("=")[0].split()[0] == "ZONE"
|
|
1515
|
-
or upper.startswith("DATASETAUXDATA")
|
|
1516
|
-
or upper.startswith("VARAUXDATA")
|
|
1517
|
-
or (upper.startswith("TITLE") and "=" in upper)
|
|
1518
|
-
or (upper.startswith("VARIABLES") and "=" in upper)
|
|
1519
|
-
):
|
|
1701
|
+
if _is_float_block_boundary(nxt):
|
|
1520
1702
|
break
|
|
1521
1703
|
line = tokens.next_stripped()
|
|
1522
1704
|
for tok in line.split():
|
|
@@ -1547,6 +1729,10 @@ class Read:
|
|
|
1547
1729
|
to that variable's actual declared dtype (from ``DT=``, or the spec's documented
|
|
1548
1730
|
SINGLE default) right here, where each variable's array is finalized.
|
|
1549
1731
|
|
|
1732
|
+
Reading one variable at a time (rather than all of them in a single combined
|
|
1733
|
+
call) is deliberate: :meth:`_read_float_block`'s vectorized fast path already
|
|
1734
|
+
costs only a handful of C-level calls per block regardless of size.
|
|
1735
|
+
|
|
1550
1736
|
Note:
|
|
1551
1737
|
Passive and shared variables contribute a ``None`` placeholder to the
|
|
1552
1738
|
returned list. The caller resolves shared variables against the zones parsed
|
|
@@ -1649,14 +1835,70 @@ class Read:
|
|
|
1649
1835
|
def _read_int_block(tokens: _LineBuffer, n_values: int) -> npt.NDArray:
|
|
1650
1836
|
"""Read exactly *n_values* integers from *tokens* into an int64 array.
|
|
1651
1837
|
|
|
1838
|
+
Used for connectivity (node-map) data, which can run to tens of millions of
|
|
1839
|
+
entries for large volume meshes. Follows the same vectorized-fast-path /
|
|
1840
|
+
tolerant-slow-path split as :meth:`_read_float_block`; see that method's
|
|
1841
|
+
docstring for the rationale.
|
|
1842
|
+
|
|
1652
1843
|
Examples:
|
|
1653
1844
|
>>> arr = Read._read_int_block(tokens, 24)
|
|
1654
1845
|
"""
|
|
1846
|
+
if n_values <= 0:
|
|
1847
|
+
return np.empty(0, dtype=np.int64)
|
|
1848
|
+
|
|
1849
|
+
marker = tokens.position()
|
|
1850
|
+
try:
|
|
1851
|
+
arr = Read._read_int_block_fast(tokens, n_values)
|
|
1852
|
+
except ValueError:
|
|
1853
|
+
arr = None
|
|
1854
|
+
if arr is not None:
|
|
1855
|
+
return arr
|
|
1856
|
+
|
|
1857
|
+
tokens.seek(marker)
|
|
1858
|
+
return Read._read_int_block_slow(tokens, n_values)
|
|
1859
|
+
|
|
1860
|
+
@staticmethod
|
|
1861
|
+
def _read_int_block_fast(tokens: _LineBuffer, n_values: int) -> npt.NDArray | None:
|
|
1862
|
+
"""Vectorized fast path for :meth:`_read_int_block`.
|
|
1863
|
+
|
|
1864
|
+
See :meth:`_read_float_block_fast`, which this mirrors exactly except for the
|
|
1865
|
+
target dtype.
|
|
1866
|
+
|
|
1867
|
+
Example:
|
|
1868
|
+
>>> arr = Read._read_int_block_fast(tokens, 24)
|
|
1869
|
+
"""
|
|
1870
|
+
pieces: list[npt.NDArray] = []
|
|
1871
|
+
have = 0
|
|
1872
|
+
per_line: int | None = None
|
|
1873
|
+
for _ in range(_MAX_FAST_BLOCK_ITERATIONS):
|
|
1874
|
+
remaining = n_values - have
|
|
1875
|
+
if remaining <= 0:
|
|
1876
|
+
break
|
|
1877
|
+
n_lines = 1 if per_line is None else -(-remaining // per_line)
|
|
1878
|
+
batch = tokens.take_raw(n_lines)
|
|
1879
|
+
if not batch:
|
|
1880
|
+
return None
|
|
1881
|
+
piece = np.fromstring("".join(batch), dtype=np.int64, sep=" ")
|
|
1882
|
+
if per_line is None:
|
|
1883
|
+
per_line = max(piece.size, 1)
|
|
1884
|
+
pieces.append(piece)
|
|
1885
|
+
have += piece.size
|
|
1886
|
+
else:
|
|
1887
|
+
return None
|
|
1888
|
+
arr = pieces[0] if len(pieces) == 1 else np.concatenate(pieces)
|
|
1889
|
+
return arr[:n_values] if arr.size >= n_values else None
|
|
1890
|
+
|
|
1891
|
+
@staticmethod
|
|
1892
|
+
def _read_int_block_slow(tokens: _LineBuffer, n_values: int) -> npt.NDArray:
|
|
1893
|
+
"""Tolerant, token-by-token fallback for :meth:`_read_int_block`.
|
|
1894
|
+
|
|
1895
|
+
Example:
|
|
1896
|
+
>>> arr = Read._read_int_block_slow(tokens, 24)
|
|
1897
|
+
"""
|
|
1655
1898
|
values: list[int] = []
|
|
1656
1899
|
while len(values) < n_values and tokens.has_more():
|
|
1657
1900
|
nxt = tokens.peek_stripped()
|
|
1658
|
-
|
|
1659
|
-
if upper.startswith("ZONE") or upper.startswith("DATASETAUXDATA"):
|
|
1901
|
+
if _is_int_block_boundary(nxt):
|
|
1660
1902
|
break
|
|
1661
1903
|
line = tokens.next_stripped()
|
|
1662
1904
|
for tok in line.split():
|
tecio/plt/_read.py
CHANGED
|
@@ -552,7 +552,7 @@ class ReadVariable:
|
|
|
552
552
|
|
|
553
553
|
# Byte-swap if needed so the result is always native-endian
|
|
554
554
|
if data.dtype.byteorder not in ("=", "|", np.dtype(dt).str[0]):
|
|
555
|
-
data = data.byteswap().newbyteorder()
|
|
555
|
+
data = data.byteswap().view(data.dtype.newbyteorder())
|
|
556
556
|
|
|
557
557
|
# Reshape for full reads of ordered zones, using the owning zone's dimensions
|
|
558
558
|
# and value location (they describe the on-disk layout including cell-centered
|