forked from pyfa-org/Pyfa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
capSim.py
211 lines (157 loc) · 6.1 KB
/
capSim.py
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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)
# set clipSize to infinite if reloads are disabled unless it's
# a cap booster module.
if not self.reload and capNeed > 0:
clipSize = 0
if self.stagger:
if clipSize == 0:
duration = int(duration/amount)
else:
stagger_amount = (duration*clipSize+10000)/(amount*clipSize)
for i in range(1, amount):
heapq.heappush(self.state,
[i*stagger_amount, duration,
capNeed, 0, clipSize])
else:
capNeed *= amount
period = lcm(period, duration)
# 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