Repository: dwavesystems/dwave-cloud-client
Pull request #174 (2018-05-11 03:00:59)

Add CLI 'dwave sample' command


Patch:
diff --git a/dwave/cloud/cli.py b/dwave/cloud/cli.py
index 72dac1c..6689497 100644
--- a/dwave/cloud/cli.py
+++ b/dwave/cloud/cli.py
@@ -1,4 +1,5 @@
 import os
+import ast
 import logging
 
 import click
@@ -249,3 +250,85 @@ def solvers(config_file, profile, id):
             for k,v in sorted(solver.properties.items()):
                 click.echo("    {}: {}".format(k, trunc(v)))
             click.echo()
+
+
+@cli.command()
+@click.option('--config-file', '-c', default=None,
+              type=click.Path(exists=True, dir_okay=False), help='Configuration file path')
+@click.option('--profile', '-p', default=None,
+              help='Connection profile (section) name')
+@click.option('--solver', '-s', 'solver_name', default=None,
+              help='Solver name to use')
+@click.option('--biases', '-h', default=None,
+              help='List/dict of biases for Ising model problem formulation')
+@click.option('--couplings', '-j', default=None,
+              help='List/dict of couplings for Ising model problem formulation')
+@click.option('--num-reads', '-n', default=1, type=int,
+              help='Number of reads/samples')
+@click.option('--verbose', '-v', default=False, is_flag=True,
+              help='Increase output verbosity')
+def sample(config_file, profile, solver_name, biases, couplings, num_reads, verbose):
+    """Submit Ising-formulated problem and return samples."""
+
+    # TODO: de-dup wrt ping
+
+    try:
+        client = Client.from_config(config_file=config_file, profile=profile)
+    except Exception as e:
+        click.echo("Invalid configuration: {}".format(e))
+        return 1
+    if config_file:
+        click.echo("Using configuration file: {}".format(config_file))
+    if profile:
+        click.echo("Using profile: {}".format(profile))
+    click.echo("Using endpoint: {}".format(client.endpoint))
+
+    try:
+        solvers = client.get_solvers()
+    except SolverAuthenticationError:
+        click.echo("Authentication error. Check credentials in your configuration file.")
+        return 1
+    except (InvalidAPIResponseError, UnsupportedSolverError):
+        click.echo("Invalid or unexpected API response.")
+        return 2
+
+    if solver_name and solver_name in solvers:
+        solver = solvers[solver_name]
+    else:
+        try:
+            solver = client.get_solver()
+        except (ValueError, KeyError):
+            if solvers:
+                _, solver = next(iter(solvers.items()))
+            else:
+                click.echo("No solvers available.")
+                return 1
+
+    click.echo("Using solver: {}".format(solver.id))
+
+    try:
+        linear = ast.literal_eval(biases) if biases else []
+    except Exception as e:
+        click.echo("Invalid biases: {}".format(e))
+
+    try:
+        quadratic = ast.literal_eval(couplings) if couplings else {}
+    except Exception as e:
+        click.echo("Invalid couplings: {}".format(e))
+
+    click.echo("Using qubit biases: {!r}".format(linear))
+    click.echo("Using qubit couplings: {!r}".format(quadratic))
+    click.echo("Number of samples: {}".format(num_reads))
+
+    try:
+        result = solver.sample_ising(linear, quadratic, num_reads=num_reads).result()
+    except Exception as e:
+        click.echo(e)
+        return 2
+
+    if verbose:
+        click.echo("Result: {!r}".format(result))
+
+    click.echo("Samples: {!r}".format(result['samples']))
+    click.echo("Occurrences: {!r}".format(result['occurrences']))
+    click.echo("Energies: {!r}".format(result['energies']))
diff --git a/perf/multiple.py b/perf/multiple.py
index 697aece..e929a9c 100644
--- a/perf/multiple.py
+++ b/perf/multiple.py
@@ -6,15 +6,12 @@ from dwave.cloud.qpu import Client
 from dwave.cloud.computation import Future
 
 
-# increase logging verbosity for root logger
-logging.getLogger('dwave.cloud').setLevel(logging.DEBUG)
-
 # setup local logger
 formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
 handler = logging.StreamHandler()
 handler.setFormatter(formatter)
 logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
+logger.setLevel(logging.INFO)
 logger.addHandler(handler)
 
 
diff --git a/perf/simple.py b/perf/simple.py
index a9dd7f5..8fe75cd 100644
--- a/perf/simple.py
+++ b/perf/simple.py
@@ -5,15 +5,12 @@ import logging
 from dwave.cloud.qpu import Client
 
 
-# increase logging verbosity for root logger
-logging.getLogger('dwave.cloud').setLevel(logging.DEBUG)
-
 # setup local logger
 formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
 handler = logging.StreamHandler()
 handler.setFormatter(formatter)
 logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
+logger.setLevel(logging.INFO)
 logger.addHandler(handler)
 
 
@@ -24,11 +21,16 @@ with Client.from_config(profile='prod') as client:
     solver = client.get_solver()
     comp = solver.sample_qubo({})
 
-    logger.info("Computation")
+    logger.info("Problem submitted:")
     logger.info(" - time received: %s", comp.time_received)
     logger.info(" - time solved: %s", comp.time_solved)
     logger.info(" - parse time: %s", comp.parse_time)
     logger.info(" - remote status: %s", comp.remote_status)
+    logger.info(" - min_eta: %s", comp.eta_min)
 
     result = comp.result()
-    logger.info("Result received.")
+    logger.info("Result received:")
+    logger.info(" - time received: %s", comp.time_received)
+    logger.info(" - time solved: %s", comp.time_solved)
+    logger.info(" - parse time: %s", comp.parse_time)
+    logger.info(" - remote status: %s", comp.remote_status)


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 3ffc3eb..bbae80b 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -180,6 +180,39 @@ class TestCli(unittest.TestCase):
                                      '--profile', test_config_profile])
         self.assertEqual(result.exit_code, 0)
 
+    def test_sample(self):
+        config_file = 'dwave.conf'
+        profile = 'profile'
+        biases = '[0]'
+        couplings = '{(0, 4): 1}'
+        num_reads = '10'
+
+        with mock.patch('dwave.cloud.cli.Client') as m:
+
+            runner = CliRunner()
+            with runner.isolated_filesystem():
+                touch(config_file)
+                result = runner.invoke(cli, ['sample',
+                                            '--config-file', config_file,
+                                            '--profile', profile,
+                                            '-h', biases,
+                                            '-j', couplings,
+                                            '-n', num_reads])
+
+            # proper arguments passed to Client.from_config?
+            m.from_config.assert_called_with(config_file=config_file, profile=profile)
+
+            # get solver called?
+            c = m.from_config(config_file=config_file, profile=profile)
+            c.get_solvers.assert_called_with()
+            c.get_solver.assert_called_with()
+
+            # sampling method called on solver?
+            s = c.get_solver()
+            s.sample_ising.assert_called_with([0], {(0, 4): 1}, num_reads=10)
+
+        self.assertEqual(result.exit_code, 0)
+
 
 if __name__ == '__main__':
     unittest.main()