From 457825e861917f5363a6af08d793896357c6ccd3 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 7 Aug 2020 12:00:45 -0700 Subject: [PATCH 1/3] Add display hook. --- .../qsharp-core/qsharp/clients/iqsharp.py | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/Python/qsharp-core/qsharp/clients/iqsharp.py b/src/Python/qsharp-core/qsharp/clients/iqsharp.py index b0ed6a097d..591ec603c3 100644 --- a/src/Python/qsharp-core/qsharp/clients/iqsharp.py +++ b/src/Python/qsharp-core/qsharp/clients/iqsharp.py @@ -18,6 +18,7 @@ import os import jupyter_client +from functools import partial from io import StringIO from collections import defaultdict from typing import List, Dict, Callable, Any @@ -26,6 +27,13 @@ from qsharp.serialization import map_tuples, unmap_tuples +try: + from IPython.display import display + display_raw = partial(display, raw=True) +except: + def display_raw(content): + pass + ## VERSION REPORTING ## try: @@ -126,13 +134,13 @@ def get_packages(self) -> List[str]: return self._execute("%package", raise_on_stderr=False) def simulate(self, op, **params) -> Any: - return self._execute_callable_magic('simulate', op, **params) + return self._execute_callable_magic('simulate', op, quiet=False, params=params) def toffoli_simulate(self, op, **params) -> Any: - return self._execute_callable_magic('toffoli', op, **params) + return self._execute_callable_magic('toffoli', op, quiet=False, params=params) def estimate(self, op, **params) -> Dict[str, int]: - raw_counts = self._execute_callable_magic('estimate', op, **params) + raw_counts = self._execute_callable_magic('estimate', op, quiet=False, params=params) # Note that raw_counts will have the form: # [ # {"Metric": "", "Sum": ""}, @@ -157,18 +165,43 @@ def capture(msg): data = unmap_tuples(json.loads(msg["content"]["data"]["application/json"])) for component, version in data["rows"]: versions[component] = LooseVersion(version) - self._execute("%version", output_hook=capture, **kwargs) + self._execute("%version", output_hook=capture, params=kwargs) return versions ## Internal-Use Methods ## - def _execute_magic(self, magic : str, raise_on_stderr : bool = False, **params) -> Any: - return self._execute(f'%{magic} {json.dumps(map_tuples(params))}', raise_on_stderr=raise_on_stderr) + def _execute_magic(self, magic : str, raise_on_stderr : bool = False, quiet : bool = True, params=None) -> Any: + return self._execute( + f'%{magic} {json.dumps(map_tuples(params))}', + raise_on_stderr=raise_on_stderr, quiet=quiet, params=params + ) + + def _execute_callable_magic(self, magic : str, op, + raise_on_stderr : bool = False, quiet : bool = True, params=None + ) -> Any: + return self._execute_magic( + f"{magic} {op._name}", + raise_on_stderr=raise_on_stderr, quiet=quiet, params=params + ) + + def _handle_message(self, msg, handlers=None, error_callback=None, fallback_hook=None): + if handlers is None: + handlers = {} + if fallback_hook is None: + fallback_hook = self.kernel_client._output_hook_default + msg_type = msg['msg_type'] + if msg_type in handlers: + handlers[msg_type](msg) + else: + if error_callback is not None and msg['msg_type'] == 'stream' and msg['content']['name'] == 'stderr': + error_callback(msg['content']['text']) + else: + fallback_hook(msg) - def _execute_callable_magic(self, magic : str, op, raise_on_stderr : bool = False, **params) -> Any: - return self._execute_magic(f"{magic} {op._name}", raise_on_stderr=raise_on_stderr, **params) + def _execute(self, input, return_full_result=False, raise_on_stderr : bool = False, output_hook=None, quiet=True, params=None): + if params is None: + params = {} - def _execute(self, input, return_full_result=False, raise_on_stderr : bool = False, output_hook=None, **kwargs): logger.debug(f"sending:\n{input}") # make sure the server is still running: @@ -179,16 +212,25 @@ def _execute(self, input, return_full_result=False, raise_on_stderr : bool = Fal results = [] errors = [] - if output_hook is None: - output_hook = self.kernel_client._output_hook_default - def _output_hook(msg): - if msg['msg_type'] == 'execute_result': - results.append(msg) - else: - if raise_on_stderr and msg['msg_type'] == 'stream' and msg['content']['name'] == 'stderr': - errors.append(msg['content']['text']) - else: - output_hook(msg) + + def log_error(msg): + if msg['msg_type'] == 'stream' and msg['content']['name'] == 'stderr': + errors.append(msg['content']['text']) + + handlers = { + 'execute_result': (lambda msg: results.append(msg)) + } + if not quiet: + handlers['display_data'] = ( + lambda msg: display_raw(msg['content']['data']) + ) + + _output_hook = partial( + self._handle_message, + error_callback=log_error if raise_on_stderr else None, + fallback_hook=output_hook, + handlers=handlers + ) try: if self._busy: @@ -199,7 +241,7 @@ def _output_hook(msg): # propagate to a Jupyter protocol error. raise AlreadyExecutingError("Cannot execute through the IQ# client while another execution is completing.") self._busy = True - reply = self.kernel_client.execute_interactive(input, output_hook=_output_hook, **kwargs) + reply = self.kernel_client.execute_interactive(input, output_hook=_output_hook, **params) finally: self._busy = False From 0dfc8181caa079b1b264b3eebab8ef6ea410ecfb Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 7 Aug 2020 12:47:36 -0700 Subject: [PATCH 2/3] Go back to **kwargs. --- .../qsharp-core/qsharp/clients/iqsharp.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Python/qsharp-core/qsharp/clients/iqsharp.py b/src/Python/qsharp-core/qsharp/clients/iqsharp.py index 591ec603c3..5c96435a76 100644 --- a/src/Python/qsharp-core/qsharp/clients/iqsharp.py +++ b/src/Python/qsharp-core/qsharp/clients/iqsharp.py @@ -133,14 +133,14 @@ def add_package(self, name : str) -> None: def get_packages(self) -> List[str]: return self._execute("%package", raise_on_stderr=False) - def simulate(self, op, **params) -> Any: - return self._execute_callable_magic('simulate', op, quiet=False, params=params) + def simulate(self, op, **kwargs) -> Any: + return self._execute_callable_magic('simulate', op, **kwargs) - def toffoli_simulate(self, op, **params) -> Any: - return self._execute_callable_magic('toffoli', op, quiet=False, params=params) + def toffoli_simulate(self, op, **kwargs) -> Any: + return self._execute_callable_magic('toffoli', op, **kwargs) - def estimate(self, op, **params) -> Dict[str, int]: - raw_counts = self._execute_callable_magic('estimate', op, quiet=False, params=params) + def estimate(self, op, **kwargs) -> Dict[str, int]: + raw_counts = self._execute_callable_magic('estimate', op, **kwargs) # Note that raw_counts will have the form: # [ # {"Metric": "", "Sum": ""}, @@ -165,23 +165,28 @@ def capture(msg): data = unmap_tuples(json.loads(msg["content"]["data"]["application/json"])) for component, version in data["rows"]: versions[component] = LooseVersion(version) - self._execute("%version", output_hook=capture, params=kwargs) + self._execute("%version", output_hook=capture, _quiet_=True, **kwargs) return versions ## Internal-Use Methods ## - def _execute_magic(self, magic : str, raise_on_stderr : bool = False, quiet : bool = True, params=None) -> Any: + def _execute_magic(self, magic : str, raise_on_stderr : bool = False, _quiet_ : bool = False, **kwargs) -> Any: return self._execute( - f'%{magic} {json.dumps(map_tuples(params))}', - raise_on_stderr=raise_on_stderr, quiet=quiet, params=params + f'%{magic} {json.dumps(map_tuples(kwargs))}', + raise_on_stderr=raise_on_stderr, _quiet_=_quiet_, + **kwargs ) def _execute_callable_magic(self, magic : str, op, - raise_on_stderr : bool = False, quiet : bool = True, params=None + raise_on_stderr : bool = False, + _quiet_ : bool = False, + **kwargs ) -> Any: return self._execute_magic( f"{magic} {op._name}", - raise_on_stderr=raise_on_stderr, quiet=quiet, params=params + raise_on_stderr=raise_on_stderr, + _quiet_=_quiet_, + **kwargs ) def _handle_message(self, msg, handlers=None, error_callback=None, fallback_hook=None): @@ -198,9 +203,7 @@ def _handle_message(self, msg, handlers=None, error_callback=None, fallback_hook else: fallback_hook(msg) - def _execute(self, input, return_full_result=False, raise_on_stderr : bool = False, output_hook=None, quiet=True, params=None): - if params is None: - params = {} + def _execute(self, input, return_full_result=False, raise_on_stderr : bool = False, output_hook=None, _quiet_ : bool = False, **kwargs): logger.debug(f"sending:\n{input}") @@ -220,7 +223,7 @@ def log_error(msg): handlers = { 'execute_result': (lambda msg: results.append(msg)) } - if not quiet: + if not _quiet_: handlers['display_data'] = ( lambda msg: display_raw(msg['content']['data']) ) @@ -241,7 +244,7 @@ def log_error(msg): # propagate to a Jupyter protocol error. raise AlreadyExecutingError("Cannot execute through the IQ# client while another execution is completing.") self._busy = True - reply = self.kernel_client.execute_interactive(input, output_hook=_output_hook, **params) + reply = self.kernel_client.execute_interactive(input, output_hook=_output_hook, **kwargs) finally: self._busy = False From 66523ea70d44c6756db0786d890bfa8c06fee551 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 7 Aug 2020 14:10:12 -0700 Subject: [PATCH 3/3] Fix _execute_magic. --- src/Python/qsharp-core/qsharp/clients/iqsharp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Python/qsharp-core/qsharp/clients/iqsharp.py b/src/Python/qsharp-core/qsharp/clients/iqsharp.py index 5c96435a76..749e8d4f40 100644 --- a/src/Python/qsharp-core/qsharp/clients/iqsharp.py +++ b/src/Python/qsharp-core/qsharp/clients/iqsharp.py @@ -173,8 +173,7 @@ def capture(msg): def _execute_magic(self, magic : str, raise_on_stderr : bool = False, _quiet_ : bool = False, **kwargs) -> Any: return self._execute( f'%{magic} {json.dumps(map_tuples(kwargs))}', - raise_on_stderr=raise_on_stderr, _quiet_=_quiet_, - **kwargs + raise_on_stderr=raise_on_stderr, _quiet_=_quiet_ ) def _execute_callable_magic(self, magic : str, op,