diff --git a/dimod/generators/__init__.py b/dimod/generators/__init__.py
index b6548fd6..b2f27548 100644
--- a/dimod/generators/__init__.py
+++ b/dimod/generators/__init__.py
@@ -16,4 +16,4 @@
 from dimod.generators.chimera import chimera_anticluster
 from dimod.generators.constraints import combinations
 from dimod.generators.fcl import frustrated_loop
-from dimod.generators.random import uniform, randint
+from dimod.generators.random import uniform, randint, ran_r
diff --git a/dimod/generators/random.py b/dimod/generators/random.py
index 74313138..a96133ab 100644
--- a/dimod/generators/random.py
+++ b/dimod/generators/random.py
@@ -21,7 +21,7 @@ import numpy.random
 from dimod.binary_quadratic_model import BinaryQuadraticModel
 from dimod.decorators import graph_argument
 
-__all__ = ['uniform', 'randint']
+__all__ = ['uniform', 'ran_r', 'randint']
 
 
 @graph_argument('graph')
@@ -133,3 +133,73 @@ def randint(graph, vartype, low=0, high=1, cls=BinaryQuadraticModel,
 
     return cls.from_numpy_vectors(ldata, (irow, icol, qdata), offset, vartype,
                                   variable_order=variables)
+
+
+@graph_argument('graph')
+def ran_r(r, graph, cls=BinaryQuadraticModel, seed=None):
+    """Generate an Ising model for a RANr problem.
+
+    In RANr problems all linear biases are zero and quadratic values are uniformly
+    selected integers between -r to r, excluding zero. This class of problems is relevant
+    for binary quadratic models (BQM) with spin variables (Ising models).
+
+    This generator of RANr problems follows the definition in [Kin2015]_\ .
+
+    Args:
+        r (int):
+            Order of the RANr problem.
+
+        graph (int/tuple[nodes, edges]/:obj:`~networkx.Graph`):
+            The graph to build the BQM for. Either an
+            integer n, interpreted as a complete graph of size n, or
+            a nodes/edges pair, or a NetworkX graph.
+
+        cls (:class:`.BinaryQuadraticModel`):
+            Binary quadratic model class to build from.
+
+        seed (int, optional, default=None):
+            Random seed.
+
+    Returns:
+        :obj:`.BinaryQuadraticModel`.
+
+    Examples:
+
+    >>> import networkx as nx
+    >>> K_7 = nx.complete_graph(7)
+    >>> bqm = dimod.generators.random.ran_r(1, K_7)
+
+    .. [Kin2015] James King, Sheir Yarkoni, Mayssam M. Nevisi, Jeremy P. Hilton,
+        Catherine C. McGeoch. Benchmarking a quantum annealing processor with the
+        time-to-target metric. https://arxiv.org/abs/1508.05087
+
+    """
+    if not isinstance(r, int):
+        raise TypeError("r should be a positive integer")
+    if r < 1:
+        raise ValueError("r should be a positive integer")
+
+    if seed is None:
+        seed = numpy.random.randint(2**32, dtype=np.uint32)
+    rnd = numpy.random.RandomState(seed)
+
+    variables, edges = graph
+
+    index = {v: idx for idx, v in enumerate(variables)}
+
+    if edges:
+        irow, icol = zip(*((index[u], index[v]) for u, v in edges))
+    else:
+        irow = icol = tuple()
+
+    ldata = np.zeros(len(variables))
+
+    rvals = np.empty(2*r)
+    rvals[0:r] = range(-r, 0)
+    rvals[r:] = range(1, r+1)
+    qdata = rnd.choice(rvals, size=len(variables))
+
+    offset = 0
+
+    return cls.from_numpy_vectors(ldata, (irow, icol, qdata), offset, vartype='SPIN',
+                                  variable_order=variables)
diff --git a/dimod/sampleset.py b/dimod/sampleset.py
index 40c050d4..48f0ba39 100644
--- a/dimod/sampleset.py
+++ b/dimod/sampleset.py
@@ -987,15 +987,19 @@ class SampleSet(abc.Iterable, abc.Sized):
             other fields are.
 
         """
-
         _, indices, inverse = np.unique(self.record.sample, axis=0,
                                         return_index=True, return_inverse=True)
 
+        # unique also sorts the array which we don't want, so we undo the sort
+        order = np.argsort(indices)
+        indices = indices[order]
+
         record = self.record[indices]
 
         # fix the number of occurrences
         record.num_occurrences = 0
         for old_idx, new_idx in enumerate(inverse):
+            new_idx = order[new_idx]
             record[new_idx].num_occurrences += self.record[old_idx].num_occurrences
 
         # dev note: we don't check the energies as they should be the same
diff --git a/docs/reference/generators.rst b/docs/reference/generators.rst
index f948c9bb..25bc5cad 100644
--- a/docs/reference/generators.rst
+++ b/docs/reference/generators.rst
@@ -43,4 +43,5 @@ Random
    :toctree: generated/
 
    randint
-   uniform
\ No newline at end of file
+   ran_r
+   uniform

SampleSet.aggregate() does not preserve order, destroys equality test
Consider (dimod v0.8.10):
```
import dimod
bqm = dimod.BinaryQuadraticModel({}, {'ab': 1}, 0, dimod.SPIN)
ss1 = dimod.SampleSet.from_samples_bqm([{'a': 1, 'b': -1}, {'a': -1, 'b': 1}], bqm)
ss2 = ss1.aggregate()
```
then we have:
```
>>> ss1
SampleSet(rec.array([([ 1, -1], -1., 1), ([-1,  1], -1., 1)],
          dtype=[('sample', 'i1', (2,)), ('energy', '<f8'), ('num_occurrences', '<i8')]), ['a', 'b'], {}, 'SPIN')

>>> ss2
SampleSet(rec.array([([-1,  1], -1., 1), ([ 1, -1], -1., 1)],
          dtype=[('sample', 'i1', (2,)), ('energy', '<f8'), ('num_occurrences', '<i8')]), ['a', 'b'], {}, 'SPIN')

>>> ss1 == ss2
False
```

Not sure if this qualifies as a bug in your book, but we can call it a feature (request). :smile: 
diff --git a/tests/test_generators.py b/tests/test_generators.py
index 1e0bd712..d4d367e2 100644
--- a/tests/test_generators.py
+++ b/tests/test_generators.py
@@ -75,6 +75,38 @@ class TestRandomRandint(unittest.TestCase):
 
         self.assertNotEqual(bqm2, bqm1)
 
+class TestRandomRanR(unittest.TestCase):
+    def test_singleton(self):
+        bqm = dimod.generators.random.ran_r(1, 1)
+
+        # should have a single node
+        self.assertEqual(len(bqm), 1)
+        self.assertIn(0, bqm.variables)
+
+    def test_empty(self):
+        bqm = dimod.generators.random.ran_r(1, 0)
+
+        # should have no nodes
+        self.assertEqual(len(bqm), 0)
+
+    def test_seed(self):
+        bqm0 = dimod.generators.random.ran_r(3, 100, seed=506)
+        bqm1 = dimod.generators.random.ran_r(3, 100, seed=506)
+
+        self.assertEqual(bqm0, bqm1)
+
+        bqm2 = dimod.generators.random.ran_r(3, 100, seed=123)
+
+        self.assertNotEqual(bqm2, bqm1)
+
+    def test_values(self):
+        bqm = dimod.generators.random.ran_r(5, 10)
+
+        self.assertFalse(all(bqm.linear.values()))
+        self.assertTrue(all(val != 0 for val in bqm.quadratic.values()))
+        self.assertTrue(all(val <= 5 for val in bqm.quadratic.values()))
+        self.assertTrue(all(val >= -5 for val in bqm.quadratic.values()))
+
 
 class TestChimeraAnticluster(unittest.TestCase):
     def test_singletile(self):
diff --git a/tests/test_sampleset.py b/tests/test_sampleset.py
index 141552a8..176627ee 100644
--- a/tests/test_sampleset.py
+++ b/tests/test_sampleset.py
@@ -261,6 +261,52 @@ class TestAggregate(unittest.TestCase):
         self.assertEqual(samples,
                          dimod.SampleSet.from_samples(([[-1, 1], [-1, 1]], 'ab'), dimod.SPIN, energy=[0.0, 0.0]))
 
+    def test_order_preservation_2x2_unique(self):
+        bqm = dimod.BinaryQuadraticModel({}, {'ab': 1}, 0, dimod.SPIN)
+
+        # these are unique so order should be preserved
+        ss1 = dimod.SampleSet.from_samples_bqm([{'a': 1, 'b': -1},
+                                                {'a': -1, 'b': 1}],
+                                               bqm)
+        ss2 = ss1.aggregate()
+
+        self.assertEqual(ss1, ss2)
+
+    def test_order_preservation(self):
+        bqm = dimod.BinaryQuadraticModel({}, {'ab': 1}, 0, dimod.SPIN)
+
+        ss1 = dimod.SampleSet.from_samples_bqm([{'a': 1, 'b': -1},
+                                                {'a': -1, 'b': 1},
+                                                {'a': 1, 'b': -1}],
+                                               bqm)
+        ss2 = ss1.aggregate()
+
+        target = dimod.SampleSet.from_samples_bqm([{'a': 1, 'b': -1},
+                                                   {'a': -1, 'b': 1}],
+                                                  bqm,
+                                                  num_occurrences=[2, 1])
+
+        self.assertEqual(target, ss2)
+
+    def test_order_preservation_doubled(self):
+        bqm = dimod.BinaryQuadraticModel({}, {'ab': 1, 'bc': -1}, 0, dimod.SPIN)
+        ss1 = dimod.SampleSet.from_samples_bqm(([[1, 1, 0],
+                                                 [1, 0, 0],
+                                                 [0, 0, 0],
+                                                 [0, 0, 0],
+                                                 [1, 1, 0],
+                                                 [1, 0, 0],
+                                                 [0, 0, 0]], 'abc'),
+                                               bqm)
+
+        target = dimod.SampleSet.from_samples_bqm(([[1, 1, 0],
+                                                    [1, 0, 0],
+                                                    [0, 0, 0]], 'abc'),
+                                                  bqm,
+                                                  num_occurrences=[2, 2, 3])
+
+        self.assertEqual(target, ss1.aggregate())
+
 
 class TestAppend(unittest.TestCase):
     def test_sampleset1_append1(self):

pip install -e .[all]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y libboost-dev
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
codecov==2.1.13
coverage==6.2
Cython==3.0.12
decorator==5.1.1
-e git+https://github.com/dwavesystems/dimod.git@e33ca5045e31ee2d9d58515f017fb6be5276cd8e#egg=dimod
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
jsonschema==2.6.0
mock==2.0.0
networkx==2.0
numpy==1.15.0
packaging==21.3
pandas==0.22.0
pbr==6.1.1
pluggy==1.0.0
py==1.11.0
pymongo==3.7.1
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.27.1
simplejson==3.16.0
six==1.11.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: dimod
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=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - coverage==6.2
      - cython==3.0.12
      - decorator==5.1.1
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jsonschema==2.6.0
      - mock==2.0.0
      - networkx==2.0
      - numpy==1.15.0
      - packaging==21.3
      - pandas==0.22.0
      - pbr==6.1.1
      - pluggy==1.0.0
      - py==1.11.0
      - pymongo==3.7.1
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.27.1
      - simplejson==3.16.0
      - six==1.11.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/dimod

tests/test_generators.py::TestRandomRanR::test_empty tests/test_generators.py::TestRandomRanR::test_seed tests/test_generators.py::TestRandomRanR::test_singleton tests/test_generators.py::TestRandomRanR::test_values tests/test_sampleset.py::TestAggregate::test_order_preservation tests/test_sampleset.py::TestAggregate::test_order_preservation_2x2_unique tests/test_sampleset.py::TestAggregate::test_order_preservation_doubled
tests/test_generators.py::TestRandomUniform::test_empty tests/test_generators.py::TestRandomUniform::test_seed tests/test_generators.py::TestRandomUniform::test_singleton tests/test_generators.py::TestRandomRandint::test_empty tests/test_generators.py::TestRandomRandint::test_seed tests/test_generators.py::TestRandomRandint::test_singleton tests/test_generators.py::TestChimeraAnticluster::test_empty tests/test_generators.py::TestChimeraAnticluster::test_multitile tests/test_generators.py::TestChimeraAnticluster::test_seed tests/test_generators.py::TestChimeraAnticluster::test_singletile tests/test_generators.py::TestChimeraAnticluster::test_singletile_not_subgraph tests/test_generators.py::TestChimeraAnticluster::test_singletile_subgraph tests/test_generators.py::TestFCL::test_seed tests/test_generators.py::TestFCL::test_singletile tests/test_generators.py::TestCombinations::test_2_choose_1 tests/test_generators.py::TestCombinations::test_3_choose_0 tests/test_generators.py::TestCombinations::test_5_choose_3 tests/test_generators.py::TestCombinations::test_5_choose_3_spin tests/test_generators.py::TestCombinations::test_5_choose_3_strength_4 tests/test_sampleset.py::Test_as_samples::test_copy_false tests/test_sampleset.py::Test_as_samples::test_dict_with_inconsistent_labels tests/test_sampleset.py::Test_as_samples::test_dict_with_labels tests/test_sampleset.py::Test_as_samples::test_empty_dict tests/test_sampleset.py::Test_as_samples::test_empty_list tests/test_sampleset.py::Test_as_samples::test_empty_list_labelled tests/test_sampleset.py::Test_as_samples::test_empty_ndarray tests/test_sampleset.py::Test_as_samples::test_iterator tests/test_sampleset.py::Test_as_samples::test_iterator_labelled tests/test_sampleset.py::Test_as_samples::test_list_of_empty tests/test_sampleset.py::Test_as_samples::test_mixed_sampletype tests/test_sampleset.py::Test_as_samples::test_ndarray tests/test_sampleset.py::Test_as_samples::test_ndarray_labelled tests/test_sampleset.py::TestConstruction::test_from_bqm_single_sample tests/test_sampleset.py::TestConstruction::test_from_bqm_with_sorting tests/test_sampleset.py::TestConstruction::test_from_samples tests/test_sampleset.py::TestConstruction::test_from_samples_empty tests/test_sampleset.py::TestConstruction::test_from_samples_fields_multiple tests/test_sampleset.py::TestConstruction::test_from_samples_fields_single tests/test_sampleset.py::TestConstruction::test_from_samples_iterator tests/test_sampleset.py::TestConstruction::test_from_samples_single_sample tests/test_sampleset.py::TestConstruction::test_from_samples_str_labels tests/test_sampleset.py::TestConstruction::test_from_samples_with_aggregation tests/test_sampleset.py::TestConstruction::test_mismatched_shapes tests/test_sampleset.py::TestConstruction::test_shorter_samples tests/test_sampleset.py::TestEq::test_ordered tests/test_sampleset.py::TestAggregate::test_aggregate_simple tests/test_sampleset.py::TestAppend::test_overlapping_variables tests/test_sampleset.py::TestAppend::test_sampleset1_append1 tests/test_sampleset.py::TestAppend::test_sampleset2_append1 tests/test_sampleset.py::TestAppend::test_sampleset2_append2 tests/test_sampleset.py::TestAppend::test_sampleset2_append3 tests/test_sampleset.py::TestAppend::test_two_samplesets tests/test_sampleset.py::TestLowest::test_all_equal tests/test_sampleset.py::TestLowest::test_empty tests/test_sampleset.py::TestLowest::test_tolerance tests/test_sampleset.py::TestTruncate::test_typical tests/test_sampleset.py::TestTruncate::test_unordered tests/test_sampleset.py::TestIteration::test_data_reverse tests/test_sampleset.py::TestIteration::test_iterator tests/test_sampleset.py::TestSerialization::test_empty_with_bytes tests/test_sampleset.py::TestSerialization::test_from_serializable_empty_v1 tests/test_sampleset.py::TestSerialization::test_from_serializable_empty_variables_v1 tests/test_sampleset.py::TestSerialization::test_from_serializable_triu_v1 tests/test_sampleset.py::TestSerialization::test_functional_json tests/test_sampleset.py::TestSerialization::test_functional_simple_shapes tests/test_sampleset.py::TestSerialization::test_functional_str tests/test_sampleset.py::TestSerialization::test_functional_with_info tests/test_sampleset.py::TestSerialization::test_triu_with_bytes tests/test_sampleset.py::TestSerialization::test_tuple_variable_labels tests/test_sampleset.py::TestPandas::test_sample_column tests/test_sampleset.py::TestPandas::test_simple tests/test_sampleset.py::TestFirst::test_empty tests/test_sampleset.py::TestDataVectors::test_empty tests/test_sampleset.py::TestDataVectors::test_view tests/test_sampleset.py::Test_concatenate::test_empty tests/test_sampleset.py::Test_concatenate::test_simple tests/test_sampleset.py::Test_concatenate::test_variables_order tests/test_sampleset.py::Test_concatenate::test_variables_order_and_vartype
dimod/generators/random.py docs/reference/generators.rst dimod/sampleset.py dimod/generators/__init__.py
dimod/generators/random.py docs/reference/generators.rst dimod/sampleset.py dimod/generators/__init__.py