Result files¶
A single-material two-dimensional phase-field run of the YAML runner writes one
result.vtkhdf by default (the multi-material route does not write it). The file is HDF5 and a valid VTKHDF file at the same
time. ParaView 5.13 or later opens it directly,
and phast.load_result reads it on macOS, Linux and Windows with h5py and
NumPy only. The layout is specified in Result file layout.
This page describes how to write, check, view and read these files.
The result file is the file to open, validate and cite. The runner still writes
its CSV files and, where configured, the trajectory store (training_data.h5),
so that existing scripts keep working. Older run directories load through the
same reader.
The runner writes the result file unless output.result is false. A run
directory that holds both result.vtkhdf and training_data.h5 is read from the
result file, so step in field() counts stored steps and the field names are
those of the result file (section “Reading”). A script that reads step labels or
field names of the trajectory store (step_0020, H_nodal, eps_xx) from
such a directory changes to the stored-step convention, or reads the store
directly with h5py. Setting output.result: false restores the former output
set, which holds no result file.
Writing¶
The YAML runner¶
python -m phast run <config.yaml> writes <output_dir>/result.vtkhdf with the
defaults of the solver type. The configuration can set output.result to a
mapping of the settings below, which are all optional:
output:
result:
every: 1 # stored steps; history rows are written for every increment
fields: [displacement, damage, H, stress, strain]
derived_location: cell # cell | quadrature_point
derived_every: 1 # 1 = in the ParaView view; N > 1 = in /derived every N-th increment
size_limit_gb: 4 # above the estimate, derived fields move to /derived
layout: 2.0 # 2.1 adds the items of section 8 of the layout
crack_tips: false # true or {threshold: 0.5, direction: x}; implies layout 2.1
output.result: true, or leaving the key out, uses the defaults and
output.result: false writes no file. The defaults of the settings
depend on the solver type: a quasi-static run stores every increment, and an explicit run stores
every output.h5_every increments and also stores velocity and acceleration.
When the estimated size of the stored state exceeds size_limit_gb, the runner
stores fewer increments (it prints the value of every that it chose) unless
every is set. The meaning of each setting is in section 3 of the layout.
The runner records one row of /history for every converged increment: the
time, the load parameter, the signed reaction of each fixed or loaded node set,
the energies, and the iteration counts. Quasi-static runs use a pseudo-time of
one unit per increment. Explicit runs store the accumulated physical time and
the time step that each increment used.
Four properties of the runner’s file need attention.
With
solver.adaptive_dt: truethe runner evaluates the load ramp at the nominal timestep * dt, as it did before the result file existed, so the storedload_factorfollows that nominal time whilehistory/timefollows the accumulated step sizes. Constant-step runs are not affected.The runner does not write the per-solve table
history/substepsof layout 2.1, because the damage solver does not report the record of each solve. The writer and the validator support the table.If the mesh changes during a run (adaptive refinement), the runner closes the result file at the last increment on the initial mesh, records the reason in
meta, and continues with its other outputs. The writer supports changing meshes (section 8.6 of the layout); the runner does not use that yet.crack_tipsfollows one tip on T3 meshes. It is refused for Q4 meshes.
Any other solver¶
from phast.io.result import Field, ResultWriter, UnstructuredMesh
mesh = UnstructuredMesh(points, {"T3": cells}, node_sets={"top": top})
fields = [Field("displacement", "node", "x,y", "mm", "state"),
Field("damage", "node", "d", "1", "state"),
Field("H", "cell", "H", "MPa", "history")]
with ResultWriter(run_dir / "result.vtkhdf", mesh, fields, dim=2,
analysis="plane_strain", run_id="sent-001",
history_units={"load_parameter": "mm"}) as w:
for inc in range(n_increments):
... # solve the increment
w.append(dict(increment=inc, time=t, dt=dt, load_parameter=u_top, damage_solved=1),
fields=dict(displacement=u, damage=d, H=H))
The writer refuses what the layout forbids: a state or history field that is not
float64, a missing H_damage_input under lagged coupling, a history column
without units, an energy column without its method, and a stored step that lacks
one of the fields in the view. ImageGrid replaces UnstructuredMesh for voxel
grids. SoftStop turns SIGTERM and SIGUSR1 into a flag: the loop finishes the
increment, closes with status = time_limited and exits with code 11
(TIME_LIMIT_EXIT_CODE).
The writer creates result.vtkhdf.partial and writes it on local disk, not in a
synchronised folder. A run whose output
directory lies under a synchronised folder (a path component CloudStorage,
OneDrive, Dropbox, Mobile Documents or Google Drive) issues a warning and
still writes the file; keep run directories on local disk and copy them afterwards. After the file closes, the writer validates it, renames it
to result.vtkhdf and writes result.vtkhdf.sha256. A .partial file that
remains belongs to a run that was killed. It still opens with all its complete
steps.
Checking and archiving¶
python -m phast result validate run/result.vtkhdf
python -m phast result recover run/result.vtkhdf.partial -o run/result.recovered.vtkhdf
python -m phast result finalize --archive run/result.vtkhdf
validate reports ERROR, WARN and INFO findings and exits with 1 on any ERROR.
recover writes a trimmed, validated copy of an interrupted file and never
modifies the source. finalize --archive rewrites a closed file with gzip level
4, shuffle and fletcher32, compares the copy value by value with the source, and
then replaces the file and its checksum. Validate a file before copying it to
synchronised storage.
Continuing an interrupted run¶
A solver that stores its own checkpoints can continue an interrupted file in place instead of writing a new one:
from phast.io.result import ResultWriter, ResumeRefused
w = ResultWriter.resume("run/result.vtkhdf", mesh, fields, upto_increment=40, run_id="run-001")
The call cuts the history rows and stored steps after the checkpoint increment,
keeps the bytes before the cut as result.vtkhdf.pre-resume-1.bak, records the
resume in the file (resume_count and the list resumes in the attributes of
meta) and returns a writer that appends from the next increment. It refuses
with ResumeRefused, before changing anything, a file that was closed complete,
a changing mesh, another run or mesh, another field list, or a checkpoint that
is not among the stored rows. The rules are item 11 of the writer rules in
docs/user_guide/result_file_layout.md. The YAML runner does not resume a run.
Viewing¶
ParaView: open
result.vtkhdf. The time steps, the mesh or voxel grid and every array in/VTKHDFappear without a plugin or a sidecar file. Tensors are plain multi-component arrays in the orderxx, yy, zz, yz, xz, xy(2D:xx, yy, xy), so VTK tensor filters must not be applied to them directly. ParaView 5.13 shows no arrays of a voxel run with exactly one stored step, and the validator warns about such files.Standard figures:
python -m phast figures <run>writes the setup sheet, the field sheets at the first, peak-load and last stored steps (2D maps; for voxel runs mid-plane sections and an isometric view with a cut-away corner) and the evolution sheet, withfigures/manifest.json. The images are below 2000 px on both axes. The command reads layout-2.x files and refuses other run directories.
Reading¶
import phast
r = phast.load_result("run") # a run folder or a .vtkhdf file
r.steps(), r.times() # stored steps and their accumulated time
d = r.field("damage", step=-1) # 2D vectors come back with 2 components
H = r.field("H", step=0) # (E, n_qp) when kept per quadrature point
rows = r.history("history") # /history as rows; CSV files keep their names
cols = r.history_columns() # /history as arrays
mesh = r.mesh_arrays() # points, cells per block, node sets, graph edges
mats, bcs = r.materials(), r.boundary_conditions()
step is the index of a stored step, not the step label of the trajectory store
(step_0020): in an explicit run with output.h5_every: 20 the label 40 is the stored step 2
of the result file, and field("damage", step=40) raises ResultLoadError when
the file holds fewer steps. The result file also names its fields as stored (H appears
as history_field, strain and stress as multi-component arrays), and it has none
of the trajectory-only fields (H_nodal, psi_plus, eps_xx). history/step_index maps each load
increment to its stored step, or to -1 when no fields were stored for it.
field() returns stored arrays only; it does not derive stress, strain or other
quantities that the run did not store. Code that needs float32 converts while
reading (np.asarray(x, dtype=np.float32)), because no second copy of a run is
written.
phast.load_result also accepts the older run directories (a manifest or
metadata file with training_data.h5 or training_data.zarr). For those, only
the methods listed in Results and visualization are
available. Methods that describe the mesh, the steps and the layout need a
layout-2.x file and raise ResultLoadError otherwise.
Layout 2.1 files¶
A file that uses an item of section 8 of the layout has
phast_schema_version = 2.1.0. phast result validate checks every file with the
schema of its own version, and phast.load_result reads both. The runner writes
2.1 files when output.result.layout: 2.1 or output.result.crack_tips is set.
The reader methods for the added items are:
r.crack_tips() # increment, tip_id, position, threshold, time, speed
r.history("substeps") # one row per damage solve (text columns as names)
r.initial_conditions() # initial state, 2D vectors with 2 components
r.validation() # name, value, tolerance, passed of the run's comparisons
r.mesh_versions(), r.step_mesh() # runs whose mesh changes (refinement)
r.field("damage", step=1) # the array of that step, on that step's mesh
r.mesh_arrays(step=1) # points, cells and maps of that step's mesh
phast figures draws 2.1 files, except runs whose mesh changes. It draws one
mesh and reports that the mesh changes.
Checks that need ParaView¶
The automated tests read the files with h5py and NumPy. Two scripts open the
conformance files in ParaView and compare what ParaView reads with the expected
values. They run with ParaView’s own Python (pvpython), on macOS, Linux or
Windows, and exit with 1 on any mismatch.
cd tests
python -m result_v2.fixtures fixtures_out
python -m result_v2.fixtures_21 fixtures_out_21
pvpython result_v2/paraview_check.py fixtures_out
pvpython result_v2/paraview_check_21.py fixtures_out_21
Run them after any change of the writer that affects the /VTKHDF group, and on
each ParaView version that the project supports. The files written by the YAML
runner open in ParaView in the same way as the conformance files.
Limits¶
The layout covers two- and three-dimensional runs. One-dimensional runs (line cells) are outside it.
The result file is a record of a run. It does not hold the solver state, so it is not a checkpoint from which a run is restarted; a solver that keeps its own checkpoints continues the file with
ResultWriter.resume.The YAML runner writes two-dimensional T3 and Q4 results of the staggered phase-field routes. The solid-mechanics, plasticity and interface, and one-dimensional bar routes do not write a result file, and they accept
output.resultwithout effect. Results of other solvers need the writer API above.The run directories written before the result file existed keep their formats, and the reader does not convert or rewrite them.