Add deadtime optimisation#755
Conversation
Codecov Report
@@ Coverage Diff @@
## main DiamondLightSource/hyperion#755 +/- ##
==========================================
- Coverage 94.19% 92.56% -1.63%
==========================================
Files 37 37
Lines 1808 1869 +61
==========================================
+ Hits 1703 1730 +27
- Misses 105 139 +34
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
| ) | ||
| ) | ||
|
|
||
| if upper_limit < lower_limit: |
There was a problem hiding this comment.
I think this check is implied in the if statement before, so I will remove this
25f8d94 to
4bf3e1f
Compare
DominicOram
left a comment
There was a problem hiding this comment.
Good work, thank you. Some comments in code
| def deadtime_is_transmission_optimised( | ||
| direction, deadtime, deadtime_threshold, transmission | ||
| ) -> tuple[bool, bool]: | ||
| flip_direction = False | ||
| if direction: | ||
| if deadtime >= deadtime_threshold: | ||
| flip_direction = True | ||
| return False, flip_direction | ||
| else: | ||
| # The 0.9 is hardcoded in GDA | ||
| if transmission >= 0.9: | ||
| return True, flip_direction | ||
| else: | ||
| if deadtime <= deadtime_threshold: | ||
| return True, flip_direction | ||
| return False, flip_direction |
There was a problem hiding this comment.
Should: This function will benefit from a docstring to explain parameters, what it's doing etc.
| direction = True | ||
| LOGGER.info(f"Target deadtime is {deadtime_threshold}") | ||
|
|
||
| while True: |
There was a problem hiding this comment.
Should: while True can accidentally end up infinite quickly. Can we have a max_cycles like for the other optimiser
| def deadtime_optimisation( | ||
| attenuator, xspress3mini, zebra, transmission, increment, deadtime_threshold | ||
| ): | ||
| direction = True |
There was a problem hiding this comment.
Nit: It would be more readable to have this set as an enum for positive/negative rather than True/False
| total_time = xspress3mini.channel_1.total_time.get() | ||
| reset_ticks = xspress3mini.channel_1.reset_ticks.get() | ||
|
|
||
| LOGGER.info(f"Current total time = {total_time}") | ||
| LOGGER.info(f"Current reset ticks = {reset_ticks}") | ||
| deadtime = 0 | ||
| if total_time != reset_ticks: | ||
| deadtime = 1 - abs(total_time - reset_ticks) / (total_time) |
There was a problem hiding this comment.
Should: Can we document what this calculation is doing and why?
There was a problem hiding this comment.
e.g. what does reset_ticks mean?
| def deadtime_optimisation( | ||
| attenuator, xspress3mini, zebra, transmission, increment, deadtime_threshold | ||
| ): |
There was a problem hiding this comment.
Should: Can we have a docstring on this function and type hints?
There was a problem hiding this comment.
I'll add a docstring to the total counts version as well
| if transmission > 0.999: | ||
| transmission = 1 |
There was a problem hiding this comment.
Should: I think @neil-i03 was talking about wanting a max value on transmission that we can set in a config somewhere. 100% is too high. Can you pull out a max/min into constants for now and have a separate function that checks them and throws errors if they're reached
| # The 0.9 is hardcoded in GDA | ||
| if transmission >= 0.9: | ||
| return True, flip_direction |
There was a problem hiding this comment.
Must: Are we sure this is right? It looks wrong to me because if the deadtime is less than threshold and the transmission less than 90% the function returns nothing
There was a problem hiding this comment.
I think this is the 90% transmission limit in GDA Neil was talking about. If the transmission is >=90% and deadtime is still higher than the threshold, it just uses that as the optimised transmission. I'll add parameters for max and min as you suggested in previous comment. Also I'll add a logging message if it uses the maximum transmission as the optimised value.
| yield from arm_zebra(zebra) | ||
|
|
||
|
|
||
| def deadtime_is_transmission_optimised( |
There was a problem hiding this comment.
Could: It might be cleaner if the function returned whether you're optimised and then which direction to move in, rather than the flip
There was a problem hiding this comment.
Could: It would be even more clear if we just separate it out into two functions, one to work out if we're there and one to work out if to flip. It might lead to some repeated work but clarity is more important than efficiency here
There was a problem hiding this comment.
Should: Can we have some separate tests on this function
| break | ||
|
|
||
| if flip_direction: | ||
| direction = not direction |
There was a problem hiding this comment.
Should: We need some tests that cause the direction to be flipped
| def test_deadtime_calc_new_transmission_gets_correct_value(): | ||
| assert deadtime_calc_new_transmission(True, 0.05, 2) == 0.1 | ||
| assert deadtime_calc_new_transmission(False, 0.05, 2) == 0.025 | ||
| assert deadtime_calc_new_transmission(True, 1, 2) == 1 |
There was a problem hiding this comment.
Nit: Better done using parameterize, will be clearer when one fails
| return status | ||
|
|
||
|
|
||
| @pytest.mark.skip(reason="Flakey test which is refactored in another PR") |
d85f7e1 to
76c9209
Compare
| deadtime = 0 | ||
|
|
||
| """ Deadtime is the time after each event during which the detector cannot record another event. | ||
| The reset ticks PV stops ticking while the detector is unable to process events, so the absolute difference between the total time and the |
There was a problem hiding this comment.
I'm not 100% sure about this description of the reset ticks, but given the formula and definition for deadtime it looks like it must be something like this
There was a problem hiding this comment.
I don't think we should have comments in that we're not sure about. Let's look more inhttps://github.com/DiamondLightSource/python-artemis/issues/776
| def get_new_direction(direction: Direction, deadtime, deadtime_threshold): | ||
| if direction == Direction.POSITIVE: | ||
| if deadtime >= deadtime_threshold: | ||
| direction = Direction.NEGATIVE | ||
| return direction | ||
|
|
||
|
|
||
| def calculate_new_direction(direction: Direction, deadtime, deadtime_threshold): | ||
| if direction == Direction.POSITIVE: | ||
| if deadtime > deadtime_threshold: | ||
| direction = Direction.NEGATIVE | ||
| return direction |
There was a problem hiding this comment.
Should: These functions are basically the same, only one is used, can we remove the other?
Fixes #754
Link to dodal PR (if required): DiamondLightSource/dodal#102
To test:
Confirm deadtime optimisation logic is the same of that in the GDA script mx-config/scripts/attenuation_optimiser.py, in the run_xspress3mini_optimisation_routine()
Confirm new tests run and are sensible