Repository: dwavesystems/dwave-cloud-client
Pull request #184 (2018-05-18 23:15:13)

Better support for generating and submitting of random problems


Patch:
diff --git a/dwave/cloud/cli.py b/dwave/cloud/cli.py
index 6689497..cce49dd 100644
--- a/dwave/cloud/cli.py
+++ b/dwave/cloud/cli.py
@@ -6,7 +6,8 @@ import click
 from timeit import default_timer as timer
 
 from dwave.cloud import Client
-from dwave.cloud.utils import readline_input, click_info_switch
+from dwave.cloud.utils import (
+    readline_input, click_info_switch, generate_valid_random_problem, strtrunc)
 from dwave.cloud.package_info import __title__, __version__
 from dwave.cloud.exceptions import (
     SolverAuthenticationError, InvalidAPIResponseError, UnsupportedSolverError,
@@ -226,10 +227,6 @@ def solvers(config_file, profile, id):
     all solvers available on configured endpoint.
     """
 
-    def trunc(s, maxlen=60):
-        s = str(s)
-        return s[:(maxlen-3)]+'...' if len(s) > maxlen else s
-
     with Client.from_config(config_file=config_file, profile=profile) as client:
         solvers = client.get_solvers().values()
 
@@ -244,11 +241,11 @@ def solvers(config_file, profile, id):
             click.echo("Solver: {}".format(solver.id))
             click.echo("  Parameters:")
             for param, desc in sorted(solver.parameters.items()):
-                click.echo("    {}: {}".format(param, trunc(desc) if desc else '?'))
+                click.echo("    {}: {}".format(param, strtrunc(desc) if desc else '?'))
             solver.properties.pop('parameters', None)
             click.echo("  Properties:")
             for k,v in sorted(solver.properties.items()):
-                click.echo("    {}: {}".format(k, trunc(v)))
+                click.echo("    {}: {}".format(k, strtrunc(v)))
             click.echo()
 
 
@@ -263,25 +260,31 @@ def solvers(config_file, profile, id):
               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('--random-problem', '-r', default=False, is_flag=True,
+              help='Submit a valid random problem using all qubits')
 @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):
+def sample(config_file, profile, solver_name, biases, couplings, random_problem,
+           num_reads, verbose):
     """Submit Ising-formulated problem and return samples."""
 
     # TODO: de-dup wrt ping
 
+    def echo(s, maxlen=100):
+        click.echo(s if verbose else strtrunc(s, maxlen))
+
     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))
+        echo("Using configuration file: {}".format(config_file))
     if profile:
-        click.echo("Using profile: {}".format(profile))
-    click.echo("Using endpoint: {}".format(client.endpoint))
+        echo("Using profile: {}".format(profile))
+    echo("Using endpoint: {}".format(client.endpoint))
 
     try:
         solvers = client.get_solvers()
@@ -304,21 +307,23 @@ def sample(config_file, profile, solver_name, biases, couplings, num_reads, verb
                 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))
+    echo("Using solver: {}".format(solver.id))
 
-    try:
-        quadratic = ast.literal_eval(couplings) if couplings else {}
-    except Exception as e:
-        click.echo("Invalid couplings: {}".format(e))
+    if random_problem:
+        linear, quadratic = generate_valid_random_problem(solver)
+    else:
+        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))
+    echo("Using qubit biases: {!r}".format(linear))
+    echo("Using qubit couplings: {!r}".format(quadratic))
+    echo("Number of samples: {}".format(num_reads))
 
     try:
         result = solver.sample_ising(linear, quadratic, num_reads=num_reads).result()
@@ -329,6 +334,6 @@ def sample(config_file, profile, solver_name, biases, couplings, num_reads, verb
     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']))
+    echo("Samples: {!r}".format(result['samples']))
+    echo("Occurrences: {!r}".format(result['occurrences']))
+    echo("Energies: {!r}".format(result['energies']))
diff --git a/dwave/cloud/utils.py b/dwave/cloud/utils.py
index f48a20d..1180fc3 100644
--- a/dwave/cloud/utils.py
+++ b/dwave/cloud/utils.py
@@ -4,6 +4,7 @@ from datetime import datetime
 from dateutil.tz import UTC
 from functools import wraps
 import itertools
+import random
 
 import six
 import readline
@@ -66,6 +67,19 @@ def active_qubits(linear, quadratic):
     return active
 
 
+def generate_valid_random_problem(solver):
+    """Generates an Ising problem formulation valid for a particular solver,
+    using all qubits and all couplings."""
+
+    h_range = solver.properties['h_range']
+    j_range = solver.properties['j_range']
+
+    lin = {qubit: random.uniform(*h_range) for qubit in solver.nodes}
+    quad = {edge: random.uniform(*j_range) for edge in solver.undirected_edges}
+
+    return lin, quad
+
+
 def uniform_iterator(sequence):
     """Uniform (key, value) iteration on a `dict`,
     or (idx, value) on a `list`."""
@@ -144,3 +158,8 @@ def datetime_to_timestamp(dt):
 
     epoch = datetime.utcfromtimestamp(0).replace(tzinfo=UTC)
     return (dt - epoch).total_seconds()
+
+
+def strtrunc(s, maxlen=60):
+    s = str(s)
+    return s[:(maxlen-3)]+'...' if len(s) > maxlen else s


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 30e157d..9d3dee9 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -3,7 +3,7 @@ from collections import OrderedDict
 
 from dwave.cloud.utils import (
     readline_input, uniform_iterator, uniform_get, strip_head, strip_tail,
-    active_qubits)
+    active_qubits, generate_valid_random_problem)
 from dwave.cloud.testing import mock
 
 
@@ -56,6 +56,18 @@ class TestUtils(unittest.TestCase):
         with mock.patch("six.moves.input", side_effect=[val], create=True):
             self.assertEqual(readline_input("prompt", val+val), val)
 
+    def test_generate_valid_random_problem(self):
+        class MockSolver(object):
+            nodes = [0, 1, 3]
+            undirected_edges = {(0, 1), (1, 3), (0, 4)}
+            properties = dict(h_range=[2, 2], j_range=[-1, -1])
+        mock_solver = MockSolver()
+
+        lin, quad = generate_valid_random_problem(mock_solver)
+
+        self.assertDictEqual(lin, {0: 2.0, 1: 2.0, 3: 2.0})
+        self.assertDictEqual(quad, {(0, 1): -1.0, (1, 3): -1.0, (0, 4): -1.0})
+
 
 if __name__ == '__main__':
     unittest.main()