Skip to content

Conversation

Scienfitz
Copy link
Collaborator

Implements #651

Also added some things to transformation compression for transformations that use absolute positional arguments of preceeded by a pure input shift

Turned into a match statement. Also added compressions for transformations which use absolute position if preceded by a pure input shift.
@Scienfitz Scienfitz self-assigned this Oct 13, 2025
@Copilot Copilot AI review requested due to automatic review settings October 13, 2025 15:37
@Scienfitz Scienfitz requested a review from AVHopp as a code owner October 13, 2025 15:37
@Scienfitz Scienfitz added the enhancement Expand / change existing functionality label Oct 13, 2025
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements vshift and hshift methods for the Transformation class to enable convenient vertical and horizontal shifting operations. It also enhances transformation compression to handle transformations that use absolute positional arguments preceded by pure input shifts.

  • Added vshift and hshift methods to the base Transformation class
  • Enhanced transformation compression logic to handle positional transformations after pure input shifts
  • Added scalar addition and subtraction operators to the Interval class

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
baybe/transformations/base.py Implements vshift and hshift methods for transformation shifting
baybe/transformations/utils.py Enhances compression logic with pattern matching for positional transformations
baybe/utils/interval.py Adds scalar arithmetic operators for interval shifting
tests/test_transformations.py Adds comprehensive tests for the new shifting functionality
CHANGELOG.md Documents the new features

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@Scienfitz Scienfitz force-pushed the feature/transformation_shifts branch from d8721b3 to 46ff8eb Compare October 13, 2025 15:43
return self | AbsoluteTransformation()

def vshift(self, shift: float | int, /) -> Transformation:
"""Add a constant to the transformation (vertical shift)."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this a technical detail? As a user, I do not care that this technically adds a constant, I only care about the shift. So I would propose "Shift the transformation vertically by a given amount."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this docstring puts both aspects whats done and technically into an extremely short wording. Are you suggesting I need to shorten it further?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had the same thought as @AVHopp. Perhaps let's add the "action" first and the (less important) technical explanation second?

Suggested change
"""Add a constant to the transformation (vertical shift)."""
"""Shift the transformation vertically (i.e. add a constant to its output)."""

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is the exact opposite of what Alex suggested I will let you both figure it out

for me: its longer and says the same, but it would be nicely consistent between hshift and vshift so imo its acceptable

return self + shift

def hshift(self, shift: float | int, /) -> Transformation:
"""Prepend a shift to the input (horizontal shift)."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Prepend a shift to the input (horizontal shift)."""
"""Shift the transformation horizontally (i.e. add a constant to its input)."""

Copy link
Collaborator Author

@Scienfitz Scienfitz Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that you wrote add here makes we think maybe we need to discuss this convention here: For hshift I am not adding the constant, I am subtracting it. This amounts to what one would phrase as shift right or shift up on the x axis for a positive number. Do you agree with it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think your implementation makes sense and is the most intuitive. And then my docstring here would need to be adjusted.

def test_positional_shift_transformation_compression(t, expected):
"""Simple input shifts are compressed correctly."""
t1 = AffineTransformation(shift=-2) | t
t2 = t.hshift(2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about tests for vshift?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vshift has been added to some of the existing tests, see top of the test file changes


return self | AbsoluteTransformation()

def vshift(self, shift: float | int, /) -> Transformation:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering if these names are idea. Pro: they are short 👍🏼 But normally, we try to avoid abbreviations and are explicit instead + our methods usually start with verbs. Can we just collect some alternative ideas and then decide if we keep it or not?

Trivial choice:

  • shift_horizontally / shift_vertically

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its in accordance with well known hstack and vstack functions + its short. Also what they do is easy to understand so I see 0 reason to lengthen the names (they are probably also commonly used in chaining which suffers visually from long names)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if @AVHopp also agrees, let's keep it

aggregated.append(t)
last = aggregated.pop() if aggregated else None
match (last, t):
case AffineTransformation(), AffineTransformation():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember that discussion that I initiated in the Hufhaus when I presented the idea of the new transformation framework? It was about which "standard parameters" we want to include in the base framework and I mentioned that a transformation always consists of three components:

  1. the actual nonlinearity
  2. a potential affine input shift
  3. a potential affine output shift

And we discussed whether it makes sense to include any of the latter two in a "generic form" into the interface. Had we done it, this part wouldn't be necessary, since the code would trivially compress to the two corresponding input shift parameters.

Don't get me wrong: I'm not saying I regret this or we should change it now. But since you seem to see the need for introducing this logic for some reason, I thought I'd point it out again, so that we can think about if a generic solution is preferable?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am confused, is this comment on the line or on the overall block?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the overall block

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we hade decided otherwise it wouldn't be necessary
-> which means it is currently necessary, because we decided otherwise

Do you not agree with this or what exactly do you want to discuss?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely sure myself 😄 I'm trying to recap what exactly we're actually trying to achieve with the compression, and consequently, what should be the right approach.

To clarify why I originally introduced it: equality comparison. If you have something like transformation = AffineTransformation() | IdentityTransformation, this is really same thing as just the identity, but without some additional machinery, we'll never be able to compare that two targets carrying these transforms are the same (same for the transforms themselves).

This raises two questions to me:

  1. When should compression happen? At the moment when we combine them, which means modifying the objects themselves? Or only for the eq-comparison?
  2. Who is responsible for triggering the compression? In the current version, it's the part of the code that calls the compression function. Which has the consequence that this function contains the specific logic for all transforms, and when not called, no compression happens. A potential alternative may be to let move the logic to the transforms themselves, into the corresponding dunders. Logic: The triangular transform itself knows best what it means for its parameters when the entire transform is h-shifted.

Any thoughts on this? If we see no clear winner, also don't have to bother at this moment an can refine later when things start to get out of hands. But perhaps this context triggers some thoughts in you.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we have the situation A | B and imo a compression should happen whenever A can fully be abstracted into B, which is what happens here:

  • A is a pure input hshift
  • B is a transformation that stores its definition in terms of absolute positions. Those can be adjusted by what A shifts, resulting in a new B'

It came up in #657 before we made the clamp changes, there were some equality tests failing of the character described above (The fails are not relevant anymore because the approach was changed, but the compression to me makes sense regardless), indicating that these compressions are missing.

Compression also has a purpose beyond just equality checks: The computation is probably more efficient and less error prone if there are less steps.

Regarding who is responsible, isnt the answer simply ChainedTransformation? The compression is not yet that complicated to warrant more retructuring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Expand / change existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants