Results I/O (binary, VTK, PNG)

OpenPFC separates the kernel interface ResultsWriter from frontend implementations under include/openpfc/frontend/io/. How you attach writers depends on whether you use the JSON-driven App path or a custom main.

ResultsWriter (kernel)

include/openpfc/kernel/simulation/results_writer.hpp — abstract writer that sessions call from the on_save hook of pfc::sim::run when pfc::time::do_save is true. Implementations live in the frontend (binary/VTK) or in your app.

Session wiring

Sessions build named writers from JSON fields[] with pfc::ui::parse_result_writers_from_json (below), call pfc::apply_writer_domain(writer, field) so global size / local box come from the Field, and write each field on the on_save hook while bumping their own result counter. Unit tests can drive a writer directly with a small Field and a mock writer; no simulator object is needed.

Binary output (MPI-IO)

include/openpfc/frontend/io/binary_writer.hpp — BinaryWriter: raw binary, collective MPI-IO. Documented caveats: all ranks in the communicator must participate consistently in write() to avoid deadlock. Both BinaryWriter::set_domain and kernel BinaryReader::set_domain fail closed on invalid geometry (non-positive extents, negative offsets, or a piece outside the global box); see binary_field_io_spec.md.

Format (layout, filename printf pattern, collectives): binary_field_io_spec.md.

JSON-driven App path

simulation_wiring_writers.hpp parse_result_writers_from_json takes a ResultsWriterCatalog at the call site (e.g. default_results_writer_catalog() for built-in binary). It returns named BinaryWriters (or catalog vtk/hdf5): for each fields[] entry it uses field["data"] as the path template. $NAME and ${NAME} environment references are expanded when the writer is created (unset or empty variables are a hard error); increment templating (%04d) runs afterwards. Sessions attach those writers on the on_save hook.

Requirements in settings: saveat > 0, fields array with name and data.

VTK (ParaView / VisIt)

include/openpfc/frontend/io/vtk_writer.hpp — VTKWriter: .vti / .pvti output. Extent/origin/spacing and local point-count checks are implemented in vtk_writer_validate.hpp (pfc::io::vtk_validate), separate from XML and file I/O. For programmatic output, construct VTKWriter, set_domain, set_origin, set_spacing, then add_results_writer or call from your step loop. See examples/output/vtk-series/model.cpp and Doxygen on VTKWriter.

PNG (2D grayscale, quick look)

include/openpfc/frontend/io/png_writer.hpp — pfc::io::write_mpi_scalar_field_png_xy: gathers a single z-slab (nz == 1 globally) to rank 0 and writes an 8-bit grayscale PNG. Used for lightweight visualization (e.g. Allen–Cahn apps/allen_cahn), not the main spectral App JSON pipeline.

Collective contract: All ranks in the communicator must participate consistently. Buffer size validation is fail-closed (communicator-wide agreement via MPI_Allreduce) before the MPI_Allgather/MPI_Gatherv calls, preventing deadlocks when a rank provides an incorrectly sized local_field.

Choosing a path

Goal

Mechanism

Field dumps and post-processing

BinaryWriter + BinaryReader; retain geometry and time metadata.

Durable accepted-state restart bundles

Checkpoint publication, with metadata and accepted field bricks.

Interactive visualization

Set fields[].writer to "vtk", or use SnapshotSeries / VTKWriter in code.

Quick 2D snapshot

png_writer.hpp helpers

Crystal front and orientation

pfc::observables::planar_front_position reduces the root-mean-square of a periodic density along the two directions transverse to a chosen axis. The position is the linear crossing of a caller-supplied level, so the reported front can move by less than one grid spacing. Velocity is not a spatial derivative and does not unwrap the box: it is (x(t1) - x(t0)) / (t1 - t0) for two samples whose validity is FrontValidity::valid.

region_orientation estimates one real wave A cos(k · x + φ) from order-2 central differences and folds k ~ -k so the first nonzero component is positive. four_fold and six_fold then rotate about +z into [0, π/2) and [0, π/3). cubic keeps the proper cubic image of k or -k with the greatest z, then y, then x. label_grains votes owned cells for the nearest supplied candidate under that representative.

pfc::io::record_crystal_diagnostics writes those values on the normal diagnostics-series path. The columns are front_position, front_velocity, front_valid, wave_x, wave_y, wave_z, orientation_angle, orientation_valid, voting_cells and one grain_i fraction per candidate. The tungsten and aluminum test drivers call this function. Neither application keeps a copy of the estimator. The host implementation does not read device memory.

See also

Compact selections from resident arrays

pfc::core::gather in sparse_vector_ops_gpu.hpp selects GPU-resident double, float, or std::uint64_t values into a SparseVector. This preserves full-width identity values without converting through floating point. An application can reuse a fixed index list for a slice or a downsampled selection and download just its selected values:

using Backend = pfc::backend::HIPTag;
pfc::core::SparseVector<Backend, std::uint64_t> selected(indices);
pfc::core::gather(selected, resident_ids.data(), resident_ids.size());
auto compact_ids = selected.data().to_host();

Indices are sorted by the constructor, with duplicate entries retained. The matching selected values use that sorted order. The gather downloads the index list for bounds validation, leaves its output unchanged on an out-of-bounds index, and completes its device work before returning. Inputs must be ready on the default stream; synchronize a producer on a nondefault stream before calling gather. Source and selection storage must be disjoint. An empty selection accepts a null source. A nonempty null source is rejected. The CPU gather shares the successful selection and error-message contract; its existing bounds-check loop can partially write before an error.

This operation does not compute front locations, aggregate fields, or identity-dependent reductions: those are consumer policy. Scatter remains double only; typed selections do not establish a Float32 scatter capability.