def __init__(self): self.planet = Earth() degrees = 2 self.tiles = [] for lat in range(-89, 91, degrees): r = cos(lat * pi/180) row = [] d = 2 / r lon = d/2 while lon <= 180: row = ([(lat, -lon, self.planet.sample(lat, -lon))] + row + [(lat, lon, self.planet.sample(lat, lon))]) lon += d self.tiles.append(row) try: with open(self.ADJ_CACHE, 'r') as f: res, self.adj = load(f) if res != len(self.tiles): raise Exception('Resolution mismatch') except Exception as er: print 'Cached adjacency list failed:', repr(er) self.adj = {} def addadj(t1, t2): if t1 in self.adj: adj = self.adj[t1] else: adj = [] self.adj[t1] = adj adj.append(t2) def addadjes(t1, t2): addadj(t1, t2) addadj(t2, t1) limit = 1.5 * degrees * (pi/180) for i in range(1, len(self.tiles)): for j in range(len(self.tiles[i])): c1 = self.tiles[i][j][0:2] for k in range(len(self.tiles[i-1])): if distance(c1, self.tiles[i-1][k][0:2]) < limit: addadjes((j,i),(k,i-1)) addadj((j,i),(j-1 if j > 0 else len(self.tiles[i])-1, i)) addadj((j,i),(j+1 if j < len(self.tiles[i])-1 else 0, i)) with open(self.ADJ_CACHE, 'w') as f: dump((len(self.tiles), self.adj), f, 0) xmax = max([len(self.tiles[i]) for i in range(len(self.tiles))]) dimensions = xmax, len(self.tiles) (self.direction, self.precipitation, self.convective, self.seabased, self.temperature) = [ClimateDict(dimensions) for i in range(5)] self._tilt = self._season = self._radius = self._spin = None self.dirty = True
class ClimateSimulation(object): ADJ_CACHE = '.adj.pickle' maxelevation = 9000.0 temprange = (-25.0, 50.0) sealevel = 0 breezedistance = 10 def __init__(self): self.planet = Earth() degrees = 2 self.tiles = [] for lat in range(-89, 91, degrees): r = cos(lat * pi/180) row = [] d = 2 / r lon = d/2 while lon <= 180: row = ([(lat, -lon, self.planet.sample(lat, -lon))] + row + [(lat, lon, self.planet.sample(lat, lon))]) lon += d self.tiles.append(row) try: with open(self.ADJ_CACHE, 'r') as f: res, self.adj = load(f) if res != len(self.tiles): raise Exception('Resolution mismatch') except Exception as er: print 'Cached adjacency list failed:', repr(er) self.adj = {} def addadj(t1, t2): if t1 in self.adj: adj = self.adj[t1] else: adj = [] self.adj[t1] = adj adj.append(t2) def addadjes(t1, t2): addadj(t1, t2) addadj(t2, t1) limit = 1.5 * degrees * (pi/180) for i in range(1, len(self.tiles)): for j in range(len(self.tiles[i])): c1 = self.tiles[i][j][0:2] for k in range(len(self.tiles[i-1])): if distance(c1, self.tiles[i-1][k][0:2]) < limit: addadjes((j,i),(k,i-1)) addadj((j,i),(j-1 if j > 0 else len(self.tiles[i])-1, i)) addadj((j,i),(j+1 if j < len(self.tiles[i])-1 else 0, i)) with open(self.ADJ_CACHE, 'w') as f: dump((len(self.tiles), self.adj), f, 0) xmax = max([len(self.tiles[i]) for i in range(len(self.tiles))]) dimensions = xmax, len(self.tiles) (self.direction, self.precipitation, self.convective, self.seabased, self.temperature) = [ClimateDict(dimensions) for i in range(5)] self._tilt = self._season = self._radius = self._spin = None self.dirty = True @property def tilt(self): return self._tilt @tilt.setter def tilt(self, value): if self._tilt != value: self._tilt = value self.temperature.clear() self.convective.clear() @property def season(self): return self._season @season.setter def season(self, value): if self._season != value: self._season = value self.temperature.clear() self.convective.clear() @property def radius(self): return self._radius @radius.setter def radius(self, value): if self._radius != value: self._radius = value self.direction.clear() self.convective.clear() @property def spin(self): return self._spin @spin.setter def spin(self, value): if self._spin != value: self._spin = value self.direction.clear() def insolation(self, y): theta = 2 * pi * (y - len(self.tiles)/2)/len(self.tiles)/2 theta += (self.tilt * pi/180) * self.season ins = max(0, cos(theta)) return 0.5 + (ins - 0.5) * cos(self.tilt * pi/180) def resetclimate(self, direction, temperature, convective): res = max([len(r) for r in self.tiles]), len(self.tiles) c = cells(self.radius) e2 = 2 * exp(1) for y in range(res[1]): if direction: n = abs(y + 0.5 - res[1]/2)/(float(res[1]/2)/c) n = int(n) & 1 n = n if y >= res[1]/2 else not n d = 180 - 180 * n s = self.spin ce = 2 * s * sin(2 * pi * (y - res[1]/2)/res[1]/2) d += atan2(ce, 1) * 180/pi d %= 360 if temperature: ins = self.insolation(y) for x in range(len(self.tiles[y])): if direction: self.direction[(x,y)] = d if temperature: h = self.tiles[y][x][2] t = (ins * (1-(h - self.sealevel)/(self.maxelevation - self.sealevel)) if h > self.sealevel else ins) self.temperature[(x,y)] = t if convective: p = (cos((self.tiles[y][x][0]*2*c + self.tilt*self.season)*pi/180) + 1)/2 self.convective[(x,y)] = p if direction: self.seabased[(x,y)] = None if direction: self.sadj = {} for (x,y), ns in self.adj.iteritems(): c = self.tiles[y][x][0:2] d = self.direction[(x,y)] self.sadj[(x,y)] = sorted(self.adj[(x,y)], key=lambda a: cos( (bearing(c, self.tiles[a[1]][a[0]][0:2]) - d) * pi / 180)) self._definemapping() if direction: self._seabreeze() if direction or convective: self._totalprecipitation() self.dirty = True def _seabreeze(self): frontier = [] d = 0 for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): if self.tiles[y][x][2] <= self.sealevel: self.seabased[(x,y)] = d frontier.append((x,y)) while d < self.breezedistance and frontier: d += 1 frontier = self._propagate(frontier, d) for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): seabased = self.seabased[(x,y)] if seabased is None: h = 0 else: h = ((d - seabased)/float(d))**2 p = min(1.0, h + self.convective[(x,y)]) self.seabased[(x,y)] = h def _totalprecipitation(self): d = self.breezedistance for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): p = min(1.0, self.seabased[(x,y)] + self.convective[(x,y)]) self.precipitation[(x,y)] = p def _propagate(self, sources, d): frontier = [] for s in sources: for a in [p for (p,w) in self._destmap[s]]: if self.seabased[a] is None: self.seabased[a] = d frontier.append(a) return frontier def _definemapping(self): mapping = {} dests = {} def addmap(s, d, w): if d in mapping: l = mapping[d] else: l = [] mapping[d] = l l.append((s,w)) if s in dests: l = dests[s] else: l = [] dests[s] = l l.append((d,w)) seen = set() # map destinations for every tile for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): nws = [(a, cos((bearing( self.tiles[y][x][0:2], self.tiles[a[1]][a[0]][0:2]) - self.direction[(x,y)])*pi/180)) for a in self.sadj[(x,y)]] nws = [nw for nw in nws if nw[1] > 0] for n, w in nws: addmap((x,y), n, w) seen.add(n) # map from sources for any tile that hasn't been targeted for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): if (x,y) not in seen: nws = [(a, -cos((bearing( self.tiles[y][x][0:2], self.tiles[a[1]][a[0]][0:2]) - self.direction[a])*pi/180)) for a in self.sadj[(x,y)]] nws = [nw for nw in nws if nw[1] > 0] for n, w in nws: addmap(n, (x,y), w) # normalize weights self._mapping = {} for (d, sws) in mapping.iteritems(): t = sum([w for (s, w) in sws]) self._mapping[d] = [(s, w/t) for (s,w) in sws] self._destmap = {} for (s, dws) in dests.iteritems(): t = sum([w for (d, w) in dws]) self._destmap[s] = [(d, w/t) for (d,w) in dws] def sources(self, p): return self._mapping[p] def average(self): self.update() return self._getaverage() def _getaverage(self): c = [[(0,0) for x in range(len(self.tiles[y]))] for y in range(len(self.tiles))] for y in range(len(self.tiles)): for x in range(len(self.tiles[y])): c[y][x] = (self.tiles[y][x][2], self.temperature[(x,y)], self.precipitation[(x,y)]) return c def classify(self, seasons): ss = [] for s in seasons: self.season = s ss.append(self.average()) seasons = [] for y in range(len(ss[0])): row = [] for x in range(len(ss[0][y])): row.append((ss[0][y][x][0], [ss[i][y][x][1:] for i in range(len(ss))])) seasons.append(row) return ClimateClassification(seasons, self.temprange) def update(self): d, t, c = [not dic for dic in self.direction, self.temperature, self.convective] if d or t or c: self.resetclimate(d, t, c)