Skip to content

Issue2193 - #2197

Draft
CoePaul wants to merge 3 commits into
mainfrom
issue2193
Draft

Issue2193#2197
CoePaul wants to merge 3 commits into
mainfrom
issue2193

Conversation

@CoePaul

@CoePaul CoePaul commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Work in progress - To be written

Instructions to reviewer on how to test:

  1. Do thing x
  2. Confirm thing y happens

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}

* Start with adding JSON validation for system specification
  JSON dict-like structure expectations

* Pydantic BaseModel classes map sub-structures ( sub-dicts ) within
  the system description structure here adding
   JSON validation for energy_interval
   [ the range of x-ray energies (in keV ) over which an absorption curve is valid ]
   JSON validation for absorption fit curve parameters
   [ the scaling constant, roll-off vs energy and an optional residuals polynomial ]
* Use pydantic BaseModel classes to capture blobs from Transmission System
  specification config JSON files for I19 beamlines EH-1 / EH-2
@CoePaul CoePaul self-assigned this Sep 4, 2026
@CoePaul
CoePaul requested a review from a team as a code owner September 4, 2026 20:02
@CoePaul
CoePaul marked this pull request as draft September 4, 2026 20:02
def get_motor_position_mm(self) -> float: ...


class AbsorberWedge(AbsorptionContributer):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Having two things called AbsorverWedge and WedgeAbsorber is confusing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah I know - I will have to rationalise the names -
each class is allegedly taking on some unique responsibility
but there may well be scope for deleting a lot of it later when the duplications of function become more obvious

The fact / bad smell that the name is nearly a duplicate - and would be an exact duplicate if I hadn't deliberately avoided a name clash - is definitely a clue that things are not yet in a good state

Some things are over in the "maths library" and others are in the i19 specific transmission system gubbins ( and the alleged reason for them being here is to participate in the "business logic" )
my current dilemma is to not put i19 specific aspects onto the general maths wedge

still trashing it out - as You can tell

Comment on lines +36 to +69
def __init__(
self,
*,
axis_label: str,
motor_position_reader: ReadableLinearMotor,
system_config: SystemConfiguration,
):
wedge_spec: WedgeSpec = WedgesConfig.extract_wedge_specifications(
system_configuration=system_config, wedge_identifier=axis_label
)
self.motor_reader: ReadableLinearMotor = motor_position_reader
self.scale: LateralMotorSpec = (
LateralMotorsConfig.extract_motors_specifications(
system_configuration=system_config, motor_identifier=axis_label
)
)
absorber_material_name: str = wedge_spec.material
absorption_spec = (
MaterialAbsorptionSpectralConfig.extract_absorber_material_specifications(
system_configuration=system_config, material_name=absorber_material_name
)
)
absorption_spectrum: MaterialAbsorptionSpectrum = absorption_spec.as_spectrum()
tapered_geometry: TaperedGeometryProvider = WedgeGeometry(
taper_cotangent=wedge_spec.geometry.taper_cotangent,
tip_mm=wedge_spec.geometry.tip,
)
self.absorber: Final[VariableDepth] = WedgeAbsorber(
spectrum=absorption_spectrum,
geometry_model=tapered_geometry,
)
self.id: str = axis_label
self.voids: list[OpenInterval] = wedge_spec.geometry.voids
self.absent_when_out = AbsenceFromBeam()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you remove SystemConfiguration, as previously suggested, I would suggest this become something more like:

    def __init__(
        self,
        *,
        axis_label: str,
        motor_position_reader: ReadableLinearMotor,
        full_spec: TransmissionSystemSpec,
    ):
        wedge_spec: WedgeSpec = full_spec.wedges[axis_label]
        self.motor_reader: ReadableLinearMotor = motor_position_reader
        self.scale: LateralMotorSpec = full_spec.lateral_motors[axis_label]
        absorber_material_name: str = wedge_spec.material
        absorption_spec = full_spec.materials[absorber_material_name]
        absorption_spectrum: MaterialAbsorptionSpectrum = absorption_spec.as_spectrum()
        ...

which is a lot more concise and understandable IMO

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indeed You are correct

)


class ReadableLinearMotor(Protocol):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this protocol just a placeholder? I feel like we can just use the concrete linear motor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

placeholder / strut thing - it will become the linear motor RO when that exists
although the joy of using the Protocol here is that if we leave it in place - we can use anything later
on the calling side and it won't matter

xray_energy_kev=xray_energy_kev, motor_position_mm=motor_position_mm
)

def is_feasible(self, *, xray_energy_kev: float, demand_bn: float) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: I feel like this could reuse more of the parent's overridden method? E.g. you could just override _absorption_exceeds_demand with _threshold_absorption_exceeds_demand and then you won't need to repeat is_feasible here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I get that impression here or in similar places a lot
( I don't instantly remember where I was when I had that sense, nor exactly where this is )
but I did try things in the spirit You suggest.

In a more mature form - such things might reduce to that form - but I found there were more
child specific things than I could just discount -

in other words - this ( or at least similar ) seemingly superfluous duplication has been done
consciously - even if my reasons might not stand up to more exacting scrutiny

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants