Skip to content

Commit

Permalink
PCVL-767 resources channel logs (#445)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarionQuandela authored Aug 23, 2024
1 parent 7c14ba1 commit 014e14b
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 19 deletions.
40 changes: 39 additions & 1 deletion perceval/components/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys

from multipledispatch import dispatch
from numpy import inf
from typing import Dict, Callable, Union, List
Expand All @@ -40,7 +42,6 @@
from .source import Source



class Processor(AProcessor):
"""
Generic definition of processor as a source + components (both unitary and non-unitary) + ports
Expand Down Expand Up @@ -267,6 +268,7 @@ def samples(self, max_samples: int, max_shots: int = None, progress_callback=Non
sampling_simulator.set_selection(self._min_detected_photons, self.post_select_fn, self.heralds)
sampling_simulator.set_threshold_detector(self.is_threshold)
sampling_simulator.keep_heralds(False)
self.log_resources(sys._getframe().f_code.co_name, {'max_samples': max_samples, 'max_shots': max_shots})
logger.info(f"Start a local {'perfect' if self._source.is_perfect() else 'noisy'} sampling", channel.general)
res = sampling_simulator.samples(self._inputs_map, max_samples, max_shots, progress_callback)
logger.info("Local sampling complete!", channel.general)
Expand Down Expand Up @@ -297,8 +299,44 @@ def probs(self, precision: float = None, progress_callback: Callable = None) ->
postprocessed_res.normalize()
res['physical_perf'] = res['physical_perf']*pperf if 'physical_perf' in res else pperf
res['results'] = postprocessed_res
self.log_resources(sys._getframe().f_code.co_name, {'precision': precision})
return res

@property
def available_commands(self) -> List[str]:
return ["samples" if isinstance(self.backend, ASamplingBackend) else "probs"]

def log_resources(self, method: str, extra_parameters: Dict):
"""Log resources of the processor
:param method: name of the method used
:param extra_parameters: extra parameters to log.
Extra parameter can be:
- max_samples
- max_shots
- precision
"""
extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None}
my_dict = {
'layer': 'Processor',
'backend': self.backend.name,
'm': self.circuit_size,
'method': method
}
if isinstance(self._input_state, BasicState):
my_dict['n'] = self._input_state.n
elif isinstance(self._input_state, StateVector):
my_dict['n'] = max(self._input_state.n)
elif isinstance(self._input_state, SVDistribution):
my_dict['n'] = self._input_state.n_max
else:
logger.info(f"Cannot get n for type {type(self._input_state)}", channel.resource)
if extra_parameters:
my_dict.update(extra_parameters)
if self.noise: # TODO: PCVL-782
my_dict['noise'] = self.noise.__dict__()
elif self.source:
my_dict['source'] = self.source.__dict__()
logger.log_resources(my_dict)
19 changes: 14 additions & 5 deletions perceval/components/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from perceval.utils.logging import logger, channel
from typing import Dict, List, Union

DISTINGUISHABLE_KEY = 'distinguishable'
INDISTINGUISHABLE_KEY = 'indistinguishable'

class Source:
r"""Definition of a source
Expand All @@ -55,15 +57,15 @@ def __init__(self,
multiphoton_component: float = 0,
indistinguishability: float = 1,
losses: float = 0,
multiphoton_model: str = "distinguishable", # Literal["distinguishable", "indistinguishable"]
multiphoton_model: str = DISTINGUISHABLE_KEY, # Literal[DISTINGUISHABLE_KEY, INDISTINGUISHABLE_KEY]
context: Dict = None) -> None:

assert 0 < emission_probability <= 1, "emission_probability must be in ]0;1]"
assert 0 <= losses <= 1, "losses must be in [0;1]"
assert 0 <= multiphoton_component <= 1, "multiphoton_component must be in [0;1]"
assert emission_probability * multiphoton_component <= 0.5,\
"emission_probability * g2 higher than 0.5 can not be computed for now"
assert multiphoton_model in ["distinguishable", "indistinguishable"], \
assert multiphoton_model in [DISTINGUISHABLE_KEY, INDISTINGUISHABLE_KEY], \
"invalid value for multiphoton_model"

self._emission_probability = emission_probability
Expand All @@ -86,7 +88,7 @@ def from_noise_model(noise: NoiseModel):
multiphoton_component=noise.g2,
indistinguishability=noise.indistinguishability,
losses=1 - noise.transmittance,
multiphoton_model="distinguishable" if noise.g2_distinguishable else "indistinguishable")
multiphoton_model=DISTINGUISHABLE_KEY if noise.g2_distinguishable else INDISTINGUISHABLE_KEY)

def get_tag(self, tag, add=False):
if add:
Expand Down Expand Up @@ -141,7 +143,7 @@ def _generate_one_photon_distribution(self):
# Approximation distinguishable photons are pure
distinguishable_photon = self.get_tag("discernability_tag", add=True)
second_photon = self.get_tag("discernability_tag", add=True) \
if self._multiphoton_model == "distinguishable" else 0 # Noise photon or signal
if self._multiphoton_model == DISTINGUISHABLE_KEY else 0 # Noise photon or signal

(p1to1, p2to1, p2to2) = self._get_probs()
p0 = 1 - (p1to1 + 2 * p2to1 + p2to2) # 2 * p2to1 because of symmetry
Expand All @@ -163,7 +165,7 @@ def _generate_one_photon_distribution(self):
@property
def partially_distinguishable(self):
return self._indistinguishability != 1 \
or (self._multiphoton_model == "distinguishable" and self._multiphoton_component)
or (self._multiphoton_model == DISTINGUISHABLE_KEY and self._multiphoton_component)

def probability_distribution(self, nphotons: int = 1) -> SVDistribution:
r"""returns SVDistribution on 1 mode associated to the source
Expand Down Expand Up @@ -222,3 +224,10 @@ def __eq__(self, value: object) -> bool:
self._indistinguishability == value._indistinguishability and \
self._context == value._context and \
self.simplify_distribution == value.simplify_distribution

def __dict__(self) -> Dict:
return {'g2': self._multiphoton_component,
'transmittance': 1 - self._losses,
'brightness': self._emission_probability,
'indistinguishability': self._indistinguishability,
'g2_distinguishable': self._multiphoton_model == DISTINGUISHABLE_KEY}
22 changes: 22 additions & 0 deletions perceval/runtime/remote_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def prepare_job_payload(self, command: str, circuitless: bool = False, inputless
if self._noise is not None:
payload['noise'] = serialize(self._noise)
j['payload'] = payload
self.log_resources(command, self._parameters)
return j

def resume_job(self, job_id: str) -> RemoteJob:
Expand Down Expand Up @@ -330,3 +331,24 @@ def estimate_expected_samples(self, nshots: int, param_values: dict = None) -> i
"""
p_interest = self._compute_sample_of_interest_probability(param_values=param_values)
return round(nshots * p_interest)

def log_resources(self, command: str, extra_parameters: Dict):
"""Log resources of the remote processor
:param method: name of the method used
:param extra_parameters: extra parameters to log
"""
extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None}
my_dict = {
'layer': 'RemoteProcessor',
'platform': self.name,
'm': self.circuit_size,
'command': command
}
if self._input_state:
my_dict['n'] = self._input_state.n
if self.noise:
my_dict['noise'] = self.noise.__dict__()
if extra_parameters:
my_dict.update(extra_parameters)
logger.log_resources(my_dict)
27 changes: 27 additions & 0 deletions perceval/simulators/noisy_sampling_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import math
import time
import sys

from typing import Callable, Dict, Tuple

from perceval.backends import ASamplingBackend
Expand Down Expand Up @@ -335,6 +338,8 @@ def samples(self,

res = self._noisy_sampling(new_input, provider, max_samples, max_shots, progress_callback)
res['physical_perf'] *= pre_physical_perf
self.log_resources(sys._getframe().f_code.co_name, {
'n': svd.n_max, 'max_samples': max_samples, 'max_shots': max_shots})
return res

def sample_count(self,
Expand All @@ -345,3 +350,25 @@ def sample_count(self,
sampling = self.samples(svd, max_samples, max_shots, progress_callback)
sampling['results'] = samples_to_sample_count(sampling['results'])
return sampling

def log_resources(self, method: str, extra_parameters: Dict):
"""Log resources of the noisy sampling simulator
:param method: name of the method used
:param extra_parameters: extra parameters to log
Extra parameter can be:
- max_samples
- max_shots
"""
extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None}
my_dict = {
'layer': 'NoisySamplingSimulator',
'backend': self._backend.name,
'm': self._backend._circuit.m,
'method': method
}
if extra_parameters:
my_dict.update(extra_parameters)
logger.log_resources(my_dict)
44 changes: 36 additions & 8 deletions perceval/simulators/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,23 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from ._simulator_utils import _to_bsd, _inject_annotation, _merge_sv, _annot_state_mapping
from .simulator_interface import ISimulator
from perceval.components import ACircuit
from perceval.utils import BasicState, BSDistribution, StateVector, SVDistribution, PostSelect, global_params, \
DensityMatrix, post_select_distribution, post_select_statevector
from perceval.backends import AProbAmpliBackend
from perceval.utils.density_matrix_utils import extract_upper_triangle
import sys

from copy import copy
from multipledispatch import dispatch
from numbers import Number
from typing import Callable, Set, Union, Optional, List
from scipy.sparse import csc_array, csr_array
from typing import Callable, Set, Union, Optional, List, Dict

from perceval.backends import AProbAmpliBackend
from perceval.components import ACircuit
from perceval.utils import BasicState, BSDistribution, StateVector, SVDistribution, PostSelect, global_params, \
DensityMatrix, post_select_distribution, post_select_statevector
from perceval.utils.density_matrix_utils import extract_upper_triangle
from perceval.utils.logging import logger

from ._simulator_utils import _to_bsd, _inject_annotation, _merge_sv, _annot_state_mapping
from .simulator_interface import ISimulator


class Simulator(ISimulator):
Expand Down Expand Up @@ -404,6 +408,7 @@ def probs_svd(self, input_dist: SVDistribution, progress_callback: Optional[Call
res = self._probs_svd_fast(svd, p_threshold, progress_callback)

res, self._logical_perf = post_select_distribution(res, self._postselect, self._heralds, self._keep_heralds)
self.log_resources(sys._getframe().f_code.co_name, {'n': input_dist.n_max})
return {'results': res,
'physical_perf': self._physical_perf,
'logical_perf': self._logical_perf}
Expand Down Expand Up @@ -473,6 +478,8 @@ def evolve(self, input_state: Union[BasicState, StateVector]) -> StateVector:
self.DEBUG_merge_count += 1
result_sv += evolved_in_s * probampli
result_sv, _ = post_select_statevector(result_sv, self._postselect, self._heralds, self._keep_heralds)
self.log_resources(sys._getframe().f_code.co_name, {
'n': input_state.n if isinstance(input_state.n, int) else max(input_state.n)})
return result_sv

def evolve_svd(self,
Expand Down Expand Up @@ -569,3 +576,24 @@ def _get_density_matrix_input_list(dm: DensityMatrix) -> list:
if dm.mat[k, k] != 0:
input_list.append(dm.inverse_index[k])
return input_list

def log_resources(self, method: str, extra_parameters: Dict):
"""Log resources of the simulator
:param method: name of the method used
:param extra_parameters: extra parameters to log
Extra parameter can be:
- n
"""
extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None}
my_dict = {
'layer': 'Simulator',
'backend': self._backend.name,
'm': self._backend._circuit.m,
'method': method
}
if extra_parameters:
my_dict.update(extra_parameters)
logger.log_resources(my_dict)
26 changes: 21 additions & 5 deletions perceval/utils/logging/loggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,25 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from os import path
import warnings

import json
import traceback
import warnings
import logging as py_log

from abc import ABC, abstractmethod
from typing import Dict
from os import path

from exqalibur import logging as exq_log

from ..persistent_data import PersistentData
from .config import LoggerConfig, _CHANNELS, _ENABLE_FILE

DEFAULT_CHANNEL = exq_log.channel.user


class ILogger(ABC):
class ALogger(ABC):
@abstractmethod
def enable_file(self):
pass
Expand Down Expand Up @@ -72,8 +77,19 @@ def error(self, msg: str, channel: exq_log.channel = DEFAULT_CHANNEL, exc_info=N
def critical(self, msg: str, channel: exq_log.channel = DEFAULT_CHANNEL, exc_info=None):
pass

def log_resources(self, my_dict: Dict):
"""Log resources as a dictionary with:
- level: info
- channel: resources
- serializing the dictionary as json so it can be easily deserialize
:param my_dict: resources dictionary to log
"""
self.info(json.dumps(my_dict), exq_log.channel.resources)


class ExqaliburLogger(ILogger):
class ExqaliburLogger(ALogger):
def initialize(self):
persistent_data = PersistentData()
if persistent_data.is_writable():
Expand Down Expand Up @@ -154,7 +170,7 @@ def critical(self, msg: str, channel: exq_log.channel = DEFAULT_CHANNEL, exc_inf
exq_log.critical(str(msg), channel)


class PythonLogger(ILogger):
class PythonLogger(ALogger):
def __init__(self) -> None:
self._logger = py_log.getLogger()
self._config = LoggerConfig()
Expand Down
Loading

0 comments on commit 014e14b

Please sign in to comment.