Example #1
0
    def test_grads_no_error_correct_pred(self):
        """
        Case in which the prediction is correct.
        All learnable parameters should have a gradient value
        that does not cause errors with an optimizer.

        Passes if no errors occurs.
        """
        node = gen_node_at(ZERO)
        marble = gen_marble_at(ZERO)
        model = NenwinModel([node], [marble])
        model.make_timestep(1.0)
        optim = torch.optim.Adam(model.parameters())
        optim.zero_grad()

        loss_fun = NenwinLossFunction([node], model, 0, 1)

        target_index = 0
        result = loss_fun(target_index)

        try:
            result.backward()
            optim.step()
        except RuntimeError as e:
            self.fail(f"Error occurred during backprop/optim step: {e}")
Example #2
0
    def test_particle_gradients_moving_node(self):
        """
        Base case: Marble being attracted by a moving Node,
        when using Marble's variables to compute loss,
        also Node.pos should receive gradients 
        when computing backprop on the loss.
        """
        self.node = Node(pos=ZERO,
                         vel=torch.tensor([0.1]),
                         acc=ZERO,
                         mass=1,
                         attraction_function=NewtonianGravity(),
                         marble_stiffness=1,
                         node_stiffness=1,
                         marble_attraction=1,
                         node_attraction=0)
        self.model = NenwinModel([self.node], [self.marble])
        self.model.make_timestep(1.0)
        self.model.make_timestep(1.0)
        self.model.make_timestep(1.0)

        loss = 2 * self.marble.acc
        loss.backward()

        self.assertIsNotNone(self.marble.init_pos.grad)
        self.assertIsNotNone(self.node.init_pos.grad)
        self.assertIsNotNone(self.node.init_vel.grad)
        self.assertIsNotNone(self.node._PhysicalParticle__mass.grad)
Example #3
0
    def setUp(self):
        """

        Sketch:

         |
        ↑| M_1       M_2        M_3
        y|  ↓         ↓          ↓
         |            
        0| N_1       N_2        N_0
         |             
         |
         +-------------------------------
           -10         0         10   x→


        Marbles M_1 and M_2 are moving towards
        MarbleEaterNodes N_1 and N_2 respectively.
        They arrive at the same moment. 
        Only N_0 is the target output, 
        which will not receive any Marble.
        M_3 remains stationary.
        """
        self.pos_weight = 0.5
        self.vel_weight = 0.5

        self.nodes = (gen_node_at(torch.tensor([10.0, 0])),
                      gen_node_at(torch.tensor([-10.0, 0])),
                      gen_node_at(torch.tensor([0.0, 0]))
                      )
        self.marble_1 = gen_marble_at(torch.tensor([-10.0, 10.0]),
                                    vel=torch.tensor([0, -3.0]),
                                    datum="M_1")
        self.marble_2 = gen_marble_at(torch.tensor([0.0, 10.0]),
                                    vel=torch.tensor([0, -3.0]),
                                    datum="M_2")
        self.marble_3 = gen_marble_at(torch.tensor([10.0, 10.0]),
                                    vel=torch.tensor([0, 0.0]),
                                    datum="M_3")
        marbles = [self.marble_1, self.marble_2, self.marble_3]
        self.model = NenwinModel(self.nodes, marbles)

        for _ in range(50):  # Should be enough for the Marble to be eaten
            self.model.make_timestep(0.1)

        assert len(self.model.marbles) == 1, "Testcase badly designed."
        assert self.nodes[0].num_marbles_eaten == 0, "Testcase badly designed."
        

        self.loss_fun = NenwinLossFunction(self.nodes, self.model,
                                           vel_weight=self.vel_weight,
                                           pos_weight=self.pos_weight)

        self.target_index = 0
        self.wrong_node_index = 1
        self.loss = self.loss_fun(self.target_index)
Example #4
0
    def test_make_timestep_1(self):
        """
        Base base: single moving Marble, with constant velocity.
        """
        marble = Marble(ZERO, np.array([10]), ZERO, 1, None, None, 0, 0, 0, 0)
        model = NenwinModel([], [marble])

        model.make_timestep(time_passed=1)
        expected_new_pos = torch.tensor([10], dtype=torch.float)

        self.assertTrue(torch.allclose(marble.pos, expected_new_pos))
Example #5
0
    def test_eaten_marbles_disappear(self):

        # Both are generated at the smame pos, so immediately eaten.
        node = MarbleEaterNode(ZERO, ZERO, ZERO, 10, NewtonianGravity(), 1, 1,
                               1, 0, 10)
        marble = Marble(ZERO, ZERO, ZERO, 10, NewtonianGravity(), datum=None)
        model = NenwinModel([node], [marble])
        model.make_timestep(1.0)

        self.assertSetEqual(model.marbles, set([]))
        self.assertNotIn(marble, model._NenwinModel__all_particles)
Example #6
0
    def test_add_marbles_empty(self):
        """
        Corner case: add 0 new Marbles.
        """
        marble = generate_dummy_marble()
        model = NenwinModel([], set([marble]))

        new_marbles = []

        model.add_marbles(new_marbles)

        expected = set([marble])
        self.assertSetEqual(expected, model.marbles)
Example #7
0
    def setUp(self):
        """

        Sketch:

         |
        ↑| M_1                  M_3
        y|  ↓                    ↓
         |            
        0| N_1        ←M_2      N_0
         |             
         |
         +-------------------------------
           -10         0         10   x→


        Marbles M_1 and M_2 are moving towards N_1,
        but should arrive at N_0.
        M_3 arrives correctly at N_0, at the same moment.

        """
        self.pos_weight = 0.5
        self.vel_weight = 0.5

        self.nodes = (gen_node_at(torch.tensor([10.0, 0])),
                      gen_node_at(torch.tensor([-10.0, 0])))
        self.marble_1 = gen_marble_at(ZERO,
                                    vel=torch.tensor([-3.0, 0]),
                                    datum="M_1")
        self.marble_2 = gen_marble_at(torch.tensor([-10.0, 10.0]),
                                    vel=torch.tensor([0, -3.0]),
                                    datum="M_2")
        self.marble_3 = gen_marble_at(torch.tensor([10.0, 10.0]),
                                    vel=torch.tensor([0, -3.0]),
                                    datum="M_3")
        marbles = [self.marble_1, self.marble_2, self.marble_3]
        self.model = NenwinModel(self.nodes, marbles)

        for _ in range(50):  # Should be enough for the Marble to be eaten
            self.model.make_timestep(0.1)

        assert len(self.model.marbles) == 0, "Testcase badly designed."
        

        self.loss_fun = NenwinLossFunction(self.nodes, self.model,
                                           vel_weight=self.vel_weight,
                                           pos_weight=self.pos_weight)

        self.target_index = 0
        self.wrong_node_index = 1
        self.loss = self.loss_fun(self.target_index)
Example #8
0
    def test_reset(self):
        # Node and Marble have some arbitrary nonzero initial motion vars.
        node = Node(torch.tensor([1.]), torch.tensor([2.]), torch.tensor([3.]),
                    4, NewtonianGravity(), 1, 1, 1, 1)
        marble = Marble(torch.tensor([1.1]), torch.tensor([2.2]),
                        torch.tensor([3.3]), 4.4, NewtonianGravity(), None)
        model = NenwinModel([node], [marble])
        model.make_timestep(10)
        model.make_timestep(10)
        model.make_timestep(10)

        # Verify that the motion variables have changed
        self.assertFalse(check_close(node.pos, node.init_pos))
        self.assertFalse(check_close(marble.vel, marble.init_vel))
        self.assertFalse(check_close(marble.acc, marble.init_acc))

        model.reset()

        # Now they should be the original values again.
        self.assertTrue(check_close(node.pos, node.init_pos))
        self.assertTrue(check_close(node.vel, node.init_vel))
        self.assertTrue(check_close(node.acc, node.init_acc))
        self.assertTrue(check_close(marble.pos, marble.init_pos))
        self.assertTrue(check_close(marble.vel, marble.init_vel))
        self.assertTrue(check_close(marble.acc, marble.init_acc))
Example #9
0
 def test_get_params(self):
     """
     Nodes and Marbles should be registered as submodules
     of the model within the PyTorch framework,
     and hence all learnable parameters should be
     obtainable via model.
     """
     marbles = (generate_dummy_marble(), )
     nodes = (generate_dummy_node(), )
     model = NenwinModel(nodes, marbles)
     # 8 parameters per particle: init_pos, init_vel, init_acc, mass,
     # and the 4 stiffness / attraction params
     result = tuple(model.parameters())
     print(result)
     self.assertEqual(len(result), 16)
Example #10
0
    def test_get_params_add_marbles(self):
        """
       Later added marbles should not be registered as submodules.
        """
        marbles = (generate_dummy_marble(), )
        nodes = (generate_dummy_node(), )
        model = NenwinModel(nodes)

        model.add_marbles(marbles)

        # 8 parameters per particle: init_pos, init_vel, init_acc, mass,
        # and the 4 stiffness / attraction params
        result = tuple(model.parameters())
        print(result)
        self.assertEqual(len(result), 8)
Example #11
0
    def test_add_marbles(self):
        """
        Base case: add two marbles, should appear in marbles() getter.
        Old marbles should remain as well.
        """
        marble = generate_dummy_marble()
        model = NenwinModel([], set([marble]))

        new_marbles = (generate_dummy_marble(), generate_dummy_marble())

        model.add_marbles(new_marbles)

        expected = set([marble, *new_marbles])
        self.assertSetEqual(expected, model.marbles)
        self.assertSetEqual(expected, model._NenwinModel__all_particles)
Example #12
0
    def test_find_min_weighted_distance_to(self):
        """
        Base case: target particle not in Model. Weights differ.
        """
        marble_positions = (
            torch.tensor([0.0, 0]),
            torch.tensor([10.0, 10.0])
        )

        marble_velocities = (
            torch.tensor([7.5, 7.5]),
            torch.tensor([5.0, 5.0])
        )

        marbles = [gen_marble_at(pos=pos, vel=vel)
                   for pos, vel in zip(marble_positions, marble_velocities)]
        model = NenwinModel([], marbles)

        target = Marble(torch.tensor([15.0, 15.0]), ZERO, ZERO, 0, None, None)

        pos_weight = 1
        vel_weight = 2

        expected = velocity_weighted_distance(target, marbles[0],
                                              pos_weight, vel_weight)
        result = find_min_weighted_distance_to(target, model,
                                               pos_weight, vel_weight)
        torch.testing.assert_allclose(result, expected)
Example #13
0
def gen_architecture_b(
    input_placer_type: type
) -> Tuple[NenwinModel, VelInputPlacer, Tuple[Node]]:
    """
    Same as architecture A, but with four additional Nodes
    (normal Nodes, no MarbleEaterNodes) at:
        * (2.5, 2.5), (-2.5, 2.5), (2.5, -2.5), (-2.5, -2.5)

    So the normal Nodes surround the input-region, 
    and the MarbleEaterNodes are to the left and right of this.

    Returns:
        * Model holding the architecture descibed above.
        * VelInputPlacer with the input region as described above.
        * Tuple of the two MarbleEaterNodes.
    """

    eater_positions = [(-10, 0), (10, 0)]
    node_positions = [(0, -5), (-5, 0), (5, 0), (0, 5), (2.5, 2.5),
                      (-2.5, 2.5), (2.5, -2.5), (-2.5, -2.5)]

    input_region_pos = np.array((-2.5, -1))
    input_region_size = np.array((5, 2))
    mass = 1
    radius = 0.5

    attraction_function = NewtonianGravity()

    nodes = gen_nodes(attraction_function, mass, node_positions)
    eater_nodes = gen_eater_nodes(attraction_function, mass, radius,
                                  eater_positions)
    model = NenwinModel(nodes + eater_nodes)
    input_placer = input_placer_type(input_region_pos, input_region_size)

    return model, input_placer, eater_nodes
Example #14
0
 def test_repr_2(self):
     """
     Base case: no Nodes, no set initial Marbles given.
     """
     expected = "NenwinModel(set(),set())"
     result = repr(NenwinModel([]))
     self.assertEqual(expected, result)
Example #15
0
def visualization_demo():
    attract_funct = NewtonianGravity()
    marbles = set()
    marble_1 = Marble(np.array([460, 460]),
                      np.array([0, 0]),
                      np.array([0, 0]),
                      mass=20,
                      attraction_function=attract_funct,
                      datum=None,
                      marble_stiffness=1,
                      node_stiffness=0,
                      marble_attraction=0,
                      node_attraction=1)
    marble_2 = Marble(np.array([550, 550]),
                      np.array([10, 10]),
                      np.array([0, 0]),
                      mass=40,
                      attraction_function=attract_funct,
                      datum=None,
                      marble_stiffness=1,
                      node_stiffness=0,
                      marble_attraction=0,
                      node_attraction=1)
    marble_3 = Marble(np.array([450, 500]),
                      np.array([2, -2]),
                      np.array([0, 0]),
                      mass=40,
                      attraction_function=attract_funct,
                      datum=None,
                      marble_stiffness=1,
                      node_stiffness=0,
                      marble_attraction=0,
                      node_attraction=1)
    marbles = set((marble_1, marble_2, marble_3))

    node_1 = Node(np.array([500, 500]),
                  np.array([0, 0]),
                  np.array([0, 0]),
                  mass=200,
                  attraction_function=attract_funct,
                  marble_stiffness=1,
                  node_stiffness=1,
                  marble_attraction=1,
                  node_attraction=1)

    node_2 = Node(np.array([400, 400]),
                  np.array([0, 0]),
                  np.array([0, 0]),
                  mass=200,
                  attraction_function=attract_funct,
                  marble_stiffness=0.7,
                  node_stiffness=1,
                  marble_attraction=1,
                  node_attraction=1)
    nodes = set((node_1, node_2))

    model = NenwinModel(nodes, marbles)
    simulation = Simulation(model, None, None, MockPipe())
    visualization = NenwinVisualization((1500, 1000), simulation, model)
    visualization.run(10, 0.01)
Example #16
0
def gen_architecture_c(
    input_placer_type: type
) -> Tuple[NenwinModel, VelInputPlacer, Tuple[Node]]:
    """
    Generate the following architecture:
        * The input region is at (0, 0) and has size (6, 1)
            (So it has vertices {(0, 0), (0, 1), (6, 0), (6, 1)})
        * There are two MarbleEaterNodes, at (1, 4) and (5, 4)
        * There are five normal Nodes, 
            at (1, 2), (2, 3), (3, 2), (4, 3) and (5, 2).

    Returns:
        * Model holding the architecture descibed above.
        * VelInputPlacer with the input region as described above.
        * Tuple of the two MarbleEaterNodes.
    """

    eater_positions = [(1, 4), (5, 4)]
    node_positions = [(1, 2), (2, 3), (3, 2), (4, 3), (5, 2)]

    input_region_pos = np.array((0, 0))
    input_region_size = np.array((6, 1))
    mass = 1
    radius = 0.5

    attraction_function = NewtonianGravity()

    nodes = gen_nodes(attraction_function, mass, node_positions)
    eater_nodes = gen_eater_nodes(attraction_function, mass, radius,
                                  eater_positions)
    model = NenwinModel(nodes + eater_nodes)
    input_placer = input_placer_type(input_region_pos, input_region_size)

    return model, input_placer, eater_nodes
Example #17
0
    def test_find_most_promising_marble_to_2(self):
        """
        Base case: target particle not in Model. Weights differ.
        """
        marble_positions = (
            (0, 0),
            (10.0, 10.0)
        )

        marble_velocities = (
            (7.5, 7.5),
            (5.0, 5.0)
        )

        marbles = [Marble(torch.tensor(pos, dtype=torch.float),
                          torch.tensor(vel, dtype=torch.float),
                          ZERO, 0, None, None)
                   for pos, vel in zip(marble_positions, marble_velocities)]
        model = NenwinModel([], marbles)

        target = Marble(torch.tensor([15.0, 15.0]), ZERO, ZERO, 0, None, None)

        expected = marbles[0]

        pos_weight = 1
        vel_weight = 2
        result = find_most_promising_marble_to(target, model,
                                               pos_weight, vel_weight)
        self.assertIs(result, expected)
Example #18
0
    def test_find_most_promising_marble_to_1(self):
        """
        Base case: target particle not in Model. Both weights are 1.
        """
        marble_positions = (
            (0, 0),
            (10.2, 10.1),
            (5, 10),
            (12.9, 3.2),
            (9.9, -0.7)
        )

        marble_velocities = (
            (0, 0),
            (5, 5.1),
            (10, -100),
            (1.2, 1),
            (0, 0)
        )

        marbles = [Marble(torch.tensor(pos, dtype=torch.float),
                          torch.tensor(vel, dtype=torch.float),
                          ZERO, 0, None, None)
                   for pos, vel in zip(marble_positions, marble_velocities)]
        model = NenwinModel([], marbles)

        target = Marble(torch.tensor([15.0, 15.0]), ZERO, ZERO, 0, None, None)

        expected = marbles[1]

        self.assertIs(find_most_promising_marble_to(
            target, model, 1, 1), expected)
Example #19
0
 def test_nodes_getter(self):
     """
     nodes() getter should return only and all non-Marble Nodes.
     """
     marble = generate_dummy_marble()
     node = generate_dummy_node()
     model = NenwinModel(set([node]), set([marble]))
     self.assertSetEqual(set([node]), model.nodes)
Example #20
0
    def setUp(self):

        self.pos_weight = 0.5
        self.vel_weight = 0.5

        self.node = gen_node_at(ZERO)
        self.marble = gen_marble_at(torch.tensor([10., 10.]))
        self.model = NenwinModel([self.node], [self.marble])
        self.model.make_timestep(0.1)  # too short to cross the distance

        self.loss_fun = NenwinLossFunction([self.node], self.model,
                                           vel_weight=self.vel_weight,
                                           pos_weight=self.pos_weight)

        assert self.node.num_marbles_eaten == 0, "Testcase badly desgined."

        self.target_index = 0
        self.loss = self.loss_fun(self.target_index)
Example #21
0
 def test_no_initial_marbles(self):
     """
     In vitro test: should not raise error if no Marbles provided.
     """
     try:
         model = NenwinModel([])
     except:
         self.fail("Initialization of NenwinModel without initial_marbles" +
                   " should not fail.")
Example #22
0
    def setUp(self):
        self.node = Node(pos=ZERO,
                         vel=ZERO,
                         acc=ZERO,
                         mass=1,
                         attraction_function=NewtonianGravity(),
                         marble_stiffness=1,
                         node_stiffness=1,
                         marble_attraction=1,
                         node_attraction=0)
        self.marble = Marble(pos=np.array([5]),
                             vel=ZERO,
                             acc=ZERO,
                             mass=1,
                             attraction_function=ATTRACT_FUNCT,
                             datum=None)

        self.model = NenwinModel([self.node], [self.marble])
Example #23
0
    def test_error_if_no_other_marbles(self):
        """
        Cannot find closest Marble if no other Marbles exist.
        """
        target = Marble(torch.tensor([12.87, 2.9]), ZERO, ZERO, 0, None, None)
        model = NenwinModel([], [target])

        with self.assertRaises(RuntimeError):
            find_closest_marble_to(target, model)
Example #24
0
    def test_no_output(self):
        nodes = [gen_node_at(torch.tensor([10., 10]))]
        marbles = [gen_marble_at(ZERO)]
        model = NenwinModel(nodes, marbles)
        loss_fun = NenwinLossFunction(nodes, model, 0, 1)

        result = loss_fun._find_loss_case(0)
        expected = LossCases.no_prediction

        self.assertEqual(result, expected)
Example #25
0
    def setUp(self):
        """

        Sketch:

         |
        ^|
        y|
         |
        0| N_1        <M        N_0
         |
         |
         +-------------------------------
           -10         0         10   x>


        Marble M starts with moving towards N_1, but should arrive at N_0.

        """
        self.pos_weight = 0.5
        self.vel_weight = 0.5

        self.nodes = (gen_node_at(torch.tensor([10.0, 0])),
                      gen_node_at(torch.tensor([-10.0, 0])))
        self.marble = gen_marble_at(ZERO,
                                    vel=torch.tensor([-3.0, 0]),
                                    datum="original")
        self.model = NenwinModel(self.nodes, [self.marble])

        for _ in range(50):  # Should be enough for the Marble to be eaten
            self.model.make_timestep(0.1)

        assert len(self.model.marbles) == 0, "Testcase badly designed."

        self.loss_fun = NenwinLossFunction(self.nodes, self.model,
                                           vel_weight=self.vel_weight,
                                           pos_weight=self.pos_weight)

        self.target_index = 0
        self.wrong_node_index = 1
        self.loss = self.loss_fun(self.target_index)
Example #26
0
    def test_correct_output(self):
        node = gen_node_at(ZERO)
        marble = gen_marble_at(ZERO)
        model = NenwinModel([node], [marble])
        node.eat(marble)
        loss_fun = NenwinLossFunction([node], model, 0, 1)

        target_index = 0
        result = loss_fun._find_loss_case(target_index)
        expected = LossCases.correct_prediction

        self.assertEqual(result, expected)
Example #27
0
    def test_wrong_output(self):
        nodes = [gen_node_at(torch.tensor([10., 10])),
                 gen_node_at(torch.tensor([11., 11]))]
        marbles = [gen_marble_at(ZERO), gen_marble_at(torch.tensor([11., 11]))]
        model = NenwinModel(nodes, marbles)
        nodes[1].eat(marbles[1])
        loss_fun = NenwinLossFunction(nodes, model, 0, 1)

        target_index = 0  # Node at index 1 ate the Marble
        result = loss_fun._find_loss_case(target_index)
        expected = LossCases.wrong_prediction

        self.assertEqual(result, expected)
Example #28
0
    def test_make_timestep_2(self):
        """
        Base case: initial stationairy Marble, gets attracted and accelerated.
        """
        node = Node(pos=ZERO,
                    vel=ZERO,
                    acc=ZERO,
                    mass=1,
                    attraction_function=ATTRACT_FUNCT,
                    marble_stiffness=1,
                    node_stiffness=1,
                    marble_attraction=1,
                    node_attraction=0)
        marble = Marble(pos=np.array([5]),
                        vel=ZERO,
                        acc=ZERO,
                        mass=1,
                        attraction_function=ATTRACT_FUNCT,
                        datum=None)

        model = NenwinModel([node], [marble])
        time_passed = 1

        expected_pos, expected_vel = runge_kutta_4_step(marble.pos,
                                                        marble.vel,
                                                        -ATTRACT_FUNCT.value,
                                                        duration=time_passed)
        model.make_timestep(time_passed)

        self.assertTrue(torch.allclose(marble.pos, expected_pos, atol=0.01))
        self.assertTrue(torch.allclose(marble.vel, expected_vel, atol=0.01))
        self.assertTrue(
            torch.isclose(marble.acc,
                          torch.tensor(-ATTRACT_FUNCT.value),
                          atol=0.01))

        self.assertTrue(torch.allclose(node.pos, ZERO))
        self.assertTrue(torch.allclose(node.vel, ZERO))
        self.assertTrue(torch.allclose(node.acc, ZERO))
Example #29
0
    def test_grads_value_correct_pred(self):
        """
        Case in which the prediction is correct.
        All learnable parameters should have a gradient value
        that does not affect the values of the weights.
        """
        node = gen_node_at(ZERO)
        marble = gen_marble_at(torch.tensor([1.1, 1.1]))
        model = NenwinModel([node], [marble])
        for _ in range(1000):
            model.make_timestep(0.1)

        assert node.num_marbles_eaten == 1, "Testcase badly desgined."

        loss_fun = NenwinLossFunction([node], model, 0, 1)

        self.target_index = 0  # Node at index 1 ate the Marble
        result = loss_fun(self.target_index)
        result.backward(retain_graph=True)

        self.assertIsNone(marble.init_pos.grad)
        self.assertIsNone(marble.init_vel.grad)
        self.assertIsNone(marble.mass.grad)
Example #30
0
    def test_value_loss_correct_output(self):
        """
        If the prediction is correct, the loss should be 0.0.
        """
        node = gen_node_at(ZERO)
        marble = gen_marble_at(ZERO)
        model = NenwinModel([node], [marble])
        node.eat(marble)
        loss_fun = NenwinLossFunction([node], model, 0, 1)

        target_index = 0
        result = loss_fun(target_index)
        expected = 0.0

        self.assertAlmostEqual(result, expected)