1votos

Ecosistema en Haskell

por josejuan hace 1 año

Cuando se fijan reglas arbitrarias (como las indicadas en el enunciado) la dificultad estriba en encontrar parámetros que no colapsen el sistema. Si las reglas son sencillas, puede calcularse el EDO para obtener un equilibrio. Aquí se implementa para N especies, muerte por inanición, limite por densidad, alcance visión, velocidad, reproductivilidad, ... en el vídeo se ven los ciclos en los que los lobos agonizan, los conejos superpueblan y otra vez los lobos colonizan, conejos agonizan, etc...

Simular un ecosistema que cumpla estas seis condiciones:

1
2
3
4
5
6
7
8
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
52
53
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
-- vídeo: https://youtu.be/W-UFLh0YBf8 
{-# LANGUAGE TupleSections #-} 
import Graphics.Gloss 
import Graphics.Gloss.Interface.IO.Simulate 
import Data.Map (Map, (!)) 
import qualified Data.Map as Map 
import Control.Monad 
import Data.Fixed (mod') 
import System.Random 
import Data.List (sort) 
 
k_WORLD_SIZE = 1000.0 
 
data Kind = Caret | Rabbit | Wolf {- add more entities, configuration at bottom -} 
            deriving (Eq, Ord, Enum, Bounded, Show) 
 
data Animal = Animal { kind       :: Kind 
                     , position   :: Point 
                     , velocity   :: Point 
                     , energy     :: Float 
                     , togrowing  :: Int 
                     } deriving (Eq, Ord, Show) 
 
type World = Map Int Animal 
 
renderAnimal :: Animal → Picture 
renderAnimal (Animal k p _ e _) = Translate (fst p) (snd p) $ Pictures [Color (kColor k) $ circleSolid (logBase 1.75 e), Color (greyN 0.95) $ circle (kVision k)] 
 
addAnimal :: World → Animal → World 
addAnimal w a = if Map.null w then Map.insert 1 a w else Map.insert (1 + maximum (Map.keys w)) a w 
 
randomPos :: Animal → IO Animal 
randomPos a = do  x ← randomRIO (0, k_WORLD_SIZE) 
                  y ← randomRIO (0, k_WORLD_SIZE) 
                  η $ a { position = (x, y) } 
 
randomDir :: Animal → IO Animal 
randomDir a = do  z ← randomRIO (0, 2 × π) 
                  let (dx, dy) = velocity a 
                      r = √(dx × dx + dy × dy) 
                  η $ a { velocity = (r × cos z, r × sin z) } 
 
randomAnimal :: Kind → IO Animal 
randomAnimal k = do  v ← (kVelocity k ×) ↥ randomRIO (0.7, 1.0) 
                     e ← (kEnergy   k ×) ↥ randomRIO (0.4, 1.0) 
                     g ← randomRIO (0, kToGrow k ÷ 2) 
                     randomPos (Animal k (0,0) (v,0) e g) ↪ randomDir 
 
worldCreate :: IO World 
worldCreate = foldM add Map.empty [minBound … maxBound] 
  where add w k = foldM (λw _ → addAnimal w ↥ randomAnimal k) w [1 … kNumberOf k] 
 
worldRender :: World → IO Picture 
worldRender = η ∘ Translate (-0.5 × k_WORLD_SIZE) (-0.5 × k_WORLD_SIZE) 
                ∘ Pictures ∘ map renderAnimal 
                ∘ Map.elems 
 
worldStep :: ViewPort → Float → World → IO World 
worldStep _ dt w = do 
  let densities = Map.fromListWith (+) $ map (,0) [minBound … maxBound] ⧺ map ((,1) ∘ kind ∘ snd) (Map.assocs w) 
  foldM (simulateAnimal densities dt) w (Map.keys w) 
 
simulateAnimal :: Map Kind Int → Float → World → Int → IO World 
simulateAnimal densities dt w k = 
  case Map.lookup k w of 
    𝑁   → η w 
    𝐽 a → do 
            (a', w') ← moveAnimal dt a ↪ eatAnimal w 
            (a'', w'') ← growAnimal densities a' w' 
            if energy a'' < kMinEnergy (kind a'') 
              then η $ Map.delete k w'' 
              else η $ Map.insert k a'' $ Map.delete k w'' 
 
nears :: World → Animal → [Kind] → [(Float, Int, Animal)] 
nears w a h = sort [(d, k, v) | (k, v) ← Map.assocs w, kind v ∈ h, let d = dist a v, d ≤ kVision (kind a)] 
 
growAnimal :: Map Kind Int → Animal → World → IO (Animal, World) 
growAnimal densities a w = 
  if togrowing a < kToGrow (kind a) 
    then 
        if densities!(kind a) < kDensity (kind a) 
          then η (a { togrowing = togrowing a + 1 }, w) 
          else η (a, w) 
    else do 
           a' ← randomAnimal (kind a) 
           η (a { togrowing = 0 }, addAnimal w a') 
 
eatAnimal :: World → Animal → IO (Animal, World) 
eatAnimal w a = 
  let u = kind a 
      r = kVision u 
  in  case nears w a (kEat u) of 
        [] → η (a { energy = energy a × kConsumption u }, w) 
        ((d, k, v): _) → if d < 0.1 × r 
                           then η (a { energy = energy a + energy v }, Map.delete k w) 
                           else η (a { velocity = goTo (velocity a) (position a) (position v) }, w) 
 
goTo :: Point → Point → Point → Point 
goTo (vx, vy) (ox, oy) (dx, dy) = 
  let r = √(vx² + vy²) 
      mx = dx - ox 
      my = dy - oy 
      mr = √(mx² + my²) 
      s d = if abs d < k_WORLD_SIZE - abs d then 1 else -1 
  in  (r × mx × s mx / mr, r × my × s my / mr) 
 
dist :: Animal → Animal → Float 
dist (Animal _ (x1, y1) _ _ _) (Animal _ (x2, y2) _ _ _) = √((min dx (k_WORLD_SIZE - dx))² + (min dy (k_WORLD_SIZE - dy))²) 
  where dx = abs (x1 - x2) 
        dy = abs (y1 - y2) 
 
moveAnimal :: Float → Animal → IO Animal 
moveAnimal dt a = η $ a { position = updatePosition dt (position a) (velocity a) } 
 
updatePosition :: Float → Point → Point → Point 
updatePosition dt (x, y) (vx, vy) = ( (k_WORLD_SIZE + x + dt × vx) `mod'` k_WORLD_SIZE 
                                    , (k_WORLD_SIZE + y + dt × vy) `mod'` k_WORLD_SIZE ) 
 
main = do 
    let window = InWindow "Ecosistema" (size, size) (0, 0) 
        size   = round k_WORLD_SIZE 
    world ← worldCreate 
    simulateIO window white 30 world worldRender worldStep 
 
 
 
 
-- Parameters configuration 
kVelocity Caret  = 0 
kVelocity Rabbit = 20 
kVelocity Wolf   = 25 
 
kVision Caret  = 40 
kVision Rabbit = 60 
kVision Wolf   = 100 
 
kEnergy Caret  = 5 
kEnergy Rabbit = 30 
kEnergy Wolf   = 50 
 
kConsumption Caret  = 1.0 
kConsumption Rabbit = 0.995 
kConsumption Wolf   = 0.9975 
 
kMinEnergy Caret  = 1 
kMinEnergy Rabbit = 5 
kMinEnergy Wolf   = 20 
 
kNumberOf Caret  = 100 
kNumberOf Rabbit = 50 
kNumberOf Wolf   = 3 
 
kToGrow Caret  =  200 
kToGrow Rabbit = 1500 
kToGrow Wolf   = 7000 
 
kDensity Caret  = 500 
kDensity Rabbit = 100 
kDensity Wolf   = 20 
 
kColor  Caret  = green 
kColor  Rabbit = orange 
kColor  Wolf   = blue 
 
kEat    Caret  = [] 
kEat    Rabbit = [Caret] 
kEat    Wolf   = [Rabbit] 
1 comentario
0votos

Escrito por Germán M hace 1 año

Asombroso. Sólo faltan los hábitats y agregar ciclos de cambios externos. Mi idea con los hábitats y los ciclos era introducir fenómenos que afectaran a los seres vivos "desde afuera", para que todo no fuera sólo verlos nacer, comerse entre ellos y morir. Pero admito que las reglas son complicadas. Voy a proponer otro ecosistema con reglas más sencillas. En esta solución, el equilibrio entre aleatoriedad y estabilidad parece perfectamente logrado. Me intriga el trasfondo matemático detrás de esto y la sintaxis de este lenguaje Haskell, que es nuevo para mí. Espero llegar a desentrañar ambas cosas. ¡Mil gracias por el aporte!

Comenta la solución

Tienes que identificarte para poder publicar tu comentario.