Skip to content

Implementing qkd#96

Merged
marcosfrenkel merged 9 commits into
masterfrom
implementing_qkd
Jul 24, 2025
Merged

Implementing qkd#96
marcosfrenkel merged 9 commits into
masterfrom
implementing_qkd

Conversation

@SoroushHoseini

Copy link
Copy Markdown
Contributor

Start merging the qkd logic to the master branch so we can work more on improving the code base later

@Benjamin-Nussbaum Benjamin-Nussbaum left a comment

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.

Do we need to instantiate a new Client every time an endpoint is called? Passing Clients and DeviceDrivers as arguments will increase maintainability.

Comment thread scripts/http_server

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.

Imports should be one per line (run ruff/mypy separately on this/other scripts for now)

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.

What do you mean? They are, ruff checked that didn't it? What file?

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.

It doesn't check the scripts automatically at the moment because it only looks for .py files.

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.

What is the script? What is the file? I'm pretty sure I checked everything

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.

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.

Comment thread src/pqnstack/app/main.py
Comment on lines +110 to +126
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

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.

Good idea to separate out this functionality.

  • Return type should be TimeTaggerDevice
  • Add arguments for the settings values (eliminates None check and decouples from a global settings object.
  • 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

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.

Not sure how to do this except for changing the return type, how would you recommend doing that?

@Benjamin-Nussbaum Benjamin-Nussbaum Jul 24, 2025

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.

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 tagger

something like that, with descriptive argument names and type hints. Users unpack settings.timetagger on the calling side.

Comment thread src/pqnstack/app/main.py
Comment on lines +129 to +171
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)

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.

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.

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.

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.

Comment thread src/pqnstack/app/main.py
Comment on lines -39 to +65
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()

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.

get_settings() should presumably parse a config file or similar so as to actually return something useful

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.

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.

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.

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.

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.

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?

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.

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.

@SoroushHoseini

Copy link
Copy Markdown
Contributor Author

Do we need to instantiate a new Client every time an endpoint is called? Passing Clients and DeviceDrivers as arguments will increase maintainability.

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.

@Benjamin-Nussbaum

Copy link
Copy Markdown
Contributor

Do we need to instantiate a new Client every time an endpoint is called? Passing Clients and DeviceDrivers as arguments will increase maintainability.

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 get_timetagger()) for now.

Comment thread src/pqnstack/app/main.py Outdated
Comment on lines +212 to +288
angle = state.chsh_basis[index] + 90 * perp
assert hasattr(hwp, "move_to")
angle = state.chsh_request_basis[index] + 90 * perp
hwp = cast("Any", hwp)

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.

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.

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.

Whoops, thought I deleted that one, my bad

@SoroushHoseini

Copy link
Copy Markdown
Contributor Author

Do we need to instantiate a new Client every time an endpoint is called? Passing Clients and DeviceDrivers as arguments will increase maintainability.

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 get_timetagger()) for now.

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.

@Benjamin-Nussbaum

Benjamin-Nussbaum commented Jul 24, 2025

Copy link
Copy Markdown
Contributor

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 Depends as we've defined for the http client. See the definition at https://github.com/PublicQuantumNetwork/pqn-stack/blob/1843b58cea32d5ef6b8cff46259ee696075c733e/src/pqnstack/app/main.py#L74 and various uses throughout the file.

@marcosfrenkel marcosfrenkel merged commit 25af059 into master Jul 24, 2025
6 checks passed
@marcosfrenkel marcosfrenkel deleted the implementing_qkd branch July 24, 2025 19:18
@Benjamin-Nussbaum Benjamin-Nussbaum mentioned this pull request Jul 25, 2025
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.

3 participants