I wanted to see the effect of the new rules on missile design. Since none of the existing tools was easily adaptable and allows for nice plots, I wrote my own in python, which can handle single stage as well as double stage missiles, including multiple warheads if wanted.
I then toyed around a bit with the parameters, to show what is possible I tested a bunch of Ion-tech missiles versus each other, and as one can see the multistage designs here start outperforming the single stage design at about 50 M km
import numpy as np
from scipy.optimize import Bounds, LinearConstraint, minimize
from matplotlib import pyplot as plt
wh_msp = lambda s: s/dmg_msp
MR = lambda a, s: 10.+ a*agi_msp/s
agi_space = lambda a, s: (a-10)*s/agi_msp
e_power = lambda s, mod: s*pwr_msp*mod
speed = lambda s, s_e, mod: 20000*e_power(s_e, mod)/s
fuel_consumption = lambda s, p: fuel_eff*(max(4*(p-max_boost)/max_boost, 0)+1)*p**2.5*np.sqrt(200 / s)
duration = lambda f, s, mod: 3600*2500*f/(e_power(s, mod)*fuel_consumption(s, mod))
max_range = lambda f, s, s_e, mod, : duration(f, s_e, mod)*speed(s,s_e,mod)
agi_step = lambda s: 1.0/s
def calc_single_missile(show= False):
engine_amount = 1.
def score(x): #x: [WARHEAD, MR, ENGINE, FUEL, BOOST]
return -x[0]*min(x[1] * speed(size, x[2]*engine_amount, x[4])/(target_speed*100) - enemy_ecm*.1, 1)
def size_constr(x):
return size - payload - x[0] / dmg_msp - agi_space(x[1], size) - x[2] * engine_amount - x[3]
def range_constr(x):
return max_range(x[3] / engine_amount, size / engine_amount, x[2], x[4]) - target_range * 1000000
def print_missile(f, x):
print("###Stage 1### ({:.1f} MSP)".format(size))
print("ENG: {}*{:.2f} MSP | Fuel: {:0.2f} MSP \nBoost: {:.0f} %"
.format(int(engine_amount0), x[2],x[3], x[4]*100))
print("WH: {:.0f} | AGI: {:0.2f} MSP | Payload {:.2f}"
.format(x[0], agi_space(x[1], size), payload))
print("MR: {:.0f} ".format(x[1]))
print("Speed (final): {:.1f} km/s".format( speed(size, x[2], x[4]) ))
print("Duration (final): {:.1f} s".format( duration(x[3], x[2], x[4]) ))
print("Range 1st: {:.1f} Gm".format(max_range(x[3],size,x[2],x[4])/1000000))
print("ToHit: {:.1f} % vs {} km/s".format(100*min(x[1]*speed(size, x[2]*engine_amount, x[4])/(target_speed*100) - enemy_ecm*.1, 1),
target_speed))
print("ToHit: {:.1f} % vs 5000 km/s".format(100*min(x[1]*speed(size, x[2]*engine_amount, x[4])/(5000*100) - enemy_ecm*.1, 1)))
print("ToHit: {:.1f} % vs 10000 km/s".format(100*min(x[1]*speed(size, x[2]*engine_amount, x[4])/(10000*100) - enemy_ecm*.1, 1)))
print("ToHit: {:.1f} % vs 20000 km/s".format(100*min(x[1]*speed(size, x[2]*engine_amount, x[4])/(20000*100) - enemy_ecm*.1, 1)))
print("Score: {:.2f}\n".format(-1*f))
if fixed_dmg:
bounds = ((dmg, dmg),(10, np.inf),(0.01, 5.0),(0, np.inf),(0.3, max_boost*2))
x0 = np.array([dmg,size,size*0.5,size*0.1,1])
else:
bounds = ((0, np.inf),(10, np.inf),(0.01, 5.0),(0, np.inf),(0.3, max_boost*2))
x0 = np.array([size,size,size*0.5,size*0.1,1])
res = minimize(score, x0, method="SLSQP",
constraints=[{"type": "eq", "fun": size_constr}, {"type": "eq", "fun": range_constr}],
bounds=bounds)
w = np.floor(res['x'][0])
a = np.floor(res['x'][1])
x0 = res['x']
res = {'fun': 1}
engine_amount0 = 1
for engine_amount in [1., 2., 3.]:
dmg_list = [w, w+1] if not fixed_dmg else [dmg]
agi_list = [a, (a+1)]
for i in dmg_list:
for j in agi_list:
bounds = ((i, i), (j, j), (0.01, 5), (0, np.inf), (0.3, max_boost*2))
res0 = minimize(score, x0, method="SLSQP",
constraints=[{"type": "eq", "fun": size_constr}, {"type": "eq", "fun": range_constr}],
bounds=bounds)
if res0['fun'] < res['fun'] and abs(size_constr(res0['x'])) < 1.0e-5:
res = res0
engine_amount0 = engine_amount
engine_amount = engine_amount0
if show:
print_missile(res['fun'], res['x'])
return res['fun']*-1
def calc_multi_missile(show=False):
def score(x): #x: [WARHEAD, AGI, ENGINE, FUEL, BOOST, STAGE SIZE, ENGINE2, FUEL2, BOOST2]
return -x[0]*min(x[1]*speed(x[5], x[2], x[4])/(target_speed*100) - enemy_ecm*.1, 1)
def cruise_speed_constr(x):
return cruise_speed - speed(size, x[6], x[8])
def stage_size_constr(x):
return x[5] - payload*MIW - x[0] / dmg_msp - agi_space(x[1], x[5]) - x[2] - x[3]
def size_constr(x):
return size - x[5] - x[6] - x[7]
def range_constr(x):
return max_range(x[7], size, x[6], x[8]) - (target_range - point_defense_range) * 1000000
def pd_range_constr(x):
return max_range(x[3]/MIW, x[5]/MIW, x[2]/MIW, x[4]) - point_defense_range * 1000000
def print_missile(f, x):
print("###Stage 1### ({:.1f} MSP)".format(size))
print("ENG: {:.2f} MSP | Fuel: {:.2f} MSP \nBoost: {:.0f} %"
.format(x[6],x[7], x[8]*100))
if MIW > 1:
print("###Stage 2###({:.2f} MSP x {})".format(x[5]/MIW, MIW))
else:
print("###Stage 2###({:.2f} MSP)".format(x[5]/MIW))
print("ENG: {:.2f} MSP | Fuel: {:0.2f} MSP \nBoost: {:.0f} %"
.format(x[2]/MIW, x[3]/MIW, x[4]*100))
print("WH: {:.0f} | AGI: {:0.2f} MSP | Payload {:.2f}"
.format(x[0]/MIW, agi_space(x[1], x[5])/MIW, payload))
print("MR: {:.0f} ".format(x[1]))
print("Speed (final): {:.1f} km/s".format( speed(x[5], x[2], x[4]) ))
print("Speed (cruise): {:.1f} km/s".format( speed(size, x[6], x[8]) ))
print("Duration (final): {:.1f} s".format( duration(x[3], x[2], x[4]) ))
print("Duration (cruise): {:.1f} s".format( duration(x[7], x[6], x[8]) ))
print("Range 1st: {:.1f} Gm".format(max_range(x[7],size,x[6],x[8])/1000000))
print("Range 2nd: {:.1f} Gm".format(max_range(x[3]/MIW,x[5]/MIW,x[2]/MIW,x[4])/1000000))
print("ToHit: {:.1f} % vs {} km/s".format(100*min(x[1]*speed(x[5], x[2], x[4])/(target_speed*100) - enemy_ecm*.1, 1),
target_speed))
print("ToHit: {:.1f} % vs 5000 km/s".format(100*min(x[1]*speed(x[5], x[2], x[4])/(5000*100) - enemy_ecm*.1, 1)))
print("ToHit: {:.1f} % vs 10000 km/s".format(100*min(x[1]*speed(x[5], x[2], x[4])/(10000*100) - enemy_ecm*.1, 1)))
print("ToHit: {:.1f} % vs 20000 km/s".format(100*min(x[1]*speed(x[5], x[2], x[4])/(20000*100) - enemy_ecm*.1, 1)))
print("Score: {:.2f}\n".format(-1*f))
if fixed_dmg:
bounds = ((dmg, dmg),(0, np.inf),(0.01, 5*MIW),(0, np.inf),(0.3, max_boost*2),
(1, size),(0.01, 5),(0, np.inf),(0.3, max_boost*2))
else:
bounds = ((1, np.inf),(0, np.inf),(0.01, 5*MIW),(0, np.inf),(0.3, max_boost*2),
(1, size),(0.01, 5),(0, np.inf),(0.3, max_boost*2))
x0 = np.array([size,size,size*0.25,size*0.05,size*0.5,1,size*0.25,size*0.05,1])
res = minimize(score, x0, method = "SLSQP",
constraints=[{"type": "eq", "fun": size_constr},
{"type": "eq", "fun": range_constr},
{"type": "eq", "fun": stage_size_constr},
{"type": "eq", "fun": pd_range_constr},
{"type": "eq", "fun": cruise_speed_constr}],
bounds=bounds)
w = np.floor(res['x'][0]/MIW)
a = np.floor(res['x'][1])
s = res['x'][5]
x0 = res['x']
res = {'fun': 1 }
dmg_list = [w * MIW, (w + 1) * MIW] if not fixed_dmg else [dmg * MIW]
agi_list = [a, (a + 1)]
for i in dmg_list:
for j in agi_list:
bounds = ((i, i), (j, j), (0.01, 5*MIW), (0.01, np.inf), (0.3, max_boost*2),
(1, size), (0.01, 5),(0, np.inf), (0.3, max_boost*2))
res1 = minimize(score, x0, method="SLSQP",
constraints=[{"type": "eq", "fun": size_constr},
{"type": "eq", "fun": range_constr},
{"type": "eq", "fun": stage_size_constr},
{"type": "eq", "fun": pd_range_constr},
{"type": "eq", "fun": cruise_speed_constr}],
bounds=bounds)
if res['fun'] > res1['fun'] and res1['success']:
res = res1
if show:
print_missile(res['fun'], res['x'])
return res['fun']*-1
max_boost_tech = [1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0] #Maximum boost tech value
dmg_msp_tech = [2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 30] #DMG per MSP tech value
agi_msp_tech = [20, 32, 48, 64, 80, 100, 128, 160, 200, 240, 320, 400] #AGI per MSP tech value
pwr_msp_tech = [0.25, 0.4, 0.6, 0.8, 1.0, 1.25, 1.6, 2, 2.5, 3.0, 4.0, 5.0] #power per MSP tech value
fuel_eff_tech = [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.25, 0.2, 0.15, 0.125, 0.1] #fuel efficiency tech value
max_boost = 1.5 #Maximum boost tech value
dmg_msp = 4. #DMG per MSP tech value
agi_msp = 48. #AGI per MSP tech value
pwr_msp = 0.6 #power per MSP tech value
fuel_eff = 0.7 #fuel efficiency tech value
size = 12.0 #Target size
payload = 0.25 #MSP reserved for ECM/ECCM/Sensors per missile
fixed_dmg = False
dmg = 1 #Damage per missile
MIW = 1 #Warheads on multistage missiles
target_speed = 2400 #Speed of target
cruise_speed = 4800 #Speed in km/s for first stage of multistage missile
enemy_ecm = 0 #effective ECM level of target
point_defense_range = 5 #Separation range of multistage missile in M km
target_range = 40 #Target range in M km
y1 = []
y2 = []
y3 = []
y4 = []
MIW = 1
size = 6
plt.title("Multiple Independent Warheads")
x = np.linspace(10, 200)
target_range = 75
calc_single_missile(show = True)
calc_multi_missile(show = True)
for i in x:
target_range = i
y2.append(calc_multi_missile())
y1.append(calc_single_missile())
MIW = 2
size = 12
target_range = 75
calc_multi_missile(show = True)
for i in x:
target_range = i
y3.append(calc_multi_missile()/2)
MIW = 3
size = 18
target_range = 75
calc_multi_missile(show = True)
for i in x:
target_range = i
y4.append(calc_multi_missile()/3)
plt.plot(x, np.array(y1), label='SS, 6 MSP')
plt.plot(x, np.array(y2), label='MS, 6 MSP')
plt.plot(x, np.array(y3), label='MS,12 MSP')
plt.plot(x, np.array(y4), label='MS,18 MSP')
plt.legend()
plt.xlabel("Range M km")
plt.savefig("MIW.png")
plt.show()
plt.plot(x, 100*(np.divide(np.array(y2), np.array(y1))-1), label='MS, 6 MSP')
plt.plot(x, 100*(np.divide(np.array(y3), np.array(y1))-1), label='MS,12 MSP')
plt.plot(x, 100*(np.divide(np.array(y4), np.array(y1))-1), label='MS,18 MSP')
plt.legend()
plt.grid()
plt.ylabel("%")
plt.xlabel("Range M km")
plt.savefig("MIW_percent.png")
plt.show()
size = 1.0 #Target size
payload = 0. #MSP reserved for ECM/ECCM/Sensors per missile
fixed_dmg = True
dmg = 1 #Damage per missile
MIW = 1 #Warheads on multistage missiles
target_speed = 24000 #Speed of target
enemy_ecm = 0 #effective ECM level of target
target_range = 4 #Target range in M km
calc_single_missile(show = True)
Edit: does anyone know to display attachments inline?