Add boolean volumes#22
Conversation
|
Be a bit more creative in your use of issue closure keywords. How about changing your first line to
? Similarly, chuck in a keyword here. As much as I love all the work you are doing here, and the rate at which you are doing it, reviewing it all is preventing me from making much progress on writing the documentation that this project desperately needs. So, could you please add one or two relevant pages to the docs, that describe this solution, directly in this PR? Recall that we have four categories of documentation, and something like this would need something in at least How-To and Reference, and maybe even in Tutorial. Writing the reference is likely to be a bit tedious, and it won't be stable until we've finalized the details, so that can be left until later. Ideally, we want to see the plain G4 version next to the n4 one, to help us evaluate whether it is worth the effort. Similarly for #14, which I haven't had the time to look at, yet. In general, we shouldn't add any new utilities without documentation ... and tests, but I know that I don't need to tell you about the tests. |
I'm aware of this. I tend to add them later on when the PR is more mature, but I'm ok with doing it from the beginning.
Sure thing.
Don't waste your time on that. It's not the most urgent feature and it's not working. |
| auto vsum = 2 * vbox - vint; | ||
| auto vsub = vbox - vint; | ||
|
|
||
| auto box1 = nain4::volume<G4Box>("test_box1", water, l/2, l/2, l/2); |
There was a problem hiding this comment.
Consider using the n4 synonym for the nain4 namespace, for brevity.
| auto items = { make_tuple(union1, vsum, "add") | ||
| , make_tuple(union2, vsum, "join") | ||
| , make_tuple(subtr1, vsub, "sub") | ||
| , make_tuple(subtr2, vsub, "subtract") | ||
| , make_tuple(inter1, vint, "int") | ||
| , make_tuple(inter2, vint, "intersect") | ||
| }; | ||
|
|
||
| auto density = water->GetDensity(); | ||
| for (auto& item : items){ |
There was a problem hiding this comment.
I did, but the keywords ADD, etc. are class names and I couldn't make it work.
| auto union1 = nain4::boolean<ADD >("test_add" , box1, box2, norot, dr); | ||
| auto union2 = nain4::boolean<JOIN >("test_join" , box1, box2, norot, dr); | ||
| auto subtr1 = nain4::boolean<SUB >("test_sub" , box1, box2, norot, dr); | ||
| auto subtr2 = nain4::boolean<SUBTRACT >("test_subtract" , box1, box2, norot, dr); | ||
| auto inter1 = nain4::boolean<INT >("test_int" , box1, box2, norot, dr); | ||
| auto inter2 = nain4::boolean<INTERSECT>("test_intersect", box1, box2, norot, dr); |
There was a problem hiding this comment.
I don't like the fact that null values such as norot have to be passed in explicitly.
Maybe we can use a builder pattern (like in n4::place), to mitigate this. OTOH, there are far fewer parameters here, so it might not be worth the effort.
It's not immediately obvious whether the builder pattern would play nicely with the template arguments.
There was a problem hiding this comment.
In the constructors for boolean solids there are only two options: rotation + translation or transform. nain4::boolean just forwards the parameters. Do you have any suggestions?
There was a problem hiding this comment.
A builder which, when told to build, inspects the internal state it has accumulated, and forwards it to the G4 constructor that is appropriate. Or the most general one, filling in the blanks with null operations.
Looking at this it looks like you've overlooked the rotation matrix?
Also, looking at the 2nd and 3rd constructor in there, it looks like there are mutually exclusive alternatives: transform-3d vs 3-vec and rot-matrix. So the builder would have to prevent you from mixing them, at compile time.
More generally, at some stage I have to take a step back and see if there's a bearable way of writing typestate patterns in C++. I really want to promote the multitude of G4 pitfalls to compile-time errors.
There was a problem hiding this comment.
A builder which, when told to build, inspects the internal state it has accumulated, and forwards it to the G4 constructor that is appropriate. Or the most general one, filling in the blanks with null operations.
I understand. I will have to try it and see.
Looking at this it looks like you've overlooked the rotation matrix?
I don't follow. The norot variable in the test is there precisely because I need to pass it as an argument.
There was a problem hiding this comment.
The `norot' variable in the test is there precisely because I need to pass it as an argument.
Sorry, brain fart.
| auto union1 = nain4::boolean<ADD >("test_add" , box1, box2, norot, dr); | ||
| auto union2 = nain4::boolean<JOIN >("test_join" , box1, box2, norot, dr); | ||
| auto subtr1 = nain4::boolean<SUB >("test_sub" , box1, box2, norot, dr); | ||
| auto subtr2 = nain4::boolean<SUBTRACT >("test_subtract" , box1, box2, norot, dr); | ||
| auto inter1 = nain4::boolean<INT >("test_int" , box1, box2, norot, dr); | ||
| auto inter2 = nain4::boolean<INTERSECT>("test_intersect", box1, box2, norot, dr); |
There was a problem hiding this comment.
It would be good if the name were optional, with a default being derived from the names of the two constituent volumes and the operation being applied to them.
| auto union1 = nain4::boolean<ADD >("test_add" , box1, box2, norot, dr); | ||
| auto union2 = nain4::boolean<JOIN >("test_join" , box1, box2, norot, dr); | ||
| auto subtr1 = nain4::boolean<SUB >("test_sub" , box1, box2, norot, dr); | ||
| auto subtr2 = nain4::boolean<SUBTRACT >("test_subtract" , box1, box2, norot, dr); | ||
| auto inter1 = nain4::boolean<INT >("test_int" , box1, box2, norot, dr); | ||
| auto inter2 = nain4::boolean<INTERSECT>("test_intersect", box1, box2, norot, dr); |
There was a problem hiding this comment.
IIUC, your solution always combines two volumes to produce a third. What happens if I want to use that in a further boolean operation?
In the very vague thoughts I had about this when opening #5, I was thinking of an interface that would allow chaining, or even arbitrary trees of operations.
As I don't recall having used these boolean operations myself in real code, I have no clue whether real Geant4 use cases have a need for this, even though it would be the right the to do from an idealistic perspective.
There was a problem hiding this comment.
What happens if I want to use that in a further boolean operation?
I haven't tried, but I wouldn't expect a problem with that. Maybe I'm wrong.
There was a problem hiding this comment.
I'm not expecting any technical problems, just verbosity. So the question is whether we can come up with a better interface for chaining boolean operations.
There was a problem hiding this comment.
I think we can. But I have the feeling that the final interface can be decoupled from this implementation. We can offer something similar to place that uses this functionality behind the scenes.
There was a problem hiding this comment.
Well, yes, this would be an implementation details of the interface. So the client would end up not seeing this at all.
We also need to have a good look at G4MultiUnion.
We have to be wary of creating beautiful, convenient interfaces, which generate inefficient code and promote bad practices.
There was a problem hiding this comment.
We also need to have a good look at G4MultiUnion.
I'm aware of this, but I postponed its implementation to a later stage since it needs special care.
Given that the point of the PR is to close that issue, I'd say it should go in from the beginning. |
I guess there's no reason for this, other than you reusing Given that all this boolean solid stuff uses I'm thinking about the more general interface discussed in one of the review conversations, and I don't see any reason why there should be any logical volumes. |
|
What would be the problems with an interface like this: G4VSolid* first = ...;
// etc.
G4VSolid* funky_solid = n4::boolvol(first)
.add(second).at(position).rot(rotaion)
.sub(third).trans(transform_3d)
.name("Optional override of default name constructed from components and ops")
.now();? |
Correct.
I don't see a problem. But I do appreciate skipping the somewhat unnecessary step of creating a solid that adds more noise than signal. |
|
And it would subsume the use cases from your tests, e.g. auto union1 = n4::boolean<ADD>("test_add", box1, box2, norot, dr);would become auto union1 = n4::boolean(box1).add(box2).at(dr).now();
auto onion1 = n4::boolean(box1).add(box2).at(dr).name("test_add").now(); // If you *really* want the name |
Indeed. So |
|
|
I do like the syntax though. I think it is very clear and self-explanatory. The main inconvenience I find at the moment is that the input solids need to be created explicitly. So we either add an interface in n4 (which doesn't add anything over G4?) or we let the user create them in G4 (which looks dirty to me).
Is there a case for returning solids? Or is it just for flexibility? |
G4VSolid* this = n4::boolean(...). ... .solid();
G4VSolid* that = n4::boolean(...). ... .solid();
return n4::boolean(this).add(that).logical();
It would be trivial to add, so even if it were only that, it's probably worth while.
How about n4::boolean<G4Sphere>("mihuevo-L", ...)
.add<G4Shpere>("mihuevo-R", ...).at(dr)
.name("mishuevos")
.logical();? I guess we can overload the templates with non-templated versions which still take pre-constructed solids. |
Yes, I agree. It can help the assembly of complex boolean compositions.
I like it. In practice, the case of pre-constructed solids will probably prevail, but that's as much as can be done, I think. |
Actually, |
|
So to summarize, we are going directly to this interface, right? |
Hmm ... maybe I'm not fully understanding what you are saying.
Yes, I think so. |
|
Ooops, the proposed interface doesn't mention materials, which are needed if we want it to return logical volumes. Maybe a |
|
Ah, it's OK: the whole thing must have the same material, right?. So
would be enough. We could even do
but that's probably too cute and cryptic. |
The templated methods you proposed can become difficult to read relatively easily, I think. So the natural choice would be to create the input solids separately and then feed them into
Regarding the whole |
You mean because of the bazillion arguments that they take, they would drown out the structure with low-level noise? Yes, quite possibly. So do you think we should not provide them?
I do think that n4::boolean(thys).add(that).at(position).logical(water);is a huge improvement over ... well, I can't be bothered to figure out the equivalent bare G4. [Aside: looking at the above, it might be nice if the end were spelt
I don't think there is much that can be done to improve the situation: these solids do need a bunch of different construction arguments, and they have to be passed in somewhere. If all you're doing is creating a simple solid, then I don't think n4 can improve over G4 in any significant way. So I think it's perfectly OK for the earlier example to be preceded with auto thys = new G4Sphere(lots, of, noisy, arguments, that, must, be, given, somewhere); |
I agree. I meant an n4 interface to create
I kinda like |
Te-he. Me too. Just a little bit. But this is an example of where G4 doesn't do anything terribly wrong. Probably because it's difficult to imagine how to mess up something so simple. [Hmm, now you've got me thinking about whether these solids' multiple parameters can be organized more conveniently.]
I know what you mean, but we have to be pragmatic and not excessively purist. n4 tries to help you write G4 code, where it can: it's not a complete replacement for G4. I think it's an important message to send to potential users, that n4 does not force you into anything: the whole of G4 (familiarity, warts, bugs and all) remains available to you, but we can help to make some things easier, clearer and more robust. |
|
What's the reason for |
Hmm. Two things, I guess
More generally,
and So all our, as yet unimplemented, typestates should give compile-time errors if you try to build before all of the mandatory values have been set. In other words, it's probably there, setting us up for the conceptually correct thing to do, but a number of things have not been implemented in this case, yet. Does this make any sense? |
|
Yes. I was slightly confused because I didn't see any reason for a childless case, mostly. So if IIUC, at the moment optional's job is to protect against nullptrs? |
|
No, I don't think it protects against |
|
No, but it gives you an error, instead of a segfault, which is a small improvement. |
|
I think that with all the Let's get this done now, so that we can see if the |
A simple proposal to close #5.
Pro: extremely simple
Con: creates unnecessary logical volumes
IMHO, the pro outweighs the con.