Implementing qkd#96
Conversation
Benjamin-Nussbaum
left a comment
There was a problem hiding this comment.
Do we need to instantiate a new Client every time an endpoint is called? Passing Clients and DeviceDrivers as arguments will increase maintainability.
There was a problem hiding this comment.
Imports should be one per line (run ruff/mypy separately on this/other scripts for now)
There was a problem hiding this comment.
What do you mean? They are, ruff checked that didn't it? What file?
There was a problem hiding this comment.
It doesn't check the scripts automatically at the moment because it only looks for .py files.
There was a problem hiding this comment.
What is the script? What is the file? I'm pretty sure I checked everything
There was a problem hiding this comment.
the script is http_server --- you can check it manually with uv run ruff check scripts/http_server. I realize this is a problem, so I will look into automating it when I'm back in town.
| def _get_timetagger(client: Client) -> DeviceDriver: | ||
| if settings.timetagger is None: | ||
| logger.error("No timetagger configured") | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="No timetagger configured", | ||
| ) | ||
|
|
||
| tagger = client.get_device(settings.timetagger[0], settings.timetagger[1]) | ||
| if tagger is None: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail="Could not find time tagger device", | ||
| ) | ||
|
|
||
| logger.debug("Time tagger device found: %s", tagger) | ||
| return tagger |
There was a problem hiding this comment.
Good idea to separate out this functionality.
- Return type should be
TimeTaggerDevice - Add arguments for the
settingsvalues (eliminatesNonecheck and decouples from a globalsettingsobject. - Double check elsewhere in the file that if there is a
tagger, it was retrieved via this function (single source of truth)
Consider similar functions for other Devices
There was a problem hiding this comment.
Not sure how to do this except for changing the return type, how would you recommend doing that?
There was a problem hiding this comment.
def _get_timetagger(client: Client, arg_describing_settings.timetagger[0], same_for_1) -> TimeTaggerDevice:
tagger = client.get_device(arg_desc..., same_for...)
if tagger is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Could not find time tagger device",
)
logger.debug("Time tagger device found: %s", tagger)
return taggersomething like that, with descriptive argument names and type hints. Users unpack settings.timetagger on the calling side.
| async def _count_coincidences( | ||
| measurement_config: MeasurementConfig, | ||
| tagger: DeviceDriver | None = None, | ||
| tagger_address: str | None = None, | ||
| http_client: httpx.AsyncClient | None = None, | ||
| ) -> int: | ||
| if tagger is None and tagger_address is None: | ||
| msg = "Either tagger or tagger_address must be provided" | ||
| raise ValueError(msg) | ||
|
|
||
| if tagger_address is not None and http_client is None: | ||
| msg = "http_client must be provided if tagger_address is provided" | ||
| raise ValueError(msg) | ||
|
|
||
| if tagger_address is None: | ||
| assert tagger is not None | ||
| assert hasattr(tagger, "measure_coincidence") | ||
| count = tagger.measure_coincidence( | ||
| measurement_config.channel1, | ||
| measurement_config.channel2, | ||
| measurement_config.binwidth, # might have to cast to int | ||
| int(measurement_config.duration * 1e12), | ||
| ) | ||
| else: | ||
| assert http_client is not None | ||
| r = await http_client.get( | ||
| f"http://{tagger_address}/timetagger/measure?duration={measurement_config.duration}&binwidth={measurement_config.binwidth}&channel1={measurement_config.channel1}&channel2={measurement_config.channel2}&dark_count={measurement_config.dark_count}" | ||
| ) | ||
| # TODO: Handle other status codes | ||
| if r.status_code != status.HTTP_200_OK: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="Failed to measure coincidences", | ||
| ) | ||
| count = cast("int", r.json()) | ||
| if not isinstance(count, int): | ||
| logger.error("Invalid response from timetagger: %s", count) | ||
| raise HTTPException( | ||
| status_code=status.HTTP_406_NOT_ACCEPTABLE, | ||
| detail="Invalid response from timetagger", | ||
| ) | ||
| logger.debug("Measured %d coincidences", count) | ||
| return int(count) |
There was a problem hiding this comment.
This function is trying to do too many things. I suggest just passing the tagger itself, in which case the type hint should be TimeTaggerDevice. If users want to get the tagger from the address, they should use the existing function get_timetagger.
There was a problem hiding this comment.
Now that I think about it, it might be better to be an endpoint, it would be good for a timetagger node to be available to return coincidence counts to anyone who wants to ask. Not sure if we already have something like that.
This was something implemented by @marcosfrenkel , so he should probably comment on why, but I think the reasoning is that you want to make sure the timetagger is connected and all good before you start a protocol, and all of these checks were repeated a lot before he made this function.
| settings = Settings("", "", 0, CHSHSettings()) | ||
| static_typecheck_msg = "Please set the global 'settings' variable before use." | ||
|
|
||
|
|
||
| def get_settings() -> Settings: | ||
| raise NotImplementedError(static_typecheck_msg) | ||
|
|
||
|
|
||
| settings = get_settings() |
There was a problem hiding this comment.
get_settings() should presumably parse a config file or similar so as to actually return something useful
There was a problem hiding this comment.
What is that line "settings = Settings("", "", 0, CHSHSettings())"? That does not appear in my code. Also, the function is only meant for ruff / mypy checks, we put in the settings separately. I am pretty sure we can in a future PR implement settings from config files, probably in a PR where we finally handle state better and handle devices better. Though I am not sure if mypy / ruff could parse the config file reading anyway, we might need the base code to have something like this, then whoever is installing this has to make their settings config.
There was a problem hiding this comment.
That's the diff showing what was removed in this changeset.
Anything included as "special treatment" just to satisfy CI checks is treating the symptom rather than the disease. If this patch ends up being included in the final changeset anyway, just be aware that it is additional technical debt that will need to be addressed.
There was a problem hiding this comment.
How do we deal with it though? I am pretty sure if we had get settings make settings from a config file, then mypy and ruff would stiff through a fit? Maybe have it check for a config file to pull the settings file from, and if not riase the not implemented error?
There was a problem hiding this comment.
My initial guess as to why it's complaining if you just return Settings() is because there are required arguments. That issue can be fixed by specifying default values for all fields in Settings.
The problem is we can't pass clients or device drivers into a function that is expected to be called via html since they only take json-able objects. |
Then maybe just refactor to use a single function (as with |
| angle = state.chsh_basis[index] + 90 * perp | ||
| assert hasattr(hwp, "move_to") | ||
| angle = state.chsh_request_basis[index] + 90 * perp | ||
| hwp = cast("Any", hwp) |
There was a problem hiding this comment.
It seems like hwp = cast("Any", hwp) is not really saying anything. If this Device were properly parsed, the typechecker would be aware of the type and available methods.
There was a problem hiding this comment.
Whoops, thought I deleted that one, my bad
Now this could be me being very wrong for future config file making, but what if we make a a client and store it in the settings state and use that every time? Then again, I could see it being a problem trying to serialize it later. Clients are designed to be fairly ephemeral objects anyway though, is there a big issue in making one for the purpose of a function then killing it? @marcosfrenkel would have to chime in but I think that is how they were designed. |
In that case, we should make it an annotated dependency argument using FastAPI |
Start merging the qkd logic to the master branch so we can work more on improving the code base later