Skip to content

Add ElementResultsPlotter.history_canonical for fiber/IP reduction - #98

Merged
nmorabowen merged 1 commit into
mainfrom
guppi/epic-lamport-3ad688
May 20, 2026
Merged

Add ElementResultsPlotter.history_canonical for fiber/IP reduction#98
nmorabowen merged 1 commit into
mainfrom
guppi/epic-lamport-3ad688

Conversation

@nmorabowen

Copy link
Copy Markdown
Owner

Summary

Adds er.plot.history_canonical(...) — a sibling to the existing
er.plot.history(...) that takes a canonical engineering name (e.g.
"strain_11") and collapses the matching fiber / IP / layer columns
with a chosen reduction. One curve per element on the time axis.

Motivation

Fiber buckets (section.fiber.stress, section.fiber.strain) expand
a single quantity into n_fibers × n_ip (or n_fibers × n_layers × n_ip for layered shells) raw columns. Until now the only way to plot
one was to pick a single fiber/IP column by name:

er.plot.history("eps11_f3_ip0", element_ids=ids)

That's awkward when you actually want the mean over fibers, the
peak across IPs, or the max(|.|) across the whole cross-section.

What lands

ax, meta = er.plot.history_canonical(
    canonical_name,                     # "strain_11", "stress_11", ...
    *,
    over="all",                         # "all" | "fibers" | "ips" | "layers"
    reduce="mean",                      # mean | median | max | min | abs_max | sum
    fiber_idx=None, ip_idx=None, layer_idx=None,
    element_ids=None,
    ax=None, x_axis="time",
    annotate_stage_boundaries=True,
    **plot_kwargs,
)
  • over declares which axis the reduction collapses; the partner
    anchors become required when over != "all" (e.g. over="fibers"
    needs ip_idx= so the reduction collapses fibers at a fixed IP
    rather than silently folding in every IP too).
  • meta["columns_used"] lists the raw columns folded in — useful for
    audit and downstream labelling.
  • meta otherwise mirrors history(): x, y_per_element, plus a
    reduced series and stage_boundaries on multi-stage results.

The reduction logic lives in two private module-level helpers
(_filter_canonical_columns, _reduce_fiber_ip) so the regex matching
and the dispatch table are unit-testable in isolation — no HDF5
fixture needed.

Examples

# Mean ε11 over every fiber and IP, one curve per element:
er.plot.history_canonical("strain_11", reduce="mean")

# Max(|σ11|) over fibers at IP 0:
er.plot.history_canonical("stress_11", over="fibers", ip_idx=0,
                           reduce="abs_max")

# Max over IPs of fiber 0:
er.plot.history_canonical("stress_11", over="ips", fiber_idx=0,
                           reduce="max", element_ids=[1, 2, 3])

Design decisions (locked with the user up front)

Choice Picked Why
API shape New sibling method (not extending history) Keeps the existing history(component=...) signature unambiguous; no canonical/raw name collision.
Reduction scope Within-element only Matches the stated need; keeps signature small. Across-elements reduction can land later if needed.
Public data reducer No (private helpers only) Sibling-method-only path was the explicit pick.

Tests

  • tests/unit/elements/test_history_canonical_reduce.py24 new tests:
    every over= mode against three column-naming patterns (compressed
    fibers, layered no-fibers, layered with fibers), all six reduce ops
    with hand-computed expected values, fiber-anchor substring
    disambiguation (_f1 vs _f10), every validation path.
  • tests/integration/test_element_plotting.py — 5 new end-to-end tests
    against the solid_partition_dir section.fiber.stress bucket;
    auto-skip in environments without the heavy fixture (per existing
    conftest conventions).

Locally: 125 passed, 6 skipped (skips are the new integration
tests on a fixture-less environment; everything else green).

Docs

  • docs/ElementResults.md — Plotting section
    updated; the new method gets two example calls and a paragraph on
    the over= / anchor pattern.
  • docs/nodes_elements_cuts_plotting_guide.md
    — new comprehensive usage guide covering nodes, elements, section
    cuts, and plotting (written earlier in the same session; includes
    history_canonical in §4.2).

Example

  • examples/solid_mixed_example.py
    — new section 11 slotted in right after the
    "integrate_canonical doesn't work for fibers" section (the natural
    narrative complement), demonstrating all three over= modes and the
    missing-anchor error path. Old 11/12/13 renumbered to 12/13/14;
    module docstring and main() updated.

Versioning

This is a backward-compatible feature (MINOR per the repo's semver
policy). Not bumping pyproject.toml here — past PRs (#95, #96) merged
without bumps and the version was rolled in a dedicated release PR
(#97). Same pattern applies here.

Follow-up flagged separately

While auditing docs I noticed docs/elements_guide.md
calls er.plot.xy(...) in three places — but xy() only exists on
NodalResults, not ElementResults. That predates this PR and is
out of scope; flagged as its own task chip.

🤖 Generated with Claude Code

Fiber buckets (section.fiber.stress, section.fiber.strain) expand a
single engineering quantity into n_fibers * n_ip (or
n_fibers * n_layers * n_ip for layered shells) raw columns. Until now
the only way to plot a time history from one was to pick one specific
fiber*IP column via er.plot.history("sigma11_f3_ip0", ...). This adds
a sibling method that takes the canonical engineering name and folds
the matching columns with a chosen reduction.

API
---
    ax, meta = er.plot.history_canonical(
        canonical_name,                     # "strain_11", "stress_11", ...
        *,
        over="all",                         # "all" | "fibers" | "ips" | "layers"
        reduce="mean",                      # mean | median | max | min | abs_max | sum
        fiber_idx=None, ip_idx=None, layer_idx=None,
        element_ids=None,
        ax=None, x_axis="time",
        annotate_stage_boundaries=True,
        **plot_kwargs,
    )

`over` declares which axis the reduction collapses; the others must be
pinned via the matching anchor when `over` is not "all" (e.g.
`over="fibers"` requires `ip_idx`). `meta["columns_used"]` lists which
raw columns were folded in, useful for audit.

Implementation factors the column-filter + reduction into two private
module-level helpers (_filter_canonical_columns, _reduce_fiber_ip) so
the regex matching and the dispatch table can be unit-tested in
isolation against synthetic DataFrames — no HDF5 fixture needed.

Tests
-----
* tests/unit/elements/test_history_canonical_reduce.py — 24 tests
  covering: column filtering across the three META naming patterns
  (compressed fibers, layered no-fibers, layered with fibers), all six
  reduce ops with hand-computed expected values, fiber-anchor
  substring disambiguation (_f1 vs _f10), every validation path.
* tests/integration/test_element_plotting.py — 5 end-to-end tests
  against the solid_partition_dir section.fiber.stress bucket
  (auto-skip in environments without the heavy fixture).

Docs
----
* docs/ElementResults.md — Plotting section updated with examples and
  a paragraph on the over=/anchor pattern.
* docs/nodes_elements_cuts_plotting_guide.md — new comprehensive usage
  guide covering nodes, elements, section cuts, and plotting (includes
  history_canonical in §4.2).

Example
-------
* examples/solid_mixed_example.py — new section 11 slotted in right
  after the "integrate_canonical doesn't work for fibers" section,
  demonstrating all three over= modes and the missing-anchor error
  path. Old 11/12/13 renumbered to 12/13/14; docstring and main()
  updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nmorabowen
nmorabowen merged commit 307efa0 into main May 20, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant