dynars
1from dynars._dynars import ( 2 Binout, 3 BinoutEditor, 4 Cmp, 5 D3plot, 6 D3plotEditor, 7 D3plotWriter, 8 Deck, 9 Entity, 10 File, 11 Finding, 12 FsiforField, 13 IncludeNode, 14 InterfaceField, 15 IntforWriter, 16 Keyword, 17 KeywordFile, 18 Predicate, 19 Report, 20 Rule, 21 Severity, 22 StateBlock, 23 Workspace, 24 open_d3plot, 25 parse_binout, 26 parse_deck, 27 parse_include_tree, 28 parse_keyword_file, 29 write_keyword, 30) 31from dynars import injury, kw, signal # submodules: dynars.signal.*, dynars.injury.* 32from dynars.binout import build_series 33 34# Friendly alias for the result-block enum (dynars.Block.Displacement). 35Block = StateBlock 36from dynars.schema import ( 37 Card, 38 Float, 39 FloatArray, 40 Int, 41 IntArray, 42 Str, 43 keyword, 44 parse_keyword, 45 rows, 46) 47 48__all__ = [ 49 "IncludeNode", 50 "KeywordFile", 51 "parse_include_tree", 52 "parse_keyword_file", 53 "write_keyword", 54 # deck: parse once, validate + navigate 55 "parse_deck", 56 "Deck", 57 "Entity", 58 "Keyword", 59 "File", 60 # workspace: batch-parse/validate many decks sharing *INCLUDEs 61 "Workspace", 62 "Rule", 63 "Predicate", 64 "Report", 65 "Finding", 66 "Severity", 67 "Cmp", 68 # binary results 69 "Binout", 70 "D3plot", 71 "D3plotWriter", 72 "D3plotEditor", 73 "IntforWriter", 74 "BinoutEditor", 75 "StateBlock", 76 "InterfaceField", 77 "FsiforField", 78 "Block", 79 "build_series", 80 "parse_binout", 81 "open_d3plot", 82 # post-processing submodules: dynars.signal.*, dynars.injury.* 83 "signal", 84 "injury", 85 # schema authoring 86 "keyword", 87 "parse_keyword", 88 "rows", 89 "Card", 90 "Int", 91 "Float", 92 "Str", 93 "IntArray", 94 "FloatArray", 95 "kw", 96]
A parsed LS-DYNA keyword file: keyword blocks with lossless round-trip, columnar bulk access as numpy arrays, and block-level editing.
Replace a block's keyword. Cards are re-emitted in free format; the rest of the file stays byte-for-byte intact.
Parse a keyword against a user-defined schema, returning a dict of columns (numpy arrays for numeric fields, lists for strings).
Low-level: the Python @keyword class layer lowers to this. cards
is a list of cards, each a list of (name, type, width, count) field
tuples where type is "int" | "float" | "str".
Parse an LS-DYNA keyword file and return the include tree.
Releases the GIL during parsing so other Python threads can run.
Parse an LS-DYNA keyword file into an editable [PyKeywordFile].
Releases the GIL during the file read and block split.
Author a single-keyword deck from columnar arrays and write it to path —
the inverse of the columnar read path (Deck.table / parse_keyword).
columns maps field name to a numpy int64/float64 array (or a list[str]),
all the same length N; the cards are emitted in dict order, in free (comma)
format, straight from Rust with no per-row Python objects. Writes
*KEYWORD / *<name> / N card lines / *END. Rows are formatted in
parallel with the GIL released.
Parse a deck (root + all includes) once and return a navigable [PyDeck].
A parsed LS-DYNA deck (root + all includes). Parse once with
[parse_deck], then validate (validate) and navigate
(part, material, …) off the same object — no second parse. The
resolution indices are built lazily on first use.
Run a set of rules over this deck, reusing the parse. No default
rule set — pass the rules you want (e.g. Rule.references_resolve()).
The *PART with this id, or None if none is defined. Ids are global
(post-*INCLUDE_TRANSFORM); the sign is ignored, so |id| also matches.
The *MAT with this id, or None if none is defined. Ids are global
(post-*INCLUDE_TRANSFORM); the sign is ignored, so |id| also matches.
The *SECTION with this id, or None if none is defined. Ids are global
(post-*INCLUDE_TRANSFORM); the sign is ignored, so |id| also matches.
The *DEFINE_CURVE with this id, or None if none is defined. Ids are
global (post-*INCLUDE_TRANSFORM); the sign is ignored, so |id| also
matches.
Bulk columnar read of every occurrence of keyword across the whole
deck (root + includes) using the built-in library, as a dict of numpy
arrays (numeric fields) and string lists. The fast path alongside
part/material/… navigation — the deck is the one columnar entry,
include-aware (unlike the per-file KeywordFile). Raises KeyError if
the keyword isn't in the built-in library (use table_with).
Bulk columnar read across the whole deck against a user-defined schema —
the escape hatch for a keyword not in the built-in library. cards is a
list of cards, each a list of (name, type, width, count) field tuples;
type is "int" | "float" | "str".
Register a user schema for a keyword the built-in library doesn't cover,
so navigation (keywords, part, …) gets named, typed field access for
it. cards is a list of cards, each a list of (name, type, width,
count) field tuples; type is "int" | "float" | "str". Keyed by
canonical base — registering the same base twice replaces it.
The deck's parsed files as File handles — the root first, then each
*INCLUDEd file in include order. File-first navigation: pick a file,
then read/edit its keywords.
The first parsed file whose path ends with suffix (e.g. "sub.k" or
"mesh/part.k"), as a File — the way into a specific include. None
if nothing matches.
A handle to one entity: typed field access, source location, and
reference-following. Keeps its [PyDeck] alive.
Overwrite a named field in place, preserving every other byte of the
deck. Returns "in_place", or "reflowed" if the value overflowed its
fixed column (that one card re-emitted in free format), or None if the
field isn't found. Realise the change with the owning file's write /
to_bytes (deck.file(...) / deck.files()).
Follow this entity's first field that references a *MAT to that
material, or None if there is no such field or it doesn't resolve.
Follow this entity's first field that references a *SECTION to that
section, or None if there is no such field or it doesn't resolve.
Follow this entity's first field that references an *EOS to that equation
of state, or None if there is no such field or it doesn't resolve.
Follow this entity's first field that references a *HOURGLASS to that
hourglass definition, or None if there is no such field or it doesn't
resolve.
The effective *INCLUDE_TRANSFORM offsets applied to this entity's file
(composed down the include chain) as a dict {"idnoff": …, "ideoff": …},
or None if it sits in the root or a plain *INCLUDE. These are the
shifts that turn the file-local ids into the global ones id reports.
A keyword occurrence — one *KEYWORD block — reached by name
(Deck.keywords) or through a file (File.keywords). Read fields, and edit
one in place with set_field. Keeps its [PyDeck] alive.
Read a field by name (case-insensitive) → int / float / str. Honours a
user schema registered with register_schema.
One parsed file in a deck — the root or one *INCLUDE instance. Lists its
keywords (file-first navigation) and reads/writes its (possibly edited)
bytes. Keeps its [PyDeck] alive.
The keyword occurrences in this file as Keyword handles. With name,
only occurrences of that keyword (canonical-base match); without it,
every block in file order.
Low-level, schema-free field write: overwrite (block, row, col) passing
the fields' fixed column widths (e.g. [10]*8). For keywords dynars
ships no schema for; otherwise prefer Keyword.set_field. Returns
"in_place" / "reflowed", or None if the card/field is out of range.
An in-process batch context: parse and validate many decks that share
*INCLUDEs against one shared cache, so common files (mesh, materials) are
read, parsed, and indexed once no matter how many decks include them.
import dynars
ws = dynars.Workspace()
decks = ws.parse_decks(["variant_a/main.k", "variant_b/main.k"])
reports = ws.validate_decks(decks, [
dynars.Rule.references_resolve(),
dynars.Rule.duplicate_ids(),
])
print(ws.stats()) # {'files_parsed': ..., 'files_reused': ..., ...}
The decks handed back are ordinary Decks — validate or navigate them
individually too; a deck from a workspace reuses the shared indices whether
you call validate_decks or its own .validate(...).
Parse one deck (root + all includes), reusing any file this workspace has
already read. Returns a navigable Deck.
Parse several decks in one batch, sharing all file work across them.
Returns a list of Decks in input order; raises RuntimeError naming the
first root that fails to parse.
Validate several decks in parallel against the shared cache. Returns one
Report per deck, in order. Warms the shared definition index first, then
runs rules over every deck concurrently — a shared file's id and
connectivity indices are built once, not per deck.
A built-in declarative rule. Constructed in Python, executed in Rust.
Flag every occurrence of keyword — the keyword must not appear at all.
Flag any occurrence of keyword whose field equals one of values.
For every occurrence of keyword, if when holds (or is omitted), the
require predicate must also hold; occurrences that violate it are flagged.
Cross-keyword referential integrity: every id reference resolves (PART.mid → *MAT, *LOAD.lcid → *DEFINE_CURVE, …). Does not check element connectivity.
As references_resolve, and additionally checks that every element's
nodes are defined. Heavy on large meshes.
No two labelled definition entities of the same kind share an id (two *PART pid=5, duplicate *MAT/*SET/*SECTION/*DEFINE_CURVE ids, …). Compared on logical ids, so *INCLUDE_TRANSFORM instances don't collide.
Library definition entities nothing references — dead *MAT, *SECTION, *DEFINE_CURVE, *SET, *DEFINE_COORDINATE, … Reports at Warning severity.
A boolean predicate tree over card fields (tier 2). Evaluated in Rust.
The result of a validation run.
One rule violation with a clickable file:line.
How serious a violation is.
A comparison operator — used instead of a stringly "eq"/"ne".
LS-DYNA binout reader: walk the LSDA tree by path, read channels as numpy.
Read from the binout (lasso-style). Segments may be separate args or one
list: read("nodout", …) or read(["nodout", …]).
read()/read("nodout")→list[str]of children (a branch lists its variable names).read("nodout", "x_acceleration")→ the variable aggregated across all output states:float64[T, nodes](or[T]for a scalar-per-state channel such astime).read("nodout", "x_acceleration", id=1000001)→ one entity's historyfloat64[T];ids=[…]→float64[T, k]. Select by entity name (from the branchlegend) instead withname=/names=[…]. Selectors decode only the requested column(s) — no full matrix.KeyErrorif absent.- A literal leaf path —
read("nodout", "d000001", "x_acceleration")— returns that single state's raw array.
For the structured form (time + ids together) use read_states; for the
raw child listing of any directory use channels.
Read many paths concurrently (lock-free, GIL released), returning a
list aligned with paths. Faster than a Python loop when pulling
many channels: the reads run in parallel across cores.
Read a time-history: {"time": float64[T], "values": float64[T], "channel": str}.
time is read from the sibling time array, or synthesized as 0..T.
Aggregate a per-state variable across all state dirs, as a dict.
Full matrix (default): {"time": float64[T], "values": float64[T, C],
"ids": int64[C], "n_steps": int, "n_channels": int}.
With a selector — id/ids (by entity id) or name/names (by the
branch legend) — only those columns are decoded (no full matrix):
{"time": float64[T], "values": float64[T] or [T, k], "ids": int64[k]},
where values is 1-D for a single id/name. KeyError if absent,
ValueError if more than one selector is given. The bare-array
counterpart is read(branch, var, …).
LS-DYNA d3plot reader: control block, geometry, per-state nodal results.
Deformed node coordinates at state (0-based) as an (NUMNP, 3) array.
Deformed node coordinates for every state as a (num_states, NUMNP, 3)
array — one call, one allocation, instead of a Python loop over
node_coordinates.
Per-node displacement magnitude at state as a (NUMNP,) array.
Shell connectivity: (conn, parts) where conn is (n_shells, 4)
one-based node numbers and parts is (n_shells,).
Extract one interface-force field's values from the per-segment block
as (n_states, n_segments, k). field is an InterfaceField (intfor)
or FsiforField (FSIFOR) — no magic strings. states selects states
like block. Raises if the field isn't present in this file.
Generic result extraction: any result block across all states as an
(n_states, count, vars) numpy array in native precision. block is
a StateBlock (or its lowercase name string). Node blocks are
(…, 3); element blocks return the solver's raw packed per-entity
layout — reshape by integration points/layers as needed. Raises if
the block is absent.
states selects which states to return: None = all; an int (or
negative int, from the end) = one state; a sequence of ints = those
states. Selecting fewer states reads/copies only those.
When the selected states are single-precision and contiguous within one family file, the result is a zero-copy read-only view straight over the memory map (no allocation, no copy). Otherwise the selection is copied into a fresh array (in parallel for large blocks).
Whether this is an interface-force (intfor) database. In an intfor
file the contact segments are in the shell slot:
block(StateBlock.Shell) gives (n_states, n_segments, nv2d) and
shell_connectivity() the segment nodes; split the per-segment values
with interface_fields.
Whether this is an FSIFOR (ALE) interface-force file — use
FsiforField values with segment_field.
Build a single-precision d3plot from a mesh + per-state nodal results.
Add shell elements: conn is (M, 4) one-based node ids; parts is
an optional (M,) part id per shell (default 1).
Add solid elements: conn is (M, 8) one-based node ids; parts is
an optional (M,) part id per solid (default 1).
Set user IDs written into the NARBS numbering section (default 1..N): node IDs (length N), shell/solid element IDs, and part IDs.
Append a state: time, deformed coords disp (N,3), and optional
vel/acc (N,3). Velocity/acceleration presence is fixed by the
first state added.
Emit double-precision (8-byte word) output when double is true (default
single precision). Values are stored as f64, so this is lossless.
Number of through-thickness integration points packed into each shell
result record (MAXINT). set_shell_results' innermost dim must be
n_layers * per_layer.
Add beam elements: conn is (M, 3) one-based node ids (end, end,
orientation); parts optional (M,) part id (default 1).
Add thick-shell elements: conn is (M, 8) one-based node ids; parts
optional (M,) part id (default 1).
Per-solid result block, (n_states, n_solids, vars) — the same raw
layout D3plot.solid_results() returns. Sets NV3D.
Per-shell result block, (n_states, n_shells, vars). Sets NV2D.
Per-beam result block, (n_states, n_beams, vars). Sets NV1D.
Per-thick-shell result block, (n_states, n_tshells, vars). Sets NV3DT.
A whole-model global scalar history (one value per state) at field's slot.
A per-node thermal/auxiliary field history at field. See
D3plotWriter.set_node_field (Rust) for the per-node widths.
Per-element deletion flags for one family (block): (n_states, n_elem),
1 = alive, 0 = deleted (mdlopt 2).
Per-node deletion flags: (n_states, numnp), 1 = alive (mdlopt 1).
User beam / thick-shell element IDs for the NARBS numbering section.
SPH particles: materials (P,), n_vars per particle, results
(n_states, P, n_vars).
Airbag / CPM: geometry (n_airbags, n_geom_vars), chamber state
(n_states, n_airbags, n_airbag_vars), particle state (n_states,
n_particles, n_particle_vars).
Rigid bodies: bodies is a list of (part_id, node_ids, active_node_ids);
motion is (n_states, n_bodies, k) (k = 12 with a rigid road, else 24).
Rigid road: node ids (P,), node coords (P, 3), segments a list of
(road_id, [4 node ids per segment]), motion (n_states, n_roads, 6).
Edit an existing d3plot family in place: overwrite node coordinates or a result block at chosen states; everything else is preserved byte-for-byte.
Overwrite a result block (a StateBlock) at state with data
(count, vars) — the same layout D3plot.block(...) returns.
Build an interface-force (intfor) file: contact segments + per-state
nodal motion + per-segment interface values.
Add contact segments: conn is (M, 4) one-based node ids; ids is
an optional (M,) segment id per segment (default 1..M).
Declare the intfor per-segment field layout (nv2d = their sum).
Editable binout: a directory tree of typed datasets that writes back a complete LSDA file. Construct new, or open an existing file and mutate it (save re-emits the whole file).
A per-entity result block in a state. Node blocks are (N, 3); element blocks
are (N, vars) where vars is the solver's packed per-element layout
(stresses, plastic strain, history variables, per integration point/layer) —
returned raw for the caller to reshape.
This is the single source of truth for block identity: the reader/writer use
it directly, and (with the python feature) it is exported to Python as the
StateBlock enum — no magic strings.
A per-segment field in an interface-force (intfor) file. These partition
the segment result block (StateBlock::Shell) in this order and sum to
nv2d. Exported to Python as the InterfaceField enum — no magic strings.
A per-segment field in an FSIFOR (ALE interface-force) file. These are
single-value fields in this fixed order; the file carries as many as |nv2d|.
Exported to Python as the FsiforField enum.
46def build_series( 47 branch: str, 48 ids: Sequence[int] | np.ndarray, 49 channels: Mapping[str, np.ndarray], 50 *, 51 times: Sequence[float] | np.ndarray | None = None, 52 cycles: Sequence[int] | np.ndarray | None = None, 53 labels: Sequence[str] | None = None, 54 title: str | None = None, 55 editor: BinoutEditor | None = None, 56) -> BinoutEditor: 57 """Build a binout time-series branch and return the :class:`BinoutEditor`. 58 59 Parameters 60 ---------- 61 branch: 62 Top-level group name, e.g. ``"nodout"``, ``"elout"``, ``"rcforc"``. 63 ids: 64 Entity ids (nodes/elements/…), 1-D, length ``nent``. 65 channels: 66 Mapping of channel name -> array. A 2-D array is ``[nstate, nent]`` (one 67 row per state); a 1-D array is treated as a per-state scalar ``[nstate]``. 68 Numeric arrays keep their dtype; pass float32 to match LS-DYNA output. 69 times, cycles: 70 Optional per-state ``time`` (float64) and ``cycle`` (int32), length 71 ``nstate``. ``time`` is strongly recommended — post-processors key on it. 72 labels: 73 Optional per-entity text labels (written as the 80-char ``legend`` block). 74 title: 75 Optional run title (80-char metadata field). 76 editor: 77 Add the branch to an existing editor instead of a fresh one (so several 78 branches can share one file). 79 80 Returns 81 ------- 82 BinoutEditor 83 Ready to ``.write(path)``. 84 """ 85 e = editor if editor is not None else BinoutEditor() 86 ids = np.asarray(ids) 87 if ids.ndim != 1: 88 raise ValueError("ids must be 1-D") 89 nent = int(ids.shape[0]) 90 91 # Infer the number of states from the first 2-D channel, else from times. 92 nstate = None 93 for arr in channels.values(): 94 a = np.asarray(arr) 95 if a.ndim == 2: 96 nstate = int(a.shape[0]) 97 break 98 if nstate is None: 99 if times is not None: 100 nstate = int(np.asarray(times).shape[0]) 101 elif cycles is not None: 102 nstate = int(np.asarray(cycles).shape[0]) 103 else: 104 raise ValueError("cannot infer number of states: pass a 2-D channel, times, or cycles") 105 106 # Validate shapes up front so we fail before writing anything. 107 for name, arr in channels.items(): 108 a = np.asarray(arr) 109 if a.ndim == 2 and a.shape != (nstate, nent): 110 raise ValueError(f"channel {name!r} has shape {a.shape}, expected {(nstate, nent)}") 111 if a.ndim == 1 and a.shape[0] != nstate: 112 raise ValueError(f"scalar channel {name!r} has length {a.shape[0]}, expected {nstate}") 113 if a.ndim not in (1, 2): 114 raise ValueError(f"channel {name!r} must be 1-D (scalar/state) or 2-D (state x entity)") 115 116 # metadata 117 e.set([branch, "metadata", "ids"], ids.astype(np.int64)) 118 e.set([branch, "metadata", "legend"], _legend_int8(labels, nent)) 119 if title is not None: 120 e.set([branch, "metadata", "title"], _fixed_int8(title, 80)) 121 122 # per-state dirs 123 times = None if times is None else np.asarray(times, dtype=np.float64) 124 cycles = None if cycles is None else np.asarray(cycles, dtype=np.int32) 125 for s in range(nstate): 126 d = f"d{s + 1:06d}" 127 if times is not None: 128 e.set([branch, d, "time"], np.array([times[s]], dtype=np.float64)) 129 if cycles is not None: 130 e.set([branch, d, "cycle"], np.array([cycles[s]], dtype=np.int32)) 131 for name, arr in channels.items(): 132 a = np.asarray(arr) 133 if a.ndim == 2: 134 e.set([branch, d, name], np.ascontiguousarray(a[s])) 135 else: 136 e.set([branch, d, name], np.asarray(a[s]).reshape(1)) 137 return e
Build a binout time-series branch and return the BinoutEditor.
Parameters
branch:
Top-level group name, e.g. "nodout", "elout", "rcforc".
ids:
Entity ids (nodes/elements/…), 1-D, length nent.
channels:
Mapping of channel name -> array. A 2-D array is [nstate, nent] (one
row per state); a 1-D array is treated as a per-state scalar [nstate].
Numeric arrays keep their dtype; pass float32 to match LS-DYNA output.
times, cycles:
Optional per-state time (float64) and cycle (int32), length
nstate. time is strongly recommended — post-processors key on it.
labels:
Optional per-entity text labels (written as the 80-char legend block).
title:
Optional run title (80-char metadata field).
editor:
Add the branch to an existing editor instead of a fresh one (so several
branches can share one file).
Returns
BinoutEditor
Ready to .write(path).
Open an LS-DYNA binout for reading (mirrors [PyBinout::new]).
Open an LS-DYNA d3plot for reading (mirrors [PyD3plot::new]).
93def keyword(name: str, repeat: bool = True): 94 """Register a class as the schema for keyword `name`. 95 96 Fields directly on the class define a single card; a ``cards = [...]`` list 97 of card classes defines a multi-card layout. `repeat=True` (the default) 98 parses the card group repeatedly over the block body — the common case for 99 `*NODE`, `*ELEMENT_*`, multiple `*PART`s, etc. Pass `repeat=False` only to 100 read a single entity per block. 101 """ 102 103 def deco(cls: type) -> type: 104 cls._dynars_schema = _lower(name, cls, repeat) 105 _REGISTRY[name.upper()] = cls 106 return cls 107 108 return deco
Register a class as the schema for keyword name.
Fields directly on the class define a single card; a cards = [...] list
of card classes defines a multi-card layout. repeat=True (the default)
parses the card group repeatedly over the block body — the common case for
*NODE, *ELEMENT_*, multiple *PARTs, etc. Pass repeat=False only to
read a single entity per block.
111def parse_keyword(kf, schema): 112 """Parse a keyword from `kf` (a `KeywordFile`) and return a dict of columns: 113 numpy arrays for numeric fields, lists for string fields. 114 115 `schema` may be a `@keyword` class, or a keyword name (str). A name is first 116 looked up among your registered `@keyword` classes, then falls back to 117 dynars' built-in keyword library (generated from the pyDYNA field database), 118 so common keywords parse with no declaration at all. 119 """ 120 if isinstance(schema, str): 121 cls = _REGISTRY.get(schema.upper()) 122 if cls is None: 123 return kf.parse_builtin(schema) # fall back to the built-in library 124 else: 125 cls = schema 126 name, cards, repeat = cls._dynars_schema 127 return kf.parse_schema(name, cards, repeat)
Parse a keyword from kf (a KeywordFile) and return a dict of columns:
numpy arrays for numeric fields, lists for string fields.
schema may be a @keyword class, or a keyword name (str). A name is first
looked up among your registered @keyword classes, then falls back to
dynars' built-in keyword library (generated from the pyDYNA field database),
so common keywords parse with no declaration at all.
130def rows(columns): 131 """Iterate a parsed keyword's columns as per-row dicts — handy for 132 low-volume keywords (materials, sections, ...): 133 134 for mat in dynars.rows(dynars.parse_keyword(kf, "MAT_ELASTIC")): 135 print(mat["MID"], mat["E"]) 136 137 The columns stay columnar/numpy; this is a lazy view, so bulk keywords 138 should index the arrays directly rather than materialize millions of dicts. 139 """ 140 if not columns: 141 return 142 n = len(next(iter(columns.values()))) 143 for i in range(n): 144 yield {k: v[i] for k, v in columns.items()}
Iterate a parsed keyword's columns as per-row dicts — handy for low-volume keywords (materials, sections, ...):
for mat in dynars.rows(dynars.parse_keyword(kf, "MAT_ELASTIC")):
print(mat["MID"], mat["E"])
The columns stay columnar/numpy; this is a lazy view, so bulk keywords should index the arrays directly rather than materialize millions of dicts.
Base for a keyword card (one line). Subclass and assign fields in order.
44def Int(width: int) -> _Field: 45 """A signed-integer field `width` columns wide (fixed format).""" 46 return _Field("int", width)
A signed-integer field width columns wide (fixed format).
49def Float(width: int) -> _Field: 50 """A floating-point field `width` columns wide (fixed format).""" 51 return _Field("float", width)
A floating-point field width columns wide (fixed format).
54def Str(width: int) -> _Field: 55 """A string field `width` columns wide (fixed format).""" 56 return _Field("str", width)
A string field width columns wide (fixed format).
59def IntArray(count: int, width: int) -> _Field: 60 """`count` consecutive integer fields, returned as one `(N, count)` column.""" 61 return _Field("int", width, count)
count consecutive integer fields, returned as one (N, count) column.
64def FloatArray(count: int, width: int) -> _Field: 65 """`count` consecutive float fields, returned as one `(N, count)` column.""" 66 return _Field("float", width, count)
count consecutive float fields, returned as one (N, count) column.