docs: def relabel_variables(self, mapping, inplace=True):
          """Relabel the variables and nodes according to the given mapping.
  
          Args:
              mapping (dict): a dict mapping the current variable/node labels
                  to new ones.
              inplace (bool, optional, default=True):
                  If True, the specification is updated in-place; otherwise, a new specification
                  is returned.
  
          Returns:
              :class:`.Specification`: A Specification with the variables
              relabeled according to mapping. If copy=False returns itself,
              if copy=True returns a new Specification.
  
          """
          graph = self.graph
          ising_linear_ranges = self.ising_linear_ranges
          ising_quadratic_ranges = self.ising_quadratic_ranges
  
          try:
              old_labels = set(iterkeys(mapping))
              new_labels = set(itervalues(mapping))
          except TypeError:
              raise ValueError("mapping targets must be hashable objects")
  
          for v in new_labels:
              if v in graph and v not in old_labels:
                  raise ValueError(('A variable cannot be relabeled "{}" without also relabeling '
                                    "the existing variable of the same name").format(v))
  
          if not inplace:
              return Specification(nx.relabel_nodes(graph, mapping, copy=True),  # also checks the mapping
                                   tuple(mapping.get(v, v) for v in self.decision_variables),
                                   self.feasible_configurations,  # does not change
                                   vartype=self.vartype,  # does not change
                                   ising_linear_ranges={mapping.get(v, v): ising_linear_ranges[v] for v in graph},
                                   ising_quadratic_ranges={mapping.get(v, v): {mapping.get(u, u): r
                                                                               for u, r in iteritems(neighbors)}
                                                           for v, neighbors in iteritems(ising_quadratic_ranges)})
          else:
              # now we need the ising_linear_ranges and ising_quadratic_ranges
              shared = old_labels & new_labels
  
              if shared:
                  # in this case we need to transform to an intermediate state
                  # counter will be used to generate the intermediate labels, as an easy optimization
                  # we start the counter with a high number because often variables are labeled by
                  # integers starting from 0
                  counter = itertools.count(2 * len(self))
  
                  old_to_intermediate = {}
                  intermediate_to_new = {}
  
                  for old, new in iteritems(mapping):
                      if old == new:
                          # we can remove self-labels
                          continue
  
                      if old in new_labels or new in old_labels:
  
                          # try to get a new unique label
                          lbl = next(counter)
                          while lbl in new_labels or lbl in old_labels:
                              lbl = next(counter)
  
                          # add it to the mapping
                          old_to_intermediate[old] = lbl
                          intermediate_to_new[lbl] = new
  
                      else:
                          old_to_intermediate[old] = new
                          # don't need to add it to intermediate_to_new because it is a self-label
  
                  Specification.relabel_variables(self, old_to_intermediate, inplace=True)
                  Specification.relabel_variables(self, intermediate_to_new, inplace=True)
                  return self
  
              # modifies graph in place
              nx.relabel_nodes(self.graph, mapping, copy=False)
  
              # this is always a new object
              self.decision_variables = tuple(mapping.get(v, v) for v in self.decision_variables)
  
              # we can just relabel in-place without worrying about conflict
              for v in old_labels:
                  if v in mapping:
                      ising_linear_ranges[mapping[v]] = ising_linear_ranges[v]
                      del ising_linear_ranges[v]
  
              # need to do the deeper level first
              for neighbors in itervalues(ising_quadratic_ranges):
                  for v in list(neighbors):
                      if v in mapping:
                          neighbors[mapping[v]] = neighbors[v]
                          del neighbors[v]
  
              # now the top level
              for v in old_labels:
                  if v in mapping:
                      ising_quadratic_ranges[mapping[v]] = ising_quadratic_ranges[v]
                      del ising_quadratic_ranges[v]
  
              return self