Author Topic: Missile Calculator  (Read 3291 times)

0 Members and 1 Guest are viewing this topic.

Offline Whitecold (OP)

  • Commander
  • *********
  • W
  • Posts: 330
  • Thanked: 88 times
Missile Calculator
« on: August 25, 2018, 11:05:22 AM »
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


Code: [Select]
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?
 

Offline Kytuzian

  • Sub-Lieutenant
  • ******
  • K
  • Posts: 132
  • Thanked: 9 times
Re: Missile Calculator
« Reply #1 on: August 25, 2018, 12:29:30 PM »
Since you just want to show an image, you should just be able to use the [ img][/img ] tags. You just have to upload the image somewhere and put the url between the tags.

But about the graph, can you explain what exactly I'm looking at (e.g., what is the y axis?)
 

Offline Whitecold (OP)

  • Commander
  • *********
  • W
  • Posts: 330
  • Thanked: 88 times
Re: Missile Calculator
« Reply #2 on: August 25, 2018, 02:50:50 PM »
Since you just want to show an image, you should just be able to use the [ img][/img ] tags. You just have to upload the image somewhere and put the url between the tags.

But about the graph, can you explain what exactly I'm looking at (e.g., what is the y axis?)
Designs are rate by expected damage per missile. In this case, the larger missiles are normalized per warhead.
 

Offline Jorgen_CAB

  • Admiral of the Fleet
  • ***********
  • J
  • Posts: 2822
  • Thanked: 673 times
Re: Missile Calculator
« Reply #3 on: September 27, 2018, 04:33:32 PM »
Since you just want to show an image, you should just be able to use the [ img][/img ] tags. You just have to upload the image somewhere and put the url between the tags.

But about the graph, can you explain what exactly I'm looking at (e.g., what is the y axis?)
Designs are rate by expected damage per missile. In this case, the larger missiles are normalized per warhead.

What about electronics, separation range and all that... The fact that MS missiles increase range I think is a given but there are many other things that can effect their effectiveness in a real situation other than pure range and damage rating.

I would say that strapping decently sized and ranged fast and powerful missiles on returnable fighter platforms will outperform any multi-staged missile most of the time. Especially now that Res 1 sensors are that much more effective on your sensor scouts so the detection range of large multi-staged missiles are going to be quite extensive.
 

Offline Whitecold (OP)

  • Commander
  • *********
  • W
  • Posts: 330
  • Thanked: 88 times
Re: Missile Calculator
« Reply #4 on: September 27, 2018, 05:07:14 PM »
What about electronics, separation range and all that... The fact that MS missiles increase range I think is a given but there are many other things that can effect their effectiveness in a real situation other than pure range and damage rating.

I would say that strapping decently sized and ranged fast and powerful missiles on returnable fighter platforms will outperform any multi-staged missile most of the time. Especially now that Res 1 sensors are that much more effective on your sensor scouts so the detection range of large multi-staged missiles are going to be quite extensive.
You can vary separation range, and test different electronics (payload includes all MSP allocated to sensors/ECM/ECCM, and the ECM level is the effective difference between Missile ECCM and target ECM)
The result however is non-trivial, as usually your effective range is given by your sensors, and you want the most effective missile for that range and size. For short ranges, single stage is the way to go.
As far for fighters vs MSM, the calculator can only help you a bit. I'd say a fighter defense looks a bit different than a missile defense. Destroyers also benefit from the new sensors, a 12HS Res5 sensor roughly matches a 1HS Res200 you could expect on a fighter, and if your fighters catch some fire, they are toast too.
Also, multistage doesn't need to be large, S10 should be enough, and missiles in C# are likely being bigger than they used to be if you assume you'll need ECM+ECCM
 

Offline Jorgen_CAB

  • Admiral of the Fleet
  • ***********
  • J
  • Posts: 2822
  • Thanked: 673 times
Re: Missile Calculator
« Reply #5 on: September 27, 2018, 05:59:43 PM »
I certainly appreciate the tool because it is good for interesting test scenarios. I still don't believe that multi-stage missile will be that useful unless you fight against the AI only. Against someone with a dedicated defence sensor net around their capital ships they are problematic.

Given how sensors work you will first need to get through the sensor net of a destroyer flotillas scouts. Once you swept away them the destroyers will be vulnerable to relatively short range missile attacks through fighters or FAC... at least at early to mid level technology.

If you want to fire really long range missiles you  probably have to rely on missiles with their own sensors since even fire-controls will have much shorter overall max range now as well. At the tech level you suggested missile fire-controls able to shoot at a 10.000t ship at extreme ranges are going to be pretty damn big and expensive and not very effective at smaller ships.

In my opinion... capital ships will mainly become support ships pretty fast now... at least until you have decently small and efficient cloaking devises. Mainly given how effective small versus large sensors functions. I will most likely only use Destroyers as defensive ships and make them into dedicated anti-missile/fighter and FAC platforms to protect my carriers who bring the main strike force with their fighter and FAC crafts.

The major problem with large resolution sensors in C# will be their relative low range versus the range at which they are detected from even the smallest fighter, not to mention a dedicated passive sensor scout. Finding a big ship through scanning space with 100-200 resolution scanners will probably not be a viable tactic anymore, at least not unless you have a very good indication of where to search for the enemy ships. Large sensor will be extremely expensive to both research and build in comparison to how effective they are as opposed to many smaller ones.