Alright, I was able to finish the code while commuting on the bus. Here it is.
#supremum of n in vector
sup <- function(n, vector) return(min(vector[vector>=n]))
#infimum of n in vector
inf <- function(n, vector) return(max(vector[vector<=n]))
#for this specific application we want a particular form of rounding where every other 0.5 is rounded up
#and every other 0.5 is rounded down
special_round <- function(vector){
flipflop <- 0
for(x in 1:length(vector)){
if ((vector[x] - floor(vector[x])) == 0.5){
if (flipflop %% 2 == 0){
vector[x] <- ceiling(vector[x])
flipflop <- flipflop + 1
} else {
vector[x] <- floor(vector[x])
flipflop <- flipflop + 1
}
}
}
return(round(vector,0))
}
#the math behind this tool is given in https://fractalsoftworks.com/forum/index.php?topic=25536.75
#general parameters
timelimit <- 500
#delta means how long instantaneous firing takes to happen. Do not set too low to avoid calculation issues,
#or too high to avoid distorting results
delta <- 0.001
firing_cycle <- function(chargeup,chargedown,burstsize,burstdelay){
#0. use the same notation as in the mathematical proof
a1 <- chargeup
#cooldown
a <- chargeup+chargedown
b <- burstsize
#1. calculate z
if (burstdelay==0) z <- delta/burstsize else z <- burstdelay
#2. calculate cycle length
c <- a+b*z
#3. deal with trivial degenerate cases
#3.1. the firing cycle is exactly 1 second
if (c==1){
vector <- vector(mode="double", length=timelimit)
for (x in 1:floor(a1)) vector[x] <- 0
vector[ceiling(a1)] <- (a1-floor(a1))*b
for (x in ((ceiling(a1)+1):timelimit)) vector[x] <- b
return(special_round(vector))
}
#4. calculate how long the firing cycle should be. If we can reach an even number
#in less than timelimit, use that, else timelimit
cycleremainder <- c - floor(c)
cyclelength <- 0
if (cycleremainder==0) cyclelength <- c
if (cycleremainder > 0) cyclelength <- c*1/cycleremainder
cyclelength <- max(timelimit, cyclelength)
#5. calculate lower points, midpoints and upper points for the geometric integral
M <- vector(mode="double", length=0)
m <- 0
index <- 0
while(m < cyclelength){
m <- a1+b*z/2+index*c
M <- c(M,m)
index <- index+1
}
L <- M-b*z/2
U <- M+b*z/2
vector <- vector(mode="double", length=cyclelength)
#6. case c > 1
if (c>1){
for (x in 1:cyclelength){
m <- sup(x-1, M)
if (length(U[U <= x]) != 0) u <- sup(m, U) else u <- min(U)
l <- inf(m, L)
#if there is a midpoint in the interval
mpresent <- 0
if (m>=x-1 & m<=x) mpresent <- 1
if (mpresent==1) vector[x] <- min(x,u)-max(x-1,l)
#if there is no midpoint in the interval there still might be a lower bound, if we are below m
if (mpresent==0) vector[x] <- vector[x]+max(0,min(1,x-l))
#finally, we might be above the previous midpoint but within its upperbound
prevm <- (inf(x-1,M))
if (length(U[U <= prevm]) != 0){
prevu <- sup(prevm, U)
if (mpresent==0) vector[x] <- vector[x]+max(0,min(1,prevu-(x-1)))
} else {
if (mpresent==0 & x > M[1]) vector[x] <- vector[x]+max(0,min(1,U[1]-(x-1)))
}
vector[x] <- 1/z*vector[x]
}
return(special_round(vector))
}
#7. case c < 1
if (c<1) {
for (x in 1:cyclelength){
#there is at least one and possibly many midpoints within the interval
#compute how many midpoints there are in the interval
n_m <- length(M[M >= x-1 & M <= x])
#case there is only 1 midpoint in the interval
if (n_m == 1){
m <- sup(x-1, M)
if (length(U[U <= x]) != 0) u <- sup(m, U) else u <- min(U)
l <- inf(m, L)
#the part around the midpoint
vector[x] <- vector[x] + min(x,m+b*z/2)-max(x-1,m-b*z/2)
#there might be a lower point of the next firing interval within the interval
nextl <- sup(m, L)
vector[x] <- vector[x]+max(0,min(1,x-nextl))
#there might be an upper point of the previous firing interval within the interval
if (length(U[U <= m]) != 0){
prevu <- inf(m, U)
vector[x] <- vector[x]+max(0,min(1,prevu-(x-1)))
} else {
if (x-1 > M[1]) vector[x] <- vector[x]+max(0,min(1,U[1]-(x-1)))
}
}
#case there are 2 or more midpoints in the interval
if (n_m >= 2){
#compute the highest and the lowest
m1 <- sup(x-1, M)
m2 <- inf(x, M)
if (length(U[U <= m2]) != 0) u <- sup(m2, U) else u <- min(U)
l <- inf(m1, L)
#the part around the lowest midpoint
vector[x] <- vector[x] + sup(m1,U)-max(x-1,l)
#the part around the uppermost midpoint
vector[x] <- vector[x] + min(x,u)-inf(m2,L)
#any extra midpoints
vector[x] <- vector[x] + max(0,n_m-2)*b*z
#there might be a lower point of the next firing interval within the interval
nextl <- sup(m2, L)
vector[x] <- vector[x]+max(0,min(1,x-nextl))
#there might be an upper point of the previous firing interval within the interval
if (length(U[U <= m1]) != 0){
prevu <- inf(m1, U)
vector[x] <- vector[x]+max(0,min(1,prevu-(x-1)))
} else {
if (x - 1 > M[1]) vector[x] <- vector[x]+max(0,min(1,U[1]-(x-1)))
}
}
vector[x] <- 1/z*vector[x]
}
return(special_round(vector))
}
}
Now it can handle even very densely firing weapons like
> firing_cycle(0.5,0,10,.01)
[1] 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10
[35] 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
[69] 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20
[103] 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10
[137] 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
[171] 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20
[205] 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10
[239] 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
[273] 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20
[307] 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10
[341] 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
[375] 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20
[409] 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10
[443] 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
[477] 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20 20 10 20
> firing_cycle(0.1,0,10,.1)
[1] 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9
[35] 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8
[69] 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7
[103] 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6
[137] 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5
[171] 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5
[205] 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6 7 8 9 10 9 8 7 6 5 5 6
[239] 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7
[273] 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8
[307] 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9
[341] 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10
[375] 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9
[409] 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8
[443] 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7
[477] 6 9 5 6 7 8 9 10 9 8 7 6 9 5 6 7 8 9 10 9 8 7 6 9
I'm not sure that it would improve code readability to substitute descriptive variable names, sorry. The current names relate to the mathematical description of computing the integral geometrically.
Basically, if you know what computing the integral geometrically means and can understand the math, then something like
#l is the infimum of m, l in L
l <- inf(m, L)
vector <- vector + max(x-1,l)
is, at least to me, much more readable and easy to make sure the program is doing what it should be doing than
HighestLowerBoundBelowm <- HighestLowerBound(MiddlePoint, LowerBoundVector)
TimeSeriesVector <- TimeSeriesVector + max(IntervalUpperBound-1, HighestLowerBoundBelowm)
which just makes my head ache, and if you can't read the equations then how would you know whether the program is doing what it's supposed to be doing anyway? It's highly unintuitive if you don't know the integral behind it that this program should produce what it does.
You are the better programmer of course and feel free to do it differently if you end up translating this to python. Explanation of one letter variables:
a1 - chargeup
a - cooldown (chargeup+chargedown)
b - burst size
c - cycle length (firing time + cooldown)
M - vector storing middle points of firing cycle
L - vector storing lower bounds of firing cycle
U - vector storing upper bounds of firing cycle
u - upper bound of firing cycle immediately above m (m2)
l - lower bound of firing cycle immediately below m (m1)
m - lowest middle point above x-1
prevu - highest upper bound below m
z - burst delay
One problem still not addressed is that for very large burst delay (say, z=4 or more) 1/z will be so low that rounding the results from this approach will produce a vector full of zeroes instead of what we want (ie. while it returns the correct distribution you can no longer convert it to integers by simply rounding). There should be a switch to revert to the previously written function in that case, or, alternatively, a function to re-compute the distribution with a lower z and insert zeroes to match the original (seemingly not that challenging to write). But before adding more code, do you think this is a thing that actually exists that people make weapons with burst delay 4?