示例#1
0
    def test_add_edit_var_required_after_suggestions(self):
        "Solver works with REQUIRED strength after many suggestions"
        solver = SimplexSolver()
        a = Variable(name='a')
        b = Variable(name='b')

        solver.add_stay(a, STRONG, 0)
        solver.add_constraint(Constraint(a, Constraint.EQ, b, REQUIRED))
        solver.resolve()

        self.assertEqual(b.value, 0)
        self.assertEqual(a.value, 0)

        solver.add_edit_var(a, REQUIRED)
        solver.begin_edit()
        solver.suggest_value(a, 2)
        solver.resolve()

        self.assertEqual(a.value, 2)
        self.assertEqual(b.value, 2)

        solver.suggest_value(a, 10)
        solver.resolve()

        self.assertEqual(a.value, 10)
        self.assertEqual(b.value, 10)
示例#2
0
    def test_multiedit1(self):
        # This test stresses the edit session stack. begin_edit() starts a new
        # "edit variable group" and "end_edit" closes it, leaving only the
        # previously opened edit variables still active.
        x = Variable('x')
        y = Variable('y')
        w = Variable('w')
        h = Variable('h')
        solver = SimplexSolver()

        # Add some stays
        solver.add_stay(x)
        solver.add_stay(y)
        solver.add_stay(w)
        solver.add_stay(h)

        # start an editing session
        solver.add_edit_var(x)
        solver.add_edit_var(y)

        with solver.edit():
            solver.suggest_value(x, 10)
            solver.suggest_value(y, 20)

            # Force the system to resolve.
            solver.resolve()

            self.assertAlmostEqual(x.value, 10)
            self.assertAlmostEqual(y.value, 20)
            self.assertAlmostEqual(w.value, 0)
            self.assertAlmostEqual(h.value, 0)

            # Open a second set of variables for editing
            solver.add_edit_var(w)
            solver.add_edit_var(h)

            with solver.edit():
                solver.suggest_value(w, 30)
                solver.suggest_value(h, 40)

            # Close the second set...
            self.assertAlmostEqual(x.value, 10)
            self.assertAlmostEqual(y.value, 20)
            self.assertAlmostEqual(w.value, 30)
            self.assertAlmostEqual(h.value, 40)

            # Now make sure the first set can still be edited
            solver.suggest_value(x, 50)
            solver.suggest_value(y, 60)

        self.assertAlmostEqual(x.value, 50)
        self.assertAlmostEqual(y.value, 60)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)
    def test_multiedit1(self):
        # This test stresses the edit session stack. begin_edit() starts a new
        # "edit variable group" and "end_edit" closes it, leaving only the
        # previously opened edit variables still active.
        x = Variable('x')
        y = Variable('y')
        w = Variable('w')
        h = Variable('h')
        solver = SimplexSolver()

        # Add some stays
        solver.add_stay(x)
        solver.add_stay(y)
        solver.add_stay(w)
        solver.add_stay(h)

        # start an editing session
        solver.add_edit_var(x)
        solver.add_edit_var(y)

        with solver.edit():
            solver.suggest_value(x, 10)
            solver.suggest_value(y, 20)

            # Force the system to resolve.
            solver.resolve()

            self.assertAlmostEqual(x.value, 10)
            self.assertAlmostEqual(y.value, 20)
            self.assertAlmostEqual(w.value, 0)
            self.assertAlmostEqual(h.value, 0)

            # Open a second set of variables for editing
            solver.add_edit_var(w)
            solver.add_edit_var(h)

            with solver.edit():
                solver.suggest_value(w, 30)
                solver.suggest_value(h, 40)

            # Close the second set...
            self.assertAlmostEqual(x.value, 10)
            self.assertAlmostEqual(y.value, 20)
            self.assertAlmostEqual(w.value, 30)
            self.assertAlmostEqual(h.value, 40)

            # Now make sure the first set can still be edited
            solver.suggest_value(x, 50)
            solver.suggest_value(y, 60)

        self.assertAlmostEqual(x.value, 50)
        self.assertAlmostEqual(y.value, 60)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)
    def test_multiedit3(self):
        MIN = 100
        MAX = 500

        width = Variable('width')
        height = Variable('height')
        top = Variable('top')
        bottom = Variable('bottom')
        left = Variable('left')
        right = Variable('right')

        solver = SimplexSolver()

        iw = Variable('window_innerWidth', random.randrange(MIN, MAX))
        ih = Variable('window_innerHeight', random.randrange(MIN, MAX))

        solver.add_constraint(Constraint(width, Constraint.EQ, iw, strength=STRONG, weight=0.0))
        solver.add_constraint(Constraint(height, Constraint.EQ, ih, strength=STRONG, weight=0.0))
        solver.add_constraint(Constraint(top, Constraint.EQ, 0, strength=WEAK, weight=0.0))
        solver.add_constraint(Constraint(left, Constraint.EQ, 0, strength=WEAK, weight=0.0))
        solver.add_constraint(Constraint(bottom, Constraint.EQ, top + height, strength=MEDIUM, weight=0.0))
        # Right is at least left + width
        solver.add_constraint(Constraint(right, Constraint.EQ,  left + width, strength=MEDIUM, weight=0.0))
        solver.add_stay(iw)
        solver.add_stay(ih)

        # Propegate viewport size changes.
        for i in range(0, 30):

            # Measurement should be cheap here.
            iwv = random.randrange(MIN, MAX)
            ihv = random.randrange(MIN, MAX)

            solver.add_edit_var(iw)
            solver.add_edit_var(ih)

            with solver.edit():
                solver.suggest_value(iw, iwv)
                solver.suggest_value(ih, ihv)
                # solver.resolve()

            self.assertAlmostEqual(top.value, 0)
            self.assertAlmostEqual(left.value, 0)
            self.assertLessEqual(bottom.value, MAX)
            self.assertGreaterEqual(bottom.value, MIN)
            self.assertLessEqual(right.value, MAX)
            self.assertGreaterEqual(right.value, MIN)
示例#5
0
    def test_add_edit_var_required(self):
        "Solver works with REQUIRED strength"
        solver = SimplexSolver()

        a = Variable(name='a')

        solver.add_stay(a, STRONG, 0)
        solver.resolve()

        self.assertEqual(a.value, 0)

        solver.add_edit_var(a, REQUIRED)
        solver.begin_edit()
        solver.suggest_value(a, 2)
        solver.resolve()

        self.assertEqual(a.value, 2)
示例#6
0
    def test_multiedit2(self):

        x = Variable('x')
        y = Variable('y')
        w = Variable('w')
        h = Variable('h')

        solver = SimplexSolver()
        solver.add_stay(x)
        solver.add_stay(y)
        solver.add_stay(w)
        solver.add_stay(h)
        solver.add_edit_var(x)
        solver.add_edit_var(y)

        solver.begin_edit()
        solver.suggest_value(x, 10)
        solver.suggest_value(y, 20)
        solver.resolve()
        solver.end_edit()

        self.assertAlmostEqual(x.value, 10)
        self.assertAlmostEqual(y.value, 20)
        self.assertAlmostEqual(w.value, 0)
        self.assertAlmostEqual(h.value, 0)

        solver.add_edit_var(w)
        solver.add_edit_var(h)

        solver.begin_edit()
        solver.suggest_value(w, 30)
        solver.suggest_value(h, 40)
        solver.end_edit()

        self.assertAlmostEqual(x.value, 10)
        self.assertAlmostEqual(y.value, 20)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)

        solver.add_edit_var(x)
        solver.add_edit_var(y)

        solver.begin_edit()
        solver.suggest_value(x, 50)
        solver.suggest_value(y, 60)
        solver.end_edit()

        self.assertAlmostEqual(x.value, 50)
        self.assertAlmostEqual(y.value, 60)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)
    def test_multiedit2(self):

        x = Variable('x')
        y = Variable('y')
        w = Variable('w')
        h = Variable('h')

        solver = SimplexSolver()
        solver.add_stay(x)
        solver.add_stay(y)
        solver.add_stay(w)
        solver.add_stay(h)
        solver.add_edit_var(x)
        solver.add_edit_var(y)

        solver.begin_edit()
        solver.suggest_value(x, 10)
        solver.suggest_value(y, 20)
        solver.resolve()
        solver.end_edit()

        self.assertAlmostEqual(x.value, 10)
        self.assertAlmostEqual(y.value, 20)
        self.assertAlmostEqual(w.value, 0)
        self.assertAlmostEqual(h.value, 0)

        solver.add_edit_var(w)
        solver.add_edit_var(h)

        solver.begin_edit()
        solver.suggest_value(w, 30)
        solver.suggest_value(h, 40)
        solver.end_edit()

        self.assertAlmostEqual(x.value, 10)
        self.assertAlmostEqual(y.value, 20)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)

        solver.add_edit_var(x)
        solver.add_edit_var(y)

        solver.begin_edit()
        solver.suggest_value(x, 50)
        solver.suggest_value(y, 60)
        solver.end_edit()

        self.assertAlmostEqual(x.value, 50)
        self.assertAlmostEqual(y.value, 60)
        self.assertAlmostEqual(w.value, 30)
        self.assertAlmostEqual(h.value, 40)
示例#8
0
class TTSolver:

  def initialSetup(self, layer):
    self.solver = SimplexSolver()
    self.nodehash = {}
    for p in layer.paths:
      for n in p.nodes:
        nid = n.hash()
        self.nodehash[nid] = {
          "node": n,
          "xvar": Variable("x"+str(nid), n.position.x),
          "yvar": Variable("y"+str(nid), n.position.y),
          "affected": set()
        }
    self.setConstraintsFromHints(layer)

  def yvar(self,n):
    if not n:
      raise ValueError
    if not (n.hash() in self.nodehash):
      raise KeyError(n)
    return self.nodehash[n.hash()]["yvar"]

  def xvar(self,n):
    if not n:
      raise ValueError
    if not (n.hash() in self.nodehash):
      raise KeyError(n)
    return self.nodehash[n.hash()]["xvar"]

  def setConstraintsFromHints(self, layer):
    c = []
    for h in layer.hints:
      if h.type == TAG:
        try:
          if h.options() == DIAGONAL or (h.options() < 8 and not h.horizontal):
            v1 = self.yvar(h.originNode)
            v2 = self.yvar(h.targetNode)
            yValue = v1.value - v2.value
            ystem = v1 - v2
            c.append(ystem == yValue)

          if h.options() == DIAGONAL or (h.options() < 8 and h.horizontal):
            v1 = self.xvar(h.originNode)
            v2 = self.xvar(h.targetNode)
            xValue = v1.value - v2.value
            xstem = v1 - v2
            c.append(xstem == xValue)

          if h.options() == PROPORTIONAL_TRIPLE:
            if h.horizontal:
              v1 = self.xvar(h.originNode)
              v2 = self.xvar(h.targetNode)
              v3 = self.xvar(h.otherNode1)
              proportion = safediv(h.targetNode.position.x - h.originNode.position.x , h.otherNode1.position.x - h.targetNode.position.x)
            else:
              v1 = self.yvar(h.originNode)
              v2 = self.yvar(h.targetNode)
              v3 = self.yvar(h.otherNode1)
              proportion = safediv(h.targetNode.position.y - h.originNode.position.y, h.otherNode1.position.y - h.targetNode.position.y)
            d1 = v2 - v1
            d2 = v3 - v2
            c.append(d2 * proportion == d1)

          if h.options() == PROPORTIONAL_QUAD:
            on2 = h.valueForKey_("otherNode2") # Glyphs bug
            if h.horizontal:
              v1 = self.xvar(h.originNode)
              v2 = self.xvar(h.targetNode)
              v3 = self.xvar(h.otherNode1)
              v4 = self.xvar(on2)
              proportion = safediv(h.targetNode.position.x - h.originNode.position.x, on2.position.x - h.otherNode1.position.x)
            else:
              v1 = self.yvar(h.originNode)
              v2 = self.yvar(h.targetNode)
              v3 = self.yvar(h.otherNode1)
              v4 = self.yvar(on2)
              proportion = safediv(h.targetNode.position.y - h.originNode.position.y, on2.position.y - h.otherNode1.position.y)
            d1 = v2 - v1
            d2 = v4 - v3
            # print(d1,d2,proportion)
            c.append(d2 * proportion == d1)
        except ValueError:
          print("I found a busted constraint. It'll get cleaned up soon.")
    for cs in c:
      self.solver.add_constraint(cs)

  def _diagonalConstraints(self, c, n1,n2):
    v1 = self.xvar(n1)
    v2 = self.xvar(n2)
    xValue = v1.value - v2.value
    xstem = v1 - v2
    v1 = self.yvar(n1)
    v2 = self.yvar(n2)
    yValue = v1.value - v2.value
    ystem = v1 - v2
    c.append(xstem == xValue)
    c.append(ystem == yValue)

  def _makeEditable(self,node):
    n = self.nodehash[node.hash()]
    self.solver.add_edit_var(n["xvar"])
    self.solver.add_edit_var(n["yvar"])

  def _suggest(self,node):
    n = self.nodehash[node.hash()]
    # print("Suggesting ",n["node"].position.x,n["node"].position.y)
    self.solver.suggest_value(n["xvar"], n["node"].position.x)
    self.solver.suggest_value(n["yvar"], n["node"].position.y)

  def setStayFromNodes(self, layer):
    nodes = filter(lambda x:x.hash() in self.nodehash, layer.selection)
    if len(nodes) < 1:
      return

    temporaryConstraints = []
    c = []
    for p in layer.paths:
      for n in p.nodes:
        if n.type != GSOFFCURVE:
          # Constrain left handle and this node
          if n.prevNode.type == GSOFFCURVE and not n.prevNode.selected:
              self._diagonalConstraints(c, n, n.prevNode)
          if n.nextNode.type == GSOFFCURVE and not n.nextNode.selected:
            self._diagonalConstraints(c, n, n.nextNode)

    for cs in c:
      temporaryConstraints.append(self.solver.add_constraint(cs, strength=WEAK))

    # print("Putting stuff into solver")
    for n in nodes:
      self._makeEditable(n)
      if n.type != GSOFFCURVE:
        if n.nextNode.type == GSOFFCURVE:
          self._makeEditable(n.nextNode)
        if n.prevNode.type == GSOFFCURVE:
          self._makeEditable(n.prevNode)
      else:
        if n.nextNode.type == CURVE and n.nextNode.smooth:
          self._makeEditable(n.nextNode)
          self._makeEditable(n.nextNode.nextNode)
        elif n.prevNode.type == CURVE and n.prevNode.smooth:
          self._makeEditable(n.prevNode)
          self._makeEditable(n.prevNode.prevNode)

    with self.solver.edit():
      for n in nodes:
        self._suggest(n)
        if n.type != GSOFFCURVE:
          if n.nextNode.type == GSOFFCURVE:
            self._suggest(n.nextNode)
          if n.prevNode.type == GSOFFCURVE:
            self._suggest(n.prevNode)
        else:
          if n.nextNode.type == CURVE and n.nextNode.smooth:
            self._suggest(n.nextNode)
            self._suggest(n.nextNode.nextNode)
          elif n.prevNode.type == CURVE and n.prevNode.smooth:
            self._suggest(n.prevNode)
            self._suggest(n.prevNode.prevNode)

    for j in nodes:
      n = self.nodehash[j.hash()]
      # print("Got: ",n["xvar"],n["yvar"])

    for c in temporaryConstraints:
      self.solver.remove_constraint(c)

  def updateGlyphWithSolution(self):
    for i in self.nodehash:
      n = self.nodehash[i]
      # print(n["node"], "->", n["xvar"].value, n["yvar"].value)
      n["node"].position = (n["xvar"].value, n["yvar"].value)

  def updateSolverFromGlyph(self):
    for i in self.nodehash:
      n = self.nodehash[i]
      n["xvar"].value = n["node"].position.x
      n["yvar"].value = n["node"].position.y
      n["xstay"] = self.solver.add_stay(n["xvar"], strength=WEAK)
      n["ystay"] = self.solver.add_stay(n["yvar"], strength=WEAK)
      self.solver.add_edit_var(n["xvar"])
      self.solver.add_edit_var(n["yvar"])

    if len(self.nodehash) > 0:
      with self.solver.edit():
        for i in self.nodehash:
          n = self.nodehash[i]
          self.solver.suggest_value(n["xvar"], n["node"].position.x)
          self.solver.suggest_value(n["yvar"], n["node"].position.y)
示例#9
0
    def test_quadrilateral(self):
        "A simple version of the quadrilateral test"

        solver = SimplexSolver()

        class Point(object):
            def __init__(self, identifier, x, y):
                self.x = Variable('x' + identifier, x)
                self.y = Variable('y' + identifier, y)

            def __repr__(self):
                return u'(%s, %s)' % (self.x.value, self.y.value)

            __hash__ = object.__hash__

            def __eq__(self, other):
                return self.x.value == other[0] and self.y.value == other[1]

        points = [
            Point('0', 10, 10),
            Point('1', 10, 200),
            Point('2', 200, 200),
            Point('3', 200, 10),
            Point('m0', 0, 0),
            Point('m1', 0, 0),
            Point('m2', 0, 0),
            Point('m3', 0, 0),
        ]
        midpoints = points[4:]

        # Add point stays
        weight = 1.0
        multiplier = 2.0
        for point in points[:4]:
            solver.add_stay(point.x, WEAK, weight)
            solver.add_stay(point.y, WEAK, weight)
            weight = weight * multiplier

        for start, end in [(0, 1), (1, 2), (2, 3), (3, 0)]:
            cle = (points[start].x + points[end].x) / 2
            cleq = midpoints[start].x == cle
            solver.add_constraint(cleq)
            cle = (points[start].y + points[end].y) / 2
            cleq = midpoints[start].y == cle
            solver.add_constraint(cleq)

        cle = points[0].x + 20
        solver.add_constraint(cle <= points[2].x)
        solver.add_constraint(cle <= points[3].x)

        cle = points[1].x + 20
        solver.add_constraint(cle <= points[2].x)
        solver.add_constraint(cle <= points[3].x)

        cle = points[0].y + 20
        solver.add_constraint(cle <= points[1].y)
        solver.add_constraint(cle <= points[2].y)

        cle = points[3].y + 20
        solver.add_constraint(cle <= points[1].y)
        solver.add_constraint(cle <= points[2].y)

        for point in points:
            solver.add_constraint(point.x >= 0)
            solver.add_constraint(point.y >= 0)

            solver.add_constraint(point.x <= 500)
            solver.add_constraint(point.y <= 500)

        # Check the initial answers

        self.assertEqual(points[0], (10.0, 10.0))
        self.assertEqual(points[1], (10.0, 200.0))
        self.assertEqual(points[2], (200.0, 200.0))
        self.assertEqual(points[3], (200.0, 10.0))
        self.assertEqual(points[4], (10.0, 105.0))
        self.assertEqual(points[5], (105.0, 200.0))
        self.assertEqual(points[6], (200.0, 105.0))
        self.assertEqual(points[7], (105.0, 10.0))

        # Now move point 2 to a new location

        solver.add_edit_var(points[2].x)
        solver.add_edit_var(points[2].y)

        solver.begin_edit()

        solver.suggest_value(points[2].x, 300)
        solver.suggest_value(points[2].y, 400)

        solver.end_edit()

        # Check that the other points have been moved.
        self.assertEqual(points[0], (10.0, 10.0))
        self.assertEqual(points[1], (10.0, 200.0))
        self.assertEqual(points[2], (300.0, 400.0))
        self.assertEqual(points[3], (200.0, 10.0))
        self.assertEqual(points[4], (10.0, 105.0))
        self.assertEqual(points[5], (155.0, 300.0))
        self.assertEqual(points[6], (250.0, 205.0))
        self.assertEqual(points[7], (105.0, 10.0))
示例#10
0
    def test_multiedit3(self):
        MIN = 100
        MAX = 500

        width = Variable('width')
        height = Variable('height')
        top = Variable('top')
        bottom = Variable('bottom')
        left = Variable('left')
        right = Variable('right')

        solver = SimplexSolver()

        iw = Variable('window_innerWidth', random.randrange(MIN, MAX))
        ih = Variable('window_innerHeight', random.randrange(MIN, MAX))

        solver.add_constraint(
            Constraint(width, Constraint.EQ, iw, strength=STRONG, weight=0.0))
        solver.add_constraint(
            Constraint(height, Constraint.EQ, ih, strength=STRONG, weight=0.0))
        solver.add_constraint(
            Constraint(top, Constraint.EQ, 0, strength=WEAK, weight=0.0))
        solver.add_constraint(
            Constraint(left, Constraint.EQ, 0, strength=WEAK, weight=0.0))
        solver.add_constraint(
            Constraint(bottom,
                       Constraint.EQ,
                       top + height,
                       strength=MEDIUM,
                       weight=0.0))
        # Right is at least left + width
        solver.add_constraint(
            Constraint(right,
                       Constraint.EQ,
                       left + width,
                       strength=MEDIUM,
                       weight=0.0))
        solver.add_stay(iw)
        solver.add_stay(ih)

        # Propegate viewport size changes.
        for i in range(0, 30):

            # Measurement should be cheap here.
            iwv = random.randrange(MIN, MAX)
            ihv = random.randrange(MIN, MAX)

            solver.add_edit_var(iw)
            solver.add_edit_var(ih)

            with solver.edit():
                solver.suggest_value(iw, iwv)
                solver.suggest_value(ih, ihv)
                # solver.resolve()

            self.assertAlmostEqual(top.value, 0)
            self.assertAlmostEqual(left.value, 0)
            self.assertLessEqual(bottom.value, MAX)
            self.assertGreaterEqual(bottom.value, MIN)
            self.assertLessEqual(right.value, MAX)
            self.assertGreaterEqual(right.value, MIN)
    def test_quadrilateral(self):
        "A simple version of the quadrilateral test"

        solver = SimplexSolver()

        class Point(object):
            def __init__(self, identifier, x, y):
                self.x = Variable('x' + identifier, x)
                self.y = Variable('y' + identifier, y)

            def __repr__(self):
                return u'(%s, %s)' % (self.x.value, self.y.value)

            __hash__ = object.__hash__

            def __eq__(self, other):
                return self.x.value == other[0] and self.y.value == other[1]

        points = [
            Point('0', 10, 10),
            Point('1', 10, 200),
            Point('2', 200, 200),
            Point('3', 200, 10),

            Point('m0', 0, 0),
            Point('m1', 0, 0),
            Point('m2', 0, 0),
            Point('m3', 0, 0),
        ]
        midpoints = points[4:]

        # Add point stays
        weight = 1.0
        multiplier = 2.0
        for point in points[:4]:
            solver.add_stay(point.x, WEAK, weight)
            solver.add_stay(point.y, WEAK, weight)
            weight = weight * multiplier

        for start, end in [(0, 1), (1, 2), (2, 3), (3, 0)]:
            cle = (points[start].x + points[end].x) / 2
            cleq = midpoints[start].x == cle
            solver.add_constraint(cleq)
            cle = (points[start].y + points[end].y) / 2
            cleq = midpoints[start].y == cle
            solver.add_constraint(cleq)

        cle = points[0].x + 20
        solver.add_constraint(cle <= points[2].x)
        solver.add_constraint(cle <= points[3].x)

        cle = points[1].x + 20
        solver.add_constraint(cle <= points[2].x)
        solver.add_constraint(cle <= points[3].x)

        cle = points[0].y + 20
        solver.add_constraint(cle <= points[1].y)
        solver.add_constraint(cle <= points[2].y)

        cle = points[3].y + 20
        solver.add_constraint(cle <= points[1].y)
        solver.add_constraint(cle <= points[2].y)

        for point in points:
            solver.add_constraint(point.x >= 0)
            solver.add_constraint(point.y >= 0)

            solver.add_constraint(point.x <= 500)
            solver.add_constraint(point.y <= 500)

        # Check the initial answers

        self.assertEqual(points[0], (10.0, 10.0))
        self.assertEqual(points[1], (10.0, 200.0))
        self.assertEqual(points[2], (200.0, 200.0))
        self.assertEqual(points[3], (200.0, 10.0))
        self.assertEqual(points[4], (10.0, 105.0))
        self.assertEqual(points[5], (105.0, 200.0))
        self.assertEqual(points[6], (200.0, 105.0))
        self.assertEqual(points[7], (105.0, 10.0))

        # Now move point 2 to a new location

        solver.add_edit_var(points[2].x)
        solver.add_edit_var(points[2].y)

        solver.begin_edit()

        solver.suggest_value(points[2].x, 300)
        solver.suggest_value(points[2].y, 400)

        solver.end_edit()

        # Check that the other points have been moved.
        self.assertEqual(points[0], (10.0, 10.0))
        self.assertEqual(points[1], (10.0, 200.0))
        self.assertEqual(points[2], (300.0, 400.0))
        self.assertEqual(points[3], (200.0, 10.0))
        self.assertEqual(points[4], (10.0, 105.0))
        self.assertEqual(points[5], (155.0, 300.0))
        self.assertEqual(points[6], (250.0, 205.0))
        self.assertEqual(points[7], (105.0, 10.0))