-
Notifications
You must be signed in to change notification settings - Fork 38
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
Added hooks registered per model #965
base: master
Are you sure you want to change the base?
Conversation
Hm... I'm not 100% sold on passing in the model, but I'm OK with it going in for now as long as we're not guaranteeing this for all future time. |
I can't think of any way to do it (and have it work as expected in jupyter notebooks) without passing in the model. That said, I don't think we should merge this yet. If students need this functionality during the summer school, then we'll switch to this branch, but I'd prefer a consensus on this being the best approach before merging this into master (mostly for backwards-compatibility reasons) |
@tbekolay would you mind elaborating as to why aren't you sold on passing the model? |
I haven't thought about it too much, but the two main things that come to mind are:
|
I think those are legitimate concerns, however I can't imagine alternatives due to a limited imagination. Are there any alternatives? |
The alternative is the original proposal, with |
It's only infeasible to to it in way that makes the hooks specific to the model. But I don't see how a different architecture would allow that? |
One new worry has come up with this implementation: the way I've implemented the WeakKey stuff doesn't quite work, so this version has a memory leak. :( So that needs to get sorted out before this is a candidate for merging. |
I definitely agree. The picture in my head was that "the gui should use this hook when running this model", and so I was sort of thinking that it's owned by the gui, but each hook is tagged with a model. I'm not sure what other options there are if we want the hooks to be model-specific, which is the use cases that I had in mind.... |
I guess I agree with Jan that hooks shouldn't be model-specific. If I'm in a notebook and I say that I want something to happen when I press "play", I expect that to happen for all the GUI cells in the notebook. If I didn't want this, I would make the hook, then remove the hook later in the notebook. Hooks inside the GUI (not in a Jupyter notebook) I would expect to be specific to the model they're defined in ... but my impression was that this is possible regardless. One compromise would be to make passing in the model an optional thing. If you don't pass in a model, then it's global in a notebook, local to the file in the GUI (which may not be consistent from a developer perspective, but I would argue is what a user would expect). If you want it to be local to a model inside the notebook, then you can pass in a model. With this, I wouldn't have to remove the hook later in the situation above, I would make it a model-specific hook. |
I'm having difficulty coming up with generic things I would want to have happen based on GUI events, rather than model-specific things. Usually I'm doing things like updating plots based on data from the simulator, or sending a motor command to stop a particular motor that this particular model controls, or opening/closing an i/o connection that this model needs to run. Are there other use cases that people have in mind? Maybe my problem here is that I called these things "hooks", which is leading people to think they are generic across whatever model you're working with. What I really want is I want to be able to say "hey, when running this model, do this extra thing based on the GUI state". Is there a better name for that than "hooks"? Because it never crossed my mind during the entire development process that someone might want this to be generic across a bunch of different models within a notebook. |
I don't have use cases for these hooks, just opinions on the API; the use cases you describe seem like they would be doable with |
So maybe this isn't so much a feature that should be implemented in the GUI, but core Nengo? Something similar has come up several times before, documented in issue nengo/nengo#1118 (there only the Even if we implement some sort of hook system, it might be worth keeping the bigger picture in mind to avoid confusion with an eventual similar feature in core Nengo. |
This I don't quite follow. How would the Process class realize that I've pressed the |
Sorry, been a while since I read those use cases. I can definitely see a need for a hook for the pause button, since that's not a thing that really exists outside of the GUI. But, it seems reasonable to me that if you're in a notebook with a bunch of cells hooked up to the same robot (e.g., testing two different models to drive the same robot) you would want hitting the pause button to always stop the motors, not just stop the motors for one model. Not that I'm saying it's the best workflow, but it's a possible workflow that users might expect. Plots using information from the simulator object are something I think should be possible through other means (e.g., using probes and a plugin architecture). So I guess I can see the use case of testing out some quick hacks that can become full fledged features in the future. Which, by that logic, I can just as easily see a quick hack that tests out some change in how the GUI works as a whole and thus isn't really tied to a model. |
I don't understand this use case. When I try to imagine this use case, I can think of two sub-cases:
In either case, what's stopping them from setting up a hook that turns off all motors in each model (and communicates this between models) if that's the functionality they want? Alternatively, are you saying the hook from each individual simulator not being triggered by pausing one GUI is surprising? I would be surprised if it was surprising. Everything we do currently points to each simulator being independent. Finally, it's quite possible I'm misunderstanding where this discussion currently is. Would it be worth making a NEP to summarize what we each currently understand? |
For what it's worth @nvaidyan1 ran into a use-case that I believe could be resolved by this feature. In particular, he needs an |
Interesting use case, although I think that's probably more correctly done by defining your own Process. That's the recommended way of making Node functions that need to know when reset() happens. |
Also, putting this here as more of a note to myself, I've been mentally toying around with a slightly different approach that I think a) deals with the ambiguities and confusions discussed above b) doesn't have the weird state-tracking/memory-leak problem, and c) also gives a consistent syntax for doing other gui interface things. The syntax would be something like this:
I think the instance name
And we might even get extravagant and allow things like
Note that when run in jupyter notebook, this should still work, even though the GUI hasn't actually been created yet. Anyway, as I said, I'm mostly just posting this here to remind myself to try this approach out and see if it works better/more consistently than the options we've looked at so far. |
I don't remember all the details of the discussion. But some thoughts:
From skimming my initial hooks with decorators proposal #957, it seems the main discussion point is whether rerunning a notebook cell should register another hook. This makes me wonder if the possibility to register multiple hooks is required at all. Otherwise, each registered hook could just replace the previously registered hook to solve this problem. Another idea would be to pass in the hook functions as argument to the |
Fair enough. I'm also happy with not requiring a specific name and just searching
Ooo, good point. That's not needed at all with this. I'll edit the above post -- thank you for catching that!
The biggest one for me is that it eliminates the massive memory leak that was caused by weird global context system that my initial implementation used (and I couldn't find any other way of implementing the above syntax). It also clears up the ownership problem @tbekolay pointed out. And it eliminates the namespace cluttering and silent failing in the original proposal ( #953 ). At least, I think it will -- I haven't implemented it yet, so we'll see. |
My first reaction to this is that it is even worse. It seems to call for weird corner cases leading to weird problems. For example, in a Jupyter notebook old instances might still be accessible through
So from a purely syntactic standpoint, this makes the proposal equivalent to #957 except for using methods of an instance instead of global methods. Semantically it implies slightly different things ... and it might be good to define the desired semantics first before looking at the syntax that best expresses these semantics (and is able to implement them technically). It seems the main point where views diverge is the behavior in notebooks. In particular:
Are you talking about #953? I do not see the memory leak you're speaking of there ... My implementation in #957 has a memory leak (it was just meant as proof of concept), but that is fixable without too much effort by using weak references. I'd assume that weak references should also be applicable to the memory leak you are talking about (if it is a different one).
That was which model owns the hooks? I'm not sure it does without the |
I'm not sure there's an alternative here. This new idea that I'm working is based around creating an instance and adding the hooks to that instance. The reason I'm exploring this is that we couldn't get any consensus about how a system based on globals would work, so I'm trying out this idea of using an instance. If I use an instance, it has to have a name. I can either a) allow people to choose whatever name they want, or b) require people to use a particular name. Is there a third option you're thinking of? I can't think of one.... As I said above, I lean towards a), just for simplicity and the fact that it makes it very clear what should happen if someone creates multiple instances, but I could also be convinced about b).
Yes
I'd go with adding another hook, just to allow composition. And I'd say in most use cases the instance would be created at the same time as generating the hook, so that re-executing the cell would be fine (note that this would be true whether or not the instances required a particular name)
I think it'd be shared for all of them by default (i.e. if the InlineGUI searches inside
The same thing comes up in the non-notebook case, since
No, I'm talking about my implementation here in this PR, which does use weak references, but still has the memory leak. I spent a few weekends trying to fix the memory leak, and couldn't, and convinced myself that I couldn't find a way to fix the leak with our current system. But it's been too long since then for me to remember what the reasons were.
It clarifies that models don't own the hooks, I think. But, all of this said, I really need to spend the time to actually implement this idea. Right now, it's the idea that I'm happiest with, but I've also said that about the other ideas before they got implemented, so I really need to implement it and see if anything weird comes up. It's helpful to get this feedback, though -- thank you! |
One advantage of instantiating an object versus having a global thing is that we can do whatever we want during object instantiation, meaning that we can have the object add itself to some registry when the script is executed. This is arguably the main benefit of this approach over globals, though to a certain extent they are equivalent because that registry is still going to be a global stored somewhere in the
I would argue strongly for requiring people to pass it in. This would solve a lot of the weird magic issues in the notebook, leaving the main oddities in the Nengo GUI environment, which we control so will be able to (hopefully) figure out.
This seems like a big can of worms that I would want to keep closed if possible. |
I think this method actually means that we don't require a registry anywhere, which is part of why it appeals to me on an implementation level.
Seems reasonable to me... I'll give that a try.
Fair enough. :) |
I can't see how that's going to work personally, but it seems like it's time to try it and see. |
Ah, I completely missed that this “issue” is also an actual PR. Unfortunately, I don't see the memory leak. Given the current state of the discussion, I would favour the following:
There is, however, one more question that occurred to me: How should the decorator function behave, if it registers a hook outside of the GUI execution environment (could be in a notebook or could be in a plain script)? It could be silently ignored to make it easier to reuse code outside of the GUI, but it could also raise an exception to prevent required hooks from not being registered when used outside of the GUI. |
This is another implementation of hooks (see #957 and #953 for the previous attempts).
In #953 , the hooks were implemented by just having magic variables
on_step
,on_pause
, etc, and if you happened to define these, then the GUI would call them. However, this is a) rather magical, and b) rather non-discoverable, and c) rather annoying to debug if you're using a branch that doesn't support them.So, @jgosmann implemented a decorator-based approach. It interacted with the ExecutionEnvironment system in nengo_gui to figure out which model we should be attaching the hook to (since we might have multiple pages open looking at different models). This works great. However, if we run outside of the ExecutionEnvironment (which happens when embedding inside Jupyter notebooks), then the hooks are attached globally. This is fine for multiple open notebooks (since those are run in different kernels), but if there are multiple cells in the notebook that use nengo_gui, or if you re-execute a cell that uses hooks, then you will now have the same hook running multiple times.
In this PR, I do the decorator registration approach, but you also have to specify the model (i.e. the
nengo.Network
instance) that you're attaching the hook to. This means we don't have to do anything with the ExecutionEnvironment, and it works fine and consistently in both normal nengo_gui and in Jupyter notebooks.The syntax for this hook system is as follows: