예제 #1
0
class LifeformsSimulation(object):
    mean_temprange = (-25.0, 50.0)
    seasons = [-1, -0.5, 0, 0.5, 1, 0.5, 0, -0.5]

    def __init__(self, gridsize):
        self._timing = Timing()

        initt = self._timing.routine('simulation setup')

        initt.start('building grid')

        self._initgrid(gridsize)

        self.tiles = {}
        for v in self._grid.faces:
            x, y, z = v
            lat = 180/pi * atan2(z, sqrt(x*x + y*y))
            lon = 180/pi * atan2(y, x)
            self.tiles[v] = Tile(lat, lon)

        for t in self.tiles.values():
            t.emptyocean(self.seafloor())
            t.climate = t.seasons = None

        initt.start('building indexes')
        self.shapes = []
        self.adj = Adjacency(self._grid)
        self._glaciationt = 0
        self.initindexes()
        self.populated = {}
        self.agricultural = set()
        self.fauna = []
        self.plants = []
        self.trees = []
        self._species = None

        initt.done()

    def _initgrid(self, gridsize):
        grid = Grid()
        while grid.size < gridsize:
            grid = Grid(grid)
            grid.populate()
        self._grid = grid

    def initindexes(self):
        self._indexedtiles = []
        for t in self.tiles.values():
            self._indexedtiles.append(t)

        self._tileadj = dict()
        for v in self._grid.faces:
            self._tileadj[self.tiles[v]] = set([self.tiles[nv] for nv in self.adj[v]])

        self._index = PointTree(dict([[self._indexedtiles[i].vector, i]
                                      for i in range(len(self._indexedtiles))]))

    def nearest(self, loc):
        return self._indexedtiles[self._index.nearest(loc)[0]]

    @property
    def grid(self):
        return self._grid

    def classify(self):
        c = climate(self.tiles, self.adj, self.seasons, self.cells, self.spin, self.tilt, temprange(self.mean_temprange, self.glaciation), self.glaciation, True, {})
        for v, tile in self.tiles.items():
            tile.climate = c[v]['classification']
            tile.seasons = c[v]['seasons']

    def settle(self):
        timing = self._timing.routine('settling species')
        timing.start('classifying climate')
        self.classify()

        self.fauna = []
        self.plants = []
        self.trees = []
        lifeformsmethod.settle(self.fauna, self.plants, self.trees, self.tiles, self.adj, timing)

        self._species = None
        timing.done()

    def species(self):
        if not self._species:
            timing = self._timing.routine('indexing species')
            types = self.fauna, self.plants, self.trees
            pops = [{} for _ in types]
            for t in range(len(types)):
                p = pops[t]
                for s in types[t]:
                    for f, ss in s.seasonalrange(len(self.seasons)).items():
                        for i in ss:
                            if f not in p:
                                p[f] = [set() for _ in self.seasons]
                            p[f][i].add(s)
            self._species = pops
            timing.done()
        return self._species

    @property
    def glaciation(self):
        return self._glaciation if hasattr(self, '_glaciation') else 0.5

    @glaciation.setter
    def glaciation(self, value):
        self._glaciation = value
        self.settle()

    @staticmethod
    def seafloor():
        return igneous.extrusive(0.5)

    def loaddata(self, data):
        random.setstate(data['random'])
        self._initgrid(data['gridsize'])
        self.spin, self.cells, self.tilt = [data[k] for k in ['spin', 'cells', 'tilt']]
        self.tiles = data['tiles']
        self.shapes = data['shapes']
        self.populated = data['population']
        self.agricultural = data['agricultural']
        self._glaciationt = data['glaciationtime']
        self.initindexes()
        self.settle()

    def load(self, filename):
        self.loaddata(Data.load(filename))

    def savedata(self):
        return Data.savedata(random.getstate(), self._grid.size, 0, self.spin, self.cells, self.tilt, None, None, None, self.tiles, self.shapes, self._glaciationt, self.populated, self.agricultural, True, True, False, [], {}, {}, [], {}, {}, [], [])

    def save(self, filename):
        Data.save(filename, self.savedata())
예제 #2
0
class PlanetSimulation(object):
    temprange = (-25.0, 50.0)

    def __init__(self, r, gridsize, spin, cells, tilt, landr, dt, atmdt, lifedt):
        """Create a simulation for a planet with the given characteristics. """

        self._timing = Timing()

        initt = self._timing.routine('simulation setup')

        self.spin, self.cells, self.tilt = spin, cells, tilt

        # max speed is 100km per million years
        self._dp = 100.0/r * dt

        self._build = dt/5.0
        self._erode = dt

        tilearea = 4 * pi * r**2

        initt.start('building grid')

        self._initgrid(gridsize)

        self.tiles = {}
        for v in self._grid.faces:
            x, y, z = v
            lat = 180/pi * atan2(z, sqrt(x*x + y*y))
            lon = 180/pi * atan2(y, x)
            self.tiles[v] = Tile(lat, lon)

        initt.start('building indexes')

        self.initindexes()

        initt.start('creating initial landmass')

        tilearea /= len(self._indexedtiles)

        # the numerator of the split probability, where
        # the number of tiles in the shape is the denomenator:
        # a 50M km^2 continent has a 50/50 chance of splitting in a given step
        self._splitnum = 25e6/tilearea

        # initial location
        p = [random.uniform(-1, 1) for i in range(3)]
        p /= norm(p)

        # 0 velocity vector
        v = (0, 0, 0)

        # orienting point
        o = [0, 0, 0]
        mini = min(range(len(p)), key=lambda i: abs(p[i]))
        o[mini] = 1 if p[mini] < 0 else -1

        shape = SphericalPolygon([rotate(rotate(p, o, landr*random.uniform(0.9,1.1)), p, th)
                                  for th in [i*pi/8 for i in range(16)]])

        self._shapes = [Group([t for t in self.tiles.values() if shape.contains(t.vector)], v)]

        # initial landmass starts at elevation based on distance from center
        c = self._indexedtiles[self._index.nearest(p)[0]]
        r2 = landr*landr

        # on land, one random tile is the center of a felsic chunk
        f = random.choice(self._shapes[0].tiles)

        for t in self._indexedtiles:
            if t in self._shapes[0].tiles:
                dc = t.distance(c)
                df = t.distance(f)

                r = igneous.extrusive(max(0.5, 1 - df*df/r2))
                h = 1 - dc*dc/r2

                t.emptyland(r, h)
            else:
                t.emptyocean(self.seafloor())

        for t in self.tiles.values():
            t.climate = t.seasons = None

        initt.done()

        self._atmosphereticks = atmdt / dt
        self._lifeticks = lifedt / dt

        self._climatemappings = {}
        self._climateprof = None

        self.dirty = True

    def _initgrid(self, gridsize):
        grid = Grid()
        while grid.size < gridsize:
            grid = Grid(grid)
            grid.populate()
        self._grid = grid

    @property
    def grid(self):
        return self._grid

    @property
    def hasatmosphere(self):
        return self._atmosphereticks == 0

    @property
    def haslife(self):
        return self._lifeticks == 0

    @staticmethod
    def seafloor():
        return igneous.extrusive(0.5)

    def initindexes(self):
        self._indexedtiles = []
        for t in self.tiles.values():
            self._indexedtiles.append(t)

        self.adj = Adjacency(self._grid)
                
        self._tileadj = dict()
        for v in self._grid.faces:
            self._tileadj[self.tiles[v]] = set([self.tiles[nv] for nv in self.adj[v]])
       
        self._index = PointTree(dict([[self._indexedtiles[i].vector, i]
                                      for i in range(len(self._indexedtiles))]))

    def nearest(self, loc):
        return self._indexedtiles[self._index.nearest(loc)[0]]

    @property
    def continents(self):
        return len(self._shapes)

    @property
    def land(self):
        return int(100.0*sum([len(s.tiles) for s in self._shapes])/len(self._indexedtiles) + 0.5)

    def loaddata(self, data):
        random.setstate(data['random'])
        self._initgrid(data['gridsize'])
        self.spin, self.cells, self.tilt = [data[k] for k in ['spin', 'cells', 'tilt']]
        self._dp = data['dp']
        self._build = data['build']
        self._splitnum = data['splitnum']
        self.tiles = data['tiles']
        self._shapes = data['shapes']
        self._atmosphereticks = data['atmt']
        self._lifeticks = data['lifet']

        self.initindexes()
        self.dirty = True

    def load(self, filename):
        self.loaddata(Data.load(filename))

    def savedata(self):
        return Data.savedata(random.getstate(), self._grid.size, 0, self.spin, self.cells, self.tilt, self._dp, self._build, self._splitnum, self.tiles, self._shapes, 0, {}, set(), self._atmosphereticks, self._lifeticks, False, [], {}, {}, [], {}, {}, [], [])

    def save(self, filename):
        Data.save(filename, self.savedata())

    def update(self):
        """Update the simulation by one timestep."""

        stept = self._timing.routine('simulation step')

        stept.start('determining tile movements')

        old = set([t for shape in self._shapes for t in shape.tiles])
        new = dict()

        overlapping = {}
        for t in self._indexedtiles:
            overlapping[t] = []

        for i in range(len(self._shapes)):
            speed = norm(self._shapes[i].v)
            group, v = move(self._indexedtiles,
                            self._shapes[i].tiles,
                            self._shapes[i].v,
                            self._tileadj,
                            self._index)
            self._shapes[i] = Group(list(group.keys()), v)
            for dest, sources in group.items():
                if dest in new:
                    new[dest].append(TileMovement(sources, speed))
                else:
                    new[dest] = [TileMovement(sources, speed)]
                overlapping[dest].append(i)

        stept.start('applying tile movements')

        collisions = {}

        newe = {}

        seen = set()
        for dest, movements in new.items():
            # get all the source tiles contributing to this one
            newe[dest] = NextTileValue(movements)
            if not dest in seen:
                try:
                    old.remove(dest)
                except KeyError:
                    # calculate the amount to build up the leading edge
                    newe[dest].build(self._build * sum([m.speed for m in movements])/len(movements))
                seen.add(dest)

            for pair in combinations(overlapping[dest], 2):
                if pair in collisions:
                    collisions[pair] += 1
                else:
                    collisions[pair] = 1

        # apply the new values
        for t, e in newe.items():
            e.apply(t)

        # clear out abandoned tiles
        for t in old:
            t.emptyocean(self.seafloor())

        # record each continent's total pre-erosion above-sea size
        heights = [sum([t.elevation for t in s.tiles]) for s in self._shapes]

        if self.hasatmosphere:
            stept.start('"simulating" climate')

            seasons = [0.1*v for v in list(range(-10,10,5)) + list(range(10,-10,-5))]
            c = climate(self.tiles, self.adj, seasons, self.cells, self.spin, self.tilt, self.temprange, 0.5, self.haslife, self._climatemappings, self._climateprof)

            if self._climateprof:
                self._climateprof.dump_stats('climate.profile')

            for v, tile in self.tiles.items():
                tile.climate = c[v]['classification']
                tile.seasons = c[v]['seasons']

            stept.start('determining erosion')

            erosion = erode(self.tiles, self.adj)

            for t in self.tiles.values():
                t.erode(erosion, self._erode)

            for t in self.tiles.values():
                # if the tile is in at least one shape, apply the erosion materials
                if len(overlapping[t]) > 0:
                    if len(erosion[t].materials) > 0:
                        t.deposit(sedimentary.deposit(erosion[t].materials, self.haslife, False, t.climate))
                # otherwise, require a certain threshold
                elif sum([m.amount for m in erosion[t].materials]) > 1.5:
                    t.deposit(sedimentary.deposit(erosion[t].materials, self.haslife, True, t.climate))
                    sourceshapes = set()
                    for e in erosion[t].sources:
                        for shape in overlapping[e]:
                            sourceshapes.add(shape)
                    for s in sourceshapes:
                        if not t in self._shapes[s].tiles:
                            self._shapes[s].tiles.append(t)
                    overlapping[t] = list(sourceshapes)
            if self._lifeticks:
                self._lifeticks -= 1
        else:
            self._atmosphereticks -= 1

        stept.start('applying isostatic effects')

        for s, h in zip(self._shapes, heights):
            dh = (h - sum([t.elevation for t in s.tiles]))/float(len(s.tiles))
            for t in s.tiles:
                t.isostasize(dh)

        stept.start('performing random intrusions')

        for t in self.tiles.values():
            if t.subduction > 0:
                if random.random() < 0.1:
                    t.intrude(igneous.intrusive(max(0, min(1, random.gauss(0.85, 0.15)))))
                    t.transform(metamorphic.contact(t.substance[-1], t.intrusion))

        stept.start('applying regional metamorphism')

        for t in self.tiles.values():
            t.transform(metamorphic.regional(t.substance[-1], t.subduction > 0))

        for t in self.tiles.values():
            t.cleartemp()

        stept.start('merging overlapping shapes')

        # merge shapes that overlap a lot
        groups = []
        for pair, count in collisions.items():
            if count > min([len(self._shapes[i].tiles) for i in pair])/10:
                for group in groups:
                    if pair[0] in group:
                        group.add(pair[1])
                        break
                    elif pair[1] in group:
                        group.add(pair[0])
                        break
                else:
                    groups.append(set(pair))

        gone = []
        for group in groups:
            largest = max(group, key=lambda i: len(self._shapes[i].tiles))
            tiles = list(self._shapes[largest].tiles)
            v = array(self._shapes[largest].v) * len(tiles)
            for other in group:
                if other != largest:
                    v += array(self._shapes[other].v) * len(self._shapes[other].tiles)
                    tiles += self._shapes[other].tiles
                    gone.append(self._shapes[other])
            self._shapes[largest].tiles = list(set(tiles))
            v /= len(tiles)
            self._shapes[largest].v = v
        for s in gone:
            self._shapes.remove(s)

        stept.start('randomly splitting shapes')

        # occaisionally split big shapes
        for i in range(len(self._shapes)):
            if random.uniform(0,1) > self._splitnum / len(self._shapes[i].tiles):
                self._shapes[i:i+1] = [Group(ts, self._shapes[i].v + v * self._dp)
                                       for ts, v in split(self._shapes[i].tiles)]

        stept.done()

        self.dirty = True
예제 #3
0
class PrehistorySimulation(object):
    coastprox = 2
    range = 6
    seasons = [-1, -0.5, 0, 0.5, 1, 0.5, 0, -0.5]
    mean_temprange = (-25.0, 50.0)
    minriverelev = 5
    minriverprecip = 0.5
    glaciationstep = 16
    anthroglacial = 6

    def __init__(self, gridsize, spin, cells, tilt):
        self._timing = Timing()

        initt = self._timing.routine('simulation setup')

        self.spin, self.cells, self.tilt = spin, cells, tilt

        initt.start('building grid')

        self._initgrid(gridsize)

        self.tiles = {}
        for v in self._grid.faces:
            x, y, z = v
            lat = 180/pi * atan2(z, sqrt(x*x + y*y))
            lon = 180/pi * atan2(y, x)
            self.tiles[v] = Tile(lat, lon)

        for t in self.tiles.values():
            t.emptyocean(self.seafloor())
            t.climate = t.seasons = None
            t.candidate = False

        initt.start('building indexes')
        self.shapes = []
        self.adj = Adjacency(self._grid)
        self._glaciationt = 0
        self.initindexes()
        self.populated = {}
        self.agricultural = set()

        initt.done()

    def _initgrid(self, gridsize):
        grid = Grid()
        while grid.size < gridsize:
            grid = Grid(grid)
            grid.populate()
        self._grid = grid

    def initindexes(self):
        self._indexedtiles = []
        for t in self.tiles.values():
            self._indexedtiles.append(t)

        self._tileadj = dict()
        for v in self._grid.faces:
            self._tileadj[self.tiles[v]] = set([self.tiles[nv] for nv in self.adj[v]])

        self._index = PointTree(dict([[self._indexedtiles[i].vector, i]
                                      for i in range(len(self._indexedtiles))]))

    def nearest(self, loc):
        return self._indexedtiles[self._index.nearest(loc)[0]]

    def newrace(self):
        # TODO make unique
        vs, cs = phonemes()
        name = output.write(random.choice(list(lexicon(vs, cs, round(random.gauss(-0.5, 1)), 0.5, 0.5, None, 1000))))
        return name[0].upper() + name[1:]

    def update(self):
        stept = self._timing.routine('simulation step')

        stept.start('identifying glaciers')
        gs = [sum([1 for t in s.tiles if t.climate and t.climate.koeppen[0] == u'E']) for s in self.shapes]

        stept.start('iterating climate')
        glaciation = 0.5 - math.cos(self._glaciationt*math.pi/self.glaciationstep)/2
        c = climate(self.tiles, self.adj, self.seasons, self.cells, self.spin, self.tilt, temprange(self.mean_temprange, glaciation), glaciation, True, {})
        for v, tile in self.tiles.items():
            tile.climate = c[v]['classification']
            tile.seasons = c[v]['seasons']
            if not habitable(tile) and tile in self.populated:
                del self.populated[tile]

        stept.start('applying isostasy')
        for s, g in zip(self.shapes, gs):
            dg = sum([1 for t in s.tiles if t.climate and t.climate.koeppen[0] == u'E']) - g
            dh = 0.6 * dg / len(s.tiles)
            for t in s.tiles:
                t.isostasize(dh)

        self._glaciationt += 1

        if not self.populated:
            stept.start('genesis')
            self.populated = eden(self.tiles, self._tileadj, self.newrace())

        stept.start('running rivers')
        rivers = run(self.tiles.values(), self._tileadj, self.minriverelev, self.minriverprecip)

        stept.start('sparking agriculture')
        gfactor = math.pow(glaciation, 2)  # Agriculture more likely in interglacial period
        for r in rivers:
            for t in r:
                if t in self.populated and t not in self.agricultural and random.random() < gfactor * agprob(t.climate.koeppen):
                    self.agricultural.add(self.populated[t])

        popcache = {}
        for i in range(self.anthroglacial):
            stept.start('migration {}'.format(i))
            if not expandpopulation(rivers, self._tileadj, self.populated, self.agricultural, self.range, self.coastprox, popcache):
                break
        stept.start('identifying distinct populations')
        racinate(self.tiles.values(), self._tileadj, self.populated, self.newrace, self.agricultural, self.range)

        stept.done()

    @property
    def grid(self):
        return self._grid

    @property
    def peoples(self):
        return len({p for p in self.populated.values()})

    @staticmethod
    def seafloor():
        return igneous.extrusive(0.5)

    def loaddata(self, data):
        random.setstate(data['random'])
        self._initgrid(data['gridsize'])
        self.spin, self.cells, self.tilt = [data[k] for k in ['spin', 'cells', 'tilt']]
        self.tiles = data['tiles']
        self.shapes = data['shapes']
        self.populated = data['population']
        self.agricultural = data['agricultural']
        self._glaciationt = data['glaciationtime']
        self.initindexes()

    def load(self, filename):
        self.loaddata(Data.load(filename))

    def savedata(self):
        return Data.savedata(random.getstate(), self._grid.size, 0, self.spin, self.cells, self.tilt, None, None, None, self.tiles, self.shapes, self._glaciationt, self.populated, self.agricultural, True, True, False, [], {}, {}, [], {}, {}, [], [])

    def save(self, filename):
        Data.save(filename, self.savedata())