diff --git a/dwave/embedding/transforms.py b/dwave/embedding/transforms.py
index 7506fc1..b72d317 100644
--- a/dwave/embedding/transforms.py
+++ b/dwave/embedding/transforms.py
@@ -120,7 +120,10 @@ def embed_bqm(source_bqm, embedding, target_adjacency, chain_strength=1.0,
         if any(u not in target_adjacency for u in chain):
             raise InvalidNodeError(v, next(u not in target_adjacency for u in chain))
 
-        b = bias / len(chain)
+        try:
+            b = bias / len(chain)
+        except ZeroDivisionError:
+            raise MissingChainError(v)
 
         target_bqm.add_variables_from({u: b for u in chain})
 
diff --git a/dwave/system/samplers/dwave_sampler.py b/dwave/system/samplers/dwave_sampler.py
index 10aee29..d6280b4 100644
--- a/dwave/system/samplers/dwave_sampler.py
+++ b/dwave/system/samplers/dwave_sampler.py
@@ -285,21 +285,13 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
         return nodelist
 
     @_failover
-    def sample_ising(self, h, J, warnings=None, **kwargs):
+    def sample(self, bqm, warnings=None, **kwargs):
         """Sample from the specified Ising model.
 
         Args:
-            h (dict/list):
-                Linear biases of the Ising model. If a dict, should be of the
-                form `{v: bias, ...}` where `v` is a spin-valued variable and
-                `bias` is its associated bias. If a list, it is treated as a
-                list of biases where the indices are the variable labels,
-                except in the case of missing qubits in which case 0 biases are
-                ignored while a non-zero bias set on a missing qubit raises an
-                error.
-
-            J (dict[(int, int): float]):
-                Quadratic biases of the Ising model.
+            bqm (:class:`~dimod.BinaryQuadraticModel`):
+                The binary quadratic model. Must match :attr:`.nodelist` and
+                :attr:`.edgelist`.
 
             warnings (:class:`~dwave.system.warnings.WarningAction`, optional):
                 Defines what warning action to take, if any. See
@@ -335,104 +327,46 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
         for explanations of technical terms in descriptions of Ocean tools.
 
         """
-        nodes = self.solver.nodes  # set rather than .nodelist which is a list
 
-        if isinstance(h, list):
-            # to be consistent with the cloud-client, we ignore the 0 biases
-            # on missing nodes.
-            h = dict((v, b) for v, b in enumerate(h) if b or v in nodes)
-
-        # developer note: in the future we should probably catch exceptions
-        # from the cloud client, but for now this is simpler/cleaner. We use
-        # the solver's nodes/edges because they are a set, so faster lookup
-        edges = self.solver.edges
-        if not (all(v in nodes for v in h) and
-                all((u, v) in edges or (v, u) in edges for u, v in J)):
+        solver = self.solver
+
+        if not (solver.nodes.issuperset(bqm.linear) and
+                solver.edges.issuperset(bqm.quadratic)):
             msg = "Problem graph incompatible with solver."
             raise BinaryQuadraticModelStructureError(msg)
 
-        future = self.solver.sample_ising(h, J, **kwargs)
-
-        # do as much as possible after the future is returned
-
-        variables = set(h).union(*J)
+        future = solver.sample_bqm(bqm, **kwargs)
 
         if warnings is None:
             warnings = self.warnings_default
         warninghandler = WarningHandler(warnings)
-        warninghandler.energy_scale((h, J))
-
-        hook = _result_to_response_hook(variables, dimod.SPIN, warninghandler)
-        return dimod.SampleSet.from_future(future, hook)
-
-    @_failover
-    def sample_qubo(self, Q, warnings=None, **kwargs):
-        """Sample from the specified QUBO.
-
-        Args:
-            Q (dict):
-                Coefficients of a quadratic unconstrained binary optimization (QUBO) model.
-
-            warnings (:class:`~dwave.system.warnings.WarningAction`, optional):
-                Defines what warning action to take, if any. See
-                :mod:`~dwave.system.warnings`. The default behaviour is defined
-                by :attr:`warnings_default`, which itself defaults to
-                :class:`~dwave.system.warnings.IGNORE`
-
-            **kwargs:
-                Optional keyword arguments for the sampling method, specified per solver in
-                :attr:`.DWaveSampler.parameters`. D-Wave System Documentation's
-                `solver guide <https://docs.dwavesys.com/docs/latest/doc_solver_ref.html>`_
-                describes the parameters and properties supported on the D-Wave system.
-
-        Returns:
-            :class:`dimod.SampleSet`: A `dimod` :obj:`~dimod.SampleSet` object.
-            In it this sampler also provides timing information in the `info`
-            field as described in the D-Wave System Documentation's
-            `timing guide <https://docs.dwavesys.com/docs/latest/doc_timing.html>`_.
+        warninghandler.energy_scale(bqm)
 
-        Examples:
-            This example submits a two-variable QUBO mapped directly to qubits
-            0 and 4 on a D-Wave system. Given sufficient reads (here 100), the quantum
-            computer should return the best solutions, :math:`{0, 1}` or
-            :math:`{1, 0}` on qubits 0 and 4, respectively, as its first sample
-            (samples are ordered from lowest energy).
-
-            >>> from dwave.system import DWaveSampler
-            >>> sampler = DWaveSampler()
-            >>> Q = {(0, 0): -1, (4, 4): -1, (0, 4): 2}
-            >>> sampleset = sampler.sample_qubo(Q, num_reads=100)
-            >>> sampleset.first.sample[0] != sampleset.first.sample[4]
-            True
-
-        See `Ocean Glossary <https://docs.ocean.dwavesys.com/en/latest/glossary.html>`_
-        for explanations of technical terms in descriptions of Ocean tools.
+        # need a hook so that we can check the sampleset (lazily) for
+        # warnings
+        def _hook(computation):
+            sampleset = computation.sampleset
 
-        """
+            if warninghandler is not None:
+                warninghandler.too_few_samples(sampleset)
+                if warninghandler.action is WarningAction.SAVE:
+                    sampleset.info['warnings'] = warninghandler.saved
 
-        # developer note: in the future we should probably catch exceptions
-        # from the cloud client, but for now this is simpler/cleaner. We use
-        # the solver's nodes/edges because they are a set, so faster lookup
-        nodes = self.solver.nodes
-        edges = self.solver.edges
-        if not all(u in nodes if u == v else ((u, v) in edges or (v, u) in edges)
-                   for u, v in Q):
-            msg = "Problem graph incompatible with solver."
-            raise BinaryQuadraticModelStructureError(msg)
+            return sampleset
 
-        future = self.solver.sample_qubo(Q, **kwargs)
+        return dimod.SampleSet.from_future(future, _hook)
 
-        # do as much as possible after the future is returned
-
-        variables = set().union(*Q)
-
-        if warnings is None:
-            warnings = self.warnings_default
-        warninghandler = WarningHandler(warnings)
-        warninghandler.energy_scale((Q,))
+    def sample_ising(self, h, *args, **kwargs):
+        # to be consistent with the cloud-client, we ignore the 0 biases
+        # on missing nodes for lists
+        if isinstance(h, list):
+            if len(h) > self.solver.num_qubits:
+                msg = "Problem graph incompatible with solver."
+                raise BinaryQuadraticModelStructureError(msg)
+            nodes = self.solver.nodes
+            h = dict((v, b) for v, b in enumerate(h) if b and v in nodes)
 
-        hook = _result_to_response_hook(variables, dimod.BINARY, warninghandler)
-        return dimod.SampleSet.from_future(future, hook)
+        return super().sample_ising(h, *args, **kwargs)
 
     def validate_anneal_schedule(self, anneal_schedule):
         """Raise an exception if the specified schedule is invalid for the sampler.
@@ -530,34 +464,3 @@ class DWaveSampler(dimod.Sampler, dimod.Structured):
         for (t0, s0), (t1, s1) in zip(anneal_schedule, anneal_schedule[1:]):
             if abs((s0 - s1) / (t0 - t1)) > max_slope:
                 raise ValueError("the maximum slope cannot exceed {}".format(max_slope))
-
-
-def _result_to_response_hook(variables, vartype, warninghandler=None):
-
-    def _hook(computation):
-        result = computation.result()
-
-        # get the samples. The future will return all spins so filter for the ones in variables
-        samples = [[sample[v] for v in variables] for sample in result.get('solutions')]
-
-        # construct the info field (add timing, problem id)
-        info = {}
-        if 'timing' in result:
-            info.update(timing=result['timing'])
-        if hasattr(computation, 'id'):
-            info.update(problem_id=computation.id)
-
-        sampleset = dimod.SampleSet.from_samples((samples, variables), info =info, vartype=vartype,
-                                                 energy=result['energies'],
-                                                 num_occurrences=result.get('num_occurrences', None),
-                                                 sort_labels=True)
-
-        if warninghandler is not None:
-            warninghandler.too_few_samples(sampleset)
-
-            if warninghandler.action is WarningAction.SAVE:
-                sampleset.info['warnings'] = warninghandler.saved
-
-        return sampleset
-
-    return _hook
diff --git a/requirements.txt b/requirements.txt
index 8d7ea4f..85c8a01 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,12 @@
 --extra-index-url https://pypi.dwavesys.com/simple
 
 dimod==0.9.0
-dwave-cloud-client==0.7.0
+dwave-cloud-client==0.7.1
 dwave-networkx==0.8.4
 dwave-drivers==0.4.4
 dwave-tabu==0.2.2
 homebase==1.0.1
-minorminer==0.1.8
+minorminer==0.1.9
 six==1.11.0
 mock==2.0.0
 numpy==1.18.0
diff --git a/setup.py b/setup.py
index 293797e..d7b0c0f 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@ exec(open(os.path.join(".", "dwave", "system", "package_info.py")).read())
 
 
 install_requires = ['dimod>=0.9.0,<0.10.0',
-                    'dwave-cloud-client>=0.6.0,<0.8.0',
+                    'dwave-cloud-client>=0.7.1,<0.8.0',
                     'dwave-networkx>=0.8.4',
                     'networkx>=2.0,<3.0',
                     'homebase>=1.0.0,<2.0.0',

Empty target array yields ZeroDivisionError
**Description**
An embedding with an empty chain, such as `embedding = {0: []}` yields a `ZeroDivisionError: float division by zero` error at https://github.com/dwavesystems/dwave-system/blob/8e7ffe9e9fa209572d508e7515d36aced2929a5f/dwave/embedding/transforms.py#L123

**To Reproduce**
```
import dimod
from dwave.system import DWaveSampler, FixedEmbeddingComposite

embedding = {0: []}
fixed_sampler = FixedEmbeddingComposite(DWaveSampler(), embedding)

bias = 0
linear = {i: bias for i in range(4)}
quadratic = {(i, i + 1): -1 for i in range(4 - 1)}

bqm = dimod.BinaryQuadraticModel(linear, quadratic, 0, dimod.SPIN)

response = fixed_sampler.sample(bqm, auto_scale=False, num_reads=100)
print(type(response))
```

**Expected behavior**
`raise MissingChainError`

**Environment:**
 - OS: macOS 10.14.6
 - Python version: 3.7
 - dwave-systems: 0.9.1

diff --git a/tests/test_dwavesampler.py b/tests/test_dwavesampler.py
index ed57660..5466378 100644
--- a/tests/test_dwavesampler.py
+++ b/tests/test_dwavesampler.py
@@ -69,58 +69,30 @@ class MockSolver():
                                  'chains': ""},
                   'chip_id': 'MockSolver'}
 
-    def sample_ising(self, h, J, **kwargs):
-        for key in kwargs:
-            if key not in self.properties['parameters']:
-                raise ValueError
-        result = {'num_variables': 2048,
-                  'format': 'qp',
-                  'num_occurrences': [1],
-                  'active_variables': list(range(2048)),
-                  'solutions': [[random.choice((-1, +1)) for __ in range(2048)]],
-                  'timing': {'total_real_time': 11511, 'anneal_time_per_run': 20,
-                             'post_processing_overhead_time': 2042, 'qpu_sampling_time': 164,
-                             'readout_time_per_run': 123,
-                             'qpu_delay_time_per_sample': 21,
-                             'qpu_anneal_time_per_sample': 20,
-                             'total_post_processing_time': 2042,
-                             'qpu_programming_time': 8740,
-                             'run_time_chip': 164,
-                             'qpu_access_time': 11511,
-                             'qpu_readout_time_per_sample': 123},
-                  'occurrences': [1]}
-        result['samples'] = result['solutions']
-        result['energies'] = [dimod.ising_energy(sample, h, J) for sample in result['samples']]
-        future = Future()
-        future.id = uuid4()
-        future.set_result(result)
-        return future
+    def sample_bqm(self, bqm, num_reads=1, **kwargs):
+        info = dict(timing={'total_real_time': 11511, 'anneal_time_per_run': 20,
+                            'post_processing_overhead_time': 2042,
+                            'qpu_sampling_time': 164,
+                            'readout_time_per_run': 123,
+                            'qpu_delay_time_per_sample': 21,
+                            'qpu_anneal_time_per_sample': 20,
+                            'total_post_processing_time': 2042,
+                            'qpu_programming_time': 8740,
+                            'run_time_chip': 164,
+                            'qpu_access_time': 11511,
+                            'qpu_readout_time_per_sample': 123},
+                    problem_id=5)
+
+        samples = np.random.choice(tuple(bqm.vartype.value),
+                                   size=(num_reads, len(bqm)))
 
-    def sample_qubo(self, Q, **kwargs):
-        for key in kwargs:
-            if key not in self.properties['parameters']:
-                raise ValueError
-        result = {'num_variables': 2048,
-                  'format': 'qp',
-                  'num_occurrences': [1],
-                  'active_variables': list(range(2048)),
-                  'solutions': [[random.choice((0, 1)) for __ in range(2048)]],
-                  'timing': {'total_real_time': 11511, 'anneal_time_per_run': 20,
-                             'post_processing_overhead_time': 2042, 'qpu_sampling_time': 164,
-                             'readout_time_per_run': 123,
-                             'qpu_delay_time_per_sample': 21,
-                             'qpu_anneal_time_per_sample': 20,
-                             'total_post_processing_time': 2042,
-                             'qpu_programming_time': 8740,
-                             'run_time_chip': 164,
-                             'qpu_access_time': 11511,
-                             'qpu_readout_time_per_sample': 123},
-                  'occurrences': [1]}
-        result['samples'] = result['solutions']
-        result['energies'] = [dimod.qubo_energy(sample, Q) for sample in result['samples']]
         future = Future()
+        ss = dimod.SampleSet.from_samples_bqm((samples, bqm.variables), bqm,
+                                              info=info)
+        future.sampleset = ss
         future.id = uuid4()
-        future.set_result(result)
+        future.set_result = {}  # not actually needed
+
         return future
 
 
@@ -206,6 +178,7 @@ class TestDwaveSampler(unittest.TestCase):
 
         sampler.solver.sample_ising.side_effect = SolverOfflineError
         sampler.solver.sample_qubo.side_effect = SolverOfflineError
+        sampler.solver.sample_bqm.side_effect = SolverOfflineError
 
         with self.assertRaises(SolverOfflineError):
             sampler.sample_ising({}, {})
@@ -227,11 +200,13 @@ class TestDwaveSampler(unittest.TestCase):
 
         # one of the sample methods was called
         self.assertEqual(sampler.solver.sample_ising.call_count
-                         + sampler.solver.sample_qubo.call_count, 1)
+                         + sampler.solver.sample_qubo.call_count
+                         + sampler.solver.sample_bqm.call_count, 1)
 
         # add a side-effect
         sampler.solver.sample_ising.side_effect = SolverOfflineError
         sampler.solver.sample_qubo.side_effect = SolverOfflineError
+        sampler.solver.sample_bqm.side_effect = SolverOfflineError
 
         # and make sure get_solver makes a new mock solver
         sampler.client.get_solver.reset_mock(return_value=True)
@@ -251,6 +226,7 @@ class TestDwaveSampler(unittest.TestCase):
         # add a side-effect
         sampler.solver.sample_ising.side_effect = SolverOfflineError
         sampler.solver.sample_qubo.side_effect = SolverOfflineError
+        sampler.solver.sample_bqm.side_effect = SolverOfflineError
 
         # and make sure get_solver makes a new mock solver
         sampler.client.get_solver.side_effect = SolverNotFoundError
diff --git a/tests/test_embedding_transforms.py b/tests/test_embedding_transforms.py
index 22566b8..c73a85b 100644
--- a/tests/test_embedding_transforms.py
+++ b/tests/test_embedding_transforms.py
@@ -420,6 +420,15 @@ class TestEmbedBQM(unittest.TestCase):
         for v in itertools.chain(*embedding.values()):
             self.assertIn(v, target_bqm)
 
+    def test_empty_chain_exception(self):
+        embedding = {0: [], 1: [2], 2: [3]}
+        G = nx.cycle_graph(4)
+
+        bqm = dimod.BinaryQuadraticModel.from_qubo({(0, 0): 1})
+
+        with self.assertRaises(dwave.embedding.exceptions.MissingChainError):
+            dwave.embedding.embed_bqm(bqm, embedding, G)
+
     def test_energy_range_embedding(self):
         # start with an Ising
         bqm = dimod.BinaryQuadraticModel.from_ising({}, {(0, 1): 1, (1, 2): 1, (0, 2): 1})

pip install -e .[drivers] --extra-index-url https://pypi.dwavesys.com/simple
parse_log_pytest
requirements.txt
coverage codecov pytest
apt-get update apt-get install -y gcc python3-dev
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
click==8.1.8
codecov==2.1.13
coverage==7.2.7
decorator==4.4.2
dimod==0.9.0
dwave-cloud-client==0.7.0
dwave-drivers==0.4.4
dwave-networkx==0.8.4
-e git+https://github.com/dwavesystems/dwave-system.git@6b4c9f790f41fda29b6391fbb7add702fa4d582a#egg=dwave_system
dwave-tabu==0.2.2
exceptiongroup==1.2.2
homebase==1.0.1
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
minorminer==0.1.8
mock==2.0.0
networkx==2.6.3
numpy==1.18.0
packaging==24.0
pbr==6.1.1
plucky==0.4.3
pluggy==1.2.0
PySocks==1.7.1
pytest==7.4.4
python-dateutil==2.9.0.post0
requests==2.31.0
scipy==1.7.3
six==1.11.0
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.7
zipp==3.15.0

name: dwave-system
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - coverage==7.2.7
      - decorator==4.4.2
      - dimod==0.9.0
      - dwave-cloud-client==0.7.0
      - dwave-drivers==0.4.4
      - dwave-networkx==0.8.4
      - dwave-tabu==0.2.2
      - exceptiongroup==1.2.2
      - homebase==1.0.1
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - minorminer==0.1.8
      - mock==2.0.0
      - networkx==2.6.3
      - numpy==1.18.0
      - packaging==24.0
      - pbr==6.1.1
      - plucky==0.4.3
      - pluggy==1.2.0
      - pysocks==1.7.1
      - pytest==7.4.4
      - python-dateutil==2.9.0.post0
      - requests==2.31.0
      - scipy==1.7.3
      - six==1.11.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - zipp==3.15.0
prefix: /opt/conda/envs/dwave-system

tests/test_dwavesampler.py::TestDwaveSampler::test_sample_ising_variables tests/test_dwavesampler.py::TestDwaveSampler::test_sample_qubo_variables tests/test_dwavesampler.py::TestDwaveSampler::test_warnings_energy_range tests/test_dwavesampler.py::TestDwaveSampler::test_warnings_sampling_error tests/test_embedding_transforms.py::TestEmbedBQM::test_empty_chain_exception
tests/test_dwavesampler.py::TestDwaveSampler::test_failover_false tests/test_dwavesampler.py::TestDwaveSampler::test_failover_notfound_noretry tests/test_dwavesampler.py::TestDwaveSampler::test_failover_offline tests/test_dwavesampler.py::TestDwaveSampler::test_solver_init tests/test_dwavesampler.py::TestDWaveSamplerAnnealSchedule::test_typical tests/test_embedding_transforms.py::TestUnembedSampleSet::test_chain_break_statistics_discard tests/test_embedding_transforms.py::TestUnembedSampleSet::test_discard tests/test_embedding_transforms.py::TestUnembedSampleSet::test_discard_with_response tests/test_embedding_transforms.py::TestUnembedSampleSet::test_embedding_superset tests/test_embedding_transforms.py::TestUnembedSampleSet::test_energies_discard tests/test_embedding_transforms.py::TestUnembedSampleSet::test_energies_functional tests/test_embedding_transforms.py::TestUnembedSampleSet::test_majority_vote tests/test_embedding_transforms.py::TestUnembedSampleSet::test_majority_vote_with_response tests/test_embedding_transforms.py::TestUnembedSampleSet::test_unembed_sampleset_with_discard_matrix_typical tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_BINARY tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_NAE3SAT_to_square tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_empty tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_identity tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_only_offset tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_bqm_subclass_propagation tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_bad_chain tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_components_empty tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_embedding_not_in_adj tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_h_embedding_mismatch tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_j_index_too_large tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_nonadj tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_ising_typical tests/test_embedding_transforms.py::TestEmbedBQM::test_embed_qubo tests/test_embedding_transforms.py::TestEmbedBQM::test_embedding_with_extra_chains tests/test_embedding_transforms.py::TestEmbedBQM::test_energy_range_embedding
setup.py dwave/system/samplers/dwave_sampler.py requirements.txt dwave/embedding/transforms.py
setup.py dwave/system/samplers/dwave_sampler.py requirements.txt dwave/embedding/transforms.py