aps-8id-dys / ipython-8idiuser

8-ID-I ipython configuration for bluesky (and other)
1 stars 1 forks source link

Get qnw sample selection to work with Bluesky #273

Closed qzhang234 closed 2 years ago

qzhang234 commented 3 years ago

@prjemian I made a python script to reproduce the temperature varying and sample changing capacity in SPEC:

bp_qnw_selection.py ```python __all__ = [ 'select_sample', 'te_qnw', ] import json from bluesky import plans as bp from bluesky import plan_stubs as bps from instrument.devices import samplestage, qnw_device from instrument.session_logs import logger logger.info(__file__) qnw_params = {} def select_sample(index=None): with open('QNW_sample_definitions.json', 'r') as f: _ = json.load(f) qnw_params['sample_name']=_[index]["qnw_position"] qnw_params['samp_id_char']=_[index]["qnw_position"] qnw_params['samx_center']=_[index]["qnw_position"] qnw_params['samx_scan_halfwidth']=_[index]["samx_scan_halfwidth"] qnw_params['samx_num_points']=_[index]["samx_num_points"] qnw_params['samz_center']=_[index]["samz_center"] qnw_params['samz_scan_halfwidth']=_[index]["samz_scan_halfwidth"] qnw_params['samz_num_points']=_[index]["samz_num_points"] qnw_params['qnw_position']=_[index]["qnw_position"] qnw_params["qnw_env"] = _[index]["qnw_env"] yield from bps.mv(samplestage.qnw_x, qnw_params['qnw_position']) return qnw_params def te_qnw(set_temp=None, ramp_rate=5, temp_wait=False, tolerance=0.1): qnw_env = globals()[qnw_params["qnw_env"]] # Select object using object name (string) yield from bps.mv(qnw_env.ramprate, ramp_rate) if temp_wait is True: yield from bps.mv(qnw_env.tolerance, tolerance) yield from bps.mv(qnw_env, set_temp) if temp_wait is False: yield from bps.abs_set(qnw_env, set_temp) ```
qzhang234 commented 3 years ago

I noticed that when I put this function in ~/blueske_data/2021/2021-2/ and do %run -m it would work. However when I move it in the startup scripts of Bluesky, the functions will load but the dictionary qnw_params will not stay in the memory, hence the following te_qnw command won't run. Any idea why?

Thanks!

qzhang234 commented 3 years ago

I implemented a bypass in which I had select_sample saves the sample parameters it picked into another json file to bypass the previous issue:

New script ```python __all__ = [ 'select_sample', 'te_qnw', ] import json from bluesky import plans as bp from bluesky import plan_stubs as bps from instrument.devices import samplestage, qnw_device from instrument.session_logs import logger logger.info(__file__) import os path = "/home/beams/8IDIUSER/bluesky_data/2021/2021-2" def select_sample(index=None): file_name = 'QNW_sample_definitions.json' with open(os.path.join(path,file_name), 'r') as f1: _ = json.load(f1) json_object = json.dumps(_[index], indent = 4) with open(os.path.join(path,"current_sample.json"), 'w') as f2: f2.write(json_object) yield from bps.mv(samplestage.qnw_x, _[index]['qnw_position']) def te_qnw(set_temp=None, ramp_rate=5, temp_wait=False, tolerance=0.1): file_name = 'current_sample.json' with open(os.path.join(path,file_name), 'r') as f1: _ = json.load(f1) # qnw_env = globals()[_["qnw_env"]] qnw_env=eval(_["qnw_env"]) yield from bps.mv(qnw_env.ramprate, ramp_rate) if temp_wait is True: yield from bps.mv(qnw_env.tolerance, tolerance) yield from bps.mv(qnw_env, set_temp) if temp_wait is False: yield from bps.abs_set(qnw_env, set_temp) ```

However, the same problem remains, namely I can run the function just fine in ~/bluesky_data but not when I put this script in the startup scripts.

Upon inspection I noticed the problem is caused by the line in which I tried to use the string qnw_env1 to get the object qnw_env1 so that I can assign it to something else (the one with global()["_[qnw_env]"]). Any idea why it won't work if I put it in the start up scripts?

qzhang234 commented 3 years ago

Or should I just re-instantiate the class with the string?

image

prjemian commented 3 years ago

Need to add qnw_params into the __all__ list.

prjemian commented 3 years ago

With %run ..., you are loading the code into your global namespace. When you load it from the instrument package, te_qnw() fails when it cannot find qnw_params in the global namespace. That's what the __all__ list provides, the names of the objects to be imported by default.

prjemian commented 3 years ago

Yuck! qnw_env=eval(_["qnw_env"]) Very obscure code.

prjemian commented 3 years ago

Use a name rather than _ as a Python object. Unless you plan to ignore whatever is assigned to _.

qzhang234 commented 3 years ago

@prjemian Thanks! I'll make the changes per your suggestion.

One thing that baffled me is: If I try to access qne_env1 in my sample selection code (see pulldown tab attached), it will fall flat on my face. Something as simple as bps.mv(qnw_env1, 25) will crash and return an error saying qnw_env1 is not defined.

At the same time, samplestage.qnw_x was imported in the exact same way as qnw_env1 and I can run bps.mv(samplestage.qnw_x, 150) with no problem. This has confused me the entire last night. Any idea why?

sample selection script to be placed in the startup folder ```python __all__ = [ 'select_sample', 'te_qnw', ] import json from bluesky import plans as bp from bluesky import plan_stubs as bps from instrument.devices import samplestage, qnw_device from instrument.session_logs import logger logger.info(__file__) import os yield from bps.mv(qnw_env1, 25) path = "/home/beams/8IDIUSER/bluesky_data/2021/2021-2" def select_sample(index=None): file_name = 'QNW_sample_definitions.json' with open(os.path.join(path,file_name), 'r') as f1: _ = json.load(f1) tmp = json.dumps(_[index], indent = 4) with open(os.path.join(path,"current_sample.json"), 'w') as f2: f2.write(tmp) yield from bps.mv(samplestage.qnw_x, _[index]['qnw_position']) def te_qnw(set_temp=None, ramp_rate=5, temp_wait=False, tolerance=0.1): file_name = 'current_sample.json' with open(os.path.join(path,file_name), 'r') as f1: _ = json.load(f1) print(_["qnw_env"]) if _["qnw_env"] == "qnw_env1": yield from bps.mv(qnw_env1.ramprate, ramp_rate) if temp_wait is True: yield from bps.mv(qnw_env1.tolerance, tolerance) yield from bps.mv(qnw_env1, set_temp) if temp_wait is False: yield from bps.abs_set(qnw_env1, set_temp) if _["qnw_env"] == "qnw_env2": yield from bps.mv(qnw_env2.ramprate, ramp_rate) if temp_wait is True: yield from bps.mv(qnw_env2.tolerance, tolerance) yield from bps.mv(qnw_env2, set_temp) if temp_wait is False: yield from bps.abs_set(qnw_env2, set_temp) if _["qnw_env"] == "qnw_env3": yield from bps.mv(qnw_env3.ramprate, ramp_rate) if temp_wait is True: yield from bps.mv(qnw_env3.tolerance, tolerance) yield from bps.mv(qnw_env3, set_temp) if temp_wait is False: yield from bps.abs_set(qnw_env3, set_temp) ```
prjemian commented 3 years ago

You can push the QZ_Test branch to GitHub which will make it much easier for me to inspect the code.

prjemian commented 3 years ago

In your example code, you have defined the qnw_env1 object in the file, thus it fails at:

yield from bps.mv(qnw_env1, 25)

The code succeeds with %run ... because the symbol is defined in the global namespace of the interactive session.

You need to import that symbol.

qzhang234 commented 3 years ago

Something like this? from instrument.devices.qnw_device import qnw_env1, qnw_env2, qnw_env3

prjemian commented 3 years ago

Yes. If this file is in the devices directory, then shorten it to:

from .qnw_device import qnw_env1, qnw_env2, qnw_env3
qzhang234 commented 3 years ago

@prjemian I added qnw_params in __all__, however the values of qnw_params did not update when I run RE(select_sample('sample1') (I pushed my changes and the part of script of interest is here). Any suggestions? I guess I could always go back to writing a separate .json file but I feel like saving the dictionary in the memory is more elegant.

Another question I have is: assuming that I can read the values from qnw_params, I would like to use the string qnw_env1 to select object that has the same name. It was done using global( )[ ] and the line of script is here. I'm not sure if this is the right way to do this. Any suggestions?

prjemian commented 3 years ago

assuming that I can read the values from qnw_params, I would like to use the string qnw_env1 to select object that has the same name. It was done using global( )[ ] and the line of script is here. I'm not sure if this is the right way to do this. Any suggestions?

This is too tricky for mere mortals to understand as they try to find the source of a problem. It pays to be much more obvious.

qzhang234 commented 3 years ago

Duly noted on the string -> object part. I'll rewrite my script.

However what about the creation of qnw_params? Should I switch back to writing .json file?

Please advise.

prjemian commented 3 years ago

See this comment above: https://github.com/aps-8id-dys/ipython-8idiuser/issues/273#issuecomment-909316934

qzhang234 commented 3 years ago

I did that however I still can't load values into qnw_params

prjemian commented 3 years ago

This is the problem you report: Since qnw_params is being defined in select_sample(), a new qnw_params object is created local to the scope of this function. The function returns this new object but that action does not update the global object created on line 21. If you want to modify the object created on line 21, then the function needs this line:

    global qnw_params

The global is not necessary if you only reference the object since Python's object search starts in the function's scope, then goes to the global namespace if it is not found locally. It's different when writing a value. A new object is created locally unless you inform that the global object should be used.

That code needs other attention, too.

For example, the yield from after the return: https://github.com/aps-8id-dys/ipython-8idiuser/blob/458722eda77704457cc9bc5027bd4237f983ed67/profile_bluesky/startup/instrument/plans/qnw_sample_select.py#L29-L30

And this should be simplified. What is supposed to happen? https://github.com/aps-8id-dys/ipython-8idiuser/blob/458722eda77704457cc9bc5027bd4237f983ed67/profile_bluesky/startup/instrument/plans/qnw_sample_select.py#L36

prjemian commented 3 years ago

Could line 36 could be changed?

 qnw_env = qnw_params["qnw_env"]
prjemian commented 3 years ago

If it not that simple, how can we make it that simple? Should not need such calls to get an object from globals()[]. This type of code will cause failures with import as you have reported.

qzhang234 commented 3 years ago

Could line 36 could be changed?

 qnw_env = qnw_params["qnw_env"]

The goal is to select which qnw_env to control based on the string and I believe bps.mv requires an object as input

qzhang234 commented 3 years ago

I pushed another version to QZ_Test. This version contains a select_sample and te_qnw that loads upon startup (instead of %run -m) and work as expected.

Although I guess I could just leave it like this, I'm very curious to find out if this code can be improved since some of the actions performed here (e.g. converting string to object, passing variables from one function to another) will surely be used at other places.

Could you take a look at the script (see link here) and let me know if you have any suggestions?

qzhang234 commented 2 years ago

Closing this issue for now as it has become outdated. Will reopen if necessary.