3@brief The canonical PICGRID text grid format: its coordinate precision, header reader and
4 coordinate reader, shared by the conductor and the generators.
6Not a command. `core.py`, `grid.gen`, `ic.gen`, `profile.gen`, `reduce.gen` and `spectra.gen`
7import it from `picurv_cli/`, as they import `petsc_binary.py`. A PICGRID file is the token
8`PICGRID`, a block count, one `IM JM KM` node-count row per block, then every block's node
9coordinates in block order, one `x y z` row per node with i fastest, then j, then k. Blank lines
10and lines beginning with `#` are not part of the content.
17PICGRID_FLOAT_FORMAT =
".17e"
22 @brief Format a coordinate with round-trip-safe binary64 precision.
23 @param[in] value Coordinate value.
24 @return Formatted coordinate.
26 return format(value, PICGRID_FLOAT_FORMAT)
31 @brief Yield the content lines of a PICurv text payload, skipping blank and `#` lines.
32 @param[in] stream Open text stream.
33 @return Generator of `(line_number, stripped_line)` pairs, numbered from 1.
35 for lineno, raw
in enumerate(stream, start=1):
37 if line
and not line.startswith(
"#"):
43 @brief Consume a PICGRID header from a content-line iterator.
44 @details Leaves the iterator at the first coordinate row.
45 @param[in] lines Iterator from content_lines().
46 @param[in] path File path, for errors.
47 @param[in] expected_nblk Optional block count the caller requires.
48 @return List of `(IM, JM, KM)` node-count tuples, one per block.
49 @throws ValueError on a malformed header.
52 _, first_token = next(lines)
54 raise ValueError(f
"Grid file '{path}' is empty.")
55 if first_token !=
"PICGRID":
56 raise ValueError(f
"Grid file '{path}' must begin with the canonical PICGRID header token.")
59 _, nblk_line = next(lines)
62 raise ValueError(f
"Grid file '{path}' missing block count after PICGRID header.")
64 raise ValueError(f
"Invalid block count '{nblk_line}' in grid file '{path}'.")
66 raise ValueError(f
"Grid file '{path}' has non-positive block count: {nblk}.")
67 if expected_nblk
is not None and nblk != expected_nblk:
68 raise ValueError(f
"Grid file block count mismatch: case expects {expected_nblk}, grid contains {nblk}.")
71 for bi
in range(nblk):
73 lineno, dim_line = next(lines)
75 raise ValueError(f
"Grid file '{path}' missing dimensions for block {bi}.")
76 parts = dim_line.split()
78 raise ValueError(f
"Invalid dimensions line at {path}:{lineno}. Expected 3 integers, got: '{dim_line}'.")
80 im, jm, km = (int(parts[0]), int(parts[1]), int(parts[2]))
82 raise ValueError(f
"Invalid dimensions line at {path}:{lineno}. Non-integer values: '{dim_line}'.")
83 if im <= 0
or jm <= 0
or km <= 0:
84 raise ValueError(f
"Invalid block dimensions at {path}:{lineno}: ({im}, {jm}, {km}). Must be > 0.")
85 dims.append((im, jm, km))
91 @brief Read only the header of a PICGRID file.
92 @param[in] path PICGRID path.
93 @param[in] expected_nblk Optional block count the caller requires.
94 @return List of `(IM, JM, KM)` node-count tuples, one per block.
95 @throws ValueError on a missing file or malformed header.
97 if not os.path.isfile(path):
98 raise ValueError(f
"Grid file not found: {path}")
99 with open(path,
"r", encoding=
"utf-8")
as stream:
105 @brief Parse one coordinate row into three finite floats.
106 @param[in] lineno Line number, for errors.
107 @param[in] line Stripped content line.
108 @param[in] path File path, for errors.
110 @throws ValueError when the row is not three finite numbers.
114 raise ValueError(f
"Invalid coordinate row at {path}:{lineno}. Expected 3 floats, got: '{line}'.")
116 point = (float(parts[0]), float(parts[1]), float(parts[2]))
118 raise ValueError(f
"Invalid coordinate row at {path}:{lineno}. Non-numeric values: '{line}'.")
119 if not all(math.isfinite(value)
for value
in point):
120 raise ValueError(f
"Invalid coordinate row at {path}:{lineno}. Coordinates must be finite: '{line}'.")
126 @brief Read every block of a PICGRID file as node-coordinate arrays.
127 @details For consumers that build cell geometry, so each axis needs at least two nodes.
128 @param[in] path PICGRID path.
129 @param[in] expected_nblk Optional block count the caller requires.
130 @return List of `{"dims": (IM, JM, KM), "coords": array shaped (KM, JM, IM, 3)}`.
131 @throws ValueError on a missing or malformed file.
133 from periodic_spectral
import require_numpy
136 if not os.path.isfile(path):
137 raise ValueError(f
"Grid file not found: {path}")
138 with open(path,
"r", encoding=
"utf-8")
as stream:
142 for block_idx, (im, jm, km)
in enumerate(dims):
143 if min(im, jm, km) < 2:
144 raise ValueError(f
"PICGRID block {block_idx} dimensions {(im, jm, km)} are too small.")
146 for lineno, line
in lines:
148 if len(coords) == im * jm * km:
150 if len(coords) != im * jm * km:
151 raise ValueError(f
"Grid file '{path}' missing coordinates for block {block_idx}.")
152 blocks.append({
"dims": (im, jm, km),
153 "coords": numpy.asarray(coords, dtype=float).reshape((km, jm, im, 3))})
154 if next(lines,
None)
is not None:
155 raise ValueError(f
"Grid file '{path}' has trailing coordinate rows.")
require_numpy()
Import NumPy only for commands that need numeric reductions.
content_lines(stream)
Yield the content lines of a PICurv text payload, skipping blank and # lines.
list read_picgrid_blocks(str path, int expected_nblk=None)
Read every block of a PICGRID file as node-coordinate arrays.
list read_picgrid_header(lines, str path, int expected_nblk=None)
Consume a PICGRID header from a content-line iterator.
tuple parse_coordinate_row(int lineno, str line, str path)
Parse one coordinate row into three finite floats.
list read_picgrid_dimensions(str path, int expected_nblk=None)
Read only the header of a PICGRID file.
str format_picgrid_coordinate(float value)
Format a coordinate with round-trip-safe binary64 precision.