diff --git a/README.md b/README.md
index 1540ab6..2d82af2 100644
--- a/README.md
+++ b/README.md
@@ -34,11 +34,15 @@ pip install thants
#### Single Colony
+The single colony environment follows the [Jumanji](https://github.com/instadeepai/jumanji)
+environment API, with actions provided as an array of individual
+actions:
+
```python
-from thants.envs import ThantsMonoColony
+from thants.envs import ThantsMono
import jax
-env = ThantsMonoColony(dims=(50, 50))
+env = ThantsMono(dims=(50, 50))
key = jax.random.PRNGKey(101)
state, obs = env.reset(key)
state_history = [state]
@@ -57,15 +61,15 @@ env.animate(state_history, 100, "mono_colony.gif")
#### Multi-Colony
In the multi-colony case each colony is treated independently (and can be
-different sizes), so actions and observations are list/tuples of arrays/structs.
+different sizes), so actions, observations, timesteps are list/tuples of
+arrays/structs:
```python
-from thants.envs import ThantsMultiColony
+from thants.envs import Thants
import jax
import jax.numpy as jnp
-
-env = ThantsMultiColony((50, 100))
+env = Thants((50, 100))
key = jax.random.PRNGKey(101)
state, obs = env.reset(key)
state_history = [state]
@@ -83,6 +87,9 @@ for _ in range(50):
env.animate(state_history, 100, "multi_colony.gif")
```
+Preset simple environments can be imported from `thants.envs.ThantsDual` and
+`thants.envs.ThantsQuad` with 2 and 4 colonies respectively.
+
## Environment
diff --git a/src/thants/envs/__init__.py b/src/thants/envs/__init__.py
index 1626d9a..7551fba 100644
--- a/src/thants/envs/__init__.py
+++ b/src/thants/envs/__init__.py
@@ -1,2 +1,3 @@
-from thants.envs.mono import ThantsMonoColony
-from thants.envs.multi import ThantsMultiColony
+from thants.envs.mono import ThantsMono
+from thants.envs.multi import Thants
+from thants.envs.presets import ThantsDual, ThantsQuad
diff --git a/src/thants/envs/mono.py b/src/thants/envs/mono.py
index f8caa13..a66b7e7 100644
--- a/src/thants/envs/mono.py
+++ b/src/thants/envs/mono.py
@@ -7,7 +7,7 @@
from jumanji.viewer import Viewer
from matplotlib.animation import FuncAnimation
-from thants.envs.multi import ThantsMultiColony
+from thants.envs.multi import Thants
from thants.generators.colonies.mono import (
BasicColonyGenerator,
ColonyGenerator,
@@ -20,7 +20,7 @@
from thants.types import Observations, State
-class ThantsMonoColony(Environment):
+class ThantsMono(Environment):
"""Environment with a single colony"""
def __init__(
@@ -78,7 +78,7 @@ def __init__(
"""
colony_generator = colony_generator or BasicColonyGenerator(25, 2, (5, 5))
colony_generator = SingleColonyWrapper(colony_generator)
- self.env = ThantsMultiColony(
+ self.env = Thants(
dims=dims,
colonies_generator=colony_generator,
food_generator=food_generator,
@@ -141,6 +141,10 @@ def step(
state, timestep = self.env.step(state, [actions])
return state, timestep[0]
+ @cached_property
+ def dims(self) -> tuple[int, int]:
+ return self.env.dims
+
@cached_property
def num_agents(self) -> int:
return self.env.num_agents[0]
diff --git a/src/thants/envs/multi.py b/src/thants/envs/multi.py
index 3908e0a..a893d92 100644
--- a/src/thants/envs/multi.py
+++ b/src/thants/envs/multi.py
@@ -11,8 +11,8 @@
from thants.actions import derive_actions
from thants.generators.colonies.multi import (
- BasicColoniesGenerator,
ColoniesGenerator,
+ DualBasicColoniesGenerator,
)
from thants.generators.food import BasicFoodGenerator, FoodGenerator
from thants.generators.terrain import OpenTerrainGenerator, TerrainGenerator
@@ -31,7 +31,7 @@
from thants.viewer import ThantsMultiColonyViewer
-class ThantsMultiColony(Environment):
+class Thants(Environment):
"""
Thants environment with multiple colonies
"""
@@ -95,7 +95,7 @@ def __init__(
self.deposit_food_amount = deposit_food_amount
self.signal_deposit_amount = signal_deposit_amount
self.max_steps = max_steps
- self._colonies_generator = colonies_generator or BasicColoniesGenerator(
+ self._colonies_generator = colonies_generator or DualBasicColoniesGenerator(
[25, 25], 2, (5, 5)
)
self._food_generator = food_generator or BasicFoodGenerator((5, 5), 100, 1.0)
diff --git a/src/thants/envs/presets.py b/src/thants/envs/presets.py
new file mode 100644
index 0000000..2fd2f4b
--- /dev/null
+++ b/src/thants/envs/presets.py
@@ -0,0 +1,132 @@
+from thants.envs.multi import Thants
+from thants.generators.colonies.multi import (
+ DualBasicColoniesGenerator,
+ QuadBasicColoniesGenerator,
+)
+from thants.generators.food import BasicFoodGenerator
+
+
+class ThantsDual(Thants):
+ """
+ Environment with two evenly sized and spaced rectangular colonies
+ """
+
+ def __init__(
+ self,
+ dims: tuple[int, int] = (50, 100),
+ n_agents: int = 36,
+ n_signals: int = 2,
+ nest_dims: tuple[int, int] = (5, 5),
+ food_drop_dims: tuple[int, int] = (5, 5),
+ food_drop_interval: int = 50,
+ max_steps: int = 10_000,
+ carry_capacity: float = 1.0,
+ take_food_amount: float = 0.1,
+ deposit_food_amount: float = 0.1,
+ signal_deposit_amount: float = 0.1,
+ ) -> None:
+ """
+ Initialise the environment
+
+ Parameters
+ ----------
+ dims
+ Env dimensions
+ n_agents
+ Number of agents in each colony
+ n_signals
+ Number of signal channels
+ nest_dims
+ Rectangular dimensions of each colonies nest
+ food_drop_dims
+ Rectangular dimensions of food deposits
+ food_drop_interval
+ Interval between new randomly placed food deposits
+ max_steps
+ Maximum environment steps
+ carry_capacity
+ Ant food carrying capacity
+ take_food_amount
+ Max food that can be picked up by an ant in a single step
+ deposit_food_amount
+ Max food that can be dropped by an ant in a single step
+ signal_deposit_amount
+ Amount of signal deposited in a single step
+ """
+ food_generator = BasicFoodGenerator(food_drop_dims, food_drop_interval)
+ super().__init__(
+ dims=dims,
+ colonies_generator=DualBasicColoniesGenerator(
+ n_agents=(n_agents, n_agents), n_signals=n_signals, nest_dims=nest_dims
+ ),
+ food_generator=food_generator,
+ max_steps=max_steps,
+ carry_capacity=carry_capacity,
+ take_food_amount=take_food_amount,
+ deposit_food_amount=deposit_food_amount,
+ signal_deposit_amount=signal_deposit_amount,
+ )
+
+
+class ThantsQuad(Thants):
+ """
+ Environment with four evenly sized and spaced rectangular colonies
+ """
+
+ def __init__(
+ self,
+ dims: tuple[int, int] = (100, 100),
+ n_agents: int = 36,
+ n_signals: int = 2,
+ nest_dims: tuple[int, int] = (5, 5),
+ food_drop_dims: tuple[int, int] = (5, 5),
+ food_drop_interval: int = 100,
+ max_steps: int = 10_000,
+ carry_capacity: float = 1.0,
+ take_food_amount: float = 0.1,
+ deposit_food_amount: float = 0.1,
+ signal_deposit_amount: float = 0.1,
+ ) -> None:
+ """
+ Initialise the environment
+
+ Parameters
+ ----------
+ dims
+ Env dimensions
+ n_agents
+ Number of agents in each colony
+ n_signals
+ Number of signal channels
+ nest_dims
+ Rectangular dimensions of each colonies nest
+ food_drop_dims
+ Rectangular dimensions of food deposits
+ food_drop_interval
+ Interval between new randomly placed food deposits
+ max_steps
+ Maximum environment steps
+ carry_capacity
+ Ant food carrying capacity
+ take_food_amount
+ Max food that can be picked up by an ant in a single step
+ deposit_food_amount
+ Max food that can be dropped by an ant in a single step
+ signal_deposit_amount
+ Amount of signal deposited in a single step
+ """
+ food_generator = BasicFoodGenerator(food_drop_dims, food_drop_interval)
+ super().__init__(
+ dims=dims,
+ colonies_generator=QuadBasicColoniesGenerator(
+ n_agents=(n_agents, n_agents, n_agents, n_agents),
+ n_signals=n_signals,
+ nest_dims=nest_dims,
+ ),
+ food_generator=food_generator,
+ max_steps=max_steps,
+ carry_capacity=carry_capacity,
+ take_food_amount=take_food_amount,
+ deposit_food_amount=deposit_food_amount,
+ signal_deposit_amount=signal_deposit_amount,
+ )
diff --git a/src/thants/generators/colonies/mono.py b/src/thants/generators/colonies/mono.py
index 316f04c..dbf0198 100644
--- a/src/thants/generators/colonies/mono.py
+++ b/src/thants/generators/colonies/mono.py
@@ -2,7 +2,7 @@
import chex
-from thants.generators.colonies.utils import init_colony
+from thants.generators.colonies.utils import BBox, init_colony
from thants.types import Colony
@@ -94,6 +94,5 @@ def __call__(self, dims: tuple[int, int], key: chex.PRNGKey) -> Colony:
Colony
Colony state containing ants, signals, and nest states
"""
- return init_colony(
- dims, (0, 0), dims, self.nest_dims, self.n_agents, self.n_signals
- )
+ bounds = BBox(x0=(0, 0), x1=dims)
+ return init_colony(dims, bounds, self.nest_dims, self.n_agents, self.n_signals)
diff --git a/src/thants/generators/colonies/multi.py b/src/thants/generators/colonies/multi.py
index b9548b4..2169da4 100644
--- a/src/thants/generators/colonies/multi.py
+++ b/src/thants/generators/colonies/multi.py
@@ -4,7 +4,7 @@
import chex
from thants.generators.colonies.mono import ColonyGenerator
-from thants.generators.colonies.utils import init_colony
+from thants.generators.colonies.utils import BBox, init_colonies
from thants.types import Colony
@@ -82,9 +82,9 @@ def __call__(self, dims: tuple[int, int], key: chex.PRNGKey) -> Sequence[Colony]
return [self.generator(dims, key)]
-class BasicColoniesGenerator(ColoniesGenerator):
+class DualBasicColoniesGenerator(ColoniesGenerator):
"""
- Basic generator that create 2 evenly spaced colonies
+ Basic generator that create 2 evenly spaced rectangular colonies
"""
def __init__(
@@ -122,21 +122,65 @@ def __call__(self, dims: tuple[int, int], key: chex.PRNGKey) -> Sequence[Colony]
List of initialised colonies
"""
mid = dims[1] // 2
- return [
- init_colony(
- dims,
- (0, 0),
- (dims[0], mid),
- self.nest_dims,
- self.n_agents[0],
- self.n_signals,
- ),
- init_colony(
- dims,
- (0, mid),
- (dims[0], dims[1]),
- self.nest_dims,
- self.n_agents[1],
- self.n_signals,
- ),
+ bounds = [
+ BBox(x0=(0, 0), x1=(dims[0], mid)),
+ BBox(x0=(0, mid), x1=dims),
]
+ return init_colonies(
+ dims, self.nest_dims, self.n_agents, self.n_signals, bounds
+ )
+
+
+class QuadBasicColoniesGenerator(ColoniesGenerator):
+ """
+ Basic generator that create 4 evenly spaced rectangular colonies
+ """
+
+ def __init__(
+ self,
+ n_agents: tuple[int, int, int, int],
+ n_signals: int,
+ nest_dims: tuple[int, int],
+ ) -> None:
+ """
+ Initialise a basic generator
+
+ Parameters
+ ----------
+ n_agents
+ Number of agents in each colony
+ n_signals
+ Number of colony signal-channels
+ nest_dims
+ Rectangular nest dimensions
+ """
+ self.nest_dims = nest_dims
+ super().__init__(n_agents, n_signals)
+
+ def __call__(self, dims: tuple[int, int], key: chex.PRNGKey) -> Sequence[Colony]:
+ """
+ Initialise the pair of colonies
+
+ Parameters
+ ----------
+ dims
+ Dimensions of the environment
+ key
+ JAX random key
+
+ Returns
+ -------
+ Sequence[Colony]
+ List of initialised colonies
+ """
+
+ mid = (dims[0] // 2, dims[1] // 2)
+ bounds = [
+ BBox(x0=(0, 0), x1=mid),
+ BBox(x0=mid, x1=dims),
+ BBox(x0=(mid[0], 0), x1=(dims[0], mid[1])),
+ BBox(x0=(0, mid[1]), x1=(mid[0], dims[1])),
+ ]
+ return init_colonies(
+ dims, self.nest_dims, self.n_agents, self.n_signals, bounds
+ )
diff --git a/src/thants/generators/colonies/utils.py b/src/thants/generators/colonies/utils.py
index 4c39be4..7593858 100644
--- a/src/thants/generators/colonies/utils.py
+++ b/src/thants/generators/colonies/utils.py
@@ -1,4 +1,6 @@
import math
+from dataclasses import dataclass
+from typing import Sequence
import chex
import jax.numpy as jnp
@@ -25,10 +27,17 @@ def get_rectangular_indices(rec_dims: tuple[int, int]) -> chex.Array:
return idxs
+@dataclass
+class BBox:
+ """Rectangular region bounding box"""
+
+ x0: tuple[int, int]
+ x1: tuple[int, int]
+
+
def init_colony(
dims: tuple[int, int],
- x0: tuple[int, int],
- x1: tuple[int, int],
+ bounds: BBox,
nest_dims: tuple[int, int],
n_agents: int,
n_signals: int,
@@ -40,10 +49,8 @@ def init_colony(
----------
dims
Environment dimensions
- x0
- Ids of the origin of the rectangular region
- x1
- Dimensions of the rectangular region
+ bounds
+ Bounding box of the rectangular region
nest_dims
Rectangular nest dimensions
n_agents
@@ -56,8 +63,8 @@ def init_colony(
Colony
Initialised colony
"""
- x0 = jnp.array(x0)
- x1 = jnp.array(x1)
+ x0 = jnp.array(bounds.x0)
+ x1 = jnp.array(bounds.x1)
dims = jnp.array(dims)
centre = (x0 + ((x1 - x0) // 2))[jnp.newaxis]
d = math.ceil(math.sqrt(n_agents))
@@ -79,3 +86,43 @@ def init_colony(
signals = jnp.zeros((n_signals, *dims))
return Colony(ants=ants, signals=signals, nest=nest)
+
+
+def init_colonies(
+ env_dims: tuple[int, int],
+ nest_dims: tuple[int, int],
+ n_agents: Sequence[BBox],
+ n_signals: int,
+ bounds: Sequence[BBox],
+) -> Sequence[Colony]:
+ """
+ Initialise multiple rectangular colonies
+
+ Parameters
+ ----------
+ env_dims
+ Environment dimensions
+ nest_dims
+ Rectangular nest dimensions
+ n_agents
+ Number of agents in each colony
+ n_signals
+ Number of signal channels
+ bounds
+ Bounding boxes of each colony
+
+ Returns
+ -------
+ Sequence[Colony]
+ List of initialised colonies
+ """
+ return [
+ init_colony(
+ env_dims,
+ b,
+ nest_dims,
+ n,
+ n_signals,
+ )
+ for b, n in zip(bounds, n_agents)
+ ]
diff --git a/tests/test_envs/test_mono.py b/tests/test_envs/test_mono.py
index b493fb9..1716884 100644
--- a/tests/test_envs/test_mono.py
+++ b/tests/test_envs/test_mono.py
@@ -1,31 +1,32 @@
import chex
import jax
+import jax.numpy as jnp
import pytest
from jumanji.testing.env_not_smoke import (
check_env_does_not_smoke,
check_env_specs_does_not_smoke,
)
-from thants.envs.mono import ThantsMonoColony
+from thants.envs.mono import ThantsMono
from thants.generators.colonies.mono import BasicColonyGenerator
from thants.generators.food import BasicFoodGenerator
-from thants.types import Observations
+from thants.types import Observations, State
@pytest.fixture
-def env() -> ThantsMonoColony:
- dims = (50, 50)
- colony_generator = BasicColonyGenerator(100, 2, (5, 5))
+def env() -> ThantsMono:
+ dims = (20, 20)
+ colony_generator = BasicColonyGenerator(16, 2, (5, 5))
food_generator = BasicFoodGenerator(
- (2, 2),
- 50,
+ (5, 5),
+ 5,
)
- return ThantsMonoColony(
+ return ThantsMono(
dims=dims, colony_generator=colony_generator, food_generator=food_generator
)
-def test_env_does_not_smoke(env: ThantsMonoColony) -> None:
+def test_env_does_not_smoke(env: ThantsMono) -> None:
"""Test that we can run an episode without any errors."""
env.max_steps = 20
@@ -35,6 +36,28 @@ def select_action(action_key: chex.PRNGKey, _state: Observations) -> chex.Array:
check_env_does_not_smoke(env, select_action=select_action)
-def test_env_specs_do_not_smoke(env: ThantsMonoColony) -> None:
+def test_env_specs_do_not_smoke(env: ThantsMono) -> None:
"""Test that we can access specs without any errors."""
check_env_specs_does_not_smoke(env)
+
+
+def state_checks(env: ThantsMono, state: State) -> None:
+ assert isinstance(state, State)
+ assert state.food.shape == env.dims
+ assert state.terrain.shape == env.dims
+ assert state.colonies.ants.pos.shape == (env.num_agents, 2)
+ assert state.colonies.nests.shape == env.dims
+ assert jnp.all(
+ jnp.logical_not(jnp.logical_and(state.food > 0.0, state.colonies.nests > 0))
+ )
+
+
+def test_env_outputs(key: chex.Array, env: ThantsMono) -> None:
+ state, obs = env.reset(key)
+
+ state_checks(env, state)
+
+ actions = jnp.zeros((env.num_agents,), dtype=int)
+ state, obs = env.step(state, actions)
+
+ state_checks(env, state)
diff --git a/tests/test_envs/test_multi.py b/tests/test_envs/test_multi.py
index e495801..428f797 100644
--- a/tests/test_envs/test_multi.py
+++ b/tests/test_envs/test_multi.py
@@ -1,29 +1,31 @@
import chex
-import jax.random
+import jax
+import jax.numpy as jnp
import pytest
-from thants.envs.multi import ThantsMultiColony
-from thants.generators.colonies.multi import BasicColoniesGenerator
+from thants.envs.multi import Thants
+from thants.generators.colonies.multi import DualBasicColoniesGenerator
from thants.generators.food import BasicFoodGenerator
from thants.types import Observations, State
@pytest.fixture
-def env() -> ThantsMultiColony:
- dims = (50, 100)
- colony_generator = BasicColoniesGenerator((64, 36), 2, (5, 5))
+def env() -> Thants:
+ dims = (20, 40)
+ colony_generator = DualBasicColoniesGenerator((16, 9), 2, (5, 5))
food_generator = BasicFoodGenerator(
- (2, 2),
- 50,
+ (5, 5),
+ 5,
)
- return ThantsMultiColony(
+ return Thants(
dims=dims, colonies_generator=colony_generator, food_generator=food_generator
)
-def test_env_does_not_smoke(key: chex.Array, env: ThantsMultiColony) -> None:
+def test_env_does_not_smoke(key: chex.Array, env: Thants) -> None:
"""Test that we can run an episode without any errors."""
env.max_steps = 100
+ n_steps = 50
def step(_state: State, _: None) -> tuple[State, list[Observations]]:
k1, k2 = jax.random.split(_state.key, 2)
@@ -36,9 +38,20 @@ def step(_state: State, _: None) -> tuple[State, list[Observations]]:
state, _ = env.reset(key)
- state, obs = jax.lax.scan(step, state, None, 50)
+ state, obs = jax.lax.scan(step, state, None, n_steps)
assert isinstance(state, State)
+ assert state.food.shape == env.dims
+ assert state.terrain.shape == env.dims
+ assert state.colonies.ants.pos.shape == (sum(env.num_agents), 2)
+ assert state.colonies.nests.shape == env.dims
+ assert jnp.all(
+ jnp.logical_not(jnp.logical_and(state.food > 0.0, state.colonies.nests > 0))
+ )
+
assert isinstance(obs, list)
assert len(obs) == 2
assert all([isinstance(x, Observations) for x in obs])
+
+ for n, o in zip(env.num_agents, obs):
+ assert o.ants.shape == (n_steps, n, 2, 9)
diff --git a/tests/test_envs/test_presets.py b/tests/test_envs/test_presets.py
new file mode 100644
index 0000000..219324e
--- /dev/null
+++ b/tests/test_envs/test_presets.py
@@ -0,0 +1,47 @@
+from typing import Type
+
+import chex
+import jax
+import pytest
+
+from thants.envs import ThantsDual, ThantsQuad
+from thants.types import Observations
+
+
+@pytest.mark.parametrize(
+ "env_type, dims",
+ [
+ (ThantsDual, (20, 10)),
+ (ThantsQuad, (20, 20)),
+ ],
+)
+def test_env_runs(
+ key: chex.PRNGKey,
+ env_type: Type[ThantsDual | ThantsQuad],
+ dims: tuple[int, int],
+) -> None:
+ n_steps = 10
+ n_agents = 9
+ n_signals = 2
+
+ env = env_type(dims=dims, n_agents=n_agents, nest_dims=(3, 3), n_signals=n_signals)
+
+ env.max_steps = 100
+ state, _ = env.reset(key)
+
+ def step(_state, _):
+ k = jax.random.split(_state.key, env.num_colonies)
+ actions = [jax.random.choice(_k, 9, (n,)) for _k, n in zip(k, env.num_agents)]
+ _state, timesteps = env.step(_state, actions)
+ return _state, [t.observation for t in timesteps]
+
+ final_state, obs = jax.lax.scan(step, state, None, length=n_steps)
+
+ assert isinstance(obs, list)
+ assert all([isinstance(x, Observations)] for x in obs)
+
+ for o in obs:
+ assert o.food.shape == (n_steps, n_agents, 9)
+ assert o.signals.shape == (n_steps, n_agents, n_signals, 9)
+ assert o.terrain.shape == (n_steps, n_agents, 9)
+ assert o.ants.shape == (n_steps, n_agents, env.num_colonies, 9)
diff --git a/tests/test_generators/test_food_generator.py b/tests/test_generators/test_food_generator.py
index 53bf27d..94756b2 100644
--- a/tests/test_generators/test_food_generator.py
+++ b/tests/test_generators/test_food_generator.py
@@ -1,13 +1,27 @@
+import chex
+import jax
import jax.numpy as jnp
from thants.generators.food import BasicFoodGenerator
-def test_food_generator(key) -> None:
+def test_food_generator(key: chex.PRNGKey) -> None:
env_dims = (5, 10)
+ k1, k2 = jax.random.split(key)
+
food_generator = BasicFoodGenerator((2, 2), 10, 0.5)
- food = food_generator.init(env_dims, key)
+ food = food_generator.init(env_dims, k1)
+
+ assert food.shape == env_dims
+ assert jnp.isclose(jnp.sum(food), 2.0)
+
+ food = food_generator.update(k2, 4, food)
assert food.shape == env_dims
assert jnp.isclose(jnp.sum(food), 2.0)
+
+ food = food_generator.update(k2, 9, food)
+
+ assert food.shape == env_dims
+ assert jnp.isclose(jnp.sum(food), 4.0)
diff --git a/tests/test_generators/test_mono_colony_generators.py b/tests/test_generators/test_mono_colony_generators.py
index 291af36..26013b5 100644
--- a/tests/test_generators/test_mono_colony_generators.py
+++ b/tests/test_generators/test_mono_colony_generators.py
@@ -6,8 +6,9 @@
def test_basic_colony_generator(key) -> None:
env_dims = (5, 10)
n_agents = 8
+ n_signals = 2
- colony_generator = BasicColonyGenerator(n_agents, 2, (2, 2))
+ colony_generator = BasicColonyGenerator(n_agents, n_signals, (2, 2))
colony = colony_generator(env_dims, key)
@@ -21,3 +22,5 @@ def test_basic_colony_generator(key) -> None:
assert colony.nest.shape == env_dims
assert jnp.isclose(jnp.sum(colony.nest), 4)
+ assert colony.ants.carrying.shape == (n_agents,)
+ assert colony.signals.shape == (n_signals, *env_dims)
diff --git a/tests/test_generators/test_multi_colony_generators.py b/tests/test_generators/test_multi_colony_generators.py
index 7849d43..26e0f38 100644
--- a/tests/test_generators/test_multi_colony_generators.py
+++ b/tests/test_generators/test_multi_colony_generators.py
@@ -1,23 +1,28 @@
import jax.numpy as jnp
-from thants.generators.colonies.multi import BasicColoniesGenerator
+from thants.generators.colonies.multi import DualBasicColoniesGenerator
from thants.types import Colony
-def test_colony_generator(key):
- dims = (50, 100)
- generator = BasicColoniesGenerator([25, 25], 2, (5, 5))
+def test_colony_generator(key) -> None:
+ dims = (25, 50)
+ n_agents = (25, 16)
+ generator = DualBasicColoniesGenerator(n_agents, 2, (5, 5))
colonies = generator(dims, key)
assert isinstance(colonies, list)
assert len(colonies) == 2
assert all([isinstance(c, Colony) for c in colonies])
- assert all([c.ants.pos.shape == (25, 2) for c in colonies])
+ assert all(
+ [c.ants.pos.shape == (n, 2) for c, n in zip(colonies, generator.n_agents)]
+ )
occupation = jnp.zeros(dims, dtype=int)
- pos_0 = colonies[0].ants.pos
- occupation = occupation.at[pos_0[:, 0], pos_0[:, 1]].add(1)
- pos_1 = colonies[1].ants.pos
- occupation = occupation.at[pos_1[:, 0], pos_1[:, 1]].add(1)
- assert jnp.sum(occupation) == 50
+ for n, colony in zip(generator.n_agents, colonies):
+ pos = colony.ants.pos
+ assert pos.shape == (n, 2)
+ assert jnp.sum(colony.nest) == 25
+ occupation = occupation.at[pos[:, 0], pos[:, 1]].add(1)
+
+ assert jnp.sum(occupation) == sum(n_agents)
assert jnp.max(occupation) == 1
diff --git a/tests/test_rewards.py b/tests/test_rewards.py
index 8546cba..a98a21a 100644
--- a/tests/test_rewards.py
+++ b/tests/test_rewards.py
@@ -5,7 +5,7 @@
from thants.types import Ants, Colony, State
-def test_delivered_food_rewards():
+def test_delivered_food_rewards() -> None:
dims = (3, 1)
nest = jnp.ones(dims, dtype=bool).at[0, 0].set(False)
pos = jnp.array([[0, 0], [1, 0], [2, 0]])
diff --git a/tests/test_viewer.py b/tests/test_viewer.py
new file mode 100644
index 0000000..835e6c6
--- /dev/null
+++ b/tests/test_viewer.py
@@ -0,0 +1,42 @@
+import chex
+import jax.numpy as jnp
+import pytest
+from matplotlib.animation import FuncAnimation
+from matplotlib.pyplot import Figure
+
+from thants.envs.multi import Thants
+from thants.generators.colonies.multi import DualBasicColoniesGenerator
+from thants.generators.food import BasicFoodGenerator
+
+
+@pytest.fixture
+def env() -> Thants:
+ dims = (20, 40)
+ colony_generator = DualBasicColoniesGenerator((16, 9), 2, (5, 5))
+ food_generator = BasicFoodGenerator(
+ (5, 5),
+ 5,
+ )
+ return Thants(
+ dims=dims, colonies_generator=colony_generator, food_generator=food_generator
+ )
+
+
+def test_render(monkeypatch, key: chex.PRNGKey, env: Thants) -> None:
+ monkeypatch.setattr(Figure, "show", lambda _: None)
+ state, _ = env.reset(key)
+ env.render(state)
+
+
+def test_animation(key: chex.PRNGKey, env: Thants) -> None:
+ state, _ = env.reset(key)
+
+ states = [state]
+
+ for _ in range(2):
+ actions = [jnp.zeros((n,), dtype=int) for n in env.num_agents]
+ state, _ = env.step(state, actions)
+ states.append(state)
+
+ animation = env.animate(states)
+ assert isinstance(animation, FuncAnimation)