docs: def add_constraint(self, name, coefficients={}, ub=0):
          """
          Add a constraint to the problem. The constrain is formulated as a dictionary of variable names to
          linear coefficients.
          The constraint can only have an upper bound. To make a constraint with a lower bound, multiply
          all coefficients by -1.
          """
          if name in self._constraints:
              raise ValueError(
                  "A constraint named " + name + " already exists."
              )
          self._constraints[name] = len(self._constraints)
          self.upper_bounds = np.append(self.upper_bounds, ub)
  
          new_row = np.array([[coefficients.get(name, 0) for name in self._variables]])
          self._add_row_to_A(new_row)
          self._reset_solution()