Skip to content
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

Ophyd async slits #431

Merged
merged 11 commits into from
Apr 25, 2024
Merged

Ophyd async slits #431

merged 11 commits into from
Apr 25, 2024

Conversation

callumforrester
Copy link
Contributor

Fixes #384

Port S4SlitGaps to ophyd-async. Add tests to prove it reads correctly. Also create a group object for bunches of slits. Add slits config for I22 and P38.

Instructions to reviewer on how to test:

  1. Run unit tests

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

Comment on lines 41 to 45
Constructor, formats prefix with indices for sub-devices. For example:
S4SlitGapsGroup("MY-SLITS-{0:02d}:", range(5)) will populate the group with 4
S4SlitGaps devices with prefixes MY-SLITS-00, MY-SLITS-01, MY-SLITS-02,
MY-SLITS-03. The index to access them will be the same as the PV number, so
MY-SLITS-02 is accessed via group.slits[2].
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@DiamondJoseph and @DominicOram I'm not sure about this, just an idea I had to save boilerplate. See the i22 and p38 modules for example usage. Interested in your thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think at the very least, indices should be a Sequence[int], in case Slits[n] are disabled temporarily.

Looks like this fits with the ophyd_async ADR https://github.com/bluesky/ophyd-async/blob/7be73e3ce0f79fca2e02f77f366792ee84586ed7/docs/developer/explanations/decisions/0006-procedural-device-definitions.rst#L58 for proceedurally created devices.

I do wish it was just slits[1] rather than group.slits[1]: does group.name = "" work with blueapi still being able to get group.slits[1]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree with Sequence[int], for why the group adds another layer to the tree, see this comment bluesky/ophyd-async#181 (comment)

That said, I do agree it would be nicer, I wonder if there's a way of subclassing DeviceVector to incorporate the PV prefixing logic...

Tagging @coretl in case he has ideas

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a big fan of this. I think from a use perspective doing i03.slits_1() is more obvious than i03.slits().slits[1] given that this is how all other devices work. In other devices we only group like this if the devices make sense for the user to move together, does it make sense to the user for all of the slits to be one composite? What is one of the slit 2 fails to connect but we only need slit 4 in the plan? @dperl-dls, what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Personally I'm not a fan of a composite device here, I'd prefer slits1, slits2, etc.

However, if you decide you really want it, it does occur to me that multiple inheritance of DeviceVector and StandardDetector (rather than your existing nesting of DeviceVector within StandardDetector might work to squash down the layers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, I think we just remove the group entirely if it doesn't make semantic sense

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense and I agree with the user perspective. @DominicOram how about i03.slits()[1]?

Better but I still think separate is the best for the user, as @Tom-Willemsen says. If we're only doing this to avoid boilerplate then all for thinking about ways to make the beamline files less boilerplate-y without changing the interface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although this does create a lot of additional boilerplate in i22.py so should think more about it long-term

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not opposed to the boilerplate in this case. To be honest, long term we're probably going to need to split i22 into modules like i22.slits, i22.sample_environment, and so on, and the top-level i22 will just import * from each of those.

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 think I fear the boilerplate just because of how much GDA's per-beamline config has ballooned over the years, it's ended up split into files and subdirectories etc. in a very similar way to your suggestion here. I suppose that's not the end of the world but it would be nice to have a more concise description of the beamline to work with.

Comment on lines 11 to 12
self.x_gap = Motor(prefix + "X:SIZE")
self.y_gap = Motor(prefix + "Y:SIZE")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@DominicOram Renamed from xgap and ygap on the original for more standard pythonicness, but can revert if it will cause pain for hyperion.

Copy link
Contributor

Choose a reason for hiding this comment

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

It will cause problems but we can deal with it. Can you wait for a linked Hyperion PR before merging this though?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, no problem

@callumforrester callumforrester marked this pull request as ready for review April 11, 2024 12:08
@DominicOram
Copy link
Contributor

@dan-fernandes how does this interact with the code in #315? Does this supersede some of it?

Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

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

Some comments. Annoyingly we're more stuck than I thought we were on on getting the PV names changed for i03 too. Working on it...



class S4SlitGaps(Device):
"""Note that the S4 slits have a different PV fromat to other beamline slits"""
class S4SlitGaps(StandardReadable):
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: Why are these named after S4? My understanding is that this in now a generic slit class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I don't know what S4 is :)

Will rename to Slits

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Incidentally, and at risk of betraying my woeful knowledge of xray optics, if either you or @Tom-Willemsen can come up with a better docstring than mine then I'm all ears. Mine is basically "erm, it's a set of some sort of slits...?"

Copy link
Contributor

@Tom-Willemsen Tom-Willemsen Apr 11, 2024

Choose a reason for hiding this comment

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

This is a 4-blade slit set defined by a gap and a centre for each pair of blades.

The things I can immediately think of which we want to diambiguate from are:

  • Having different numbers of blades, e.g. a 2 blade vertical slit set might exist on some beamlines, this would only define the beam in a vertical oritentation.
  • Definining the slit set by absolute positions of each blade rather than gap and centre. Not sure, but I'd wager at least some beamlines do this - e.g. for scanning in each blade against the beam individually

Because I don't know what S4

Logically the fourth slit set along the x-ray beam path... However the numbering can be illogical sometimes. As an example VMXi used to have a slit set S2, but this was removed some time ago, so now their slits along the beam path are S1, S3, ...

Feel free to swing by my desk if you need...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, so could actually call the class Slits4Blade or something similar? For absolute vs. relative it sounds like those things can coexist in the same device?

I'd love to swing by but I'm sick!

Copy link
Contributor

Choose a reason for hiding this comment

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

We can probably assume 4-blade is the standard case, so I think naming the class Slits is fine as long as the docstring clarifies it's a 4-blade set.

For absolute vs. relative it sounds like those things can coexist in the same device?

Yes, care is just needed because you then have multiple PVs which affect the same physical blade. (e.g. moving the north blade will change the vertical gap and vice versa).

Does a picture like this help to visualise what's going on?

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, quite a lot, thanks!

super().__init__(name)


class S4SlitGapsGroup(StandardReadable):
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: As above, not sure why these have S4 in the name?

Comment on lines 11 to 12
self.x_gap = Motor(prefix + "X:SIZE")
self.y_gap = Motor(prefix + "Y:SIZE")
Copy link
Contributor

Choose a reason for hiding this comment

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

It will cause problems but we can deal with it. Can you wait for a linked Hyperion PR before merging this though?

Comment on lines 41 to 45
Constructor, formats prefix with indices for sub-devices. For example:
S4SlitGapsGroup("MY-SLITS-{0:02d}:", range(5)) will populate the group with 4
S4SlitGaps devices with prefixes MY-SLITS-00, MY-SLITS-01, MY-SLITS-02,
MY-SLITS-03. The index to access them will be the same as the PV number, so
MY-SLITS-02 is accessed via group.slits[2].
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a big fan of this. I think from a use perspective doing i03.slits_1() is more obvious than i03.slits().slits[1] given that this is how all other devices work. In other devices we only group like this if the devices make sense for the user to move together, does it make sense to the user for all of the slits to be one composite? What is one of the slit 2 fails to connect but we only need slit 4 in the plan? @dperl-dls, what do you think?

Comment on lines 45 to 67
return device_instantiation(
Slits,
"slits-01",
"-AL-SLITS-01:",
wait_for_connection,
fake_with_ophyd_sim,
)
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 would still want the slits_x() interface but no reason you can't hide it:

def _generic_slit(slit_num, wait_for_connection, fake):
        return device_instantiation(
        Slits,
        f"slits-0{slit_num}",
        f"-AL-SLITS-0{slit_num}:",
        wait_for_connection,
        fake_with_ophyd_sim,
    )

def slits_1(...):
    return _generic_slit(1, ...)
...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, is there currently anywhere sensible to put _generic_slit so that it can be shared between the i22 and p38 modules?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm.. I guess devices.util.something?

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 ended up putting it in a private module in beamlines because otherwise we were importing device_instantiation from beamline.beamline_utils into devices.util.something, then importing that back into beamlines.i22, which I could lead to circular import troubles at some point. But let me know what you think, happy to change it

@dperl-dls
Copy link
Collaborator

I think from a use perspective doing i03.slits_1() is more obvious than i03.slits().slits[1] given that this is how all other devices work. In other devices we only group like this if the devices make sense for the user to move together, does it make sense to the user for all of the slits to be one composite? What is one of the slit 2 fails to connect but we only need slit 4 in the plan?

I agree with this - I think we rarely if ever need to coordinate movements between different slits, except, like, setting the whole beamline to some standard state. It's a little academic because slits 1&2 should never actually be offline if we're running stuff, but there's no reason to actively make devices connect to them when we only need _4 99% of the time.

@callumforrester
Copy link
Contributor Author

As discussed with @DominicOram, @dperl-dls and @olliesilvester, the work to standardise the slits PVs has encountered complications so we are going to keep both versions of the slits device for now. Once that work is done we can action #462 and rationalise the implementations.

from .beamline_utils import device_instantiation


@skip_device()
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this decorator do anything here?

Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

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

Looks good to me, don't think it will have any affect on MX yet but looks like we will be able to switch to it easily when we get round to #462

@callumforrester callumforrester merged commit 081f82c into main Apr 25, 2024
11 checks passed
@callumforrester callumforrester deleted the ophyd-async-slits branch April 25, 2024 12:19
@callumforrester callumforrester mentioned this pull request Apr 26, 2024
2 tasks
@joeshannon joeshannon mentioned this pull request May 10, 2024
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.

Port Slits to ophyd-async and expand fields
6 participants