Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions examples/quantum_mc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Run integration with qibo as the backend
and compares it to other algorithms
"""

from vegasflow.quantum import quantum_wrapper, quantumflow_wrapper
from vegasflow import float_me, vegas_wrapper, plain_wrapper, run_eager
import time
import numpy as np
import tensorflow as tf

run_eager(True)
# MC integration setup
dim = 2
ncalls = int(1e5)
n_iter = 5


def symgauss(xarr):
"""symgauss test function"""
n_dim = xarr.shape[-1]
a = float_me(0.1)
n100 = float_me(100 * n_dim)
pref = tf.pow(1.0 / a / np.sqrt(np.pi), n_dim)
coef = tf.reduce_sum(tf.range(n100 + 1))
coef += tf.reduce_sum(tf.square((xarr - 1.0 / 2.0) / a), axis=1)
coef -= (n100 + 1) * n100 / 2.0
return pref * tf.exp(-coef)


def test_me(wrapper, nev):
nev = int(nev)
algo = wrapper.__name__.split("_")[0]
print(f"> Running {algo} for {nev} events")
start = time.time()
result = wrapper(symgauss, dim, n_iter, nev)
end = time.time()
print(f"This run took {end-start}\n")
return result


if __name__ == "__main__":
test_me(plain_wrapper, ncalls)
test_me(vegas_wrapper, ncalls)
test_me(quantum_wrapper, ncalls)
test_me(quantumflow_wrapper, ncalls)
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dependencies = [
]

[project.optional-dependencies]
quantum = [
"qibolab>=0.2.3"
]
docs = [
'sphinx',
'sphinx_rtd_theme',
Expand Down
2 changes: 1 addition & 1 deletion src/vegasflow/configflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
# Create and format the log handler
_console_handler = logging.StreamHandler()
_console_handler.setLevel(_log_level)
_console_format = logging.Formatter("[%(levelname)s] (%(name)s) %(message)s")
_console_format = logging.Formatter("[%(levelname)s] %(message)s")
_console_handler.setFormatter(_console_format)
logger.addHandler(_console_handler)

Expand Down
11 changes: 8 additions & 3 deletions src/vegasflow/monte_carlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ def generate_random_array(self, n_events, *args):
xjac = xjac_raw / (self.xjac * n_events)
return rnds, xjac

def _internal_sampler(self, n_events):
"""Latent uniform sampler to be digested by the MC algorithm"""
return tf.random.uniform(
(n_events, self.n_dim), minval=TECH_CUT, maxval=1.0 - TECH_CUT, dtype=DTYPE
)

def _generate_random_array(self, n_events, *args):
"""Generate a 2D array of (n_events, n_dim) points
For the weight of the given point, this function is considered
Expand All @@ -261,9 +267,8 @@ def _generate_random_array(self, n_events, *args):
`idx` : index associated to each random point
`wgt` : wgt associated to the random point
"""
rnds_raw = tf.random.uniform(
(n_events, self.n_dim), minval=TECH_CUT, maxval=1.0 - TECH_CUT, dtype=DTYPE
)
rnds_raw = self._internal_sampler(n_events)

# Now allow for the algorithm to produce the random numbers for the integration
rnds, wgts_raw, *extra = self._digest_random_generation(rnds_raw, *args)

Expand Down
2 changes: 1 addition & 1 deletion src/vegasflow/plain.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Plain implementation of the plainest possible MonteCarlo
Plain implementation of the plainest possible MonteCarlo
"""

import tensorflow as tf
Expand Down
77 changes: 77 additions & 0 deletions src/vegasflow/quantum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
A Monte Carlo integrator built upon Qibo for quantum integration
"""

import tensorflow as tf

from .configflow import DTYPE, run_eager
from .monte_carlo import MonteCarloFlow, sampler, wrapper
from .plain import PlainFlow
from .vflow import VegasFlow


class QuantumBase(MonteCarloFlow):
"""
This class serves as a basis for the quantum monte carlo integrator.
At initialization it tries to import qibolab and connect to the quantum device,
if successful, saves the reference to _quantum_sampler.

This class is compatible with all ``MonteCarloFlow`` classes, it overrides
the uniform sampling and uses the quantum device instead.
"""

_CAN_RUN_VECTORIAL = False

def __init__(self, *args, **kwargs):
# This integrator can only run for now in eager mode and needs qibolab to be installed
run_eager(True)

try:
from qibolab.instruments.qrng import QRNG
from serial.serialutil import SerialException
except ModuleNotFoundError as e:
raise ModuleNotFoundError("You can do pip install vegasflow[quantum]") from e

qaddress = "/dev/ttyACM0"
try:
# Check whether the quantum device is available and we can connect
qrng = QRNG(address=qaddress)
qrng.connect()
qrng.disconnect()
except SerialException as e:
raise SerialException(f"No quantum device found at {qaddress}") from e

print(f"Sucessfuly connected to quantum device in {qaddress}")

self._quantum_sampler = qrng
super().__init__(*args, **kwargs)

def _internal_sampler(self, n_events):
"""Sample ``n_events x n_dim`` numbers from the quantum device
and cast them to a TF DTYPE to pass down to the MC algorithm"""
self._quantum_sampler.connect()
quantum_rnds_raw = self._quantum_sampler.random((n_events, self.n_dim))
self._quantum_sampler.disconnect()
Comment on lines +52 to +54
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of connecting and disconnecting every time, why don't you keep the connection in the object? (and just disconnect on delete)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not claiming that is going to be the main performance bottleneck - but it's certainly some overhead (if it can be avoided).

Copy link
Copy Markdown
Member Author

@scarlehoff scarlehoff Jan 30, 2025

Choose a reason for hiding this comment

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

Because while I was testing I got several times an error along the lines of "someone else is using the device" which I thought maybe it was @stravos11 testing so I thought it was better to close it asap to avoid making his stuff error out in the same way.

(indeed, in the first commit I do as you suggest)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It was not me, so it is either a different kind of error that we would need to address if it appears often, or someone else tried to use at the same time, but I don't think anyone other than us and @scarrazza would. Personally, I have another qrng that I connect directly to my computer for debugging so I avoid using the one in the qrccluster.

Also, I don't think connect() and disconnect() will have any effect on others. Based on a quick experiment I did, if someone else tries to access numbers while you are sampling, your connection will automatically drop (with an error) and their job will start. So basically the last one always wins. Also, I think connect() and disconnect() have no effect on the qrng device itself, they just create/destroy the Serial object in your Python runtime.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok, then I'll redo what I was doing before and paste the error. By disconecting inmediately after use like now i didn't see the error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I actually thought that could be the case, but I was unsure about the setup. It may be that you're actually trying reconnecting multiple times somehow (e.g., instantiating the object multiple times).

In case you still experience the error, would you post it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, I think connect() and disconnect() have no effect on the qrng device itself, they just create/destroy the Serial object in your Python runtime.

@stavros11 while on the device it may have no effect (not so expert in serial interfaces...), for sure it has an effect on the OS, since it has at least to open a file descriptor for the communication. Then, this will be handled by the given fs, and potentially boil down to a tiny and likely negligible operation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Based on a quick experiment I did, if someone else tries to access numbers while you are sampling, your connection will automatically drop (with an error) and their job will start. So basically the last one always wins.

In case you still experience the error, would you post it?

I ran a long test yesterday evening and didn't experience the error again. I think I might have really clashed with you or someone else who tried to use it?

In any case, I can "force" the error by just login in twice to the node and running the same script in both, one of them will error out with:

serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)

(the crash happened in the one that ran second fwiw)

return tf.cast(quantum_rnds_raw, dtype=DTYPE)


class QuantumIntegrator(PlainFlow, QuantumBase):
pass


class QuantumFlow(VegasFlow, QuantumBase):
pass


def quantum_wrapper(*args, **kwargs):
"""Wrapper around QuantumIntegrator"""
return wrapper(QuantumIntegrator, *args, **kwargs)


def quantumflow_wrapper(*args, **kwargs):
return wrapper(QuantumFlow, *args, **kwargs)


def quantum_sampler(*args, **kwargs):
"""Wrapper sampler around QuantumIntegrator"""
return sampler(QuantumIntegrator, *args, **kwargs)
6 changes: 3 additions & 3 deletions src/vegasflow/vflow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
This module contains the VegasFlow class and all its auxiliary functions
This module contains the VegasFlow class and all its auxiliary functions

The main interfaces of this class are the class `VegasFlow` and the
`vegas_wrapper`
The main interfaces of this class are the class `VegasFlow` and the
`vegas_wrapper`
"""

import json
Expand Down
8 changes: 4 additions & 4 deletions src/vegasflow/vflowplus.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
Implementation of vegas+ algorithm:
Implementation of vegas+ algorithm:

adaptive importance sampling + adaptive stratified sampling
from https://arxiv.org/abs/2009.05112
adaptive importance sampling + adaptive stratified sampling
from https://arxiv.org/abs/2009.05112

The main interface is the `VegasFlowPlus` class.
The main interface is the `VegasFlowPlus` class.
"""

from functools import partial
Expand Down