Skip to content

refactor: remove DDScalar::resize - #77

Merged
oberbichler merged 1 commit into
mainfrom
refactor/remove-resize
Jul 26, 2026
Merged

oberbichler merged 1 commit into
mainfrom
refactor/remove-resize

Conversation

@oberbichler

Copy link
Copy Markdown
Owner

What it did

void resize(const index size) {
  static_assert(is_dynamic());
  check_valid_size(size);
  m_size = size;
  m_data.resize(data_length_from_size(size));
}

Set m_size, resized the flat buffer, nothing else — no remap.

That is correct for first order, where the layout is [f, g₀…g_{s-1}] and the moving boundary sits at the end:

size 2:      [1, 2, 3]        f=1  g=[2, 3]
resize(4):   [1, 2, 3, 0, 0]  f=1  g=[2, 3, 0, 0]
resize(1):   [1, 2]           f=1  g=[2]

For second order the g/h boundary sits in the middle and the triangular row lengths depend on the size, so the same bytes get reinterpreted:

size 2:      [1, 2, 3, 4, 5, 6]            f=1  g=[2, 3]     H=[[4,5],[5,6]]
resize(3):   [1, 2, 3, 4, 5, 6, 0,0,0,0]   f=1  g=[2, 3, 4]  H=[[5,6,0],[6,0,0],[0,0,0]]
                       ↑ was h00  ↑ were h01, h11

h00 becomes g2, and h01/h11 shift into h00/h01. Shrinking does the same in reverse: resize(1) gives H=[[3]], where the 3 is the old g1.

Why remove rather than fix

The intent was to change the number of variables without a remap, in order to overwrite the contents afterwards. A new instance is the clearer way to express that, and every real use is already covered:

intent use
keep the derivatives pad_left(new_size) / pad_right(new_size)
discard them empty(size) / zero(size)

Making resize remap would just duplicate pad_right.

Nothing used it: the only internal resize call was the m_data.resize inside it, and no C++ test or benchmark referenced it.

Verified pad_left/pad_right first

Before removing the alternative, I checked that the remapping ones are actually correct — 96 cases over {order 1, 2} × size {0,1,2,3,5,8} × padding {0,1,3,7}, compared bitwise (array_equal, not allclose) against a reference built independently from f, g and H. Values were unique throughout (100, 101, 102, …) so a permutation could not hide behind repeated numbers. 0 deviations.

order 2, s=2 → n=4:
  pad_right   g=[2,3,0,0]   H=[[4,5,0,0],[5,6,0,0],[0,0,0,0],[0,0,0,0]]
  pad_left    g=[0,0,2,3]   H=[[0,0,0,0],[0,0,0,0],[0,0,4,5],[0,0,5,6]]

The index arithmetic also holds analytically: for new_size = s + t, row i < s has (s-i) + t entries — old row then t zeros — and the new rows s+k have t-k entries, matching the final loop. The element counts sum to exactly 1 + N + N(N+1)/2 for both functions.

Tests

test_resize never touched resize — it only called pad_right and was a weaker duplicate of test_pad_right. Its size assertion moved into test_pad_right and test_pad_left, which check the full data anyway; the test itself is gone. The negative-size case for resize (from #73) goes with resize. The # resizing section header now sits above the pad tests instead of above test_eval.

Deliberately not adding a test that asserts resize is absent: if someone later adds a correct, remapping resize, such a test would fail for no good reason.

After: C++ 42/42 test cases, 421 assertions · Python 162 passed (166 − 4 removed parametrizations) · clang-format --Werror and ruff format --check clean · resize gone from all four Python types, and the replacement paths verified:

u.resize(3)          -> AttributeError: 'hyperjet.DDScalar' object has no attribute 'resize'
u.pad_right(3).data  -> [1. 2. 3. 0. 4. 5. 0. 6. 0. 0.]
u.pad_left(3).data   -> [1. 0. 2. 3. 0. 0. 0. 4. 5. 6.]

Notes

This is a breaking change to the Python API. Removing it outright rather than deprecating, per discussion: for second-order scalars it silently produced wrong derivatives, so there is no behaviour worth preserving through a deprecation cycle.

README gained a sentence in the DDScalar section pointing at pad_left/pad_right and empty/zero — resize itself was never documented.

Based on main; independent of #75 and #76.

resize() set m_size and resized the flat buffer, nothing else. That is
correct for first order, where the layout is [f, g0..g(s-1)] and the moving
boundary sits at the end — growing appends zeros, shrinking drops trailing
variables. For second order the g/h boundary sits in the middle and the
triangular row lengths depend on the size, so the same bytes get
reinterpreted:

    size 2:     [1, 2, 3, 4, 5, 6]            f=1  g=[2,3]    H=[[4,5],[5,6]]
    resize(3):  [1, 2, 3, 4, 5, 6, 0,0,0,0]   f=1  g=[2,3,4]  H=[[5,6,0],[6,0,0],[0,0,0]]

h00 becomes g2, and h01/h11 shift into h00/h01.

The intent was to change the number of variables without a remap, in order
to overwrite the contents afterwards. In practice a new instance is the
clearer way to do that, and every real use is already covered: keep the
derivatives with pad_left/pad_right, discard them with empty(size) or
zero(size).

Nothing used it. The only internal resize call was the m_data.resize inside
it, and no C++ test or benchmark referenced it. Verified that pad_left and
pad_right do remap correctly before removing the alternative: 96 cases over
{order 1, 2} x size {0,1,2,3,5,8} x padding {0,1,3,7}, compared bitwise
against a reference built independently from f, g and H, with unique values
throughout so a permutation could not hide.

Tests: test_resize never touched resize — it only called pad_right and was a
weaker duplicate of test_pad_right, so its size assertion moved into
test_pad_right and test_pad_left. The negative-size case for resize goes with
resize. The "# resizing" section now sits above the pad tests where it
belongs.

This is a breaking change to the Python API. Removing it outright rather than
deprecating it, because for second-order scalars it silently produced wrong
derivatives.
@oberbichler
oberbichler merged commit 1df8fad into main Jul 26, 2026
16 checks passed
@oberbichler
oberbichler deleted the refactor/remove-resize branch July 26, 2026 20:52
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