pycsamt.format.text#
PCSM — ASCII sibling of PCSF (pyCSAMT Common Subsurface Markup).
PCSF (pycsamt.format.io, HDF5, .pcsf) is the canonical,
machine-optimal container. PCSM (.pcsm) is a lossless, hand-editable
text projection of the exact same PCSFModel
– the same relationship netCDF (binary) has to CDL, its ncdump/
ncgen ASCII text form: PCSF stays the source of truth; PCSM is a
derived, round-trippable serialisation for scripting, teaching, diffing,
and hand inspection, not a second schema to keep in sync.
Grammar (deliberately not YAML/JSON, so a reader can be hand-written in any language with no library, the same way Occam2D/ModEM/MARE2DEM’s own native ASCII files already are):
KEYWORD valueheader lines for scalars.KEYWORD ... END_KEYWORDblocks for arrays – values may be spread across any number of lines; the terminator, not a declared count, ends the block, so a file remains easy to hand-edit (reflow, add blank lines, add/remove values) and errors are caught by comparing the collected count against the count declared earlier (NX,N_STATIONS, …) rather than silently misaligning.#starts a comment: a whole line, or the remainder of a line after data, is ignored – something none of the three native solver formats PCSM interoperates with support. The handful of genuinely free-text fields (DESCRIPTION,CREATED_BY,CREATED_AT,CRS,SURVEY_JSON,METADATA_JSON) are the one exception: their value is everything after the keyword to end of line, taken verbatim, so a value that happens to contain#is not truncated.Floats are written with Python’s
repr()(the shortest string that round-trips exactly) – unlike ModEM’s own ASCII.rhoformat (~5 significant figures), a.pcsmround-trips a resistivity volume bit-exactly back through.pcsf.
A large volume (a native grid3d, a dense mesh_unstructured
mesh) produces a large text file – the same trade-off ModEM’s own
ASCII format already accepts, inherent to any plain-text encoding of
bulk numeric data, not fixable while staying hand-editable. A path
ending in .gz is written/read gzip-compressed as a mitigation
(5.0x smaller on the real ModEM case in
examples/pcsm_conversion_demo) at the cost of no longer being
casually text-editor-readable, so it is opt-in rather than default.
Functions
|
Convert a |
|
Convert a |
|
Return a PCSF/PCSM file's |
|
Read a |
|
Read a |
|
Write a |
- pycsamt.format.text.write_pcsm(model, path, *, log10_view=False)[source]
Write a
PCSFModelto a.pcsm(ASCII) file.The same model
write_pcsf()would encode as HDF5, encoded instead as human-readable, hand-editable text. See the module docstring for the grammar.Every resistivity block carries its own encoding as an inline comment right next to the data, not only in a separate header line – e.g.
RESISTIVITY # linear ohm.m (canonical, ...)– so a reader never has to guess or scroll elsewhere to tell linear from log10. The canonicalRESISTIVITYfield is always linear ohm.m (PCSF’s own binding rule, SPEC.md section 2): this cannot be changed by an option, since a reader/consumer relies on that invariant unconditionally, and reversing a10**log10(x)round-trip is not guaranteed bit-exact the way the rest of this format’srepr()-based float round-trip is.Pass
log10_view=Trueto additionally write aRESISTIVITY_LOG10block (and, for amultilinefile, one per line) alongside it – a convenience for reading resistivity in the log space many EM inversions (Occam2D among them) actually work in. This is a write-only, clearly-labelled derived view:read_pcsm()discards it, it never becomes part of the returnedPCSFModel, and it never influences the canonicalRESISTIVITYblock. (An Occam2D-sourced model already carries its own real log10 array asRESISTIVITY_NATIVEwheneverresistivity_nativeis set – that block is unaffected by this option and is written either way.)A path ending in
.gz(e.g.model.pcsm.gz) is written gzip-compressed. Large volumes (a nativegrid3dor a densemesh_unstructuredmesh) produce large plain-text files – the same trade-off ModEM’s own ASCII.rhoformat already accepts – and gzip substantially reduces that in practice (measured on the real, bundledwilly_27freq_watex_line02_sample590,400-cell ModEM volume, seeexamples/pcsm_conversion_demo: 19.7 MB plain, 3.9 MB gzipped – 5.0x smaller, real inverted resistivity compressing far better than synthetic/random test data would – vs. 4.1 MB for the equivalent.pcsf). A gzipped file is no longer something to casually open in a text editor, so this stays opt-in rather than the default: PCSM’s purpose is hand-editability, which a compressed file gives up.- Parameters:
model (PCSFModel) – The model to serialize. Validated before anything is written.
path (path-like) – Destination file. Parent directories are created if missing. A
.gzsuffix writes a gzip-compressed file.log10_view (bool, default False) – Also write a
RESISTIVITY_LOG10convenience block (see above). Off by default so existing files/output size are unaffected unless explicitly requested.
- Returns:
The path written to.
- Return type:
Examples
>>> import numpy as np >>> from pycsamt.format import Grid2DGeometry, PCSFModel >>> from pycsamt.format.text import write_pcsm, read_pcsm >>> geometry = Grid2DGeometry(x=np.array([0.0, 100.0]), z=np.array([10.0, 50.0])) >>> model = PCSFModel( ... geometry=geometry, ... resistivity=np.array([[100.0, 120.0], [50.0, 60.0]]), ... source_backend="occam2d", ... ) >>> path = write_pcsm(model, "example.pcsm") >>> round_tripped = read_pcsm(path) >>> gz_path = write_pcsm(model, "example.pcsm.gz") >>> with_log10 = write_pcsm(model, "example_log10.pcsm", log10_view=True)
- pycsamt.format.text.read_pcsm(path)[source]
Read a
PCSFModelback from a.pcsm(ASCII) file.- Parameters:
path (path-like) – Source file.
- Returns:
Fully reconstructed and re-validated model.
- Return type:
- Raises:
ValueError – If
PCSM_VERSIONis missing, malformed, or names an unrecognised MAJOR version (seepycsamt/format/SPEC.mdsection 5), if the file is otherwise malformed, or if the reconstructed model failsPCSFModel.validate().
- pycsamt.format.text.pcsf_to_pcsm(pcsf_path, pcsm_path, *, log10_view=False)[source]
Convert a
.pcsf(HDF5) file to a.pcsm(ASCII) file.log10_viewis passed through towrite_pcsm()— see there.
- pycsamt.format.text.pcsm_to_pcsf(pcsm_path, pcsf_path)[source]
Convert a
.pcsm(ASCII) file to a.pcsf(HDF5) file.
- pycsamt.format.text.read_pcsf_or_pcsm(path)[source]
Read a
PCSFModelfrom either encoding, by extension.A single entry point for a consumer (e.g.
app/mapview’s inversion-result importer) that wants to accept whichever of the two lossless PCSF encodings (seeSPEC.mdS9) a user hands it, without duplicating the.pcsfvs..pcsm/.pcsm.gzdispatch itself. Extension is the only signal used – content is never sniffed – matching every other reader in this package.- Parameters:
path (path-like) – A
.pcsf(binary HDF5),.pcsm(ASCII), or.pcsm.gz(gzip-compressed ASCII) file.- Raises:
ValueError – If path’s extension is none of the three recognised forms.
- Return type:
- pycsamt.format.text.peek_kind(path)[source]
Return a PCSF/PCSM file’s
geometry.kindwithout loading any array data – just the root/geometry attributes for.pcsf, or theGEOMETRY_KINDheader line for.pcsm/.pcsm.gz.Meant for a caller that wants to label several candidate files (e.g. a file picker UI, deciding which are
grid2d/multilineand therefore importable by a given consumer) before committing to a fullread_pcsf_or_pcsm()load of any one of them.- Raises:
ValueError – If path’s extension is not recognised, or the kind attribute/header cannot be found (e.g. a truncated file).
- Parameters:
- Return type: