-
Notifications
You must be signed in to change notification settings - Fork 57
vshift
and hshift
for Transformation
#665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fc5458c
7ce1958
0e4d073
ac110d7
46ff8eb
0fd1e19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -102,6 +102,16 @@ def abs(self) -> Transformation: | |||||
|
||||||
return self | AbsoluteTransformation() | ||||||
|
||||||
def vshift(self, shift: float | int, /) -> Transformation: | ||||||
"""Add a constant to the transformation (vertical shift).""" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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." There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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).""" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See other comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that you wrote There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
from baybe.transformations import AffineTransformation | ||||||
|
||||||
return AffineTransformation(shift=-shift) | self | ||||||
|
||||||
def __neg__(self) -> Transformation: | ||||||
return self.negate() | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
from typing import TYPE_CHECKING | ||
|
||
import numpy as np | ||
from attrs import evolve | ||
|
||
from baybe.transformations.base import Transformation | ||
|
||
|
@@ -64,30 +65,43 @@ def compress_transformations( | |
Returns: | ||
The minimum sequence of transformations that is equivalent to the input. | ||
""" | ||
from baybe.transformations.basic import AffineTransformation, IdentityTransformation | ||
from baybe.transformations.basic import ( | ||
AffineTransformation, | ||
BellTransformation, | ||
IdentityTransformation, | ||
TriangularTransformation, | ||
TwoSidedAffineTransformation, | ||
) | ||
|
||
aggregated: list[Transformation] = [] | ||
last = None | ||
id_ = IdentityTransformation() | ||
|
||
for t in _flatten_transformations(transformations): | ||
# Drop identity transformations (and such that are equivalent to it) | ||
if t == id_: | ||
continue | ||
|
||
# Combine subsequent affine transformations | ||
if ( | ||
aggregated | ||
and isinstance(last := aggregated.pop(), AffineTransformation) | ||
and isinstance(t, AffineTransformation) | ||
): | ||
aggregated.append(combine_affine_transformations(last, t)) | ||
|
||
# Keep other transformations | ||
else: | ||
if last is not None: | ||
aggregated.append(last) | ||
aggregated.append(t) | ||
last = aggregated.pop() if aggregated else None | ||
AdrianSosic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
match (last, t): | ||
case AffineTransformation(), AffineTransformation(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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?! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the overall block There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Do you not agree with this or what exactly do you want to discuss? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 This raises two questions to me:
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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so we have the situation
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 |
||
# Two subsequent affine transformations | ||
aggregated.append(combine_affine_transformations(last, t)) | ||
case AffineTransformation(factor=1.0), BellTransformation(): | ||
AVHopp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Bell transformation after a pure input shift | ||
aggregated.append(evolve(t, center=t.center - last.shift)) | ||
case AffineTransformation(factor=1.0), TwoSidedAffineTransformation(): | ||
# 2-sided affine transformation after a pure input shift | ||
aggregated.append(evolve(t, midpoint=t.midpoint - last.shift)) | ||
case AffineTransformation(factor=1.0), TriangularTransformation(): | ||
# Triangular transformation after a pure input shift | ||
aggregated.append( | ||
evolve(t, peak=t.peak - last.shift, cutoffs=t.cutoffs - last.shift) | ||
Scienfitz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
case (None, _): | ||
aggregated.append(t) | ||
case (l, _): | ||
aggregated.append(l) | ||
aggregated.append(t) | ||
|
||
# Handle edge case when there was only a single identity transformation | ||
if not aggregated: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,10 +87,49 @@ def test_affine_transformation_compression(): | |
t = IdentityTransformation() | ||
|
||
t1 = t * 2 + 3 | t | ||
assert t1 == AffineTransformation(factor=2, shift=3) | ||
t1b = (t * 2).vshift(3) | ||
assert t1 == t1b == AffineTransformation(factor=2, shift=3) | ||
|
||
t2 = (t + 3) * 2 | t | ||
assert t2 == AffineTransformation(factor=2, shift=3, shift_first=True) | ||
t2b = t.vshift(3) * 2 | ||
assert t2 == t2b == AffineTransformation(factor=2, shift=3, shift_first=True) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"t, expected", | ||
[ | ||
param( | ||
BellTransformation(center=0, sigma=1), | ||
BellTransformation(center=2, sigma=1), | ||
id="bell", | ||
), | ||
param( | ||
TwoSidedAffineTransformation(midpoint=0, slope_left=-4, slope_right=2), | ||
TwoSidedAffineTransformation(midpoint=2, slope_left=-4, slope_right=2), | ||
id="2sided_affine", | ||
), | ||
param( | ||
TriangularTransformation(peak=0, cutoffs=(-2, 1)), | ||
TriangularTransformation(peak=2, cutoffs=(0, 3)), | ||
id="triangular_cutoffs", | ||
), | ||
param( | ||
TriangularTransformation.from_margins(peak=0.0, margins=(2, 1)), | ||
TriangularTransformation(peak=2, cutoffs=(0, 3)), | ||
id="triangular_margins", | ||
), | ||
param( | ||
TriangularTransformation.from_width(peak=0.0, width=4), | ||
TriangularTransformation(peak=2, cutoffs=(0, 4)), | ||
id="triangular_width", | ||
), | ||
], | ||
) | ||
def test_positional_shift_transformation_compression(t, expected): | ||
"""Simple input shifts are compressed correctly.""" | ||
t1 = AffineTransformation(shift=-2) | t | ||
t2 = t.hshift(2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about tests for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
assert t1 == t2 == expected | ||
|
||
|
||
def test_identity_transformation_chaining(): | ||
|
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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
andvstack
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)There was a problem hiding this comment.
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