Skip to content

swarmrl.agents.bechinger_models Module API Reference

Baeuerle2020

Bases: ClassicalAgent

See https://doi.org/10.1038/s41467-020-16161-4

Source code in swarmrl/agents/bechinger_models.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class Baeuerle2020(ClassicalAgent):
    """
    See https://doi.org/10.1038/s41467-020-16161-4
    """

    def __init__(
        self,
        act_force=1.0,
        act_torque=1,
        detection_radius_position=1.0,
        detection_radius_orientation=1.0,
        vision_half_angle=np.pi / 2.0,
        angular_deviation=1,
        acts_on_types: typing.List[int] = None,
    ):
        self.act_force = act_force
        self.act_torque = act_torque
        self.detection_radius_position = detection_radius_position
        self.detection_radius_orientation = detection_radius_orientation
        self.vision_half_angle = vision_half_angle
        self.angular_deviation = angular_deviation
        if acts_on_types is None:
            acts_on_types = [0]
        self.acts_on_types = acts_on_types

    def calc_action(self, colloids) -> typing.List[Action]:
        # get vector to center of mass
        actions = []
        for colloid in colloids:
            if colloid.type not in self.acts_on_types:
                actions.append(Action())
                continue
            other_colloids = [c for c in colloids if c is not colloid]
            colls_in_vision_pos = get_colloids_in_vision(
                colloid,
                other_colloids,
                vision_half_angle=self.vision_half_angle,
                vision_range=self.detection_radius_position,
            )
            if len(colls_in_vision_pos) == 0:
                # not detailed in the paper. take from previous model
                actions.append(Action())
                continue

            com = np.mean(
                np.stack([col.pos for col in colls_in_vision_pos], axis=0), axis=0
            )
            to_com = com - colloid.pos
            to_com_angle = angle_from_vector(to_com)

            # get average orientation of neighbours
            colls_in_vision_orientation = get_colloids_in_vision(
                colloid,
                other_colloids,
                vision_half_angle=self.vision_half_angle,
                vision_range=self.detection_radius_orientation,
            )

            if len(colls_in_vision_orientation) == 0:
                # not detailed in paper
                actions.append(Action())
                continue

            colls_in_vision_orientation.append(colloid)

            mean_orientation_in_vision = np.mean(
                np.stack([col.director for col in colls_in_vision_orientation], axis=0),
                axis=0,
            )
            mean_orientation_in_vision /= np.linalg.norm(mean_orientation_in_vision)

            # choose target orientation based on self.angular_deviation
            target_angle_choices = [
                to_com_angle + self.angular_deviation,
                to_com_angle - self.angular_deviation,
            ]
            target_orientation_choices = [
                vector_from_angle(ang) for ang in target_angle_choices
            ]

            angle_deviations = [
                np.arccos(np.dot(orient, mean_orientation_in_vision))
                for orient in target_orientation_choices
            ]
            target_angle = target_angle_choices[np.argmin(angle_deviations)]
            current_angle = angle_from_vector(colloid.director)
            angle_diff = target_angle - current_angle

            # take care of angle wraparound and bring difference to [-pi, pi]
            if angle_diff >= np.pi:
                angle_diff -= 2 * np.pi
            if angle_diff <= -np.pi:
                angle_diff += 2 * np.pi
            torque_z = np.sin(angle_diff) * self.act_torque

            actions.append(
                Action(force=self.act_force, torque=np.array([0, 0, torque_z]))
            )

        return actions

Lavergne2019

Bases: ClassicalAgent

See doi/10.1126/science.aau5347

Source code in swarmrl/agents/bechinger_models.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Lavergne2019(ClassicalAgent):
    """
    See doi/10.1126/science.aau5347
    """

    def __init__(
        self,
        vision_half_angle=np.pi / 2.0,
        act_force=1,
        perception_threshold=1,
        acts_on_types: typing.List[int] = None,
    ):
        self.vision_half_angle = vision_half_angle
        self.act_force = act_force
        self.perception_threshold = perception_threshold
        if acts_on_types is None:
            acts_on_types = [0]
        self.acts_on_types = acts_on_types

    def calc_action(self, colloids) -> typing.List[Action]:
        # determine perception value
        actions = []
        for colloid in colloids:
            if colloid.type not in self.acts_on_types:
                actions.append(Action())
                continue
            other_colloids = [c for c in colloids if c is not colloid]
            colls_in_vision = get_colloids_in_vision(
                colloid, other_colloids, vision_half_angle=self.vision_half_angle
            )
            perception = 0
            my_pos = np.copy(colloid.pos)
            for coll in colls_in_vision:
                dist = np.linalg.norm(my_pos - coll.pos)
                perception += 1 / (2 * np.pi * dist)

            # set activity on/off
            if perception >= self.perception_threshold:
                actions.append(Action(force=self.act_force))
            else:
                actions.append(Action())

        return actions