-
Notifications
You must be signed in to change notification settings - Fork 239
Fix issue run at #1602
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
base: master
Are you sure you want to change the base?
Fix issue run at #1602
Conversation
Apologies, I did not yet have time to have a look at the PR, but I will do so tomorrow ! |
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 looks really good and very close to what I had in mind 😊 Unfortunately, it also breaks everything 😅
As explained in the comments, I get the appeal of the __eq__
approach, but I don't think it is the right solution here.
brian2/core/clocks.py
Outdated
def __init__(self, times, name="eventclock*"): | ||
Nameable.__init__(self, name=name) | ||
self.variables = Variables(self) | ||
self.times = times |
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.
We should probably sort these times (given that we assume them to be sorted later). Also, maybe include a check that it does not contain the same time twice?
brian2/core/clocks.py
Outdated
def __lt__(self, other): | ||
return self.variables["t"].get_value().item() < other.variables["t"].get_value().item() | ||
|
||
def __eq__(self, other): |
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.
While I get the appeal of using __eq__
here, I don't think it is the right approach – also, it breaks everything :) The reason is that we don't want to consider two Clock
s to be equal just because their time is the same. E.g. there are places where we check whether two objects use the same clock, and those would always return True
at the start of a simulation when those clocks are at 0ms. Also, we have places where we put clocks into a set
, and Python objects that have __eq__
but not __hash__
are not hashable (and the hash of an object is not allowed to change and needs to be consistent with __eq__
so we cannot simply add a __hash__
function. I think the best approach would be to add a specific function to compare them, say same_time(self, other)
brian2/core/network.py
Outdated
or abs(time - min_time) / min(minclock_dt, dt) < Clock.epsilon_dt | ||
) | ||
for clock in self._clocks | ||
if clock == minclock |
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.
See my comment about __eq__
above
brian2/core/network.py
Outdated
] | ||
minclock, min_time, minclock_dt = min(clocks_times_dt, key=lambda k: k[1]) | ||
|
||
minclock = min(self._clocks) |
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.
While this (i.e. defining __lt__
and __le__
) is less of an issue than the equality check, for consistency/readability we should probably also have an explicit function here, or simply use something along the lines of min(self._clocks, key=lambda c: c.t)
brian2/core/network.py
Outdated
return minclock, curclocks | ||
|
||
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.
Nothing to worry about before the PR is finalized, but have a look at our documentation on how to enable pre-commit
so that it will make sure that the source code is formatted consistently: https://brian2.readthedocs.io/en/stable/developer/guidelines/style.html#code-style
Thanks a lot for taking the time to guide me through my mistakes, I must have overlooked the mention of the eq, I'll get to work and get back to you with news soon :). |
I made some changes |
Hi @mstimberg, I've made all the changes I think are needed, but I'm not too sure if they are correct, looking for your feedback. |
Hi @Samuele-DeCristofaro. I did not go through your updated code in detail yet, but I'd suggest that you'd try running a simple example that uses your branch. If I do this for the CUBA example, I get an error which strongly suggests that there is still something to fix 😊 For more thorough testing, please look at the documentation on how to run the test suite: https://brian2.readthedocs.io/en/stable/developer/guidelines/testing.html#running-the-test-suite Here's the full error I am getting: Traceback (most recent call last):
File "/home/mstimberg/git/brian2/examples/CUBA.py", line 17, in <module>
from brian2 import *
File "/home/mstimberg/git/brian2/brian2/__init__.py", line 91, in <module>
from brian2.only import *
File "/home/mstimberg/git/brian2/brian2/only.py", line 59, in <module>
set_device(all_devices["runtime"])
File "/home/mstimberg/git/brian2/brian2/devices/device.py", line 684, in set_device
_do_set_device(device, build_on_run, **kwargs)
File "/home/mstimberg/git/brian2/brian2/devices/device.py", line 698, in _do_set_device
active_device.activate(build_on_run=build_on_run, **kwargs)
File "/home/mstimberg/git/brian2/brian2/devices/device.py", line 378, in activate
self.defaultclock = Clock(dt=0.1 * ms, name="defaultclock")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 300, in __init__
super().__init__(dt, name)
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 186, in __init__
super().__init__(times, name=name)
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 76, in __init__
self.times = sorted(times)
^^^^^^^^^^^^^
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 70, in __getitem__
return self.clock.dt * timestep
^^^^^^^^^^^^^
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 257, in <lambda>
fget=lambda self: Quantity(self.dt_, dim=second.dim),
^^^^^^^^
File "/home/mstimberg/git/brian2/brian2/core/clocks.py", line 245, in _get_dt_
return self.variables["dt"].get_value().item()
~~~~~~~~~~~~~~^^^^^^
File "/home/mstimberg/git/brian2/brian2/core/variables.py", line 1646, in __getitem__
return self._variables[item]
~~~~~~~~~~~~~~~^^^^^^
KeyError: 'dt' |
Hi @mstimberg thanks for the suggestion, I'll get back to you with some more useful feedback as soon as possile. |
``Hi @mstimberg , I was working on this issue but it seemed like I just miss typed self.clock.dt in the ClockArray meanwhile I set self._dt=float(dt) in the RegularClock class, after that the CUBA example worked. Then when I tried running some examples like test_clocks or test_networks it returned this error:
I need the dt before the constructor of EventClock to set :self.variables["t"].set_value(self.times[0]) but I have to change the name or it triggers the setter and self.variables("dt") wouldn't find any attribute. |
I also think that if we wanto to keep the _dt we should also add something to reset it aswell if needed. |
Hi @Samuele-DeCristofaro. Uh, this is actually a very subtle thing and arguably a bug in Brian… When you access an object attribute that is handled via Brian's variable mechanism (like |
Hi @mstimberg thanks, now it's all more clear, I really did not understand where the issue was 😅, I was also wondering if something like this would be useful aswell:
|
Hi @mstimberg, thanks for your tips. I'm making progress, but I'm still getting an error in test_network.py. The code seems to handle the restored dt value correctly - the assertion 'assert defaultclock.dt == 0.5 * ms' passes. However, I'm still getting an array comparison error with 'assert_equal(mon.t[:], [0, 0.5] * ms)'. The actual array is [0, 0.001] while the expected one is [0, 0.0005]. Mismatched elements: 1 / 2 (50%) |
Hi @Samuele-DeCristofaro This seems to be because the time calculation in |
Hi @mstimberg before changing the approach I tried to use the initial_dt only to set the dt the first time:
, when it was time to set up the t, and now we can simply always use dt even if it's not the cleanest way, in the test_network, it didn't give any errors but it took like 30 s to simulate, then I started making some adjustments in the network file, specifically in the nextclock and the run function, and it's getting faster, now it takes 14s by making small adjustments. I can try to optimize things even more but I don't know how much faster it can get. And it seems very tricky 🥲. Could you provide any feedback on how to handle this? |
I also forgot to mention that with this new clock implementation, I tried using the old network file and the test_network worked perfectly without any changes or errors in the expected output. This means that there are no major issues in the clocks. I also feel like we’re very close to finishing this PR. I'm doing some more testing to figure out what's causing these issues. |
Hi @Samuele-DeCristofaro I feel that this is not the right way: if the |
Hi @mstimberg, alright I will then apply these changes requested, but what about the network file is there something to avoid using that could lead to some performance issues as I said before. |
Please update the PR first. I did not quite understand the performance issue, and it is much easier for me to simply try out the code and see for myself. Thanks! |
PR updated. I'm sorry, I should have updated the PR earlier, but I'm trying to manage on my own, I took the work and I don't want to ask for help too often. I also have another implemetation of the network file that simulates in 14 s, instead of the current updated which does it in 30s. I think the issue is calling the variables too often. |
Thanks for updating the PR. I tried it out, and the performance of some test cases is indeed much worse than before. A good/extreme example is the |
PR updated again, please let me know if more changes are needed. |
PR updated ready to be checked, I also added some simple checks to test_clocks.py for the new event_clock, let me know if some checks could be added up or if it's not needed at all. As you expected this new implementation is slightly slower then the initial one but I don't think there are any big issues now. I'm sorry if this took too long I'm having a busy-rough time. |
Also if we manage to merge this PR, I would be open to help more with everything if needed. |
Hi @Samuele-DeCristofaro. I think this looks good so far, we'll keep the final performance tweaking for the end. The new test looks good in general, but I'd tweak the values a bit to have irregularly spaced events (which is kind of the point of |
Hi @mstimberg, PR updated! I just wanted to ask what could be added specifically to the new test, I changed how the duplicates in the EventClock were checked and now it works for Quantity times aswell, since before I was using set but they are not hashable. |
Hi @Samuele-DeCristofaro. Sorry, it took me a while to get back to this PR. Regarding the Regarding the tests: I think your tests cover the essentials, but instead of the x = NameLister(name="x", dt=0.1 * ms, order=0)
y = NameLister(name="y", clock=EventClock(times=[0*ms, 0.5*ms, 1.5*ms]), order=1) It should give `"xyxxxxxyxxxxxxxxxxyxxxx" for a 2 second run. |
brian2/groups/group.py
Outdated
|
||
return self.run_custom_clock(code, clock, when, order, name, codeobj_class) | ||
|
||
def run_custom_clock( |
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 find this name a bit confusing, how about run_on_clock
?
Hi @mstimberg, |
Hi @Samuele-DeCristofaro. Thanks, the changes look good in general. Can you please mark the pull request as "Ready for review"? I will then have a final look.
A test that fails sometimes is a broken test, we definitely need to fix this. The behaviour should be deterministic, it should always run the exact same number of steps for a given time. But I see where the problem is coming from: at the end of each time step, we advance the clocks. At the end of a simulation with regular clocks, all clocks will therefore have the timestep/time that is > the requested run time – this is the check that we are doing to see whether we should stop the current run. For the event clock, we are never going beyond the events that it has, so it cannot be set to the next time step. Actually, this means that the current |
HI @mstimberg , I tried your tip but the test it's still not always working 😭 . I marked the PR as "Ready for reveiw". |
Add test that tests for run continuation
Hi @Samuele-DeCristofaro The problem was that |
Got it! Thanks for the help! 😃 I got confused because sometimes the test worked, so I thought there was something wrong with the run method. But I was looking at the wrong place 😅 |
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, we are getting there! I've left several nit-picking comments, but those are mostly minor issues. The main remaining task is to get C++ standalone support in place (but I can take care of that if you prefer), and to mention the new features in the documentation.
error_message="'times' must have dimensions of time, got %(dim)s", | ||
dim=times, | ||
) | ||
self._times = sorted(times) |
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 feel it is a bit confusing that the Quantity
array gets converted into a list here. But maybe more importantly, I think we will need to create a times
array with Brian's add_array
mechanism to get things to work with C++ standalone mode.
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 created the times array, does it make sense to use a numpy array instead of a list there or maybe just sorting manually.
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.
When you create the Brian variable times
below, it will already be stored internally as a numpy array. To not duplicate things, I'd not store self._times
at all, e.g. you could do times = sorted(times)
and times.append(np.inf*ms)
before passing values=times
to the add_array
function.
seen = set() | ||
duplicates = [] | ||
for time in self._times: | ||
if float(time) in seen: | ||
duplicates.append(time) | ||
else: | ||
seen.add(float(time)) | ||
if duplicates: | ||
raise ValueError( | ||
"The times provided to EventClock must not contain duplicates. " | ||
f"Duplicates found: {duplicates}" | ||
) |
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.
The error message will be slightly less useful, but I think I'd prefer a simple
if len(self._times) != len(set(self._times)):
raise ValueErrror(...)
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've already tried your solution before but set uses eq methods to compare the times so it returns an error with Quantity types.
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.
Hmm, I see. It's not about __eq__
but about Quantity
not being hashable. But we could move this test to use the numpy array we create later, or use len(set(np.asarray(times)))
or similar.
I'm very happy we are getting there !, I think I can work on the cpp standalone part as well, and I'll fix these minor issues ASAP, I have no clue about what happened to those quotes 🥲. |
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 for the updates, I made a few more minor comments. But as I said earlier, the big thing to do now would be the C++ standalone support – let me know if you need further guidance!
for i in range(4): | ||
print(event_clock[i]) |
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.
left-over print
@@ -98,7 +111,6 @@ def test_combined_clocks_with_run_at(): | |||
|
|||
# Expected output: "x" at 0,1,2,3,4ms = 5 times | |||
# "y" at 0.5, 2.5, 4.0ms = 3 times | |||
# We don't care about exact timing here, just the sequence |
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 did not mean to only remove the comment, but also to actually care about the exact output in the test below 😊
), f"Expected {expected_y_count} y's, got {y_count}" | ||
|
||
# Optional: check full string if needed | ||
print(updates) |
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.
Remove print (but check full string ;-) )
seen = set() | ||
duplicates = [] | ||
for time in self._times: | ||
if float(time) in seen: | ||
duplicates.append(time) | ||
else: | ||
seen.add(float(time)) | ||
if duplicates: | ||
raise ValueError( | ||
"The times provided to EventClock must not contain duplicates. " | ||
f"Duplicates found: {duplicates}" | ||
) |
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.
Hmm, I see. It's not about __eq__
but about Quantity
not being hashable. But we could move this test to use the numpy array we create later, or use len(set(np.asarray(times)))
or similar.
Closes #1288