存储预先计算的日出/日落数据的最佳策略?

时间:2013-01-15 17:59:42

标签: python google-app-engine python-2.7

我正在研究基于NDB的Google App Engine应用程序,该应用程序需要跟踪大量(~2000)固定位置的日/夜循环。因为纬度和经度不会改变,我可以使用像PyEphem这样的东西提前预先计算它们。我正在使用NDB。在我看来,可能的策略是:

  1. 要将一年的日出预先计算到日期时间对象中,请将其放入 将它们放入列表中,挑选列表并将其放入PickleProperty

  2. ,但将列表放入JsonProperty

  3. 使用DateTimeProperty并设置repeated = True

  4. 现在,我希望将下一个日出/日落属性编入索引,但是可以从列表中弹出并放入它自己的DateTimeProperty,这样我就可以定期使用查询来确定哪些位置已更改为周期的不同部分。整个列表不需要编入索引。

    有没有人知道这三种方法的索引和CPU负载的相对努力? repeat = True会对索引产生影响吗?

    谢谢, 戴夫

3 个答案:

答案 0 :(得分:1)

对于2000个不可变数据点 - 只需在实例启动或首次使用时计算它们,然后将其保存在内存中。这将是最便宜和最快的。

答案 1 :(得分:1)

建议“只是在实例开始时计算它们”或“预先计算这些结构并将它们输出到硬编码的python结构中”的答案似乎忽略了存储一年的日出或时间所需的时间乘数365乘数 - 如果在实例启动时完成计算,则为2000乘数。使用pyEphem,2000日出和日落需要两秒多的时间来计算。在源代码中为2000个位置存储一年的日出和日落可能会使用超过20兆字节。如果数字被有效地腌制,则需要2 * 365 * 2000 * 8 = 11,680,000字节。

更快更好地工作的方法是在一个位置设置一个最小二乘模型,而不是其他位置。如下所述,这使得总空间减少了大约70倍。

首先,如果A点和B点位于同一纬度且具有相似的高度和水平参数,则A处的日出发生在B处的恒定时间偏移 vs 日出。例如,如果A在B以西15度,一小时后在A处发生日出而在B处发生日出。其次,如果点A,B,C处于相同的经度和低纬度,则一点处的日出时间可以相当准确地计算为另外两个的线性组合。在高纬度或更高的精度,可以使用几个时间曲线的线性组合。第三,3月20日spring equinox日A点的日出时间可以用作归一化点,因此所有计算都可以归一化到相同的纬度。

下表显示了使用四种时间曲线的线性组合的精确度结果。对于距离赤道最远46°的经度,结果保持在半秒左右。对于48°至60°,结果保持在5秒内。在64°时,结果可能误差最多两分钟,而在65°,最多约六分钟。但这些时间对于大多数实际目的来说可能已经足够了。注意,在66°时,下面显示的程序会崩溃,因为它不会处理pyEphem引发的异常; “AlwaysUpError:'太阳'在2013/6/14 07:20:15仍然高于地平线”,即使66°低于Arctic Circle,66.5622°N。

很容易修改程序,以便它根据需要使用尽可能多的时间曲线(参见程序中的各种lata = ...语句),提供所需的精度,但代价是存储更多曲线和更多系数。当然,可以改变模型以使用时间曲线的子集;例如,可以存储10条曲线,并根据纬度最接近任何给定目标纬度的4进行计算。但是,对于这个演示程序,没有这样的改进。

Lat.  0.0:  Error range: -0.000000 to 0.000000 seconds
Lat.  5.0:  Error range: -0.370571 to 0.424092 seconds
Lat. 10.0:  Error range: -0.486193 to 0.557997 seconds
Lat. 15.0:  Error range: -0.414288 to 0.477041 seconds
Lat. 20.0:  Error range: -0.213614 to 0.247057 seconds
Lat. 25.0:  Error range: -0.065826 to 0.056358 seconds
Lat. 30.0:  Error range: -0.382425 to 0.323623 seconds
Lat. 35.0:  Error range: -0.585914 to 0.488351 seconds
Lat. 40.0:  Error range: -0.490303 to 0.400563 seconds
Lat. 45.0:  Error range: -0.164706 to 0.207415 seconds
Lat. 47.0:  Error range: -0.590103 to 0.756647 seconds
Lat. 48.0:  Error range: -0.852844 to 1.102608 seconds
Lat. 50.0:  Error range: -1.478688 to 1.940351 seconds
Lat. 55.0:  Error range: -3.342506 to 4.696076 seconds
Lat. 60.0:  Error range: -0.000002 to 0.000003 seconds
Lat. 61.0:  Error range: -7.012057 to 4.273954 seconds
Lat. 62.0:  Error range: -21.374033 to 12.347188 seconds
Lat. 63.0:  Error range: -51.872753 to 27.853411 seconds
Lat. 64.0:  Error range: -124.000365 to 59.661029 seconds
Lat. 65.0:  Error range: -351.425224 to 139.656187 seconds

使用上述方法,对于2000个位置中的每一个,您需要存储五个浮点数:3月20日的日出时间和四个时间曲线的四个乘数系数。 (前面提到的70倍减少来自每个位置存储5个数字,而不是365个数字。)对于每个时间曲线,存储365个数字,条目 i 是日出时间差 vs 3月20日。存储四条时间曲线使用1/500的空间来存储2000条曲线,因此曲线存储空间由乘数系数控制。

在我给出使用scipy.optimize.leastsq来解决系数的程序之前,这里有两个代码片段,可以在ipython解释器中用来制作精度表并绘制用于可视化错误的图。

import sunrise as sr
for lat in range(0, 65, 5):
    sr.lsr(lat, -110, 2013, 4)

以上显示了前面显示的大部分错误表。 lsr的第三个参数称为daySkip,值{4使lsr每四天工作一次(即一年中仅约90天),以便加快测试速度。使用sr.lsr(lat, -110, 2013, 1)会产生类似的结果,但需要四倍的时间。

sr.plotData(15,1./(24*3600))

上面告诉sunrise.plotData绘制所有内容(要近似的日出数据;模型的结果近似值;残差,缩放为秒数;以及基数曲线。)

该计划如下所示。请注意,它主要针对北半球的经度进行了测试。如果时间曲线足够对称,则该程序将按原样处理南半球的经度;如果误差太大,可以将南半球经度添加到基数曲线中,或者可以改变模型以使用赤道以南的一组单独的曲线。请注意,此程序中不计算日落。对于日落,请添加类似于next_setting(ephem.Sun())调用的previous_rising(ephem.Sun())个调用,并存储其他四个时间曲线。

#!/usr/bin/python
import ephem, numpy, scipy, scipy.optimize

# Make a set of observers (observation points)
def observers(lata, lona):
    def makeIter(x):
        if hasattr(x, '__iter__'):
            return x
        return [x]
    lata, lona = makeIter(lata), makeIter(lona)
    arr = []
    for lat in lata:
        for lon in lona:
            o = ephem.Observer()
            o.lat, o.lon, o.elevation, o.temp = str(lat), str(lon), 1400, 0
            arr.append(o)
    return tuple(arr)

# Make a year of data for an observer, equinox-relative
def riseData(observr, year, skip):
    yy = ephem.Date('{} 00:00'.format(year))
    rr = numpy.arange(0.0, 366.0, skip)
    springEquinox = 78
    observr.date = ephem.Date(yy + springEquinox)
    seDelta = observr.previous_rising(ephem.Sun()) - yy - springEquinox + 1
    for i, day in enumerate(range(0, 366, skip)):
        observr.date = ephem.Date(yy + day)
        t = observr.previous_rising(ephem.Sun()) - yy - day + 1 - seDelta
        rr[i] = t
    return numpy.array(rr)

# Make a set of time curves
def makeRarSet(lata, lona, year, daySkip):
    rar=[]
    for o in observers(lata, lona):
        r = riseData(o, year, daySkip)
        rar.append(r)
    x = numpy.arange(0., 366., daySkip)
    return (x, rar)

# data() is an object that stores curves + results
def data(s):
    return data.ss[s]

# Initialize data.ss
def setData(lata, lona, year, daySkip):
    x, rar = makeRarSet(lata, lona, year, daySkip)
    data.ss = rar

# Compute y values from model, given vector x and given params in p
def yModel(x, p):
    t = numpy.zeros(len(x))
    for i in range(len(p)):
        t += p[i] * data(i)
    return t

# Compute residuals, given params in p and data in x, y vectors.
# x = independent var,  y = dependent = observations
def residuals(p, y, x):
    err = y - yModel(x, p)
    return err

# Compute least squares result
def lsr(lat, lon, year, daySkip):
    latStep = 13.
    lata = numpy.arange(0., 66.4, latStep)
    lata = [ 88 * (1 - 1.2**-i) for i in range(8)]
    l, lata, lstep, ldown = 0, [], 20, 3
    l, lata, lstep, ldown = 0, [], 24, 4
    while l < 65:
        lata.append(l); l += lstep; lstep -= ldown
    #print 'lata =', lata
    setData(lata, lon, year, daySkip)
    x, ya = makeRarSet(lat, lon, year, daySkip)
    x, za = makeRarSet(lat, 10+lon, year, daySkip)
    data.ss.append(za[0])
    y = ya[0]
    pini = [(0 if abs(lat-l)>latStep else 0.5) for l in lata]
    pars = scipy.optimize.leastsq(residuals, pini, args=(y, x))
    data.x, data.y, data.pv = x, y, yModel(x, pars[0])
    data.par, data.err = pars, residuals(pars[0], y, x)
    #print 'pars[0] = ', pars[0]
    variance = numpy.inner(data.err, data.err)/len(y)
    #print 'variance:', variance
    sec = 1.0/(24*60*60)
    emin, emax = min(data.err), max(data.err)
    print ('Lat. {:4.1f}:  Error range: {:.6f} to {:.6f} seconds'.format(lat, emin/sec, emax/sec))

def plotData(iopt, emul):
    import matplotlib.pyplot as plt
    plt.clf()
    x = data.x
    if iopt == 0:
        iopt = 15
        emul = 1
    if iopt & 1:
        plt.plot(x, data.y)
        plt.plot(x, data.y + 0.001)
        plt.plot(x, data.y - 0.001)
    if iopt & 2:
        plt.plot(x, data.pv)
    if iopt & 4:
        plt.plot(x, emul*data.err)
    if iopt & 8:
        for ya in data.ss:
            plt.plot(x, ya)
    plt.show()

答案 2 :(得分:0)

我想说预先计算这些结构并将它们输出到你保存在生成​​的python文件中的硬编码python结构中。

只需将这些结构作为实例启动的一部分读入内存。

根据您的描述,没有理由在运行时计算这些值,并且没有理由将其存储在数据存储区中,因为它有与之相关的成本,以及RPC的一些延迟。