Skip to content

Conversation

@oliwenmandiamond
Copy link
Contributor

@oliwenmandiamond oliwenmandiamond commented Nov 25, 2025

Refactored EnergyMotorLookup class to decouple the gap and phase. You now provide an EnergyMotorLookup for gap and phase now rather than just one. EnergyMotorlookup is only LookupTable as configuration, ConfigServerEnergyMotorLookup will get file contents from config server and convert it to LookupTable. ID's can provide any way to calculate the gap and phase as it can be any implementation that is of type EnergyMotorLookup, giving more flexibility e.g a pre configured lookup table, reading from a file, auto generated or from epics. Have also update the Apple2Controller methods to be more modular so it reuses more logic between devices that implement it.

Instructions to reviewer on how to test:

  1. Check refactoring makes sense
  2. Confirm tests pass

Checks for reviewer

  • Would the PR title make sense to a scientist on a set of release notes
  • If a new device has been added does it follow the standards
  • If changing the API for a pre-existing device, ensure that any beamlines using this device have updated their Bluesky plans accordingly
  • Have the connection tests for the relevant beamline(s) been run via dodal connect ${BEAMLINE}

Relm-Arrowny and others added 30 commits October 27, 2025 12:10
…dLightSource/dodal into create_apple2_energy_motor_lookUP
Change 'nc' parameter to negative ROW_PHASE_CIRCULAR and adjust phase calculation based on pol.
Removed outdated docstring from Lookuptable class and updated initialization docstring for I10EnergyMotorLookup class.
@oliwenmandiamond oliwenmandiamond marked this pull request as ready for review November 26, 2025 15:01
@oliwenmandiamond oliwenmandiamond requested a review from a team as a code owner November 26, 2025 15:01
@oliwenmandiamond oliwenmandiamond changed the title Create abstract energy motor lookup Create abstract EnergyMotorLookup and decouple gap and phase Nov 26, 2025
@oliwenmandiamond oliwenmandiamond changed the title Create abstract EnergyMotorLookup and decouple gap and phase Create AbstractEnergyMotorLookup and decouple gap and phase Nov 26, 2025
@olliesilvester
Copy link
Collaborator

FYI Our long-term plan for LUTs with the config server is:

  • Any file format allowed for an arbitrary LUT
  • Config server has custom converters to convert each LUT to json
  • Dodal can request LUT from config server, ask it to use the converter, and get it as a python dict. Dodal can then contain utility functions to go from python dict to a more useful format (eg dataclass, two way dictionary etc)

@oliwenmandiamond
Copy link
Contributor Author

oliwenmandiamond commented Nov 26, 2025

FYI Our long-term plan for LUTs with the config server is:

* Any file format allowed for an arbitrary LUT

* Config server has custom converters to convert each LUT to json

* Dodal can request LUT from config server, ask it to use the converter, and get it as a python dict. Dodal can then contain utility functions to go from python dict to a more useful format (eg dataclass, two way dictionary etc)

How this works with this change is we request the file contents from the daq config server and process the data inside the FileReadingEnergyMotorLookup (previously just EnergyMotorLookup) and build our LookupTable by looping through each row. This is pydantic data class and is fully serializable into json and python dict. So if we want the daq config server to do the conversion instead and have our ID's just receive the processed LookupTable, we can simply move the converter logic to that side down the road.

@olliesilvester
Copy link
Collaborator

Nice yeah, that sounds good.

Copy link
Contributor

@Relm-Arrowny Relm-Arrowny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this, it look good, I just have a feeling we may have gone too far.

class EnergyMotorLookup:
class AbstractEnergyMotorLookup:
"""
Handles lookup tables for Apple2 ID, converting energy and polarisation to gap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: It no longer does both at the same time, and maybe we can also drop the Apple2 ID as it work as long as Lookuptable schema is used.

Suggested change
Handles lookup tables for Apple2 ID, converting energy and polarisation to gap
Handles lookup tables for Apple2 ID, converting energy/polarisation to motor position.

)

def get_motor_from_energy(self, energy: float, pol: Pol) -> tuple[float, float]:
def get_motor_from_energy(self, energy: float, pol: Pol) -> float:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: I am not 100% what it should be but get_motor_form_energy seem wrong now.

Suggested change
def get_motor_from_energy(self, energy: float, pol: Pol) -> float:
def get_value_from_lookuptable(self, energy: float, pol: Pol) -> float:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to find_value_in_lookup_table

await self.apple2().set(id_motor_values=id_motor_values)

async def _set_motors_from_energy_and_polarisation(
self, energy: float, pol: Pol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this, with def _set_motors_from_energy(self, value: float) The task is quite clear, we have to define how the ID should move. Now we break the task into 4 parts?
It is quite hard to know what I needed to do and I wrote the original.....

Copy link
Contributor Author

@oliwenmandiamond oliwenmandiamond Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only need to implement _get_apple2_value method

So everything that will be common to all ID's are

  • Get the current polarisation value.
  • Get the gap value from LookupTable via energy and polarisation values.
  • Get the phase value from LookupTable via energy and polarisation values.
  • Unique part for each ID is the Apple2Val based on gap, phase and polarisation values
  • Set the apple2 with the custom Apple2Val

I refactored the logic so that all ID's do this as this was being repeated across all ID implementations.

I10 seems to have an extra step after setting value on apple2 to additional jaw_phase's based on the polarisation set, so I extended it with the below.

    async def _set_apple2(self, apple2_val: Apple2Val, pol: Pol) -> None:
        await super()._set_apple2(apple2_val, pol)
        if pol != Pol.LA:
            await self.apple2().jaw_phase().set(0)
            await self.apple2().jaw_phase().set_move.set(1)

This means I decided to pass in the polarisation value for _set_apple2 method (even though not all implementations will need to use it) so i10 didn't need to call _check_and_get_pol_setpoint() again and used the cache value instead.

UPDATE: I've changed i10 to extend directly _set_motors_from_energy_and_polarisation rather than _set_apple2 and dropped pol argument

undulator design.
"""

async def _set_apple2(self, id_motor_values: Apple2Val, pol: Pol) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the intention is to allow this to be extended hence we carrying the pol, but it probably not the best practice as apple2 does not have a class member pol. there fore pol is not something that can be set in apple2.
May be we need to change the name..... we also just made a function that only does one thing which is to call an other function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the pol argument now, hope that streamlines it

@oliwenmandiamond oliwenmandiamond changed the title Create AbstractEnergyMotorLookup and decouple gap and phase Create ConfigServerEnergyMotorLookup and decouple gap and phase Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants