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-12452: Add FrameDict class #32
Conversation
from "ind1, ind2" to "from, to"
a4fceca
to
b09222d
Compare
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.
Lots of minor feedback, and two serious issues:
FrameDict
cannot be used as aFrameSet
because the latter's API does not allow for the possibility of illegal frames. I realize that AST (in particular, the relationship betweenFrame
andFrameSet
) does not follow the substitution principle anyway, but I'd prefer not to make things worse. CanFrameDict
be made a distinct class instead (with conversions to and fromFrameSet
, if need be)?- Some of
FrameDict
's mutators are not exception-safe. Fortunately, the problem can be fixed by refactoring_rebuildDict
and_addFrameToDict
.
I'm also a bit concerned by how stringly-typed the domain system is, but we can cross that bridge when we get to TransformMap
. I guess it makes perfect sense in the context of astshim
.
include/astshim/FrameDict.h
Outdated
|
||
/** | ||
A convenience wrapper around FrameSet that maintains an internal lookup table ("dictionary") | ||
of frame domain:index for those contained frames that have non-empty domains. |
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.
This statement is specific to the current implementation and is rather longer than a brief description should be. Isn't it cut off in the built documentation?
How about "A FrameSet that can be indexed by frame domain."?
include/astshim/FrameDict.h
Outdated
- Some AST frame classes have default domain names, e.g. SkyFrame defaults to "SKY". Such default | ||
names are ignored in order to reduce the chance of accidental collisions. | ||
- This class is named "FrameDict" instead of "FrameMap", even though C++ lookup tables are usually | ||
std::map or unordered_map, in order to avoid confusion with AST Mapping. |
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.
I don't think this last note is worth mentioning. It's not like you address why it would be called "FrameMap" instead of, say, "NamedFrameSet".
include/astshim/FrameDict.h
Outdated
FrameDict &operator=(FrameDict &&) = default; | ||
|
||
/// Return a deep copy of this object. | ||
std::shared_ptr<FrameSet> copy() const { return std::static_pointer_cast<FrameDict>(copyPolymorphic()); } |
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.
Did you intend to copy-convert to a shared_ptr<FrameSet>
?
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.
No. Good catch.
include/astshim/FrameDict.h
Outdated
} | ||
|
||
/** | ||
Add a Frame connected by a Mapping to an existing initial Frame. A variant of FrameSet::addFrame |
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.
Why are you disambiguating this as FrameSet::addFrame
instead of addFrame(int, Mapping const &, Frame const &)
? It seems strange to refer the user to the base class documentation.
include/astshim/FrameSet.h
Outdated
@@ -205,7 +205,7 @@ class FrameSet : public Frame { | |||
and the `current` @ref Frame of the `frame` FrameSet. This latter @ref Frame becomes | |||
the current @ref Frame in this FrameSet. | |||
*/ | |||
void addFrame(int iframe, Mapping const &map, Frame const &frame) { | |||
virtual void addFrame(int iframe, Mapping const &map, Frame const &frame) { |
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.
Making a previously un-overridable method overrideable is dangerous, because it adds another way in which the implementation of the parent class can have unexpected effects on the child class. If you want to do this, please:
- Remove the call to
addFrame
fromFrameSet(Frame const &, Mapping const &, Frame const &, std::string const &)
. This will always callFrameSet::addFrame
, which may break subclasses that depend on custom functionality in theiraddFrame
. See Effective C++, Item 9 for more details. - Document any self-use of
addFrame
by other public or protected methods (fortunately, I think the aforementioned constructor is the only one, but please double-check).
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, I was definitely in Java mode when I wrote this. I don't see a sensible way to remove the call, and the danger of unexpected side effects from a base class calling its own virtual methods only applies when the call is, in fact, virtual. So assuming the constructor really is the only self-use of addFrame
, there's no need to do anything for this item.
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.
Given the fact that it makes you uneasy and Scott Meyers suggests never calling a virtual function from a constructor or destructor, I'm inclined to change this. I added private method FrameSet::_basicAddFrame
and call that from the one FrameSet constructor and from FrameSet::addFrame
.
tests/test_frameDict.py
Outdated
self.checkPersistence(frameDict) | ||
self.checkDict(frameDict) | ||
|
||
def testFrameDictFrameSetConstructor(self): |
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.
Please try to be consistent about whether your test cases are named test_Something
or testSomething
. (I can't find anything in the style guide preferring one or the other.)
indata = np.array([[1.1, 2.1, 3.1], [1.2, 2.2, 3.2]]) | ||
predictedOut = indata * self.zoom | ||
for zoomMap in zoomMapList: | ||
assert_allclose(zoomMap.applyForward(indata), predictedOut) |
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.
Again, can predictedOut
be computed with frameDict
?
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.
I prefer the more direct test, as above.
self.assertEqual(frameDict.getFrame(frameDict.CURRENT).domain, "FRAME2") | ||
self.assertEqual(frameDict.getMapping().ident, "zoomMap") | ||
self.checkPersistence(frameDict) | ||
self.checkDict(frameDict) |
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.
Can you add a test for addFrame
that tries to use a duplicate domain? Can it check that class invariants are preserved (in particular, all domains are unique, and getIndex
can find any applicable frame by its domain)?
tests/test_frameDict.py
Outdated
frameDict.setDomain("NEWFRAME2") | ||
self.assertEqual(set(frameDict.getAllDomains()), set(["NEWFRAME1", "NEWFRAME2"])) | ||
self.assertEqual(frameDict.getIndex("NEWFRAME1"), 1) | ||
self.assertEqual(frameDict.getIndex("NEWFRAME2"), 2) |
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.
Can you add a test for setDomain
that tries to use a duplicate domain? Can it check that class invariants are preserved (in particular, all domains are unique, and getIndex
can find any applicable frame by its domain)?
tests/test_frameDict.py
Outdated
self.assertEqual(frameDict.getIndex("NEWFRAME1"), 1) | ||
self.assertEqual(frameDict.getIndex("NEWFRAME2"), 2) | ||
|
||
|
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.
Can you add tests for getFrame
, getMapping
, etc. that try to use a missing domain?
09661d5
to
ce3f00f
Compare
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.
Looks good. Some comments on remaining issues.
include/astshim/FrameDict.h
Outdated
|
||
/** | ||
Add a Frame connected by a Mapping to an existing initial Frame. A variant of FrameSet::addFrame | ||
where the initial frame is specified by domain. | ||
@copydoc FrameSet::addFrame | ||
|
||
@throws std::invalid_argument if `frame` has a non-empty domain and this FrameDict already | ||
contains a Frame with that domain |
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.
This will cause the throws section to appear after the notes section, but fine.
include/astshim/FrameDict.h
Outdated
|
||
/** | ||
Get a list of the domain names for all contained Frames that have non-empty domain names. | ||
Variant of @ref addFrame(int, Mapping const &, Frame const &) "addFrame(int, ...)" | ||
where the initial frame is specified by domain. |
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.
👍
include/astshim/FrameDict.h
Outdated
|
||
using FrameSet::getFrame; | ||
|
||
/** | ||
Get a frame specified by domain | ||
Variant of @ref FrameSet::getFrame "getFrame(int, bool)" where the frame |
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.
This still links to the base class docs instead of the derived class docs, which is likely to confuse the reader. Same story below.
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.
I changed the text in all cases. However, the links still go to the base class docs -- it appears that Doxygen does not list or provide docs for methods brought in via "using".
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.
Strange, I'll investigate.
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.
Thanks. If you can figure out why the link to getMapping(int, int)
is failing or suggest an alternate syntax that works, that would be also be appreciated.
include/astshim/FrameDict.h
Outdated
or empty domain. | ||
Build a new domain:index dict from a FrameSet | ||
*/ | ||
std::unordered_map<std::string, int> _makeNewDict(FrameSet const &frameSet) const; |
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.
Consider making this static
.
include/astshim/FrameDict.h
Outdated
} | ||
void _update(FrameSet &frameSet) { | ||
_domainIndexDict = _makeNewDict(frameSet); | ||
this->swapRawPointers(frameSet); |
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.
Might want to make it clearer (with documentation and/or parameter name) that frameSet
is what will replace this object.
Might also be worth mentioning that this method is strongly exception-safe, since that guarantee is essential to _update
's purpose. (You do say "if successful", but when reviewing FrameDict.cc
I got the mistaken impression that _update
modifies frameSet
before doing the swap.)
include/astshim/Object.h
Outdated
Swap the raw object pointers between this and another object | ||
*/ | ||
void swapRawPointers(Object &other) { | ||
std::swap(_objPtr, other._objPtr); |
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.
Bad style to explicitly use std::swap
(see Effective C++, Item 25 for why this can exclude the best swap
implementation), but unlikely to matter in practice because ObjectPtr
is defined in C.
On the other hand, since construction and assignment of ObjectPtr
are C operations, you can safely declare this method noexcept
.
#include <stdexcept> | ||
#include <vector> |
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.
Why do you need to add <limits>
, <sstream>
, or <vector>
?
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.
They are used later in the file. That need is unrelated to this ticket; I only discovered it while working on this ticket. I'll make it a separate commit.
auto copy = getFrameSet(); | ||
copy->addFrame(iframe, map, frame); | ||
_update(*copy); | ||
} |
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.
Very clean. 😄
However, this code relies on its correctness on getFrameSet()
always returning an object whose type is exactly FrameSet
(for example, if it returned a FrameDict
you'd get infinite recursion). Given that methods in FrameSet
have a history of later becoming virtual, consider documenting that getFrameSet()
always returns a base FrameSet
.
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.
Good catch. I added proper documentation.
tests/test_frameDict.py
Outdated
@@ -28,12 +28,12 @@ def test_FrameDictOneFrameConstructor(self): | |||
frameDict = ast.FrameDict(self.frame1) | |||
self.assertIsInstance(frameDict, ast.FrameDict) | |||
self.assertEqual(frameDict.nFrame, 1) | |||
self.assertEqual(frameDict.getAllDomains(), ["FRAME1"]) | |||
self.assertEqual(frameDict.getAllDomains(), set(["FRAME1"])) |
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.
Wouldn't {"FRAME1"}
be more Pythonic?
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. I changed all instances.
tests/test_frameDict.py
Outdated
self.assertEqual(frameDict.getFrame("FRAME2").domain, "FRAME2") | ||
self.assertEqual(frameDict.getFrame(frameDict.CURRENT).domain, "FRAME2") | ||
self.assertEqual(frameDict.getAllDomains(), set(["FRAME1", "FRAME2"])) |
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.
Again, why not say {"FRAME1", "FRAME2"}
?
4a032a8
to
58a9eaa
Compare
58a9eaa
to
d56ab0d
Compare
d56ab0d
to
6daaeba
Compare
No description provided.