Files
pyfa/eos/capSim.py
2014-07-30 21:23:27 -04:00

205 lines
5.7 KiB
Python

import heapq
from math import sqrt, exp
import time
DAY = 24 * 60 * 60 * 1000
def lcm(a,b):
n = a*b
while b:
a, b = b, a % b
return n / a
class CapSimulator(object):
"""Entity's EVE Capacitor Simulator"""
def __init__(self):
# simulator defaults (change in instance, not here)
self.capacitorCapacity = 100
self.capacitorRecharge = 1000
# max simulated time.
self.t_max = DAY
# take reloads into account?
self.reload = False
# stagger activations of identical modules?
self.stagger = False
# scale activation duration and capNeed to values that ease the
# calculation at the cost of accuracy?
self.scale = False
# millisecond resolutions for scaling
self.scale_resolutions = (100, 50, 25, 10)
# relevant decimal digits of capacitor for LCM period optimization
self.stability_precision = 1
def scale_activation(self, duration, capNeed):
for res in self.scale_resolutions:
mod = duration % res
if mod:
if mod > res/2.0:
mod = res-mod
else:
mod = -mod
if abs(mod) <= duration/100.0:
# only adjust if the adjustment is less than 1%
duration += mod
capNeed += float(mod)/duration * capNeed
break
return duration, capNeed
def init(self, modules):
"""prepare modules. a list of (duration, capNeed, clipSize) tuples is
expected, with clipSize 0 if the module has infinite ammo.
"""
mods = {}
for module in modules:
if module in mods:
mods[module] += 1
else:
mods[module] = 1
self.modules = mods
def reset(self):
"""Reset the simulator state"""
self.state = []
period = 1
disable_period = False
for (duration, capNeed, clipSize), amount in self.modules.iteritems():
if self.scale:
duration, capNeed = self.scale_activation(duration, capNeed)
if self.stagger:
duration = int(duration/amount)
else:
capNeed *= amount
period = lcm(period, duration)
# set clipSize to infinite if reloads are disabled unless it's
# a cap booster module.
if not self.reload and capNeed > 0:
clipSize = 0
# period optimization doesn't work when reloads are active.
if clipSize:
disable_period = True
heapq.heappush(self.state, [0, duration, capNeed, 0, clipSize])
if disable_period:
self.period = self.t_max
else:
self.period = period
def run(self):
"""Run the simulation"""
start = time.time()
self.reset()
push = heapq.heappush
pop = heapq.heappop
state = self.state
stability_precision = self.stability_precision
period = self.period
iterations = 0
capCapacity = self.capacitorCapacity
tau = self.capacitorRecharge / 5.0
cap_wrap = capCapacity # cap value at last period
cap_lowest = capCapacity # lowest cap value encountered
cap_lowest_pre = capCapacity # lowest cap value before activations
cap = capCapacity # current cap value
t_wrap = self.period # point in time of next period
t_now = t_last = 0
t_max = self.t_max
while 1:
activation = pop(state)
t_now, duration, capNeed, shot, clipSize = activation
if t_now >= t_max:
break
cap = ((1.0+(sqrt(cap/capCapacity)-1.0)*exp((t_last-t_now)/tau))**2)*capCapacity
if t_now != t_last:
if cap < cap_lowest_pre:
cap_lowest_pre = cap
if t_now == t_wrap:
# history is repeating itself, so if we have more cap now than last
# time this happened, it is a stable setup.
if cap >= cap_wrap:
break
cap_wrap = round(cap, stability_precision)
t_wrap += period
cap -= capNeed
if cap > capCapacity:
cap = capCapacity
iterations += 1
if cap < cap_lowest:
if cap < 0.0:
break
cap_lowest = cap
t_last = t_now
# queue the next activation of this module
t_now += duration
shot += 1
if clipSize:
if shot % clipSize == 0:
shot = 0
t_now += 10000 # include reload time
activation[0] = t_now
activation[3] = shot
push(state, activation)
push(state, activation)
# update instance with relevant results.
self.t = t_last
self.iterations = iterations
# calculate EVE's stability value
try:
avgDrain = reduce(float.__add__, map(lambda x: x[2]/x[1], self.state), 0.0)
self.cap_stable_eve = 0.25 * (1.0 + sqrt(-(2.0 * avgDrain * tau - capCapacity)/capCapacity)) ** 2
except ValueError:
self.cap_stable_eve = 0.0
if cap > 0.0:
# capacitor low/high water marks
self.cap_stable_low = cap_lowest
self.cap_stable_high = cap_lowest_pre
else:
self.cap_stable_low =\
self.cap_stable_high = 0.0
self.runtime = time.time()-start