Skip to content
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

DM-9595: Allow Transform to return its inverse #192

Merged
merged 4 commits into from Mar 27, 2017
Merged

Conversation

kfindeisen
Copy link
Member

This PR implements Transform::getInverse(), adds a Python-only equality test for Endpoint, and updates the style of the Transform code to match the output of clang-format (mostly changing whitespace around &).

@kfindeisen kfindeisen requested a review from r-owen March 17, 2017 02:37
outArray = transform.tranForward(inArray)
rawOutArray = toEndpoint.dataFromArray(outArray)
# Assertions must work with both lists and numpy arrays
# TODO replace with assertFloatsAlmostEqual once DM-9707 is fixed
Copy link
Contributor

Choose a reason for hiding this comment

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

My suggestion would be to avoid embedding TODOs like this in the code — we have literally hundreds of them scattered across the codebase, and nobody has time to regularly audit old code to see what actually needs to be done. Perhaps you could file a ticket on JIRA to do this work, and mark it as triggered by DM-9707? (Obviously there are thousands of tickets in JIRA which need keeping track of, but at least that means we only need to track a single source.)

Copy link
Member Author

Choose a reason for hiding this comment

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

That seems reasonable for DM-9707. Note that the similar comments for DM-9826 represent work to be done as part of DM-9826, they're just there to justify omissions to the reviewer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Consider deleting the note and calling the code done. Thenumpy.testing.assert_... functions are excellent and I see no reason to ever replace your usage of them.

Copy link
Member Author

Choose a reason for hiding this comment

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

@r-owen does that also apply to your note about replacing np.allclose?

Copy link
Contributor

Choose a reason for hiding this comment

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

np.allclose should be replaced with np.testing.assert_allclose. Do you want to do that while working on the ticket or hand it back to me to do. I didn't know about the np.testing asserts when I wrote the code.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll do it, and remove all mentions of DM-9707.

Copy link
Contributor

@r-owen r-owen left a comment

Choose a reason for hiding this comment

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

Overall it looks excellent to me. I had two substantive suggestions:

  • Please construct the inverse transform using a frame set instead of a mapping, in order to preserve information.
  • Consider implementing DM-9826 as part of this ticket, because it is trivial and provides useful capability for testing getInverse.

auto frame = self.makeFrame();
return frame->copy();
});
}

// Endpoint comparison not needed(?) in C++
Copy link
Contributor

Choose a reason for hiding this comment

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

If making this available in C++ would be little extra work please do so. Otherwise please update the comment to explain that it's not being made available in pure C++ because it is too much work and is primarily intended for unit tests (or something like that). The current note, with its question mark, makes it feel unfinished.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, I've already decided that it definitely should not be present in C++ (it's redundant with the type system).

afwGeom.SpherePointEndpoint()]
endpoints2 = [afwGeom.GenericEndpoint(n) for n in range(1, 6)] + \
[afwGeom.Point2Endpoint(), afwGeom.Point3Endpoint(),
afwGeom.SpherePointEndpoint()]
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a function or method that returns this list. Then call it in each for loop below. I can imagine other tests taking advantage of this as well (now or in the future).

It could even go into the existing geom/testUtils.py module so that test_endpoint.py could (eventually) use it.

nIn,
"{}, Map={}, nIn={}, nOut={}".format(
baseMsg, "Invertible", nIn, nOut))
# TODO: add more test cases once DM-9826 resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest implementing that ticket as part of this one; it should be trivial and the result will be a more complete and coherent solution, since it is required -- or at least very useful -- for fully testing the inverse.

Another option is to implement that ticket first, then this one. But that seems needlessly complicated for such a simple thing.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was trying to be better about organizing work, avoiding scope creep, etc., but fine. I will ask for a re-review of those changes, though.

outArray = transform.tranForward(inArray)
rawOutArray = toEndpoint.dataFromArray(outArray)
# Assertions must work with both lists and numpy arrays
# TODO replace with assertFloatsAlmostEqual once DM-9707 is fixed
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider deleting the note and calling the code done. Thenumpy.testing.assert_... functions are excellent and I see no reason to ever replace your usage of them.

inverse.getToEndpoint(), msg=msg)
self.assertEqual(transform.getToEndpoint(),
inverse.getFromEndpoint(), msg=msg)
# TODO: test hasTranForward and hasTranInverse once DM-9826 resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, if you implement that now you can provide a more complete solution

@@ -105,16 +105,22 @@ typename FromEndpoint::Array Transform<FromEndpoint, ToEndpoint>::tranInverse(
}

template <typename FromEndpoint, typename ToEndpoint>
Transform<ToEndpoint, FromEndpoint> Transform<FromEndpoint, ToEndpoint>::getInverse() const {
std::shared_ptr<ast::Mapping> inverse = _frameSet->getInverse();
Copy link
Contributor

Choose a reason for hiding this comment

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

I strongly suggest using the FrameSet constructor of Transform to construct the inverse, because it retains more information: if the Transform was originally constructed with a FrameSet, the inverse retains all the frames and individual transforms of the original.

I see two simple ways to do this:

  1. Use dynamic_pointer_cast to cast the value returned by _frameSet.getInverse() to std::shared_ptrast::FrameSet. That should never fail, but of course you will have to check it anyway.
  2. Make the inverse manually by copying the frame set swapping the BASE and CURRENT frame pointers:
auto inverse = _frameSet->copy();
inverse->setBase(_frameSet->getCurrent());
inverse->setCurrent(_frameSet->getBase());

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm... is there a way to unit test for the information that's currently missing?

Copy link
Contributor

@r-owen r-owen Mar 17, 2017

Choose a reason for hiding this comment

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

Sure...create a FrameSet and add some frames and connecting mappings. Then look for the frames after constructing the Transform and retrieving the FrameSet from it.

# TODO: add more test cases once DM-9826 resolved

def checkGetInverseWithMap(self, transform, nIn, msg):
"""Test Transform<fromName>To<toName>.getInverse with a specific mapping.
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean by "with a specific mapping"? A transform can be constructed with a mapping or a frame set, but the results is that it contains a frame set in either case.

To my mind the name suggests that the user will provide a mapping as an argument to this method (I am not suggesting you do that; the current test seems excellent).

Can you call this checkGetInverse and skip the WithMap?

Copy link
Member Author

Choose a reason for hiding this comment

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

I need to give it a distinct name from the method immediately above... checkGetInverseImpl?

Copy link
Contributor

@r-owen r-owen Mar 17, 2017

Choose a reason for hiding this comment

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

If I am reading the code correctly:

  • checkGetInverse creates all possible transforms for a given pair of endpoint names, creating each frame set or mapping as it sees fit.
  • checkGetInverseWithMap checks one transform.

I'm asking for a less confusing name scheme. To be fair I'll propose one, but do as you like:

  • Rename checkGetInverseWithMap to checkGetInverse and ditch old version of the latter.
  • Make checkTransform call checkGetInverseWithMap with each valid transform that it constructs.

Or if that's too much work for checkTransform you could make a new method testGetInverse and have it build all valid transforms that you want to test, then call checkTransform from there.

I wonder if I should break checkTransform up a bit, e.g. I could imagine a function by that name which takes a valid transform as an argument and checks basics. Then testTransform would have to construct the transforms. I'm not sure where I would move the code that tries to construct invalid transforms -- perhaps a new testX method. What do you think?

@kfindeisen kfindeisen force-pushed the tickets/DM-9595 branch 5 times, most recently from a6f13e5 to 022a7b6 Compare March 24, 2017 17:59
Copy link
Contributor

@r-owen r-owen left a comment

Choose a reason for hiding this comment

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

Looks great. I had a few minor suggestions.

Transform<ToEndpoint, FromEndpoint> Transform<FromEndpoint, ToEndpoint>::getInverse() const {
auto inverse = std::dynamic_pointer_cast<ast::FrameSet>(_frameSet->getInverse());
if (!inverse) {
// std::bad_cast more appropriate, but doesn't aid in debugging
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the code but find the note confusing. This situation should never occur, so logic_error does seem the best choice.

auto frame = self.makeFrame();
return frame->copy();
});
}

// Endpoint comparison useful in Python but not C++
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not convinced by this assertion. If it would not be a lot more work I recommend making this available to C++.

"""
for i1, point1 in enumerate(self.makeEndpoints()):
for i2, point2 in enumerate(self.makeEndpoints()):
self.assertEqual(i1 == i2, point1 == point2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Cute! But the message in case of an error will not be clear. I suggest formatting a useful error message to be passed as the msg argument to self.assertEqual. (This might be easier if you handled the i1 == i2 case separately from the other case, but I'll leave that to you).

Also, please test != as well as ==.

@@ -400,11 +513,107 @@ def checkTransformFromFrameSet(self, fromName, toName):

self.checkTransformation(permTransform, mapping=polyMap, msg=msg)

def checkAllGetInverse(self, fromName, toName):
Copy link
Contributor

Choose a reason for hiding this comment

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

A nicely written method, but what does the "All" in the name tell me? Why not "checkGetInverse"?

Several methods now use these methods to provide more predictable
exception behavior.
This change is primarily intended for unit tests that need to verify
which Endpoint is being used by a particular object.
@kfindeisen kfindeisen merged commit d093cb4 into master Mar 27, 2017
@kfindeisen kfindeisen deleted the tickets/DM-9595 branch August 22, 2018 19:22
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.

None yet

3 participants