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

signal: init value can be a ValueInfo structure with dtype, shape and… #1194

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

mguijarr
Copy link

… default value to characterize the value

Solves issue #1193 (and #1178 ?)

@mguijarr
Copy link
Author

Please comment , if you agree with the proposed change I will add a test.

ophyd/signal.py Outdated
@@ -95,7 +102,7 @@ def __init__(
self,
*,
name,
value=0.0,
value=DEFAULT_EPICSSIGNAL_VALUE,
Copy link
Contributor

Choose a reason for hiding this comment

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

If we move this here I suspect we will have to hoist the logic to handle this from EpicsSignal up to the base class.

Copy link
Author

Choose a reason for hiding this comment

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

well, it looks like DEFAULT_EPICSSIGNAL_VALUE is more used as a sentinel value to indicate if a default has been provided or not - it is currently already used in Signal class, see line 475:

if self._readback is DEFAULT_EPICSSIGNAL_VALUE:
            val = self.get()

or derived classes which do not have an explicit relation with EPICS (i.e not deriving from EpicsSignalBase).

I propose to rename it, then ? Like UNSET_VALUE or UNSPECIFIED_VALUE ?

It seems None is considered as a potential ok value. Is it the case ? Otherwise we could initialize with None.

In describe(), I think the key point is to really read the value if it is not specified. Any way to do it like this is fine for me.

What do you think ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, if we are already using it is the base class than I'm 50/50 on adding an alias (the hassle of anyone downstream using the old name needing to update is not worth it to remove the old name) vs just using it.

The concern I still have is that in Signal.get we just return self._readback which means that there is a chance of returning DEFAULT_EPICSSIGNAL_VALUE to the user (which we definitely should not do). The minimal fix is probably to raise in Signal.get if the value has never been set.

Copy link
Author

@mguijarr mguijarr Jun 4, 2024

Choose a reason for hiding this comment

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

Wouldn't it be better to initialize to None and to return None if it has not been set? I am very new to Ophyd, so I do not know the logic/philosophy behind reading signals and why DEFAULT_EPICSSIGNAL_VALUE was introduced at first place

Copy link
Author

Choose a reason for hiding this comment

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

Otherwise I can make an alias and raise in get() as you suggest, no problem 👍🏻

Tell me what you prefer

Copy link
Contributor

Choose a reason for hiding this comment

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

tl;dr: I would prefer the alias + raise in get path as the minimal change.

pyepics (the default wrapping we use to talk channel access over in Python) returns None when it fails so defaulting to None means we could not tell the difference between "failed to read" vs "never read". The logic was to have a unique sentinel that would never come to use from pyepics and would never be passed to us by the user so we could tell if we had ever been told a value for this signal. Basically detect the start up transient and allows lazy connection (each PV is its own connection dance with a server and if we never read it then it is better to never try to connect).

Going with a custom default object also helps makes clear that it is "default but invalid" where as None is frequently "default and valid" or "placeholder to tell the system to do the default thing" and I think that is an important distinction here.

I'm a bit fuzzy remember choices we made probably 8 years ago now, but I am biased to trusted my previous self 🤣

@tacaswell
Copy link
Contributor

Interesting idea!

I have some concerns that this will box us out from ever fixing EpicsSignal to trust the metadata that comes across on the channel rather than infering the dtype from the values, but I think that if the rules go 1. user passed 2. Channel provided 3. inferred would be ok.

We have been using dtype_str for providing higher-fidelitiy data type information (see discussion in bluesky/event-model#215 despite not getting that PR merged, we have started to use it in-practice in tiled) so we probably should use a different name internally.

I lean +.75 on this being a good idea .

@mguijarr
Copy link
Author

mguijarr commented Jun 4, 2024

I have some concerns that this will box us out from ever fixing EpicsSignal to trust the metadata that comes across on the channel rather than infering the dtype from the values, but I think that if the rules go 1. user passed 2. Channel provided 3. inferred would be ok.

I agree with this.

We have been using dtype_str for providing higher-fidelitiy data type information (see discussion in bluesky/event-model#215 despite not getting that PR merged, we have started to use it in-practice in tiled) so we probably should use a different name internally.

Ok I renamed shape and dtype vars to _value_shape and _value_dtype_str.

@prjemian
Copy link
Contributor

prjemian commented Jun 4, 2024

The value of DEFAULT_EPICSSIGNAL_VALUE should be set such that (lazy) programmatic use does not rely on the actual value of the default. Random value should be assigned to require use of the symbol, such as one of these:

DEFAULT_EPICSSIGNAL_VALUE = object()
DEFAULT_EPICSSIGNAL_VALUE = uuid.uuid4()

@mguijarr mguijarr force-pushed the signal_valueinfo branch 10 times, most recently from 6c7a836 to c2be5d3 Compare June 5, 2024 14:31
@mguijarr
Copy link
Author

mguijarr commented Jun 5, 2024

Hi @tacaswell , @prjemian 😄 This is ready for me. I don't know why doc would not build... ?

I added specific tests for the feature. In addition to what we discussed above, I renamed ValueInfo to NumericValueInfo because it is not adapted to strings
for example. I added source_name as a property, in order to make describe() more generic. I made that shape in describe() is a tuple rather than sometimes a list, sometimes a tuple (because, for numpy arrays, it is a tuple normally).

I would be happy to work on having EpicsSignalBase trying to specify dtype and shape from metadata but I have no idea how to do it since I am very new to EPICS, PyEpics in general. Normally it is easy to do it like @tacaswell proposes, 1. user defined , 2. EPICS metadata or 3. inference if there is nothing else... Maybe in another MR ?

@mguijarr
Copy link
Author

No news ; is there anything wrong with the proposal?

@mguijarr
Copy link
Author

mguijarr commented Jul 2, 2024

@prjemian @tacaswell I apologize in advance if you're on holidays or too busy. I really need to have proper type info in Ophyd, sorry to insist - if you think this proposal can be improved do not hesitate to tell me, or if I have to do something to make it approved ?

@prjemian
Copy link
Contributor

prjemian commented Jul 2, 2024

I'm OK with the part on which I commented. I'll leave it to Tom for the PR review.

@mguijarr mguijarr marked this pull request as draft July 9, 2024 09:09
@wakonig
Copy link

wakonig commented Oct 11, 2024

Hi @tacaswell! What's the status on this one? It would help us a lot if we could get this one merged soon. Thanks!

@mguijarr
Copy link
Author

Hi again... I am reviving this discussion, I really need the added functionality so can we find an agreement on this?

@tacaswell
Copy link
Contributor

Sorry I never found you again at NOBUGs!

ophyd/signal.py Outdated Show resolved Hide resolved
ophyd/signal.py Outdated
@@ -95,7 +104,7 @@ def __init__(
self,
*,
name,
value=0.0,
value=NumericValueInfo(float, (), 0.0),
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than merging the expected info and the default value, can we add a new key(s) of expected_dtype_and_shape or dtype and shape? In __init__ it seems reasonable to do a cast, but in describe I wonder if we should be checking if the value we have in had is actually the correct dtype/shape or not?

Copy link
Author

Choose a reason for hiding this comment

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

In my original proposal I didn't want to add more keyword args, but yeah sure - I introduced dtype and shape in latest version.

For the check in describe: I didn't change anything yet


original = Signal(name="original")
original_desc = original.describe()["original"]
assert original_desc["dtype"] == "float64"
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Ok so newest version has dtype and dtype_numpy

@tacaswell
Copy link
Contributor

The more-complete dtype information should be put in dtype_numpy. This going in everywhere at the bottom would probably be a big help (as we have been stuffing it in ad-hoc at NSLS-II).

https://github.com/bluesky/event-model/blob/a1fdaec0c45b89e513d0be9dba0bfc453982e5bb/src/event_model/schemas/event_descriptor.json#L63-L91

@mguijarr mguijarr force-pushed the signal_valueinfo branch 2 times, most recently from dc884b2 to 3a3cdb0 Compare October 29, 2024 16:52
@mguijarr
Copy link
Author

I just pushed some code, to comply with your comments. Let's see if tests pass

@tacaswell
Copy link
Contributor

I don't have a good theory as to why we are seeing the pytest issues on this PR. Can you try rebasing on current main?

@mguijarr mguijarr force-pushed the signal_valueinfo branch 6 times, most recently from 051d146 to 4634087 Compare October 31, 2024 13:02
Mathias Guijarro added 3 commits October 31, 2024 15:51
…=float, shape=scalar

Initializing with a 'simple' value will make 'describe()' use inference
to determine dtype and shape at runtime
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