-
Notifications
You must be signed in to change notification settings - Fork 132
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
Document and improve Snapshot API #819
Comments
For context, is this any different than in HOOMD 2.x? I agree that an example would be great, but I just want to make sure that this isn't a regression. The same issues arise with having to broadcast snapshots in the 2.x API, right? |
We deleted much of the v2.x snapshot docs. The rewritten docs do need improvement and these are good suggestions. The need to access global snapshots on rank 0 only is the same in v2 and v3. |
Yes, I think starting from scratch is probably best to avoid having any misleading examples in there 👍 |
In addition to this, |
Perhaps the following struggles can help inform the composing of Snapshot documentation. @joaander suggested I post them in this discussion. I was trying to read a unit cell from gsd and then start a simulation with many unit cells. Semi-intuitively I wanted to do:
but still only one unit cell is there. Next I try:
but the problem is that replicate returns Try again:
Doesn't fix it because replicate still returns I tried re-initializing the snapshot,
naturally getting "cannot initialize more than once". Some confusion points
By trial and error, I settled on this:
This works because the |
@cbkerr Taking your points in inverse order
2 . I think this may be more a documentation thing?
|
Just to clarify, the expected way to achieve what @cbkerr wants in the current API is the following, right?
@cbkerr I'm trying to get a sense for how things could be improved. Did you try this alternative? If not, was the problem that when replicate didn't return something you didn't consider that replicate must be happening in place? Is the issue what @b-butler mentioned in point 2 above, that it wasn't clear that you could set the snapshot initially, and then when replicate didn't behave as expected you didn't try the right combination of replicate in place followed by setting? @b-butler is |
1 - @b-butler, I would have used the gsd to snapshot function of #893 if it were implemented and documented (ie, removing this warning). I think it would be very common in cases where you store a base state in a .gsd file (like a unit cell; or a grid of particles without assigned identities) and make some modifications before running. |
3 - returning the modified self of Snapshot.replicate would be intuitive at first glance. Are there any technical downsides to returning the Snapshot's modified self? Would it conflict with the standard for other areas of the API? (I'm still new to it) I may have identified a root cause of the confusion, so read on. @vyasr, yes, the code you provided works BUT the middle two lines seem redundant to me. And yes, at first I assumed that replicate would modify in place, but I didn't consider that it could do so without returning anything. ~~~interlude~~~ While writing the code, I did not understand what @b-butler raised in his point 2. I assumed that I could modify the simulation state directly. I know the following quote from Brandon relates to making it harder for people to make my mistake:
I am inclined to agree, but I can't quite visualize what this would look like. Also, by "direct setting of snapshot", do you mean "setting snapshot while it's still 'attached' to the simulation State?" In other words: "setting snapshot in the same manner by which you would access the simulation snapshot?" The fundamental confusion To illustrate this confusion of syntax, note that @vyasr rephrases point 2 as follows:
Note that @Vyas is using the definition of "replicate in place" that means "replicate the snapshot in place" even though we are both talking about code that follows the syntax of case (2A). Is this a sensible way of explaining the confusion? |
I've responded to a few of your points inline below, but let me summarize what I think the two core issues are:
Does that sound right to you? If so, would you be less confused if everything worked "in place"? If so, would it help if we identified the dominant pattern (in place vs copy) throughout HOOMD and documented the exceptional cases where things are done the other way? I don't think there is any reasonable way to avoid some level of inconsistency in this fashion without either 1) incurring significant performance penalties or 2) walking back some of v3's commitment to property based access to everything. However, if you think this accurately summarizes the confusion then maybe it would be possible to make one pattern much more common than the other, making it easier to clearly document such behavior.
I don't see any technical downsides with just having
Yes, that makes sense.
Yes, I think you are correctly interpreting his comment. I think what @b-butler means here is to change the code so that my snippet would no longer work, and you would instead have to do the following:
In other words, preventing direct assignment to
Yes, this concern is very valid; even to someone who knows how Python works very well you would have to read the appropriate documentation to know which of these are copies and when things can be modified in place, etc.
|
@vyasr I agree with your 2-point summary. Knowing that I shouldn't expect consistency goes a long way toward resolving the confusion, so I think a high-level documentation page explaining the underlying reasoning and rules about when to expect copy vs. in-place could resolve this without having to make one design pattern more prevelant. I don't know if this is made more or less confusing by {knowing python really well, knowing hoomd 2 really well, just starting with hoomd 3, ...}, because I'm fairly new to all of them. Maybe we need to informally survey other new users?
Funny, from my experience with python, I've been frustrated so often with getting back references instead of copies that I've learned to expect references instead of copies! About switching to setters and getters suggested by @b-butler: I can see how using the |
To be clear Methods of With respect to this case, I think we just need to make the docs clearer for With regards to the larger conversation on reference/copies for nested data structures - HOOMD currently returns copies for most parameters and type parameters. #776 implements proxies that make it appear that the user can modify nested data in place, even though the implementation is performing a read/modify/write operation. The main exception to this are the lists of Operations, Forces, etc... which store references and Triggers/ParticleFilters which are returned by reference. Triggers and ParticleFilters are immutable objects so reference vs copy is less of an issue there. |
Okay, I think the conclusions thus far sare we should more clearly document the nature of the returned snapshot object, and make |
The logic for most of the statements above is sound and in general I think that the right choices have been made on copies vs references. I also agree that improving the documentation for I'm not sure that any of the core HOOMD devs are good people to determine how confusing this is, someone like @cbkerr is probably more representative of our user base. Perhaps we should ask for a few additional opinions. However, using getter/setter methods rather than properties specifically in cases like From a performance perspective, we had a long discussion about the use of properties vs methods in coxeter that might be instructive. My argument was that properties should be cheap to evaluate, since otherwise it hides an expensive operation behind an apparent attribute access. This is (to my knowledge) the generally accepted consensus on property usage, for instance see here and here. On the other hand, in coxeter a large part of its value is based on making things highly mutable. We ended up deciding that performance concerns in coxeter are pretty negligible anyway since even "slow" operations in that case are fast in absolute terms, and by my own statement of purpose for the project usability is a higher priority than performance. However, @b-butler also made the point that in HOOMD v3 choosing to use properties as much as possible even at the expense of hiding potentially expensive operations is an intentional design choice. Assuming that we are fine with that as a conscious design choice, I would say that we should reserve the getter/setter pattern above for cases where there is a good design reason for nested structure to have different value vs reference semantics, and otherwise use properties everywhere and just tell users up front that they should profile their code if it's slow. |
Unfortunately, Python does not support concepts of const or return by value that C++ does. This strong typing makes it clear to the developer what they are getting, though they still need to refer to the documentation (or at least the function definition) to see whether a particular get* method returns a copy or a reference. I think the best we can do here is remove the Going forward we should adopt a consistent API and carefully use properties only for quantities that are conceptually owned by the parent object. Snapshot's do not fit this test as they are an independent copy of the system state. |
Should we also implement a setter method for |
Here is a related issue in freud: glotzerlab/freud#680 |
Some developers discussed in #819 that they expect methods that make in-place modifications to return the object. This enables chaining of modifications.
Per the discussion in #819, users find it confusing that the snapshot property access returns copies and does not allow direct modification. Make these explicit methods so that the intent of the design is clear as is the performance implications. Also, prevent the type names from changing when restoring snapshots. Allowing the names to change would lead to parameters for one type applying to another.
To all who participated in this discussion, #1079 implements the changes we discussed and adds some additional documentation. If you have additional comments, please review the pull request and make them there. For now, the properties are still there, but deprecated. I will remove prior to the final 3.0.0 release. |
Description
I had a hard time figuring out how to calculate the number density of a HOOMD v3 simulation running in MPI.
sim.state.snapshot.particles.N
andsim.state.snapshot.configuration.box
are only set on the root rank, but the snapshot has to be created on all ranks (it requires collective access). It would not be possible to use the computed density for further simulation changes on any rank but the root. Luckily there are state properties likesim.state.N_particles
andsim.state.box
that can be used, but this wouldn't work if the properties of interest were arrays like particle positions.Proposed solution
@joaander @b-butler I read through the hoomd.State docstring and the snapshot description again after our conversation today. It describes the same things you told me today, but I didn't understand it when trying to work through it on my own. Maybe there are ways to make this clearer -- I would have benefitted most from a code snippet demonstrating the access pattern, as I suggest below.
We could document that MPI snapshot access patterns should look like this (if the information isn't needed on all ranks):
We should also provide a simple mpi4py example for the case where the information is needed on all ranks:
Also for what it's worth, this code snippet deadlocks because snapshot gathering is collective (this is expected and sort-of documented).
Additional context
@joaander suggested we might be able to provide a simple wrapper for broadcasting to all ranks, but that might be just as complicated to use as
mpi4py
, so it's not clear whether it would result in a net improvement for usability / user intuition.The text was updated successfully, but these errors were encountered: