Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/e3sm_quickview/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,10 +562,12 @@ async def data_loading_open(self, simulation, connectivity):
# Initialize dynamic index variables for each dimension
for dim_name in available_tracks:
index_var = f"{dim_name}_idx"
if "time" in index_var:
self.state[index_var] = 50
else:
self.state[index_var] = 0
default_idx = 50 if "time" in index_var else 0
# Clamp to the dimension: an index past the end is rejected
# by the reader, and most files have far fewer than 50 steps.
self.state[index_var] = min(
default_idx, self.source.dimensions[dim_name].size - 1
)
self.state.change(index_var)(
partial(self._on_slicing_change, dim_name, index_var)
)
Expand Down
50 changes: 7 additions & 43 deletions src/e3sm_quickview/plugins/eam_projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,40 +133,6 @@ def ProcessPoint(point, radius):
return [x, y, z]


# Slice plans keyed on the PedigreeIds array identity. Pedigree permutations
# from vtkTableBasedClipDataSet are long-run-monotonic (typically runs of
# thousands of +1-stepped indices), so we can replace fancy indexing with a
# list of slice copies and reduce the per-tick cost substantially.
_pedigree_slice_plan_cache = {}


def _get_pedigree_slice_plan(pedigree_vtk):
"""Return (starts, ends, pid_np) for the pedigree permutation.

The plan represents pedigree as a sequence of runs where each run i maps
output[starts[i]:ends[i]] ← input[pid_np[starts[i]]:pid_np[starts[i]]+len].
Cached by (id, MTime) of the pedigree VTK array — vtk_to_numpy returns
a fresh ndarray each call, so keying on ndarray identity would miss.
"""
key = (id(pedigree_vtk), pedigree_vtk.GetMTime())
entry = _pedigree_slice_plan_cache.get(key)
if entry is not None:
return entry

pid_np = numpy_support.vtk_to_numpy(pedigree_vtk)
diff = np.diff(pid_np.astype(np.int64, copy=False))
breaks = np.flatnonzero(diff != 1)
starts = np.empty(len(breaks) + 1, dtype=np.int64)
starts[0] = 0
starts[1:] = breaks + 1
ends = np.empty_like(starts)
ends[:-1] = starts[1:]
ends[-1] = len(pid_np)
entry = (starts, ends, pid_np)
_pedigree_slice_plan_cache[key] = entry
return entry


def add_cell_arrays(inData, outData, cached_output):
"""
Adds arrays not modified in inData to outData.
Expand All @@ -175,19 +141,19 @@ def add_cell_arrays(inData, outData, cached_output):
is different than the number of values in the arrays already processed
through the pipeline.

The indexed copy is done in-place into a pre-allocated output buffer
using a cached slice plan over the pedigree permutation — roughly 2x
faster than fancy numpy indexing for the clip-induced permutations we
see here.
A single fancy-index gather does this. An earlier version walked the
permutation as a list of monotonic run slices, assuming the runs were
thousands of entries long. Measured against the permutations this pipeline
actually produces — mean run 55-110 — that loop is 8-16x *slower* than one
numpy gather, because the per-run Python overhead dominates.
"""
pedigreeIds = cached_output.cell_data["PedigreeIds"]
if pedigreeIds is None:
print_error("Error: no PedigreeIds array")
return

pedigree_vtk = cached_output.GetCellData().GetArray("PedigreeIds")
with _perf.timed("add_cell_arrays.slice_plan"):
starts, ends, pid_np = _get_pedigree_slice_plan(pedigree_vtk)
pid_np = numpy_support.vtk_to_numpy(pedigree_vtk)

cached_cell_data = cached_output.GetCellData()
in_cell_data = inData.GetCellData()
Expand All @@ -214,9 +180,7 @@ def add_cell_arrays(inData, outData, cached_output):

in_np = numpy_support.vtk_to_numpy(in_array)
out_np = numpy_support.vtk_to_numpy(out_array)
for s, e in zip(starts, ends):
src_off = int(pid_np[s])
out_np[s:e] = in_np[src_off : src_off + (e - s)]
out_np[...] = in_np[pid_np]
out_array.Modified()


Expand Down
10 changes: 9 additions & 1 deletion src/e3sm_quickview/plugins/eam_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,15 @@ def SetSlicing(self, slice_str):

if invalid_slices:
print_error(f"Invalid slice indices: {', '.join(invalid_slices)}")
else:

# Mark modified whenever a *valid* slice changed, even if some
# other dimension in the same request was out of range. The
# valid values have already been stored, so skipping Modified()
# would leave the reader's state ahead of its output -- and
# because the caller resends the whole slicing dict every time,
# one stale out-of-range index would otherwise suppress every
# later change.
if self._changed_dims:
self.Modified()

except (json.JSONDecodeError, ValueError) as e:
Expand Down