Exemple #1
0
class AgentAgentInteractions(LogicNode):
    sight_soc = Float(
        default_value=3.0,
        min=0,
        help='')
    max_agent_radius = Float(
        default_value=0.3,
        min=0,
        help='')
    f_soc_max = Float(
        default_value=2e3,
        min=0,
        help='')
    cell_size = Float(
        min=0,
        help='')

    @default('cell_size')
    def _default_cell_size(self):
        return self.sight_soc + 2 * self.max_agent_radius

    def update(self):
        agents = self.simulation.agents.array
        mask = agents['active']
        agent_agent_block_list(agents, self.cell_size, mask)
Exemple #2
0
class Navigation(LogicNode):
    step = Float(
        default_value=0.1,
        min=0,
        help='Step size for meshgrid used for discretization.')
    radius = Float(
        default_value=0.5,
        min=0,
        help='')
    strength = Float(
        default_value=0.3,
        min=0, max=1,
        help='')

    def update(self):
        agents = self.simulation.agents.array
        field = self.simulation.field

        for target in range(len(field.targets)):

            #has_target = np.nonzero(agents['target'] == target)
            has_target = np.nonzero(agents['active'] & (agents['target'] == target))

            # The cognitive map for other agents
            if len(has_target) != 0:
                mgrid_f, distance_map_f, direction_map_f = field.navigation_to_target(target, self.step,
                                                                                      self.radius, self.strength)
                # Flip x and y to array index i and j
                indices_f = np.fliplr(mgrid_f.indicer(agents[has_target]['position']))
                new_direction_f = getdefault(
                    indices_f, direction_map_f, agents[has_target]['target_direction'])
                agents['target_direction'][has_target] = new_direction_f
class AvoidObstacle(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)
    exit_width = Float(
        default_value=3.0,
        min=1.0)
    ratio_obs = Float(
        default_value=0.6,
        min=0, max=1)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        domain = rectangle(0, 0, self.width, self.height)
        spawn_follower = rectangle(0, 0, self.ratio_obs * self.width, self.height)
        spawn_leader = rectangle(0, 0.5 * self.height,
                                 self.ratio_obs * self.width, 0.5 * self.height)
        target = LineString([(self.width, 0), (self.width, self.exit_width)])

        self.obstacles = (domain.exterior - target |
                          LineString([(self.ratio_obs * self.width, 0),
                                      (self.ratio_obs * self.width, 0.5 * self.height)]))
        self.targets = [target]
        self.spawns = [spawn_leader, spawn_follower]
        self.domain = domain
class Rounding(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)
    ratio = Float(
        default_value=0.6,
        min=0, max=1)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        height = self.height
        width = self.width

        domain = Polygon([(0, 0), (0, height), (width, height), (width, 0)])
        target = LineString([(0, height / 2), (0, height)])
        spawn = Polygon([(0, 0),
                         (0, height / 2),
                         (width / 2, height / 2),
                         (width / 2, 0)])

        obstacles = LineString([(0, height / 2),
                                (width * self.ratio, height / 2)]) | \
                    domain.exterior - target

        self.obstacles = obstacles
        self.targets = [target]
        self.spawns = [spawn]
        self.domain = domain
class OutdoorField(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)

    @default('domain')
    def _default_domain(self):
        return Polygon([
            (0, 0), (0, self.height),
            (self.width, self.height), (self.width, 0)
        ])

    @default('spawns')
    def _default_spawns(self):
        return [self.domain]

    @observe('width', 'height')
    def _observe_field(self, change):
        self.domain = Polygon([
            (0, 0), (0, self.height),
            (self.width, self.height), (self.width, 0)
        ])
        self.spawns = [self.domain]
Exemple #6
0
class Integrator(LogicNode):
    dt_min = Float(default_value=0.01, min=0, help='Minimum timestep')
    dt_max = Float(default_value=0.01, min=0, help='Maximum timestep')

    def update(self):
        agents = self.simulation.agents.array
        dt = velocity_verlet_integrator(agents, self.dt_min, self.dt_max)
        self.simulation.data['dt'] = dt
        self.simulation.data['time_tot'] += dt
class RoomWithOneExit(MultiAgentSimulation):
    size = Int(default_value=100, min=1)
    agent_type = Enum(default_value=Circular, values=(Circular, ThreeCircle))
    body_type = Enum(default_value='adult', values=('adult', ))
    width = Float(default_value=10.0, min=0)
    height = Float(default_value=10.0, min=0)
    exit_width = Float(default_value=1.25, min=0, max=10)
    exit_hall_width = Float(default_value=2.0, min=0)

    def attributes(self):
        orientation = 0.0
        d = dict(target=0,
                 body_type=self.body_type,
                 orientation=orientation,
                 velocity=1.0 * unit_vector(orientation),
                 angular_velocity=0.0,
                 target_direction=unit_vector(orientation),
                 target_orientation=orientation)
        return d

    @default('logic')
    def _default_logic(self):
        return Reset(self) << \
            InsideDomain(self) << (
                Integrator(self) << (
                    Fluctuation(self),
                    Adjusting(self) << (
                        Navigation(self),
                        Orientation(self)
                    ),
                    AgentAgentInteractions(self),
                    AgentObstacleInteractions(self)
                )
            )

    @default('field')
    def _default_field(self):
        return fields.RoomWithOneExit(width=self.width,
                                      height=self.height,
                                      exit_width=self.exit_width,
                                      exit_hall_width=self.exit_hall_width)

    @default('agents')
    def _default_agents(self):
        agents = Agents(agent_type=self.agent_type)

        group = AgentGroup(agent_type=self.agent_type,
                           size=self.size,
                           attributes=self.attributes)

        agents.add_non_overlapping_group(
            group, position_gen=self.field.sample_spawn(0))

        return agents
class Bar(Foo):
    b = Unicode('gotit', help="The string b.").tag(config=False)
    c = Float(help="The string c.").tag(config=True)
    bset = Set([]).tag(config=True, multiplicity='+')
    bset_values = Set([2,1,5]).tag(config=True, multiplicity='+')
    bdict = Dict().tag(config=True, multiplicity='+')
    bdict_values = Dict({1:'a','0':'b',5:'c'}).tag(config=True, multiplicity='+')
class HallwayField(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)
    ratio = Float(
        default_value=1 / 3,
        min=0, max=1)

    @default('obstacles')
    def _default_obstacles(self):
        return LineString([(0, 0), (self.width, 0)]) | \
               LineString([(0, self.height), (self.width, self.height)])

    @default('targets')
    def _default_targets(self):
        return [LineString([(0, 0), (0, self.height)]),
                LineString([(self.width, 0), (self.width, self.height)])]

    @default('spawns')
    def _default_spawns(self):
        return [rectangle(0, 0, self.ratio * self.width, self.height),
                rectangle((1 - self.ratio) * self.width, 0, self.ratio *
                          self.width, self.height)]

    @default('domain')
    def _default_domain(self):
        return self.convex_hull()

    @observe('width', 'height', 'ratio')
    def _observe_field(self, change):
        obstacles = LineString([(0, 0), (self.width, 0)]) | \
                    LineString([(0, self.height), (self.width, self.height)])
        spawn0 = rectangle(0, 0, self.ratio * self.width, self.height)
        spawn1 = rectangle((1 - self.ratio) * self.width, 0, self.ratio *
                           self.width, self.height)

        target0 = LineString([(0, 0), (0, self.height)])
        target1 = LineString([(self.width, 0), (self.width, self.height)])

        self.obstacles = obstacles
        self.spawns = [spawn0, spawn1]
        self.targets = [target0, target1]
        self.domain = self.convex_hull()
class RotationalMotion(HasTraits):
    orientation = Float(default_value=0.0,
                        min=-np.pi,
                        max=np.pi,
                        help='Orientation',
                        symbol=r'\varphi')
    angular_velocity = Float(default_value=0.0,
                             help='Angular velocity',
                             symbol=r'\omega')
    target_orientation = Float(default_value=0.0,
                               help='Target orientation',
                               symbol=r'\varphi_0')
    torque = Float(default_value=0.0, help='Torque', symbol=r'M')
    torque_prev = Float(default_value=0.0,
                        help='Previous torque',
                        symbol=r'M_{prev}')

    tau_rot = Float(
        default_value=0.2,
        min=0,
        help='Characteristic time for agent adjusting its rotational movement',
        symbol=r'\tau_{adjrot}')
    std_rand_torque = Float(default_value=0.1,
                            min=0,
                            help='Standard deviation for fluctuation torque',
                            symbol=r'\eta / I{rot}')
class AvoidPillar(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=20.0,
        min=0)
    pillar_type = Enum(
        values=('ellipse', 'rectangle'),
        default_value='ellipse')
    width_pillar = Float(
        default_value=2.0,
        min=0)
    height_pillar = Float(
        default_value=2.0,
        min=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
class Outdoor(MultiAgentSimulation):
    r"""Simulation for visualizing collision avoidance."""
    size = Int(default_value=100, min=1)
    agent_type = Enum(default_value=Circular, values=(Circular, ThreeCircle))
    body_type = Enum(default_value='adult', values=('adult', ))
    width = Float(default_value=20.0, min=0)
    height = Float(default_value=20.0, min=0)

    def attributes(self):
        orientation = np.random.uniform(-np.pi, np.pi)
        d = dict(body_type=self.body_type,
                 orientation=orientation,
                 velocity=1.0 * unit_vector(orientation),
                 angular_velocity=0.0,
                 target_direction=unit_vector(orientation),
                 target_orientation=orientation)
        return d

    @default('logic')
    def _default_logic(self):
        return Reset(self) << (Integrator(self) << (
            Fluctuation(self),
            Adjusting(self) << Orientation(self),
            AgentAgentInteractions(self),
        ))

    @default('field')
    def _default_field(self):
        return fields.OutdoorField(width=self.width, height=self.height)

    @default('agents')
    def _default_agents(self):
        agents = Agents(agent_type=self.agent_type)

        group = AgentGroup(agent_type=self.agent_type,
                           size=self.size,
                           attributes=self.attributes)

        agents.add_non_overlapping_group(
            group, position_gen=self.field.sample_spawn(0))

        return agents
class ClosedRoom(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        domain = Polygon([
            (0, 0), (0, self.height),
            (self.width, self.height), (self.width, 0)
        ])
        obstacles = domain.exterior
        spawn = domain

        self.obstacles = obstacles
        self.spawns = [spawn]
        self.domain = domain
class Bar(Foo):
    b = Unicode("gotit", help="The string b.").tag(config=False)
    c = Float(help="The string c.").tag(config=True)
    bset = Set([]).tag(config=True, multiplicity="+")
    bset_values = Set([2, 1, 5]).tag(config=True, multiplicity="+")
    bdict = Dict().tag(config=True, multiplicity="+")
    bdict_values = Dict({
        1: "a",
        "0": "b",
        5: "c"
    }).tag(config=True, multiplicity="+")
class Body(HasTraits):
    radius = Float(min=0, help='Radius', symbol='r')
    r_t = Float(min=0, help='Torso radius', symbol='r_t')
    r_s = Float(min=0, help='Shoulder radius', symbol='r_s')
    r_ts = Float(min=0,
                 help='Distance from torso to shoulder',
                 symbol='r_{ts}')
    mass = Float(min=0, help='Mass', symbol='m')
    inertia_rot = Float(min=0, help='Rotational moment', symbol='I_{rot}')
    target_velocity = Float(min=0, help='Target velocity', symbol='v_0')
    target_angular_velocity = Float(min=0,
                                    help='Target angular velocity',
                                    symbol=r'\omega_0')
class FourExitsRandomPlacing(MultiAgentSimulation):
    size = Int(default_value=100, min=0, help='Amount of herding agents')
    agent_type = Enum(default_value=Circular, values=(Circular, ThreeCircle))
    body_type = Enum(default_value='adult', values=('adult', ))
    exit_width = Float(default_value=1.25, min=0, max=10)

    def attributes(self):
        def wrapper():
            target = np.random.randint(0, len(self.field.targets))
            orientation = np.random.uniform(-np.pi, np.pi)
            d = dict(target=target,
                     body_type=self.body_type,
                     orientation=orientation,
                     velocity=np.zeros(2),
                     angular_velocity=0.0,
                     target_direction=np.zeros(2),
                     target_orientation=orientation,
                     familiar_exit=np.random.randint(0,
                                                     len(self.field.targets)))
            return d

        return wrapper

    @default('logic')
    def _default_logic(self):
        return Reset(self) << \
            InsideDomain(self) << (
                Integrator(self) << (
                    Fluctuation(self),
                    Adjusting(self) << (
                        Navigation(self),
                        Orientation(self)),
                    AgentAgentInteractions(self),
                    AgentObstacleInteractions(self)))

    @default('field')
    def _default_field(self):
        return fields.FourExitsField(exit_width=self.exit_width)

    @default('agents')
    def _default_agents(self):
        agents = Agents(agent_type=self.agent_type)

        group = AgentGroup(agent_type=self.agent_type,
                           size=self.size,
                           attributes=self.attributes())

        agents.add_non_overlapping_group(
            group,
            position_gen=self.field.sample_spawn(0),
            obstacles=geom_to_linear_obstacles(self.field.obstacles))

        return agents
class PillarInTheMiddle(Field):
    width = Float(
        default_value=20.0,
        min=0)
    height = Float(
        default_value=20.0,
        min=0)
    radius_pillar = Float(
        default_value=1.0,
        min=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        domain = rectangle(self.width / 2, self.height / 2,
                           self.width / 2, self.height / 2)
        obstacles = Point(0, 0).buffer(self.radius_pillar)

        self.domain = domain
        self.obstacles = obstacles
        self.spawns = [domain]
Exemple #18
0
class LeaderFollower(LogicNode):
    sight = Float(
        default_value=10,
        min=0,
        help='Maximum distance between agents that are accounted as neighbours '
             'that can be followed.')

    def update(self):
        agents = self.simulation.agents.array
        field = self.simulation.field

        obstacles = geom_to_linear_obstacles(field.obstacles)
        #direction = leader_follower_interaction(agents, obstacles, self.sight)
        value = leader_follower_interaction(agents, obstacles, self.sight)
Exemple #19
0
class TranslationalMotion(HasTraits):
    position = Array(default_value=(0, 0),
                     dtype=np.float64,
                     help='Position',
                     symbol=r'\mathbf{x}').valid(shape_validator(2))
    velocity = Array(default_value=(0, 0),
                     dtype=np.float64,
                     help='Velocity',
                     symbol=r'\mathbf{v}').valid(shape_validator(2))
    target_direction = Array(default_value=(0, 0),
                             dtype=np.float64,
                             help='Target direction',
                             symbol='\mathbf{\hat{e}}_0').valid(
                                 shape_validator(2), length_validator(0, 1))
    force = Array(default_value=(0, 0),
                  dtype=np.float64,
                  help='Force',
                  symbol='\mathbf{f}').valid(shape_validator(2))
    force_prev = Array(default_value=(0, 0),
                       dtype=np.float64,
                       help='Previous force',
                       symbol='\mathbf{f}_{prev}').valid(shape_validator(2))

    tau_adj = Float(
        default_value=0.5,
        min=0,
        help='Characteristic time for agent adjusting its movement',
        symbol=r'\tau_{adj}')
    k_soc = Float(default_value=1.5,
                  min=0,
                  help='Social force scaling constant',
                  symbol=r'k_{soc}')
    tau_0 = Float(default_value=3.0,
                  min=0,
                  help='Interaction time horizon',
                  symbol=r'\tau_{0}')
    mu = Float(default_value=1.2e5,
               min=0,
               help='Compression counteraction constant',
               symbol=r'\mu')
    kappa = Float(default_value=4e4,
                  min=0,
                  help='Sliding friction constant',
                  symbol=r'\kappa')
    damping = Float(default_value=500,
                    min=0,
                    help='Damping coefficient for contact force',
                    symbol=r'c_{d}')
    std_rand_force = Float(default_value=0.1,
                           min=0,
                           help='Standard deviation for fluctuation force',
                           symbol=r'\xi / m')
class RoomWithOneExit(Field):
    width = Float(
        default_value=10.0,
        min=0)
    height = Float(
        default_value=10.0,
        min=0)
    exit_width = Float(
        default_value=1.25,
        min=0, max=10)
    exit_hall_width = Float(
        default_value=2.0,
        min=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        width = self.width
        height = self.height
        exit_width = self.exit_width
        exit_hall_width = self.exit_hall_width

        self.room = LineString(
            [(width, 0), (0, 0), (0, height), (width, height)])
        self.hall = rectangle(width, (height + exit_width) / 2,
                              exit_hall_width, (height - exit_width) / 2) | \
                    rectangle(width, 0,
                              exit_hall_width, (height - exit_width) / 2)
        target = LineString(
            [(width + exit_hall_width, (height - exit_width) / 2),
             (width + exit_hall_width, (height + exit_width) / 2)])

        # Field attributes
        self.obstacles = self.room | self.hall
        self.targets = [target]
        self.spawns = [self.room.convex_hull]
        self.domain = self.convex_hull()
Exemple #21
0
class Navigation(LogicNode):
    step = Float(default_value=0.1,
                 min=0,
                 help='Step size for meshgrid used for discretization.')
    radius = Float(default_value=0.5, min=0, help='')
    strength = Float(default_value=0.3, min=0, max=1, help='')

    def update(self):
        agents = self.simulation.agents.array
        field = self.simulation.field

        for target in range(len(field.targets)):
            has_target = agents['target'] == target
            if not has_target.size:
                continue

            mgrid, distance_map, direction_map = field.navigation_to_target(
                target, self.step, self.radius, self.strength)

            # Flip x and y to array index i and j
            indices = np.fliplr(mgrid.indicer(agents[has_target]['position']))
            new_direction = getdefault(indices, direction_map,
                                       agents[has_target]['target_direction'])
            agents['target_direction'][has_target] = new_direction
Exemple #22
0
class ExitDetection(LogicNode):
    """Herding agents can detect an exit that is within exit detection range"""
    detection_range = Float(default_value=20.0, min=1.0)

    def update(self):
        agents = self.simulation.agents.array
        field = self.simulation.field

        center_door = np.stack(
            [np.mean(np.asarray(target), axis=0) for target in field.targets])
        obstacles = geom_to_linear_obstacles(field.obstacles)

        targets, has_detected = exit_detection(center_door, agents['position'],
                                               obstacles, self.detection_range)
        mask = agents['is_follower'] & has_detected
        agents['target'][mask] = targets[mask]
        agents['is_follower'][mask] = False
Exemple #23
0
class LeaderFollowerWithHerding(LogicNode):
    sight_follower = Float(
        default_value=10.0,
        min=0,
        help='Maximum distance between agents that are accounted as neighbours '
        'that can be followed.')
    size_nearest_other = Int(
        default_value=5,
        min=0,
        help='Maximum number of nearest agents inside sight_herding radius '
        'that herding agent are following.')

    # step = Float(
    #     default_value=0.05,
    #     min=0,
    #     help='Step size for meshgrid used for discretization.')
    # radius = Float(
    #     default_value=0.5,
    #     min=0,
    #     help='')
    # strength = Float(
    #     default_value=0.3,
    #     min=0, max=1,
    #     help='')

    def update(self):
        agents = self.simulation.agents.array
        field = self.simulation.field

        # FIXME: virtual obstacles add too much computational overhead
        # obstacles = geom_to_linear_obstacles(
        #     field.obstacles.buffer(0.3, resolution=3))
        obstacles = geom_to_linear_obstacles(field.obstacles)
        direction_herding = leader_follower_with_herding_interaction(
            agents, obstacles, self.sight_follower, self.size_nearest_other)
        is_follower = agents['is_follower']
        agents['target_direction'][is_follower] = direction_herding[
            is_follower]
Exemple #24
0
class TargetReached(LogicNode):
    """Detects if agents reached any of the targets in the field and updates
    count for that target.
    """
    prefix = 'target_{index}'
    epsilon = Float(
        0.02,
        min=0,
        help='Consider target reached when we are closer than this value')

    def __init__(self, simulation, *args, **kwargs):
        super().__init__(simulation, *args, **kwargs)
        self.names = []
        targets = self.simulation.field.targets[0:4] # this should be updated if more exits are set
        self.simulation.data['n_agents'] = len(self.simulation.agents.array) # store init. # of agents so base.py knows
        for index, target in enumerate(targets):
            name = self.prefix.format(index=index)
            self.names.append(name)
            self.simulation.data[name] = 0

    def update(self):
        targets = self.simulation.field.targets
        #print(targets)
        agents = self.simulation.agents.array
        mask = agents['active'] & ~agents['target_reached']
        #print(np.sum(agents['active']))
        #print(agents['target'][np.nonzero(agents['is_leader'])])

        for i in range(len(agents)):
            if not mask[i]:
                continue
            x, y = agents[i]['position']
            point = Point(x, y)
            for target, name in zip(targets, self.names):
                if point.distance(target) < self.epsilon:
                    self.simulation.data[name] += 1
                    agents['target_reached'][i] = True
                    agents['active'][i] = False
class HydraKernelProvisioner(KernelProvisionerBase):
    subkernel_name = Unicode()

    poll_interval = Float(1.0)

    @property
    def binding(self) -> "Binding":
        km: "HydraKernelManager" = self.parent
        assert hasattr(km, "binding")
        return km.binding

    async def kill(self, restart: bool = False) -> None:
        await self.send_signal(int(signal.SIGKILL))

    async def terminate(self, restart: bool = False) -> None:
        await self.send_signal(int(signal.SIGTERM))

    async def wait(self) -> "Optional[int]":
        ret = 0
        if self.has_process:
            while await self.poll() is None:
                await asyncio.sleep(self.poll_interval)

            self.reset()
        return ret

    async def cleanup(self, restart: bool = False) -> None:
        """No-op cleanup default implementation to satisfy base class."""
        pass

    def reset(self) -> None:
        """Reset the has_process state.

        In general, this function should modify the state of the provisioner
        such that has_process returns false.
        """
        pass
Exemple #26
0
class MyConfigurable(Configurable):
    a = Integer(1, help="The integer a.").tag(config=True)
    b = Float(1.0, help="The integer b.").tag(config=True)
    c = Unicode('no config')
class Bar(Foo):
    b = Unicode('gotit', help="The string b.").tag(config=False)
    c = Float(help="The string c.").tag(config=True)
    bset = Set([]).tag(config=True, multiplicity='+')
    bdict = Dict().tag(config=True, multiplicity='+')
class Bar(Foo):
    b = Unicode('gotit', help="The string b.").tag(config=False)
    c = Float(help="The string c.").tag(config=True)
class BodyType(Body):
    body_type = Unicode(help='Selected body type')
    body_types = Instance(ConfigObj,
                          help='Mapping of body type names to values')

    # Ratios of radii for shoulders and torso
    ratio_rt = Float(default_value=0,
                     min=0,
                     max=1,
                     help='Ratio between total radius and torso radius')
    ratio_rs = Float(default_value=0,
                     min=0,
                     max=1,
                     help='Ratio between total radius and shoulder radius')
    ratio_ts = Float(
        default_value=0,
        min=0,
        max=1,
        help='Ratio between total radius and distance from torso to shoulder')

    # Scales for settings values from truncated normal distribution
    # TODO: Distributions class as instance traits
    radius_mean = Float(default_value=0, min=0)
    radius_scale = Float(default_value=0, min=0)
    target_velocity_mean = Float(default_value=0, min=0)
    target_velocity_scale = Float(default_value=0, min=0)
    mass_mean = Float(default_value=0, min=0)
    mass_scale = Float(default_value=0, min=0)

    @staticmethod
    def _truncnorm(mean, abs_scale):
        """Individual value from truncnorm"""
        return np.asscalar(truncnorm(-3.0, 3.0, loc=mean, abs_scale=abs_scale))

    @default('body_types')
    def _default_body_types(self):
        return load_config(BODY_TYPES_CFG, BODY_TYPES_CFG_SPEC)

    @observe('body_type')
    def _observe_body_type(self, change):
        if change['old'] == '':
            new = change['new']
            for k, v in self.body_types[new].items():
                setattr(self, k, v)
        else:
            raise TraitError('Body type can only be set once.')

    @observe('radius_mean', 'radius_scale')
    def _observe_radius_truncnorm(self, change):
        if self.radius == 0 and self.radius_mean > 0 and self.radius_scale > 0:
            self.radius = self._truncnorm(self.radius_mean, self.radius_scale)

    @observe('radius', 'ratio_rt', 'ratio_rs', 'ratio_ts')
    def _observe_radius(self, change):
        """Set torso radius if ratio_rt changes and radius is defined or if
        radius changes and ratio_rt is defined."""
        name = change['name']
        if name == 'radius':
            if self.ratio_rt > 0:
                self.r_t = self.ratio_rt * self.radius
            if self.ratio_rs > 0:
                self.r_s = self.ratio_rs * self.radius
            if self.ratio_ts > 0:
                self.r_ts = self.ratio_ts * self.radius
        elif self.radius > 0:
            if name == 'ratio_rt':
                self.r_t = self.ratio_rt * self.radius
            elif name == 'ratio_rs':
                self.r_s = self.ratio_rs * self.radius
            elif name == 'ratio_ts':
                self.r_ts = self.ratio_ts * self.radius

    @observe('mass_mean', 'mass_scale')
    def _observe_mass_truncnorm(self, change):
        if self.mass == 0 and self.mass_mean > 0 and self.mass_scale > 0:
            self.mass = self._truncnorm(self.mass_mean, self.mass_scale)

    @observe('target_velocity_mean', 'target_velocity_scale')
    def _observe_target_velocity_truncnorm(self, change):
        if self.target_velocity == 0 and self.target_velocity_mean > 0 and self.target_velocity_scale > 0:
            self.target_velocity = self._truncnorm(self.target_velocity_mean,
                                                   self.target_velocity_scale)

    @observe('mass', 'radius')
    def _observe_inertia_rot(self, change):
        if self.inertia_rot == 0 and self.mass > 0 and self.radius > 0:
            inertia = 4.0 * np.pi
            mass = 80.0
            radius = 0.27
            self.inertia_rot = inertia * (self.mass / mass) * (self.radius /
                                                               radius)**2
class Agents(AgentsBase):
    """Set groups of agents

    Examples:
        >>> agent = Agents(agent_type=Circular)
        >>> agent.add_non_overlapping_group(...)

    """
    agent_type = Type(
        klass=AgentType,
        help='Instance of AgentType. This will be used to create attributes '
        'for the agent.')
    size_max = Int(
        allow_none=None,
        help='Maximum number of agents that can be created.None allows the '
        'size grow dynamically.')
    cell_size = Float(
        default_value=0.6,
        min=0,
        help='Cell size for block list. Value should be little over the '
        'maximum of agent radii')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.index = 0
        self.array = np.zeros(0, dtype=self.agent_type.dtype())
        # Block list for speeding up overlapping checks
        self._neighbours = MutableBlockList(cell_size=self.cell_size)

    def add_non_overlapping_group(self,
                                  speed,
                                  groupname,
                                  group,
                                  position_gen,
                                  position_iter,
                                  spawn,
                                  obstacles=None):
        """Add group of agents

        Args:
            group (AgentGroup):
            position_gen (Generator|Callable):
            obstacles (numpy.ndarray):
        """
        if self.agent_type is not group.agent_type:
            raise CrowdDynamicsException

        # resize self.array to fit new agents
        array = np.zeros(group.size, dtype=group.agent_type.dtype())
        self.array = np.concatenate((self.array, array))

        index = 0

        spawn_data = np.load("complex/spawn_complex.npy")

        while index < group.size:

            new_agent = group.members[index]

            if position_gen:
                new_agent.position = next(position_iter)
                new_agent.radius = 0.27
                new_agent.mass = 80.0
                new_agent.target_velocity = 1.15

            else:

                if groupname == "spawn_left":
                    new_agent.radius = spawn_data[index][7]
                    new_agent.mass = spawn_data[index][11]
                    #new_agent.target_velocity = spawn_data[index][13]
                    if speed == "slow":
                        new_agent.target_velocity = 0.5
                    elif speed == "fast":
                        new_agent.target_velocity = 1.55
                    new_agent.position = spawn_data[index][15]

                if groupname == "spawn_lower":
                    new_agent.radius = spawn_data[index + 50][7]
                    new_agent.mass = spawn_data[index + 50][11]
                    #new_agent.target_velocity = spawn_data[index+50][13]
                    if speed == "slow":
                        new_agent.target_velocity = 0.5
                    elif speed == "fast":
                        new_agent.target_velocity = 1.55
                    new_agent.position = spawn_data[index + 50][15]

                if groupname == "spawn_right":
                    new_agent.radius = spawn_data[index + 100][7]
                    new_agent.mass = spawn_data[index + 100][11]
                    #new_agent.target_velocity = spawn_data[index+100][13]
                    if speed == "slow":
                        new_agent.target_velocity = 0.5
                    elif speed == "fast":
                        new_agent.target_velocity = 1.55
                    new_agent.position = spawn_data[index + 100][15]

                if groupname == "spawn_upper":
                    new_agent.radius = spawn_data[index + 150][7]
                    new_agent.mass = spawn_data[index + 150][11]
                    #new_agent.target_velocity = spawn_data[index+150][13]
                    if speed == "slow":
                        new_agent.target_velocity = 0.5
                    elif speed == "fast":
                        new_agent.target_velocity = 1.55
                    new_agent.position = spawn_data[index + 150][15]

            # Agent can be successfully placed
            self.array[self.index] = np.array(new_agent)
            self._neighbours[new_agent.position] = self.index
            self.index += 1
            index += 1

        # TODO: remove agents that didn't fit from self.array
        #if self.index + 1 < self.array.size:
        #    pass

        # Array should remain contiguous
        assert self.array.flags.c_contiguous