Repository: dwavesystems/dwave-cloud-client
Pull request #256 (2018-09-28 19:02:18)

Update readthedocs badge


Patch:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index e91a16c..16a6b9f 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,3 +1,3 @@
 D-Wave welcomes contributions to Ocean projects.
 
-See how to contribute at :std:doc:`Ocean Contributors <oceandocs:CONTRIBUTING>`.
+See how to contribute at `Ocean Contributors <http://dw-docs.readthedocs.io/en/latest/CONTRIBUTING.html>`_.
diff --git a/README.rst b/README.rst
index fb3866d..69ce448 100644
--- a/README.rst
+++ b/README.rst
@@ -14,9 +14,9 @@
     :target: https://coveralls.io/github/dwavesystems/dwave-cloud-client?branch=master
     :alt: Coverage report
 
-.. image:: https://readthedocs.org/projects/dwave-cloud-client/badge/?version=latest
-    :target: http://dwave-cloud-client.readthedocs.io/en/latest/?badge=latest
-    :alt: Documentation status
+.. image:: https://readthedocs.com/projects/d-wave-systems-dwave-cloud-client/badge/?version=latest
+    :target: https://docs.ocean.dwavesys.com/projects/cloud-client/en/latest/?badge=latest
+    :alt: Documentation Status
 
 .. index-start-marker
 
diff --git a/docs/conf.py b/docs/conf.py
index 30db9ad..9a1d2fa 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -90,15 +90,4 @@ InstanceAttributeDocumenter.add_directive_header = iad_add_directive_header
 
 
 # Link to Python standard lib objects
-intersphinx_mapping = {'python': ('https://docs.python.org/3', None),
-    'dimod': ('https://docs.ocean.dwavesys.com/projects/dimod/en/latest/', None),
-    'binarycsp': ('https://docs.ocean.dwavesys.com/projects/binarycsp/en/latest/', None),
-    'cloud-client': ('https://docs.ocean.dwavesys.com/projects/cloud-client/en/latest/', None),
-    'neal': ('https://docs.ocean.dwavesys.com/projects/neal/en/latest/', None),
-    'networkx': ('https://docs.ocean.dwavesys.com/projects/dwave-networkx/en/latest/', None),
-    'system': ('https://docs.ocean.dwavesys.com/projects/system/en/latest/', None),
-    'penaltymodel': ('https://docs.ocean.dwavesys.com/projects/penaltymodel/en/latest/', None),
-    'minorminer': ('https://docs.ocean.dwavesys.com/projects/minorminer/en/latest/', None),
-    'qbsolv': ('https://docs.ocean.dwavesys.com/projects/qbsolv/en/latest/', None),
-    'oceandocs': ('https://docs.ocean.dwavesys.com/en/latest/', None),
-    'sysdocs_gettingstarted': ('https://docs.dwavesys.com/docs/latest/', None)}
+intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 4c5bb11..6dd6bec 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -6,7 +6,7 @@ Quantum computing and the types of problems it addresses use many domain-specifi
 terms.
 
 Learn the relevant terminology at
-:std:doc:`Ocean Glossary <oceandocs:glossary>`.
+`Ocean Glossary <http://dw-docs.readthedocs.io/en/latest/glossary.html>`_\ .
 
 Index
 =====
diff --git a/docs/ocean.rst b/docs/ocean.rst
index f52dbf3..1ec843a 100644
--- a/docs/ocean.rst
+++ b/docs/ocean.rst
@@ -1,10 +1,10 @@
 Ocean Overview
 ==============
 
-:std:doc:`D-Wave Ocean <oceandocs:index>` includes
+`D-Wave Ocean <http://dw-docs.readthedocs.io/en/latest/index.html>`_ includes
 various projects/repositories on Github that help solve problems on the D-Wave
 system.
 
 Learn about D-Wave's Ocean and how its projects
 work together at
-:std:doc:`D-Wave Ocean on Read the Docs <oceandocs:index>`.
+`D-Wave Ocean on Read the Docs <http://dw-docs.readthedocs.io/en/latest/index.html>`_\ .
\ No newline at end of file
diff --git a/dwave/cloud/cli.py b/dwave/cloud/cli.py
index ec86a4a..4333d8e 100644
--- a/dwave/cloud/cli.py
+++ b/dwave/cloud/cli.py
@@ -1,5 +1,4 @@
 import os
-import sys
 import ast
 import json
 import logging
@@ -11,8 +10,8 @@ from datetime import datetime, timedelta
 
 from dwave.cloud import Client
 from dwave.cloud.utils import (
-    default_text_input, click_info_switch, generate_random_ising_problem,
-    datetime_to_timestamp, utcnow, strtrunc, CLIError)
+    default_text_input, click_info_switch, generate_valid_random_problem,
+    datetime_to_timestamp, utcnow, strtrunc)
 from dwave.cloud.package_info import __title__, __version__
 from dwave.cloud.exceptions import (
     SolverAuthenticationError, InvalidAPIResponseError, UnsupportedSolverError,
@@ -164,12 +163,37 @@ def create(config_file, profile):
     return 0
 
 
-def _ping(config_file, profile, request_timeout, polling_timeout, output):
-    """Helper method for the ping command that uses `output()` for info output
-    and raises `CLIError()` on handled errors.
+@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('--request-timeout', default=None, type=float,
+              help='Connection and read timeouts (in seconds) for all API requests')
+@click.option('--polling-timeout', default=None, type=float,
+              help='Problem polling timeout in seconds (time-to-solution timeout)')
+@click.option('--json', 'json_output', default=False, is_flag=True,
+              help='JSON output')
+def ping(config_file, profile, json_output, request_timeout, polling_timeout):
+    """Ping the QPU by submitting a single-qubit problem."""
 
-    This function is invariant to output format and/or error signaling mechanism.
-    """
+    def output_error(msg, *values):
+        if json_output:
+            click.echo(json.dumps({"error": msg.format(*values)}))
+        else:
+            click.echo(msg.format(*values))
+
+    now = utcnow()
+    info = dict(datetime=now.isoformat(), timestamp=datetime_to_timestamp(now))
+
+    def stage_info(msg, **kwargs):
+        info.update(kwargs)
+        if not json_output:
+            click.echo(msg.format(**kwargs))
+
+    def flush_info():
+        if json_output:
+            click.echo(json.dumps(info))
 
     config = dict(config_file=config_file, profile=profile)
     if request_timeout is not None:
@@ -179,24 +203,29 @@ def _ping(config_file, profile, request_timeout, polling_timeout, output):
     try:
         client = Client.from_config(**config)
     except Exception as e:
-        raise CLIError("Invalid configuration: {}".format(e), code=1)
+        output_error("Invalid configuration: {!r}", e)
+        return 1
     if config_file:
-        output("Using configuration file: {config_file}", config_file=config_file)
+        stage_info("Using configuration file: {config_file}", config_file=config_file)
     if profile:
-        output("Using profile: {profile}", profile=profile)
-    output("Using endpoint: {endpoint}", endpoint=client.endpoint)
+        stage_info("Using profile: {profile}", profile=profile)
+    stage_info("Using endpoint: {endpoint}", endpoint=client.endpoint)
 
     t0 = timer()
     try:
         solvers = client.get_solvers()
     except SolverAuthenticationError:
-        raise CLIError("Authentication error. Check credentials in your configuration file.", 2)
+        output_error("Authentication error. Check credentials in your configuration file.")
+        return 2
     except (InvalidAPIResponseError, UnsupportedSolverError):
-        raise CLIError("Invalid or unexpected API response.", 3)
+        output_error("Invalid or unexpected API response.")
+        return 3
     except RequestTimeout:
-        raise CLIError("API connection timed out.", 4)
+        output_error("API connection timed out.")
+        return 4
     except Exception as e:
-        raise CLIError("Unexpected error while fetching solvers: {!r}".format(e), 5)
+        output_error("Unexpected error: {!r}", e)
+        return 5
 
     try:
         solver = client.get_solver()
@@ -206,72 +235,38 @@ def _ping(config_file, profile, request_timeout, polling_timeout, output):
         if solvers:
             _, solver = next(iter(solvers.items()))
         else:
-            raise CLIError("No solvers available.", 6)
+            output_error("No solvers available.")
+            return 6
     except RequestTimeout:
-        raise CLIError("API connection timed out.", 7)
+        output_error("API connection timed out.")
+        return 7
 
     t1 = timer()
-    output("Using solver: {solver_id}", solver_id=solver.id)
+    stage_info("Using solver: {solver_id}", solver_id=solver.id)
 
     try:
         future = solver.sample_ising({0: 1}, {})
         timing = future.timing
     except RequestTimeout:
-        raise CLIError("API connection timed out.", 8)
+        output_error("API connection timed out.")
+        return 8
     except PollingTimeout:
-        raise CLIError("Polling timeout exceeded.", 9)
+        output_error("Polling timeout exceeded.")
+        return 9
     except Exception as e:
-        raise CLIError("Sampling error: {!r}".format(e), 10)
-    finally:
-        output("Submitted problem ID: {problem_id}", problem_id=future.id)
-
+        output_error("Sampling error: {!r}", e)
+        return 10
     t2 = timer()
 
-    output("\nWall clock time:")
-    output(" * Solver definition fetch: {wallclock_solver_definition:.3f} ms", wallclock_solver_definition=(t1-t0)*1000.0)
-    output(" * Problem submit and results fetch: {wallclock_sampling:.3f} ms", wallclock_sampling=(t2-t1)*1000.0)
-    output(" * Total: {wallclock_total:.3f} ms", wallclock_total=(t2-t0)*1000.0)
-    output("\nQPU timing:")
+    stage_info("\nWall clock time:")
+    stage_info(" * Solver definition fetch: {wallclock_solver_definition:.3f} ms", wallclock_solver_definition=(t1-t0)*1000.0)
+    stage_info(" * Problem submit and results fetch: {wallclock_sampling:.3f} ms", wallclock_sampling=(t2-t1)*1000.0)
+    stage_info(" * Total: {wallclock_total:.3f} ms", wallclock_total=(t2-t0)*1000.0)
+    stage_info("\nQPU timing:")
     for component, duration in timing.items():
-        output(" * %(name)s = {%(name)s} us" % {"name": component}, **{component: duration})
-
+        stage_info(" * %(name)s = {%(name)s} us" % {"name": component}, **{component: duration})
 
-@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('--request-timeout', default=None, type=float,
-              help='Connection and read timeouts (in seconds) for all API requests')
-@click.option('--polling-timeout', default=None, type=float,
-              help='Problem polling timeout in seconds (time-to-solution timeout)')
-@click.option('--json', 'json_output', default=False, is_flag=True,
-              help='JSON output')
-def ping(config_file, profile, json_output, request_timeout, polling_timeout):
-    """Ping the QPU by submitting a single-qubit problem."""
-
-    now = utcnow()
-    info = dict(datetime=now.isoformat(), timestamp=datetime_to_timestamp(now), code=0)
-
-    def output(fmt, **kwargs):
-        info.update(kwargs)
-        if not json_output:
-            click.echo(fmt.format(**kwargs))
-
-    def flush():
-        if json_output:
-            click.echo(json.dumps(info))
-
-    try:
-        _ping(config_file, profile, request_timeout, polling_timeout, output)
-    except CLIError as error:
-        output("Error: {error} (code: {code})", error=str(error), code=error.code)
-        sys.exit(error.code)
-    except Exception as error:
-        output("Unhandled error: {error}", error=str(error))
-        sys.exit(127)
-    finally:
-        flush()
+    flush_info()
 
 
 @cli.command()
@@ -376,7 +371,7 @@ def sample(config_file, profile, solver_name, biases, couplings, random_problem,
     echo("Using solver: {}".format(solver.id))
 
     if random_problem:
-        linear, quadratic = generate_random_ising_problem(solver)
+        linear, quadratic = generate_valid_random_problem(solver)
     else:
         try:
             linear = ast.literal_eval(biases) if biases else []
diff --git a/dwave/cloud/client.py b/dwave/cloud/client.py
index 3e5941e..ddaa512 100644
--- a/dwave/cloud/client.py
+++ b/dwave/cloud/client.py
@@ -27,7 +27,6 @@ Examples:
 
 from __future__ import division, absolute_import
 
-import re
 import sys
 import time
 import logging
@@ -45,8 +44,7 @@ from dwave.cloud.package_info import __packagename__, __version__
 from dwave.cloud.exceptions import *
 from dwave.cloud.config import load_config, legacy_load_config, parse_float
 from dwave.cloud.solver import Solver
-from dwave.cloud.utils import (
-    datetime_to_timestamp, utcnow, TimeoutingHTTPAdapter, epochnow)
+from dwave.cloud.utils import datetime_to_timestamp, utcnow, TimeoutingHTTPAdapter
 
 __all__ = ['Client']
 
@@ -126,9 +124,6 @@ class Client(object):
     _POLL_BACKOFF_MIN = 1
     _POLL_BACKOFF_MAX = 60
 
-    # Tolerance for server-client clocks difference (approx) [sec]
-    _CLOCK_DIFF_MAX = 1
-
     # Poll grouping time frame; two scheduled polls are grouped if closer than [sec]:
     _POLL_GROUP_TIMEFRAME = 2
 
@@ -616,15 +611,11 @@ class Client(object):
             self._all_solvers_ready = True
             return self._solvers
 
-    def solvers(self, name=None, qpu=None, software=None, vfyc=None,
-                flux_biases=None, num_qubits=None, online=True, refresh=False):
+    def solvers(self, qpu=None, software=None, vfyc=None,
+                flux_biases=None, num_qubits=None, refresh=False):
         """Returns a filtered list of solvers handled by this client.
 
         Args:
-            name (str, default=None):
-                Regular expression a solver name/id must match. Note: your regex
-                *must match the complete solver name*, not just prefix of suffix.
-
             qpu (bool, default=None):
                 Should solver be QPU based?
 
@@ -640,12 +631,6 @@ class Client(object):
             num_qubits (int/[int,int], default=None):
                 What's the range (or exact number) of qubits solver should handle?
 
-            online (bool, default=True):
-                Should solver be online?
-
-            refresh (bool, default=False):
-                Force refresh cached list of solvers/properties
-
         Returns:
             list[Solver]: List of all solvers that satisfy the above conditions.
 
@@ -663,7 +648,7 @@ class Client(object):
 
             Get all solvers that have between 1024 and 2048 qubits:
 
-                solvers = client.solvers(num_qubits=(1024, 2048))
+                solvers = client.solvers(num_qubits=[1024, 2048])
 
             Get all solvers that have at least 2000 qubits:
 
@@ -672,12 +657,6 @@ class Client(object):
             Get the first QPU solver that accepts flux biases:
 
                 solver = client.solvers(qpu=True, flux_biases=True)[0]
-
-            Assuming only one of the two solvers named `solver1` and `solver2`
-            can be online at once, return the one that is:
-
-                solver = client.solvers(online=True, name='solver[12]')[0]
-                solver = client.solvers(online=True, name='solver1|solver2')[0]
         """
 
         def predicate(solver):
@@ -690,7 +669,7 @@ class Client(object):
             if flux_biases is not None and solver.has_flux_biases != flux_biases:
                 return False
             if num_qubits is not None:
-                if isinstance(num_qubits, (list, tuple)) and len(num_qubits) == 2:
+                if isinstance(num_qubits, list) and len(num_qubits) == 2:
                     min_qubits, max_qubits = num_qubits
                 else:
                     min_qubits, max_qubits = num_qubits, num_qubits
@@ -698,17 +677,13 @@ class Client(object):
                     return False
                 if max_qubits is not None and max_qubits < solver.num_qubits:
                     return False
-            if online is not None and solver.is_online != online:
-                return False
-            if name is not None and not re.match("^{}$".format(name), solver.id):
-                return False
             return True
 
         solvers = list(filter(predicate, self.get_solvers(refresh=refresh).values()))
         solvers.sort(key=attrgetter('id'))
         return solvers
 
-    def get_solver(self, name=None, features=None, refresh=False):
+    def get_solver(self, name=None, refresh=False):
         """Load the configuration for a single solver.
 
         Makes a blocking web call to `{endpoint}/solvers/remote/{solver_name}/`, where `{endpoint}`
@@ -721,12 +696,6 @@ class Client(object):
                 If default solver is not configured, ``None`` returns the first available
                 solver in ``Client``'s class (QPU/software/base).
 
-            features (dict, optional):
-                Dictionary of features this solver has to have. For a list of
-                feature names and values, see: :meth:`Client.solvers`. Specifying solver
-                name overrides features. To include (possibly partial) name match requirement,
-                use the ``name`` feature inside ``features`` dictionary.
-
             refresh (bool):
                 Return solver from cache (if cached with ``get_solvers()``),
                 unless set to ``True``.
@@ -755,24 +724,16 @@ class Client(object):
             >>> client.close() # doctest: +SKIP
 
         """
-        _LOGGER.debug("Looking for solver with name=%r or features=%r", name, features)
+        _LOGGER.debug("Looking for solver: %s", name)
         if name is None:
-            if features is not None:
-                # get the first solver that satisfies all features
+            if self.default_solver:
+                name = self.default_solver
+            else:
+                # get the first appropriate solver
                 try:
-                    return self.solvers(**features)[0]
+                    return self.solvers()[0]
                 except IndexError:
-                    raise SolverError("No solvers with the required features available")
-
-            else:
-                if self.default_solver:
-                    name = self.default_solver
-                else:
-                    # get the first available solver
-                    try:
-                        return self.solvers(online=True)[0]
-                    except IndexError:
-                        raise SolverError("No solvers available this client can handle")
+                    raise SolverError("No solvers available this client can handle")
 
         with self._solvers_lock:
             if refresh or name not in self._solvers:
@@ -836,7 +797,6 @@ class Client(object):
                 try:
                     try:
                         response = self.session.post(posixpath.join(self.endpoint, 'problems/'), body)
-                        localtime_of_response = epochnow()
                     except requests.exceptions.Timeout:
                         raise RequestTimeout
 
@@ -858,7 +818,6 @@ class Client(object):
 
                 # Pass on the information
                 for submission, res in zip(ready_problems, message):
-                    submission.future._set_clock_diff(response, localtime_of_response)
                     self._handle_problem_status(res, submission.future)
                     self._submission_queue.task_done()
 
@@ -881,20 +840,9 @@ class Client(object):
             This method is always run inside of a daemon thread.
         """
         try:
+            status = message['status']
+            _LOGGER.debug("Handling response for %s with status %s", message['id'], status)
             _LOGGER.trace("Handling response: %r", message)
-            _LOGGER.debug("Handling response for %s with status %s", message.get('id'), message.get('status'))
-
-            # Handle errors in batch mode
-            if 'error_code' in message and 'error_msg' in message:
-                raise SolverFailureError(message['error_msg'])
-
-            if 'status' not in message:
-                raise InvalidAPIResponseError("'status' missing in problem description response")
-            if 'id' not in message:
-                raise InvalidAPIResponseError("'id' missing in problem description response")
-
-            future.id = message['id']
-            future.remote_status = status = message['status']
 
             # The future may not have the ID set yet
             with future._single_cancel_lock:
@@ -908,6 +856,10 @@ class Client(object):
                     # If a cancel request could meaningfully be sent it has been now
                     future._cancel_sent = True
 
+            # Set the id field in the future
+            future.id = message['id']
+            future.remote_status = status
+
             if not future.time_received and message.get('submitted_on'):
                 future.time_received = parse_datetime(message['submitted_on'])
 
@@ -938,14 +890,10 @@ class Client(object):
                 self._poll(future)
             elif status == self.STATUS_CANCELLED:
                 # If canceled return error
-                raise CanceledFutureError()
+                future._set_error(CanceledFutureError())
             else:
                 # Return an error to the future object
-                errmsg = message.get('error_message', 'An unknown error has occurred.')
-                if 'solver is offline' in errmsg.lower():
-                    raise SolverOfflineError(errmsg)
-                else:
-                    raise SolverFailureError(errmsg)
+                future._set_error(SolverFailureError(message.get('error_message', 'An unknown error has occurred.')))
 
         except Exception as error:
             # If there were any unhandled errors we need to release the
@@ -1005,16 +953,6 @@ class Client(object):
         except Exception as err:
             _LOGGER.exception(err)
 
-    def _is_clock_diff_acceptable(self, future):
-        if not future or future.clock_diff is None:
-            return False
-
-        _LOGGER.debug("Detected (server,client) clock offset: approx. %.2f sec. "
-                      "Acceptable offset is: %.2f sec",
-                      future.clock_diff, self._CLOCK_DIFF_MAX)
-
-        return future.clock_diff <= self._CLOCK_DIFF_MAX
-
     def _poll(self, future):
         """Enqueue a problem to poll the server for status."""
 
@@ -1023,14 +961,10 @@ class Client(object):
             future._poll_backoff = self._POLL_BACKOFF_MIN
 
             # if we have ETA of results, schedule the first poll for then
-            if future.eta_min and self._is_clock_diff_acceptable(future):
+            if future.eta_min:
                 at = datetime_to_timestamp(future.eta_min)
-                _LOGGER.debug("Response ETA indicated and local clock reliable. "
-                              "Scheduling first polling at +%.2f sec", at - epochnow())
             else:
                 at = time.time() + future._poll_backoff
-                _LOGGER.debug("Response ETA not indicated, or local clock unreliable. "
-                              "Scheduling first polling at +%.2f sec", at - epochnow())
 
         else:
             # update exponential poll back-off, clipped to a range
@@ -1043,7 +977,7 @@ class Client(object):
 
         now = utcnow()
         future_age = (now - future.time_created).total_seconds()
-        _LOGGER.debug("Polling scheduled at %.2f with %.2f sec new back-off for: %s (future's age: %.2f sec)",
+        _LOGGER.debug("Polling scheduled at %.2f with %.2f sec new back-off for: %s (age: %.2f sec)",
                       at, future._poll_backoff, future.id, future_age)
 
         # don't enqueue for next poll if polling_timeout is exceeded by then
diff --git a/dwave/cloud/computation.py b/dwave/cloud/computation.py
index 3ecd4f7..e36428f 100644
--- a/dwave/cloud/computation.py
+++ b/dwave/cloud/computation.py
@@ -21,10 +21,8 @@ import six
 import functools
 from concurrent.futures import TimeoutError
 
-from dateutil.parser import parse
-
 from dwave.cloud.coders import decode_qp, decode_qp_numpy
-from dwave.cloud.utils import utcnow, datetime_to_timestamp
+from dwave.cloud.utils import utcnow
 
 # Use numpy if available for fast decoding
 try:
@@ -114,9 +112,6 @@ class Future(object):
         # Track how long it took us to parse the data
         self.parse_time = None
 
-        # approx. server-client clocks difference in seconds
-        self.clock_diff = None
-
         # Data from the server before it is parsed
         self._message = None
 
@@ -181,18 +176,6 @@ class Future(object):
         """Remove a completion event from this future."""
         self._other_events.remove(event)
 
-    def _set_clock_diff(self, server_response, localtime_of_response):
-        """Calculate and set the `.clock_diff`, based on headers from a server
-        response, and the local time of response received.
-
-        Based on `clock_diff`, `eta_min`/`eta_max` may or may not make sense.
-        """
-        try:
-            server_time = datetime_to_timestamp(parse(server_response.headers['date']))
-        except:
-            server_time = 0
-        self.clock_diff = abs(server_time - localtime_of_response)
-
     @staticmethod
     def wait_multiple(futures, min_done=None, timeout=None):
         """Wait for multiple :class:`Future` objects to complete.
diff --git a/dwave/cloud/exceptions.py b/dwave/cloud/exceptions.py
index 7a62dee..0972ea1 100644
--- a/dwave/cloud/exceptions.py
+++ b/dwave/cloud/exceptions.py
@@ -14,9 +14,6 @@ class SolverError(Exception):
 class SolverFailureError(SolverError):
     """An exception raised when there is a remote failure calling a solver."""
 
-class SolverOfflineError(SolverError):
-    """Action attempted on an offline solver."""
-
 class SolverAuthenticationError(SolverError):
     """An exception raised when there is an authentication error."""
 
diff --git a/dwave/cloud/package_info.py b/dwave/cloud/package_info.py
index f1f39c9..4f2ddfe 100644
--- a/dwave/cloud/package_info.py
+++ b/dwave/cloud/package_info.py
@@ -1,6 +1,6 @@
 __packagename__ = 'dwave-cloud-client'
 __title__ = 'D-Wave Cloud Client'
-__version__ = '0.4.14'
+__version__ = '0.4.7'
 __author__ = 'D-Wave Systems Inc.'
 __description__ = 'A minimal client for interacting with D-Wave cloud resources.'
 __url__ = 'https://github.com/dwavesystems/dwave-cloud-client'
diff --git a/dwave/cloud/solver.py b/dwave/cloud/solver.py
index cca4318..9a54bf0 100644
--- a/dwave/cloud/solver.py
+++ b/dwave/cloud/solver.py
@@ -17,8 +17,6 @@ from __future__ import division, absolute_import
 import json
 import logging
 
-from collections import Mapping
-
 from dwave.cloud.exceptions import *
 from dwave.cloud.coders import encode_bqm_as_qp
 from dwave.cloud.utils import uniform_iterator, uniform_get
@@ -145,11 +143,6 @@ class Solver(object):
         "Is this a virtual full-yield chip?"
         return self.properties.get('vfyc', False) == True
 
-    @property
-    def is_online(self):
-        "Is this solver online (or offline)?"
-        return self.data.get('status', 'online').lower() == 'online'
-
     @property
     def num_qubits(self):
         "The number of active (encoding) qubits."
@@ -261,14 +254,11 @@ class Solver(object):
             if key not in self.parameters and not key.startswith('x_'):
                 raise KeyError("{} is not a parameter of this solver.".format(key))
 
-        # transform some of the parameters in-place
-        self._format_params(type_, combined_params)
-
         body = json.dumps({
             'solver': self.id,
             'data': encode_bqm_as_qp(self, linear, quadratic),
             'type': type_,
-            'params': combined_params
+            'params': params
         })
         _LOGGER.trace("Encoded sample request: %s", body)
 
@@ -279,27 +269,6 @@ class Solver(object):
         self.client._submit(body, future)
         return future
 
-    def _format_params(self, type_, params):
-        """Reformat some of the parameters for sapi."""
-        if 'initial_state' in params:
-            # NB: at this moment the error raised when initial_state does not match lin/quad (in
-            # active qubits) is not very informative, but there is also no clean way to check here
-            # that they match because lin can be either a list or a dict. In the future it would be
-            # good to check.
-            initial_state = params['initial_state']
-            if isinstance(initial_state, Mapping):
-                if type_ == 'ising' and any(v == 0 for v in initial_state.values()):
-                    # initial_state is in qubo format, coerce to Ising
-                    initial_state = [2*initial_state.get(v, 2)-1 for v in range(self.properties['num_qubits'])]
-                elif type_ == 'qubo' and any(v == -1 for v in initial_state.values()):
-                    # initial_state is in ising format, coerce to QUBO
-                    initial_state = [(initial_state.get(v, 5)+1)//2 for v in range(self.properties['num_qubits'])]
-                else:
-                    initial_state = [initial_state.get(v, 3) for v in range(self.properties['num_qubits'])]
-
-                params['initial_state'] = initial_state
-            # else: support old format
-
     def check_problem(self, linear, quadratic):
         """Test if an Ising model matches the graph provided by the solver.
 
diff --git a/dwave/cloud/utils.py b/dwave/cloud/utils.py
index eacbc87..3e44334 100644
--- a/dwave/cloud/utils.py
+++ b/dwave/cloud/utils.py
@@ -5,7 +5,6 @@ from dateutil.tz import UTC
 from functools import wraps
 import itertools
 import random
-import time
 
 import six
 import click
@@ -19,8 +18,7 @@ except ImportError:  # pragma: no cover
     _numpy = False
 
 __all__ = ['evaluate_ising', 'uniform_iterator', 'uniform_get',
-           'default_text_input', 'click_info_switch', 'datetime_to_timestamp',
-           'datetime_to_timestamp', 'utcnow', 'epochnow']
+           'default_text_input', 'click_info_switch', 'datetime_to_timestamp']
 
 
 def evaluate_ising(linear, quad, state):
@@ -70,16 +68,12 @@ def active_qubits(linear, quadratic):
     return active
 
 
-def generate_random_ising_problem(solver, h_range=None, j_range=None):
+def generate_valid_random_problem(solver):
     """Generates an Ising problem formulation valid for a particular solver,
-    using all qubits and all couplings and linear/quadratic biases sampled
-    uniformly from `h_range`/`j_range`.
-    """
+    using all qubits and all couplings."""
 
-    if h_range is None:
-        h_range = solver.properties.get('h_range', [-1, 1])
-    if j_range is None:
-        j_range = solver.properties.get('j_range', [-1, 1])
+    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}
@@ -183,16 +177,6 @@ def utcnow():
     return datetime.utcnow().replace(tzinfo=UTC)
 
 
-def epochnow():
-    """Returns now as UNIX timestamp.
-
-    Invariant:
-        epochnow() ~= datetime_to_timestamp(utcnow())
-
-    """
-    return time.time()
-
-
 def strtrunc(s, maxlen=60):
     s = str(s)
     return s[:(maxlen-3)]+'...' if len(s) > maxlen else s
@@ -224,12 +208,3 @@ class TimeoutingHTTPAdapter(requests.adapters.HTTPAdapter):
         # can't use setdefault because caller always sets timeout kwarg
         kwargs['timeout'] = self.timeout
         return super(TimeoutingHTTPAdapter, self).send(*args, **kwargs)
-
-
-class CLIError(Exception):
-    """CLI command error that includes the error code in addition to the
-    standard error message."""
-
-    def __init__(self, message, code):
-        super(CLIError, self).__init__(message)
-        self.code = code


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 12a796c..86de374 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,7 +1,6 @@
 import unittest
 import tempfile
 import os
-import json
 
 from click.testing import CliRunner
 
@@ -199,37 +198,6 @@ class TestCli(unittest.TestCase):
                                      '--profile', test_config_profile])
         self.assertEqual(result.exit_code, 0)
 
-    @unittest.skipUnless(config, "No live server configuration available.")
-    def test_ping_json_live(self):
-        runner = CliRunner()
-        result = runner.invoke(cli, ['ping',
-                                     '--config-file', test_config_path,
-                                     '--profile', test_config_profile,
-                                     '--json'])
-
-        res = json.loads(result.output)
-        self.assertIn('timestamp', res)
-        self.assertIn('datetime', res)
-        self.assertIn('solver_id', res)
-        self.assertIn('code', res)
-        self.assertEqual(result.exit_code, 0)
-
-    @unittest.skipUnless(config, "No live server configuration available.")
-    def test_ping_json_timeout_error_live(self):
-        runner = CliRunner()
-        result = runner.invoke(cli, ['ping',
-                                     '--config-file', test_config_path,
-                                     '--profile', test_config_profile,
-                                     '--polling-timeout', '0.00001',
-                                     '--json'])
-
-        res = json.loads(result.output)
-        self.assertIn('timestamp', res)
-        self.assertIn('datetime', res)
-        self.assertIn('code', res)
-        self.assertIn('error', res)
-        self.assertEqual(result.exit_code, 9)
-
     def test_sample(self):
         config_file = 'dwave.conf'
         profile = 'profile'
diff --git a/tests/test_client.py b/tests/test_client.py
index 5e24e5f..2506dff 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -186,8 +186,7 @@ class FeatureBasedSolverSelection(unittest.TestCase):
                 "parameters": {"num_reads": "Number of samples to return."}
             },
             "id": "solver1",
-            "description": "A test solver 1",
-            "status": "online"
+            "description": "A test solver 1"
         })
         self.solver2 = Solver(client=None, data={
             "properties": {
@@ -218,7 +217,7 @@ class FeatureBasedSolverSelection(unittest.TestCase):
         self.client.close()
 
     def assertSolvers(self, container, members):
-        self.assertEqual(set(container), set(members))
+        return set(container) == set(members)
 
     def test_default(self):
         self.assertSolvers(self.client.solvers(), self.solvers)
@@ -242,16 +241,9 @@ class FeatureBasedSolverSelection(unittest.TestCase):
         self.assertSolvers(self.client.solvers(num_qubits=[2, None]), self.solvers)
         self.assertSolvers(self.client.solvers(num_qubits=[None, 6]), self.solvers)
 
-        self.assertSolvers(self.client.solvers(num_qubits=(3, 5)), self.solvers)
-        self.assertSolvers(self.client.solvers(num_qubits=(2, None)), self.solvers)
-        self.assertSolvers(self.client.solvers(num_qubits=(None, 6)), self.solvers)
-
         self.assertSolvers(self.client.solvers(num_qubits=[2, 4]), [self.solver1])
         self.assertSolvers(self.client.solvers(num_qubits=[4, None]), [self.solver2])
 
-        self.assertSolvers(self.client.solvers(num_qubits=(2, 4)), [self.solver1])
-        self.assertSolvers(self.client.solvers(num_qubits=(4, None)), [self.solver2])
-
     def test_range_boolean_combo(self):
         self.assertSolvers(self.client.solvers(num_qubits=3, vfyc=True), [])
         self.assertSolvers(self.client.solvers(num_qubits=[3, None], vfyc=True), [self.solver2])
@@ -259,20 +251,6 @@ class FeatureBasedSolverSelection(unittest.TestCase):
         self.assertSolvers(self.client.solvers(num_qubits=[None, 4], flux_biases=False), [self.solver1])
         self.assertSolvers(self.client.solvers(num_qubits=5, flux_biases=True), [self.solver2])
 
-    def test_online(self):
-        self.assertSolvers(self.client.solvers(online=None), self.solvers)
-        self.assertSolvers(self.client.solvers(online=False), [])
-
-    def test_name(self):
-        self.assertSolvers(self.client.solvers(name='solver1'), [self.solver1])
-        self.assertSolvers(self.client.solvers(name='solver2'), [self.solver2])
-        self.assertSolvers(self.client.solvers(name='solver'), [])
-        self.assertSolvers(self.client.solvers(name='olver1'), [])
-        self.assertSolvers(self.client.solvers(name='.*1'), [self.solver1])
-        self.assertSolvers(self.client.solvers(name='.*[12].*'), self.solvers)
-        self.assertSolvers(self.client.solvers(name='solver[12]'), self.solvers)
-        self.assertSolvers(self.client.solvers(name='^solver(1|2)$'), self.solvers)
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_mock_solver_loading.py b/tests/test_mock_solver_loading.py
index 8b24a1f..57ca75b 100644
--- a/tests/test_mock_solver_loading.py
+++ b/tests/test_mock_solver_loading.py
@@ -35,8 +35,7 @@ def solver_data(id_, incomplete=False):
             "parameters": {"num_reads": "Number of samples to return."}
         },
         "id": id_,
-        "description": "A test solver",
-        "status": "ONLINE"
+        "description": "A test solver"
     }
 
     if incomplete:
@@ -203,14 +202,6 @@ class MockSolverLoading(unittest.TestCase):
         data['properties']['parameters']['flux_biases'] = '...'
         self.assertTrue(Solver(None, data).has_flux_biases)
 
-        # test `.is_online` property
-        self.assertTrue(solver_object('dw2000').is_online)
-        data = json.loads(solver_data('test'))
-        data['status'] = 'offline'
-        self.assertFalse(Solver(None, data).is_online)
-        del data['status']
-        self.assertTrue(Solver(None, data).is_online)
-
 
 class GetEvent(Exception):
     """Throws exception when mocked client submits an HTTP GET request."""
diff --git a/tests/test_mock_submission.py b/tests/test_mock_submission.py
index 7eb3f97..7c24d73 100644
--- a/tests/test_mock_submission.py
+++ b/tests/test_mock_submission.py
@@ -10,9 +10,8 @@ import threading
 from datetime import datetime, timedelta
 from dateutil.tz import UTC
 from dateutil.parser import parse as parse_datetime
-from requests.structures import CaseInsensitiveDict
 
-from dwave.cloud.utils import evaluate_ising, generate_random_ising_problem
+from dwave.cloud.utils import evaluate_ising
 from dwave.cloud.qpu import Client, Solver
 from dwave.cloud.exceptions import SolverFailureError, CanceledFutureError
 from dwave.cloud.testing import mock
@@ -84,14 +83,6 @@ def error_reply(id_, solver_name, error):
     })
 
 
-def immediate_error_reply(code, msg):
-    """A reply saying an error has occurred (before scheduling for execution)."""
-    return json.dumps({
-        "error_code": code,
-        "error_msg": msg
-    })
-
-
 def cancel_reply(id_, solver_name):
     """A reply saying a problem was canceled."""
     return json.dumps({
@@ -104,7 +95,7 @@ def cancel_reply(id_, solver_name):
     })
 
 
-def datetime_in_future(seconds=0):
+def timestamp_in_future(seconds=0):
     now = datetime.utcnow().replace(tzinfo=UTC)
     return now + timedelta(seconds=seconds)
 
@@ -113,7 +104,7 @@ def continue_reply(id_, solver_name, now=None, eta_min=None, eta_max=None):
     """A reply saying a problem is still in the queue."""
 
     if not now:
-        now = datetime_in_future(0)
+        now = datetime.utcnow().replace(tzinfo=UTC)
 
     resp = {
         "status": "PENDING",
@@ -134,17 +125,12 @@ def continue_reply(id_, solver_name, now=None, eta_min=None, eta_max=None):
     return json.dumps(resp)
 
 
-def choose_reply(path, replies, date=None):
+def choose_reply(path, replies):
     """Choose the right response based on the path and make a mock response."""
-
-    if date is None:
-        date = datetime_in_future(0)
-
     if path in replies:
-        response = mock.Mock(['json', 'raise_for_status', 'headers'])
+        response = mock.Mock(['json', 'raise_for_status'])
         response.status_code = 200
         response.json.side_effect = lambda: json.loads(replies[path])
-        response.headers = CaseInsensitiveDict({'Date': date.isoformat()})
         return response
     else:
         raise NotImplementedError(path)
@@ -212,20 +198,6 @@ class MockSubmission(_QueryTest):
             with self.assertRaises(SolverFailureError):
                 results.samples
 
-    def test_submit_immediate_error_reply(self):
-        """Handle an (obvious) error on problem submission."""
-        with Client('endpoint', 'token') as client:
-            client.session = mock.Mock()
-            client.session.post = lambda a, _: choose_reply(a, {
-                'endpoint/problems/': '[%s]' % immediate_error_reply(400, "Missing parameter 'num_reads' in problem JSON")})
-            solver = Solver(client, solver_data('abc123'))
-
-            linear, quad = generate_random_ising_problem(solver)
-            results = solver.sample_ising(linear, quad)
-
-            with self.assertRaises(SolverFailureError):
-                results.samples
-
     def test_submit_cancel_reply(self):
         """Handle a response for a canceled job."""
         with Client('endpoint', 'token') as client:
@@ -244,16 +216,15 @@ class MockSubmission(_QueryTest):
     def test_submit_continue_then_ok_reply(self):
         """Handle polling for a complete problem."""
         with Client('endpoint', 'token') as client:
-            now = datetime_in_future(0)
-            eta_min, eta_max = datetime_in_future(10), datetime_in_future(30)
+            eta_min, eta_max = timestamp_in_future(10), timestamp_in_future(30)
             client.session = mock.Mock()
             client.session.post = lambda a, _: choose_reply(a, {
-                'endpoint/problems/': '[%s]' % continue_reply('123', 'abc123', eta_min=eta_min, eta_max=eta_max, now=now)
-            }, date=now)
+                'endpoint/problems/': '[%s]' % continue_reply('123', 'abc123', eta_min=eta_min, eta_max=eta_max)
+            })
             client.session.get = lambda a: choose_reply(a, {
                 'endpoint/problems/?id=123': '[%s]' % complete_no_answer_reply('123', 'abc123'),
                 'endpoint/problems/123/': complete_reply('123', 'abc123')
-            }, date=now)
+            })
             solver = Solver(client, solver_data('abc123'))
 
             # Build a problem
@@ -386,16 +357,15 @@ class MockSubmission(_QueryTest):
         "eta_min/earliest_estimated_completion should be respected if present in response"
 
         with Client('endpoint', 'token') as client:
-            now = datetime_in_future(0)
-            eta_min, eta_max = datetime_in_future(10), datetime_in_future(30)
+            eta_min, eta_max = timestamp_in_future(10), timestamp_in_future(30)
             client.session = mock.Mock()
             client.session.post = lambda path, _: choose_reply(path, {
-                'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123', eta_min=eta_min, eta_max=eta_max, now=now)
-            }, date=now)
+                'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123', eta_min=eta_min, eta_max=eta_max)
+            })
             client.session.get = lambda path: choose_reply(path, {
                 'endpoint/problems/?id=1': '[%s]' % complete_no_answer_reply('1', 'abc123'),
                 'endpoint/problems/1/': complete_reply('1', 'abc123')
-            }, date=now)
+            })
 
             solver = Solver(client, solver_data('abc123'))
 
@@ -412,42 +382,14 @@ class MockSubmission(_QueryTest):
         "First poll happens with minimal delay if eta_min missing"
 
         with Client('endpoint', 'token') as client:
-            now = datetime_in_future(0)
-            client.session = mock.Mock()
-            client.session.post = lambda path, _: choose_reply(path, {
-                'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123')
-            }, date=now)
-            client.session.get = lambda path: choose_reply(path, {
-                'endpoint/problems/?id=1': '[%s]' % complete_no_answer_reply('1', 'abc123'),
-                'endpoint/problems/1/': complete_reply('1', 'abc123')
-            }, date=now)
-
-            solver = Solver(client, solver_data('abc123'))
-
-            def assert_no_delay(s):
-                s and self.assertTrue(
-                    abs(s - client._POLL_BACKOFF_MIN) < client._POLL_BACKOFF_MIN / 10.0)
-
-            with mock.patch('time.sleep', assert_no_delay):
-                future = solver.sample_qubo({})
-                future.result()
-
-    @mock.patch.object(Client, "_POLL_THREAD_COUNT", 1)
-    @mock.patch.object(Client, "_SUBMISSION_THREAD_COUNT", 1)
-    def test_immediate_polling_with_local_clock_unsynced(self):
-        """First poll happens with minimal delay if local clock is way off from
-        the remote/server clock."""
-
-        with Client('endpoint', 'token') as client:
-            badnow = datetime_in_future(100)
             client.session = mock.Mock()
             client.session.post = lambda path, _: choose_reply(path, {
                 'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123')
-            }, date=badnow)
+            })
             client.session.get = lambda path: choose_reply(path, {
                 'endpoint/problems/?id=1': '[%s]' % complete_no_answer_reply('1', 'abc123'),
                 'endpoint/problems/1/': complete_reply('1', 'abc123')
-            }, date=badnow)
+            })
 
             solver = Solver(client, solver_data('abc123'))
 
diff --git a/tests/test_solver.py b/tests/test_solver.py
index 18c1fa1..a9174d2 100644
--- a/tests/test_solver.py
+++ b/tests/test_solver.py
@@ -12,7 +12,7 @@ from datetime import datetime
 
 import numpy
 
-from dwave.cloud.utils import evaluate_ising, generate_random_ising_problem
+from dwave.cloud.utils import evaluate_ising
 from dwave.cloud.qpu import Client
 from dwave.cloud.exceptions import CanceledFutureError, SolverFailureError
 import dwave.cloud.computation
@@ -40,7 +40,7 @@ class PropertyLoading(unittest.TestCase):
         """Ensure that the parameters are populated."""
         with Client(**config) as client:
             solver = client.get_solver()
-            self.assertNotIn('not_a_parameter', solver.parameters)
+            assert 'not_a_parameter' not in solver.parameters
             with self.assertRaises(KeyError):
                 solver.sample_ising({}, {}, not_a_parameter=True)
 
@@ -48,7 +48,7 @@ class PropertyLoading(unittest.TestCase):
         """Ensure that the experimental parameters are populated."""
         with Client(**config) as client:
             solver = client.get_solver()
-            self.assertNotIn('x_test', solver.parameters)
+            assert 'x_test' not in solver.parameters
             with self.assertRaises(SolverFailureError):
                 self.assertTrue(solver.sample_ising([0], {}, x_test=123).result())
 
@@ -64,11 +64,11 @@ class _QueryTest(unittest.TestCase):
         results = solver.sample_ising(linear, quad, num_reads=100, **param)
 
         # Did we get the right number of samples?
-        self.assertEqual(100, sum(results.occurrences))
+        self.assertTrue(100 == sum(results.occurrences))
 
         # Make sure the number of occurrences and energies are all correct
         for energy, state in zip(results.energies, results.samples):
-            self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
+            self.assertTrue(energy == evaluate_ising(linear, quad, state))
 
         return results
 
@@ -100,13 +100,16 @@ class Submission(_QueryTest):
 
     def test_submit_extra_qubit(self):
         """Submit a defective problem with an unsupported variable."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            # Build a linear problem and add a variable that shouldn't exist
-            linear, quad = generate_random_ising_problem(solver)
-            linear[max(solver.nodes) + 1] = 1
+            # Build a linear problem
+            linear = [0] * (max(solver.nodes) + 1)
+            quad = {}
+
+            # Add a variable that shouldn't exist
+            linear.append(1)
 
             with self.assertRaises(ValueError):
                 results = solver.sample_ising(linear, quad)
@@ -114,122 +117,109 @@ class Submission(_QueryTest):
 
     def test_submit_linear_problem(self):
         """Submit a problem with all the linear terms populated."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
-            linear, quad = generate_random_ising_problem(solver)
-            self._submit_and_check(solver, linear, {})
+
+            # Build a linear problem
+            linear = [1] * (max(solver.nodes) + 1)
+            quad = {}
+
+            # Solve the problem
+            self._submit_and_check(solver, linear, quad)
 
     def test_submit_full_problem(self):
         """Submit a problem with all supported coefficients set."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
-            linear, quad = generate_random_ising_problem(solver)
-            self._submit_and_check(solver, linear, quad)
 
-    def test_submit_list_problem(self):
-        """Submit a problem using a list for the linear terms."""
+            # Build a linear problem
+            linear = [0] * (max(solver.nodes) + 1)
+            for index in solver.nodes:
+                linear[index] = random.choice([-1, 1])
 
+            # Build a
+            quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
+
+            # Solve the problem
+            self._submit_and_check(solver, linear, quad)
+
+    def test_submit_dict_problem(self):
+        """Submit a problem using a dict for the linear terms."""
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            linear = [1 if qubit in solver.nodes else 0 for qubit in range(0, max(solver.nodes)+1)]
+            # Build a problem
+            linear = {index: random.choice([-1, 1]) for index in solver.nodes}
             quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
 
+            # Solve the problem
             self._submit_and_check(solver, linear, quad)
 
     def test_submit_partial_problem(self):
         """Submit a problem with only some of the terms set."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            # Build a linear problem, then remove half the qubits
-            linear, quad = generate_random_ising_problem(solver)
+            # Build a linear problem
+            linear = [0] * (max(solver.nodes) + 1)
+            for index in solver.nodes:
+                linear[index] = random.choice([-1, 1])
+
+            # Build a
+            quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
+
+            # Remove half the qubits
             nodes = list(solver.nodes)
             for index in nodes[0:len(nodes)//2]:
-                del linear[index]
+                linear[index] = 0
                 quad = {key: value for key, value in quad.items() if index not in key}
 
+            # Solve the problem
             self._submit_and_check(solver, linear, quad)
 
-    def test_reverse_annealing(self):
-        with Client(**config) as client:
-            solver = client.get_solver()
-
-            # skip if we don't have access to the initial_state parameter for reverse annealing
-            if 'initial_state' not in solver.parameters:
-                raise unittest.SkipTest
-
-            anneal_schedule = [(0, 1), (55.00000000000001, 0.45), (155.0, 0.45), (210.0, 1)]
-
-            # make some subset of the qubits active
-            active = [v for v in solver.properties['qubits'] if v % 2]
-
-            # make an initial state, ising problem
-            initial_state = {v: 2*bool(v % 3)-1 for v in active}
-
-            h = {v: 0.0 for v in active}
-            J = {(u, v): -1 for (u, v) in solver.properties['couplers'] if v in h and u in h}
-
-            # doesn't catch fire
-            solver.sample_ising(h, J, anneal_schedule=anneal_schedule, initial_state=initial_state).samples
-
-    def test_reverse_annealing_off_vartype(self):
-        with Client(**config) as client:
-            solver = client.get_solver()
-
-            # skip if we don't have access to the initial_state parameter for reverse annealing
-            if 'initial_state' not in solver.parameters:
-                raise unittest.SkipTest
-
-            anneal_schedule = [(0, 1), (55.00000000000001, 0.45), (155.0, 0.45), (210.0, 1)]
-
-            # make some subset of the qubits active
-            active = [v for v in solver.properties['qubits'] if v % 2]
-
-            # ising state, qubo problem
-            initial_state_ising = {v: 2*bool(v % 3)-1 for v in active}
-            Q = {(v, v): 1 for v in active}
-            fq = solver.sample_qubo(Q, anneal_schedule=anneal_schedule, initial_state=initial_state_ising)
-
-            # qubo state, ising problem
-            initial_state_qubo = {v: bool(v % 3) for v in active}
-            h = {v: 1 for v in active}
-            J = {}
-            fi = solver.sample_ising(h, J, anneal_schedule=anneal_schedule, initial_state=initial_state_qubo)
-
-            fq.samples
-            fi.samples
-
     def test_submit_batch(self):
         """Submit batch of problems."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
             result_list = []
             for _ in range(100):
-                linear, quad = generate_random_ising_problem(solver)
+
+                # Build a linear problem
+                linear = [0] * (max(solver.nodes) + 1)
+                for index in solver.nodes:
+                    linear[index] = random.choice([-1, 1])
+
+                # Build a
+                quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
+
                 results = solver.sample_ising(linear, quad, num_reads=10)
                 result_list.append([results, linear, quad])
 
             for results, linear, quad in result_list:
                 # Did we get the right number of samples?
-                self.assertEqual(10, sum(results.occurrences))
+                self.assertTrue(10 == sum(results.occurrences))
 
                 # Make sure the number of occurrences and energies are all correct
                 for energy, state in zip(results.energies, results.samples):
-                    self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
+                    self.assertTrue(energy == evaluate_ising(linear, quad, state))
 
     def test_cancel_batch(self):
         """Submit batch of problems, then cancel them."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            linear, quad = generate_random_ising_problem(solver)
+            # Build a linear problem
+            linear = [0] * (max(solver.nodes) + 1)
+            for index in solver.nodes:
+                linear[index] = random.choice([-1, 1])
+            quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
 
             max_num_reads = max(solver.properties.get('num_reads_range', [1, 100]))
 
@@ -244,22 +234,27 @@ class Submission(_QueryTest):
                 # Responses must be canceled or correct
                 try:
                     # Did we get the right number of samples?
-                    self.assertEqual(max_num_reads, sum(results.occurrences))
+                    self.assertTrue(max_num_reads == sum(results.occurrences))
 
                     # Make sure the number of occurrences and energies are all correct
                     for energy, state in zip(results.energies, results.samples):
-                        self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
-
+                        self.assertTrue(energy == evaluate_ising(linear, quad, state))
                 except CanceledFutureError:
                     pass
 
     def test_wait_many(self):
         """Submit a batch of problems then use `wait_multiple` to wait on all of them."""
-
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            linear, quad = generate_random_ising_problem(solver)
+            # Build a linear problem
+            linear = [0] * (max(solver.nodes) + 1)
+            for index in solver.nodes:
+                linear[index] = random.choice([-1, 1])
+
+            # Build a
+            quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
 
             result_list = []
             for _ in range(100):
@@ -273,20 +268,25 @@ class Submission(_QueryTest):
 
             for results, linear, quad in result_list:
                 # Did we get the right number of samples?
-                self.assertEqual(40, sum(results.occurrences))
+                self.assertTrue(40 == sum(results.occurrences))
 
                 # Make sure the number of occurrences and energies are all correct
                 for energy, state in zip(results.energies, results.samples):
-                    self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
+                    self.assertTrue(energy == evaluate_ising(linear, quad, state))
 
     def test_as_completed(self):
         """Submit a batch of problems then use `as_completed` to iterate over
         all of them."""
 
+        # Connect
         with Client(**config) as client:
             solver = client.get_solver()
 
-            linear, quad = generate_random_ising_problem(solver)
+            # Build a problem
+            linear = [0] * (max(solver.nodes) + 1)
+            for index in solver.nodes:
+                linear[index] = random.choice([-1, 1])
+            quad = {key: random.choice([-1, 1]) for key in solver.undirected_edges}
 
             # Sample the solution 100x40 times
             computations = [solver.sample_ising(linear, quad, num_reads=40) for _ in range(100)]
@@ -294,9 +294,9 @@ class Submission(_QueryTest):
             # Go over computations, one by one, as they're done and check they're OK
             for computation in dwave.cloud.computation.Future.as_completed(computations):
                 self.assertTrue(computation.done())
-                self.assertEqual(40, sum(computation.occurrences))
+                self.assertTrue(40 == sum(computation.occurrences))
                 for energy, state in zip(computation.energies, computation.samples):
-                    self.assertAlmostEqual(energy, evaluate_ising(linear, quad, state))
+                    self.assertTrue(energy == evaluate_ising(linear, quad, state))
 
 
 @unittest.skipUnless(config, "No live server configuration available.")
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3654320..61e45fa 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -4,7 +4,7 @@ from datetime import datetime
 
 from dwave.cloud.utils import (
     uniform_iterator, uniform_get, strip_head, strip_tail,
-    active_qubits, generate_random_ising_problem,
+    active_qubits, generate_valid_random_problem,
     default_text_input, utcnow)
 from dwave.cloud.testing import mock
 
@@ -58,44 +58,18 @@ class TestUtils(unittest.TestCase):
         with mock.patch("six.moves.input", side_effect=[val], create=True):
             self.assertEqual(default_text_input("prompt", val+val), val)
 
-    def test_generate_random_ising_problem(self):
+    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_random_ising_problem(mock_solver)
+        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})
 
-    def test_generate_random_ising_problem_default_solver_ranges(self):
-        class MockSolver(object):
-            nodes = [0, 1, 3]
-            undirected_edges = {(0, 1), (1, 3), (0, 4)}
-            properties = {}
-        mock_solver = MockSolver()
-
-        lin, quad = generate_random_ising_problem(mock_solver)
-
-        for q, v in lin.items():
-            self.assertTrue(v >= -1 and v <= 1)
-        for e, v in quad.items():
-            self.assertTrue(v >= -1 and v <= 1)
-
-    def test_generate_random_ising_problem_with_user_constrained_ranges(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_random_ising_problem(mock_solver, h_range=[0,0], j_range=[1,1])
-
-        self.assertDictEqual(lin, {0: 0.0, 1: 0.0, 3: 0.0})
-        self.assertDictEqual(quad, {(0, 1): 1.0, (1, 3): 1.0, (0, 4): 1.0})
-
     def test_utcnow(self):
         t = utcnow()
         now = datetime.utcnow()