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-8439: Add wrapper on astshim to take point lists #182

Merged
merged 4 commits into from Mar 13, 2017
Merged

Conversation

r-owen
Copy link
Contributor

@r-owen r-owen commented Feb 28, 2017

No description provided.

*
* @exceptsafe Provides strong exception guarantee.
*/
SpherePoint();
Copy link
Member

Choose a reason for hiding this comment

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

I'd still prefer not to let SpherePoint be default-constructible if it can be avoided; the stated default, while better than any of the alternatives, is not as obvious as it could be because the default point is (0, 0) in lon-lat representation but (1, 0, 0) in vector representation.

Do you use default constructibility anywhere? I tried looking for explicit variable declarations but didn't find any.

If you do keep the default constructor, consider declaring it noexcept.

Copy link
Contributor

@parejkoj parejkoj left a comment

Choose a reason for hiding this comment

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

The overall class designs seem reasonable to me. I would suggest having another pybind11 master take a look, since I don't really know what to look for in that code.

Some clarification questions, several things about the uses of asserts.

Also, you may want to add self.longMessage=True to the python test setUp(), so that the msg= kwargs don't overwrite the default messages. See here: https://docs.python.org/2/library/unittest.html#unittest.TestCase.longMessage

- Point3Endpoint is used for Point3D data
- SpherePointEndpoint for SpherePoint data
- GenericEndpoint is used when no other form will do; its LSST data type
is identical to the type used for ast::Mapping.tran.
Copy link
Contributor

Choose a reason for hiding this comment

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

"is identical" -> "must be identical"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer my wording. I consider it an explanation of the design and a suggestion of a place to look for more information.

template <typename PointT, typename ArrayT>
class BaseEndpoint {
public:
using Point = PointT;
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, why do you use these "using"s here, instead of either just using the T names, or calling the types the name without the T in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes PointT available to users as BaseEndpoint::Point. I suspect the template type name and the class attribute can have the same name, but I worry that using Point = Point looks too strange. @kfindeisen may have an opinion about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, ok. That makes sense as a technical reason why to do it this way.

Copy link
Member

Choose a reason for hiding this comment

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

I don't actually know whether using Point = Point would compile (though it probably would). @parejkoj, the LSST style guide recommends the PointT name (and disambiguating that we don't mean afw::geom::Point is certainly relevant here).


Do not obother to check the number of axes because that is done elsewhere.

The base implementation does nothing.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this line redundant, since it's virtual?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a default behavior that derived classes inherit and can choose to override. As such it might do something useful, but in this case it does nothing.

return data.size();
}

int _getNPoints(ndarray::Array<double, 2, 2> const & data) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you also need a _getNPoints for <double, 1, 1>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No because <double, 1, 1> is used to represent a single point. (Also these private methods are simply used as convenience methods for other code; the fact that the code compiles and passes unit tests is a fairly strong indication that it is not missing such methods).

*/
explicit BaseEndpoint(int nAxes) : _nAxes(nAxes) {}

virtual ~BaseEndpoint() {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to explicitly implement the default/delete copy and move constructors here, per RFC-209?

@@ -15,6 +15,7 @@ setupRequired(astropy)
setupRequired(python_future)
setupRequired(log)
setupRequired(pybind11)
setupRequired(astshim)
Copy link
Contributor

Choose a reason for hiding this comment

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

And they never thought there'd be a day when afw depended on AST!

# check invalid numbers of output for the given toName
for badNout in getBadNAxes(toName):
badPolyMap = makePolyMap(nIn, badNout)
with self.assertRaises(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

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

Which Exception?

inPoint = fromEndpoint.pointFromData(rawInPoint)
outPoint = transform.tranForward(inPoint)
rawOutPoint = toEndpoint.dataFromPoint(outPoint)
self.assertTrue(np.allclose(rawOutPoint, polyMap.tranForward(rawInPoint)))
Copy link
Contributor

Choose a reason for hiding this comment

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

self.assertFloatsAlmostEqual and msg= here, and the other asserts below.

self.assertTrue(np.allclose(rawInversePoint, frameSet.tranInverse(rawOutPoint)))

# forward transformation of an array of points
nPoints = 7 # arbitrary
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the comment.

for badNin in getBadNAxes(fromName):
for nIn in getNaxes(toName) + getBadNAxes(toName):
badPolyMap = makePolyMap(badNin, nOut)
with self.assertRaises(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

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

Which Exception?

@r-owen r-owen force-pushed the tickets/DM-8439 branch 3 times, most recently from aa0f4e1 to 5d8f7d7 Compare March 3, 2017 21:42
Copy link
Contributor

@parejkoj parejkoj left a comment

Choose a reason for hiding this comment

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

Thanks for adding the output operator. That'll be helpful when debugging in python.

@parejkoj
Copy link
Contributor

parejkoj commented Mar 3, 2017

I think you still need to convert all the with self.assertRaises(Exception) into catching only the specific exception that should be raised in those cases. Otherwise, strange errors that shouldn't occur would be masked by the test.

@parejkoj
Copy link
Contributor

parejkoj commented Mar 3, 2017

And I guess the remaining assertTrue(np.allclose()) are due to the assertFloatsAlmostEqual bug re: lists that you filed earlier? In that case, I'd suggest using np.testing.assert_almost_equal(), with a note about why that includes the ticket number (and mention it in the ticket you filed).

@TallJimbo TallJimbo force-pushed the tickets/DM-8467 branch 2 times, most recently from e61236b to a335cbb Compare March 7, 2017 18:12
@r-owen r-owen force-pushed the tickets/DM-8439 branch 2 times, most recently from 53f479c to 0967862 Compare March 8, 2017 00:15
@r-owen r-owen changed the base branch from tickets/DM-8467 to master March 8, 2017 00:41
Copy link
Member

@kfindeisen kfindeisen left a comment

Choose a reason for hiding this comment

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

I have a few requests for more documentation and a few suggestions for code cleanup, but I don't see any major issues in the pybind11 wrappers.

cls.def("__str__", [](Class const& self) {
std::ostringstream os;
os << self;
return os.str();
Copy link
Member

Choose a reason for hiding this comment

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

Personally I prefer buffer as more informative than os, but we can worry about when we standardize post-RFC-298.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd be surprised and disappointed if we felt we had to dictate the variable name in our standards.

Copy link
Member

Choose a reason for hiding this comment

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

Not that kind of standard. Pim expressed an interest in reducing the code duplication in our current __str__ and __repr__ implementations, presumably by creating a single centralized function to replace these lambdas.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it. I think that will work out to not wrapping __repr__ at all (which I am violating to provide a slightly nicer class name) and making __str__ = operator<< (which it is, so it can trivially modified to call the standard function when we have one).

using Class = typename PyClass::type; // C++ class associated with pybind11 wrapper class
cls.def("__repr__", [](Class const& self) {
std::ostringstream os;
os << "lsst.afw.geom." << self;
Copy link
Member

Choose a reason for hiding this comment

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

This is only correct if the output of operator<< begins with the name of the class; perhaps document that as a requirement of <<?

cls.def("makeFrame", [](Class const & self) {
auto frame = self.makeFrame();
return frame->copy();
});
Copy link
Member

Choose a reason for hiding this comment

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

Comment on why you don't wrap getNPoints, *From*?

Copy link
Member

Choose a reason for hiding this comment

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

I assume we don't ever plan to create python-only subclasses, so we don't need the protected methods?

Copy link
Contributor Author

@r-owen r-owen Mar 10, 2017

Choose a reason for hiding this comment

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

I originally didn't wrap getNPoints and etc. because they were not implemented in this base class. But I think it's considered better to wrap them, so I will.

I don't anticipate us making Python-only subclasses, and the protected methods aren't essential to doing so.

py::class_<Class, std::shared_ptr<Class>, BaseEndpoint<Point, Array>> cls(
mod, ("_BaseVectorEndpoint" + suffix).c_str());

cls.def(py::init<int>(), "nAxes"_a);
Copy link
Member

Choose a reason for hiding this comment

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

Isn't BaseVectorEndpoint a base class? Why do you need the constructor?

Copy link
Contributor Author

@r-owen r-owen Mar 10, 2017

Choose a reason for hiding this comment

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

I'll remove it. Also, I'll make the constructor for BaseEndpoint and BaseVectorEndpoint protected, since neither is intended to be used directly.

using Point = typename Class::Point;
std::stringstream os;
os << "Point" << N;
std::string pointNumStr = os.str();
Copy link
Member

Choose a reason for hiding this comment

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

An iostream is overkill for this. Use std::to_string instead.

cls.def("makeFrame", [](Class const & self) {
auto frame = self.makeFrame();
return frame->copy();
});
Copy link
Member

Choose a reason for hiding this comment

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

Code duplication with declareBaseEndpoint; factor into a common function.

os << pyClassName;
auto const frameSet = self.getFrameSet();
os << "[" << frameSet->getNin() << "->" << frameSet->getNout() << "]";
return os.str();
Copy link
Member

Choose a reason for hiding this comment

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

You might want to add a comment noting that all the distinguishing information is in pyClassName; at first I thought the result would be something like "Transform[2->2]", which is not particularly helpful.

cls.def("tranForward", (ToPoint (Class::*)(FromPoint const &) const) &Class::tranForward, "point"_a);
cls.def("tranInverse", (FromArray (Class::*)(ToArray const &) const) &Class::tranInverse, "array"_a);
cls.def("tranInverse", (FromPoint (Class::*)(ToPoint const &) const) &Class::tranInverse, "point"_a);
/// repr format is lsst.afw.geom.<ClassName>[<nIn>-><nOut>]
Copy link
Member

Choose a reason for hiding this comment

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

Doxygen comments should not be used inside function bodies. Did you mean //? [Same for __str__]

@r-owen r-owen force-pushed the tickets/DM-8439 branch 5 times, most recently from 7d60ddd to c83d116 Compare March 11, 2017 15:32
Add SpherePoint(double const lonLatRad[2])
for efficient construction from raw data.
In Python these classes are named Transform_fromPrefix_To_toPrefix_
where _fromPrefix_ and _toPrefix_ are endpoint prefixes
such as "Generic", "Point3" or "SpherePoint".
@r-owen r-owen merged commit b1ca1a8 into master Mar 13, 2017
@ktlim ktlim deleted the tickets/DM-8439 branch August 25, 2018 06:44
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