Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Pages: 1 ... 15 16 [17] 18 19 ... 32

Author Topic: Optimizing the Conquest: a Mathematical Model of Space Combat  (Read 25378 times)

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #240 on: December 10, 2022, 10:43:47 AM »

Sure. Same tests. Wouldn't sweat very small decimals as there are only so many ways to get this right so if they are similar to a few decimals it probably is. Before declaring victory though, try the non-uniform distribution and consecutive shots. Edit: oops. The first has the incorrect prob dist. Repost soon. E2: fixed. Complete prints below except I sabotaged the [\\i] to escape from bbcode


> damage <- 20
> hitstrength <- 40
> startingarmor <- 100
> hitstrengthcalc <- function(hitstrength, armor) return(max(0.15,hitstrength/(hitstrength+max(armor,0.05*startingarmor))))
> shipcells <- 10
> probabilities <- c(0.10,0.1,0.10,0.1,0.10,0.10,0.1,0.10,0.1,0.10)
> norounds <- 3
>
>
> weights <- matrix(c(0,0.5,0.5,0.5,0,0.5,1,1,1,0.5,0.5,1,1,1,0.5,0.5,1,1,1,0.5,0,0.5,0.5,0.5,0),5,5)
> weights
     [,1] [,2] [,3] [,4] [,5]
[1,]  0.0  0.5  0.5  0.5  0.0
[2,]  0.5  1.0  1.0  1.0  0.5
[3,]  0.5  1.0  1.0  1.0  0.5
[4,]  0.5  1.0  1.0  1.0  0.5
[5,]  0.0  0.5  0.5  0.5  0.0
>
> poolarmor <- function(armormatrix, index) {
+   sum <- 0
+   for(i in 1:5)for(j in 1:5) sum <- sum + weights[i,j]*armormatrix[i,index-3+j]
+   return(sum)
+ }
> print("Starting armor matrix")
[1] "Starting armor matrix"
> print(armormatrix <- matrix(startingarmor / 15, 5, shipcells+4))
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[2,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[3,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[4,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[5,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
        [,12]    [,13]    [,14]
[1,] 6.666667 6.666667 6.666667
[2,] 6.666667 6.666667 6.666667
[3,] 6.666667 6.666667 6.666667
[4,] 6.666667 6.666667 6.666667
[5,] 6.666667 6.666667 6.666667
>
> print("Equivalent armor hp:")
[1] "Equivalent armor hp:"
> fullarmorhp <- (sum(armormatrix[2:4,])+sum(armormatrix[1,2:13])+sum(armormatrix[5,2:13]))/(5*14-4)*15
> print(fullarmorhp)
[1] 100
>
> for(r in 1:norounds){
+
+ armordamagereductions <- vector(shipcells, mode="double")
+ for (x in 1:length(armordamagereductions)) {
+   armordamagereductions
  • <- hitstrengthcalc(hitstrength,poolarmor(armormatrix,x+2))

+ }
+ print("Armor damage expected based on full armor hp:")
+ print(damage*hitstrength/(hitstrength+fullarmorhp))
+
+ armordamagesatmiddlecells <- armordamagereductions*damage
+ print("Armor damage at middle cells given full shot:")
+ print(armordamagesatmiddlecells)
+ print("Probability adjusted armor damage at middle cells:")
+ for(x in 1:length(armordamagesatmiddlecells)) {
+   armordamagesatmiddlecells
  • <- armordamagesatmiddlecells
  • *probabilities

+ }
+ print(armordamagesatmiddlecells)
+ print("Total armor damage incoming at middle cells:")
+ print(sum(armordamagesatmiddlecells))
+
+ for (i in 1:length(armordamagesatmiddlecells)){
+   for (j in 1:5){
+     for (k in 1:5){
+       armormatrix[j,i+k-1] <- armormatrix[j,i+k-1] - armordamagesatmiddlecellsi]*weights[j,k]/15
+     }
+   }
+ }
+ print("New armor matrix:")
+ print((armormatrix))
+
+ print("Equivalent armor hp:")
+ fullarmorhp <- (sum(armormatrix[2:4,])+sum(armormatrix[1,2:13])+sum(armormatrix[5,2:13]))/(5*14-4)*15
+ print(fullarmorhp)
+ }
[1] "Armor damage expected based on full armor hp:"
[1] 5.714286
[1] "Armor damage at middle cells given full shot:"
 [1] 5.714286 5.714286 5.714286 5.714286 5.714286 5.714286 5.714286 5.714286 5.714286 5.714286
[1] "Probability adjusted armor damage at middle cells:"
 [1] 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286 0.5714286
[1] "Total armor damage incoming at middle cells:"
[1] 5.714286
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524
[2,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[3,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[4,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[5,] 6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524
        [,12]    [,13]    [,14]
[1,] 6.628571 6.647619 6.666667
[2,] 6.571429 6.609524 6.647619
[3,] 6.571429 6.609524 6.647619
[4,] 6.571429 6.609524 6.647619
[5,] 6.628571 6.647619 6.666667
[1] "Equivalent armor hp:"
[1] 98.7013
[1] "Armor damage expected based on full armor hp:"
[1] 5.76779
[1] "Armor damage at middle cells given full shot:"
 [1] 5.764875 5.780745 5.791107 5.795901 5.797101 5.797101 5.795901 5.791107 5.780745 5.764875
[1] "Probability adjusted armor damage at middle cells:"
 [1] 0.5764875 0.5780745 0.5791107 0.5795901 0.5797101 0.5797101 0.5795901 0.5791107 0.5780745 0.5764875
[1] "Total armor damage incoming at middle cells:"
[1] 5.785946
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.628403 6.590086 6.551735 6.551631 6.551577 6.551557 6.551557 6.551577 6.551631 6.551735
[2,] 6.628403 6.551822 6.475154 6.398435 6.359961 6.359799 6.359728 6.359728 6.359799 6.359961 6.398435
[3,] 6.628403 6.551822 6.475154 6.398435 6.359961 6.359799 6.359728 6.359728 6.359799 6.359961 6.398435
[4,] 6.628403 6.551822 6.475154 6.398435 6.359961 6.359799 6.359728 6.359728 6.359799 6.359961 6.398435
[5,] 6.666667 6.628403 6.590086 6.551735 6.551631 6.551577 6.551557 6.551557 6.551577 6.551631 6.551735
        [,12]    [,13]    [,14]
[1,] 6.590086 6.628403 6.666667
[2,] 6.475154 6.551822 6.628403
[3,] 6.475154 6.551822 6.628403
[4,] 6.475154 6.551822 6.628403
[5,] 6.590086 6.628403 6.666667
[1] "Equivalent armor hp:"
[1] 97.38631
[1] "Armor damage expected based on full armor hp:"
[1] 5.822996
[1] "Armor damage at middle cells given full shot:"
 [1] 5.816955 5.849598 5.871049 5.881035 5.883560 5.883560 5.881035 5.871049 5.849598 5.816955
[1] "Probability adjusted armor damage at middle cells:"
 [1] 0.5816955 0.5849598 0.5871049 0.5881035 0.5883560 0.5883560 0.5881035 0.5871049 0.5849598 0.5816955
[1] "Total armor damage incoming at middle cells:"
[1] 5.860439
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]    [,7]    [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.609013 6.551198 6.493276 6.492959 6.492791 6.49273 6.49273 6.492791 6.492959 6.493276
[2,] 6.609013 6.493544 6.377807 6.261915 6.203615 6.203117 6.20290 6.20290 6.203117 6.203615 6.261915
[3,] 6.609013 6.493544 6.377807 6.261915 6.203615 6.203117 6.20290 6.20290 6.203117 6.203615 6.261915
[4,] 6.609013 6.493544 6.377807 6.261915 6.203615 6.203117 6.20290 6.20290 6.203117 6.203615 6.261915
[5,] 6.666667 6.609013 6.551198 6.493276 6.492959 6.492791 6.49273 6.49273 6.492791 6.492959 6.493276
        [,12]    [,13]    [,14]
[1,] 6.551198 6.609013 6.666667
[2,] 6.377807 6.493544 6.609013
[3,] 6.377807 6.493544 6.609013
[4,] 6.377807 6.493544 6.609013
[5,] 6.551198 6.609013 6.666667
[1] "Equivalent armor hp:"
[1] 96.05439
>


> damage <- 100
> hitstrength <- 100
> startingarmor <- 100
> hitstrengthcalc <- function(hitstrength, armor) return(max(0.15,hitstrength/(hitstrength+max(armor,0.05*startingarmor))))
> shipcells <- 10
> probabilities <- c(0.00,0.05,0.10,0.15,0.20,0.20,0.15,0.10,0.05,0.00)
> norounds <- 3
>
>
> weights <- matrix(c(0,0.5,0.5,0.5,0,0.5,1,1,1,0.5,0.5,1,1,1,0.5,0.5,1,1,1,0.5,0,0.5,0.5,0.5,0),5,5)
> weights
     [,1] [,2] [,3] [,4] [,5]
[1,]  0.0  0.5  0.5  0.5  0.0
[2,]  0.5  1.0  1.0  1.0  0.5
[3,]  0.5  1.0  1.0  1.0  0.5
[4,]  0.5  1.0  1.0  1.0  0.5
[5,]  0.0  0.5  0.5  0.5  0.0
>
> poolarmor <- function(armormatrix, index) {
+   sum <- 0
+   for(i in 1:5)for(j in 1:5) sum <- sum + weights[i,j]*armormatrix[i,index-3+j]
+   return(sum)
+ }
> print("Starting armor matrix")
[1] "Starting armor matrix"
> print(armormatrix <- matrix(startingarmor / 15, 5, shipcells+4))
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[2,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[3,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[4,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
[5,] 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667 6.666667
        [,12]    [,13]    [,14]
[1,] 6.666667 6.666667 6.666667
[2,] 6.666667 6.666667 6.666667
[3,] 6.666667 6.666667 6.666667
[4,] 6.666667 6.666667 6.666667
[5,] 6.666667 6.666667 6.666667
>
> print("Equivalent armor hp:")
[1] "Equivalent armor hp:"
> fullarmorhp <- (sum(armormatrix[2:4,])+sum(armormatrix[1,2:13])+sum(armormatrix[5,2:13]))/(5*14-4)*15
> print(fullarmorhp)
[1] 100
>
> for(r in 1:norounds){
+
+ armordamagereductions <- vector(shipcells, mode="double")
+ for (x in 1:length(armordamagereductions)) {
+   armordamagereductions
  • <- hitstrengthcalc(hitstrength,poolarmor(armormatrix,x+2))

+ }
+ print("Armor damage expected based on full armor hp:")
+ print(damage*hitstrength/(hitstrength+fullarmorhp))
+
+ armordamagesatmiddlecells <- armordamagereductions*damage
+ print("Armor damage at middle cells given full shot:")
+ print(armordamagesatmiddlecells)
+ print("Probability adjusted armor damage at middle cells:")
+ for(x in 1:length(armordamagesatmiddlecells)) {
+   armordamagesatmiddlecells
  • <- armordamagesatmiddlecells
  • *probabilities

+ }
+ print(armordamagesatmiddlecells)
+ print("Total armor damage incoming at middle cells:")
+ print(sum(armordamagesatmiddlecells))
+
+ for (i in 1:length(armordamagesatmiddlecells)){
+   for (j in 1:5){
+     for (k in 1:5){
+       armormatrix[j,i+k-1] <- armormatrix[j,i+k-1] - armordamagesatmiddlecellsi]*weights[j,k]/15
+     }
+   }
+ }
+ print("New armor matrix:")
+ print((armormatrix))
+
+ print("Equivalent armor hp:")
+ fullarmorhp <- (sum(armormatrix[2:4,])+sum(armormatrix[1,2:13])+sum(armormatrix[5,2:13]))/(5*14-4)*15
+ print(fullarmorhp)
+ }
[1] "Armor damage expected based on full armor hp:"
[1] 50
[1] "Armor damage at middle cells given full shot:"
 [1] 50 50 50 50 50 50 50 50 50 50
[1] "Probability adjusted armor damage at middle cells:"
 [1]  0.0  2.5  5.0  7.5 10.0 10.0  7.5  5.0  2.5  0.0
[1] "Total armor damage incoming at middle cells:"
[1] 50
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.666667 6.583333 6.416667 6.166667 5.916667 5.750000 5.750000 5.916667 6.166667 6.416667
[2,] 6.666667 6.583333 6.333333 5.916667 5.333333 4.750000 4.416667 4.416667 4.750000 5.333333 5.916667
[3,] 6.666667 6.583333 6.333333 5.916667 5.333333 4.750000 4.416667 4.416667 4.750000 5.333333 5.916667
[4,] 6.666667 6.583333 6.333333 5.916667 5.333333 4.750000 4.416667 4.416667 4.750000 5.333333 5.916667
[5,] 6.666667 6.666667 6.583333 6.416667 6.166667 5.916667 5.750000 5.750000 5.916667 6.166667 6.416667
        [,12]    [,13]    [,14]
[1,] 6.583333 6.666667 6.666667
[2,] 6.333333 6.583333 6.666667
[3,] 6.333333 6.583333 6.666667
[4,] 6.333333 6.583333 6.666667
[5,] 6.583333 6.666667 6.666667
[1] "Equivalent armor hp:"
[1] 88.63636
[1] "Armor damage expected based on full armor hp:"
[1] 53.01205
[1] "Armor damage at middle cells given full shot:"
 [1] 51.50215 52.93339 54.75702 56.55042 57.70618 57.70618 56.55042 54.75702 52.93339 51.50215
[1] "Probability adjusted armor damage at middle cells:"
 [1]  0.000000  2.646670  5.475702  8.482564 11.541236 11.541236  8.482564  5.475702  2.646670  0.000000
[1] "Total armor damage incoming at middle cells:"
[1] 56.29234
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.666667 6.495111 6.145921 5.613169 5.066683 4.697832 4.697832 5.066683 5.613169 6.145921
[2,] 6.666667 6.495111 5.974365 5.092423 3.841630 2.577103 1.847055 1.847055 2.577103 3.841630 5.092423
[3,] 6.666667 6.495111 5.974365 5.092423 3.841630 2.577103 1.847055 1.847055 2.577103 3.841630 5.092423
[4,] 6.666667 6.495111 5.974365 5.092423 3.841630 2.577103 1.847055 1.847055 2.577103 3.841630 5.092423
[5,] 6.666667 6.666667 6.495111 6.145921 5.613169 5.066683 4.697832 4.697832 5.066683 5.613169 6.145921
        [,12]    [,13]    [,14]
[1,] 6.495111 6.666667 6.666667
[2,] 5.974365 6.495111 6.666667
[3,] 5.974365 6.495111 6.666667
[4,] 5.974365 6.495111 6.666667
[5,] 6.495111 6.666667 6.666667
[1] "Equivalent armor hp:"
[1] 75.84265
[1] "Armor damage expected based on full armor hp:"
[1] 56.86902
[1] "Armor damage at middle cells given full shot:"
 [1] 53.26066 56.62906 61.31532 66.40800 69.97700 69.97700 66.40800 61.31532 56.62906 53.26066
[1] "Probability adjusted armor damage at middle cells:"
 [1]  0.000000  2.831453  6.131532  9.961200 13.995401 13.995401  9.961200  6.131532  2.831453  0.000000
[1] "Total armor damage incoming at middle cells:"
[1] 65.83917
[1] "New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]       [,6]      [,7]      [,8]       [,9]    [,10]
[1,] 6.666667 6.666667 6.400729 5.847155 4.982363 4.06374553  3.432765  3.432765 4.06374553 4.982363
[2,] 6.666667 6.400729 5.581217 4.162851 2.113504 0.01033242 -1.219502 -1.219502 0.01033242 2.113504
[3,] 6.666667 6.400729 5.581217 4.162851 2.113504 0.01033242 -1.219502 -1.219502 0.01033242 2.113504
[4,] 6.666667 6.400729 5.581217 4.162851 2.113504 0.01033242 -1.219502 -1.219502 0.01033242 2.113504
[5,] 6.666667 6.666667 6.400729 5.847155 4.982363 4.06374553  3.432765  3.432765 4.06374553 4.982363
        [,11]    [,12]    [,13]    [,14]
[1,] 5.847155 6.400729 6.666667 6.666667
[2,] 4.162851 5.581217 6.400729 6.666667
[3,] 4.162851 5.581217 6.400729 6.666667
[4,] 4.162851 5.581217 6.400729 6.666667
[5,] 5.847155 6.400729 6.666667 6.666667
[1] "Equivalent armor hp:"
[1] 60.8792
« Last Edit: December 10, 2022, 11:07:02 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #241 on: December 10, 2022, 11:19:46 AM »

Also: note that computation of the exact value for the first hit can be done easily by hand. So for example we know that for the middle cells with 20 damage and 40 hit strength and the uniform distribution, it must be

100/15-40/140*20*(3/10*1/15+2/10*1/30)=228/35 approx 6.5142857142857142857
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #242 on: December 10, 2022, 02:20:31 PM »

I've almost gotten your result.  I started with these numbers in a file I named data.json
{
    "Shot.damage_armor_grid":{
        "test_config":{
            "decimal_places":6
        },
        "shot_spec":{
            "base_damage":10.0,
            "base_armor_damage":20.0,
            "base_shield_damage":5.0,
            "strength":40.0,
            "flux_hard":false
        },
        "armor_grid_spec":{
            "armor_rating":100.0,
            "cell_size":10.0,
            "width":10
        },
        "armor_grid_cell_values":{
            "initial":[
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667]
            ],
            "after_shot":[
                [6.666667,6.647619,6.628571,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.583333,6.666667,6.666667],
                [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.333333,6.583333,6.666667],
                [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.333333,6.583333,6.666667],
                [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.333333,6.583333,6.666667],
                [6.666667,6.647619,6.628571,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.583333,6.666667,6.666667]
            ]
        }
    }
}
and got this result
TEST FAILED
Location: test_Shot.test_damage_armor_grid
Problem: armor grid after shot does not equal expected one

grid_after_shot_expected
[[6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.583333 6.666667 6.666667]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.333333 6.583333 6.666667]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.333333 6.583333 6.666667]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.333333 6.583333 6.666667]
 [6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.583333 6.666667 6.666667]]

grid_after_shot
[[6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.628571 6.647619 6.666667]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.571429 6.609524 6.647619]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.571429 6.609524 6.647619]
 [6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333 6.571429 6.609524 6.647619]
 [6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.628571 6.647619 6.666667]]

grid_after_shot - grid_after_shot_expected
[[ 0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.045238 -0.019048  0.      ]
 [ 0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.238096  0.026191 -0.019048]
 [ 0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.238096  0.026191 -0.019048]
 [ 0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.238096  0.026191 -0.019048]
 [ 0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.        0.045238 -0.019048  0.      ]]
with this code
Code
combat_entities.py
Code
import numpy as np
import copy
"""
Damage computing module.

methods:
- hit_probability
- main

classes:
- ArmorGrid
- Ship
- Shot
- Weapon
"""
class ArmorGrid:
    """
    A Starsector ship armor grid.
   
    - Represents a horizontal frontal row of armor on a ship.
    - Includes 2 rows of vertical padding above; 2 below; 2 columns of
      left horizontal padding; 2 right.
   
    constants:
    _MINIMUM_ARMOR_FACTOR - multiply by armor rating to determine
                            minimum effective value of pooled armor for
                            damage calculations
    _MINIMUM_DAMAGE_FACTOR - least factor whereby this ArmorGrid can
                             cause incoming damage to be multiplied
    ARMOR_RATING_PER_CELL_FACTOR - multiply by armor rating to determine
                                   initial value of each cell of an
                                   ArmorGrid
    WEIGHTS - multiply by damage to the central cell to determine damage
              to surrounding ones
             
    variables:
    _minimum_armor - minimum effective value of armor for damage
                     calculations
    bounds - right bound of each cell in the middle row, except the two
             padding cells on both sides
    cells - 2d array of armor grid cell values
   
    methods:
    - _pooled_values
    - damage_factors
    """
    _MINIMUM_ARMOR_FACTOR = 0.05
    _MINIMUM_DAMAGE_FACTOR = 0.15
    ARMOR_RATING_PER_CELL_FACTOR = 1 / 15
    WEIGHTS = np.array([[0.0, 0.5, 0.5, 0.5, 0.0],
                        [0.5, 1.0, 1.0, 1.0, 0.5],
                        [0.5, 1.0, 1.0, 1.0, 0.5],
                        [0.5, 1.0, 1.0, 1.0, 0.5],
                        [0.0, 0.5, 0.5, 0.5, 0.0]])
   
    def __init__(self, armor_rating: float, cell_size: float, width: int):
        """
        armor_rating - armor rating of the ship to which this ArmorGrid
                       belongs
        cell_size - size of each armor cell, which is a square, in
                    pixels
        width - width of the armor grid in cells
        """
        self._minimum_armor = ArmorGrid._MINIMUM_ARMOR_FACTOR * armor_rating
        armor_per_cell = armor_rating * ArmorGrid.ARMOR_RATING_PER_CELL_FACTOR
        self.cells = np.full((5,width+4), armor_per_cell)
        self.bounds = np.arange(0, len(self.cells[2,2:-2])) * cell_size
       
    def _pooled_values(self) -> object:
        """
        Return the armor value pooled around each cell of the middle
        row, from third through third-last.
        """
        return np.maximum(self._minimum_armor, np.array(
            [np.sum(ArmorGrid.WEIGHTS * self.cells[0:5,i:i+5]) for i, _ in
            enumerate(self.cells[2,2:-2])]))
                 
    def damage_factors(self, hit_strength: float) -> object:
        """
        Return the factor whereby to multiply the damage of each
        non-padding cell of the grid.
        """
        return np.maximum(ArmorGrid._MINIMUM_DAMAGE_FACTOR,
                          1 / (1 + self._pooled_values() / hit_strength))
       
       
class Ship:
    """
    A Starsector ship.
   
    methods:
    - will_overload
    - overloaded
    - shield_up
   
    variables:
    weapons - container of the weapons of the ship, with structure
              to be determined
    armor_grid - ArmorGrid of the ship
    hull - amount of hitpoints the ship has
    flux_capacity - amount of flux to overload the ship
    flux_dissipation - how much flux the ship can expel without
                       actively venting
    hard_flux - hard flux amount
    soft_flux - soft flux amount
    """
    def __init__(self,
            weapons: object,
            armor_grid: object,
            hull: float,
            flux_capacity: float,
            flux_dissipation: float):
        """
        weapons - container of the weapons of the ship, with
              structure to be determined
        armor_grid - ArmorGrid of the ship
        hull - amount of hitpoints the ship has
        flux_capacity - amount of flux to overload the ship
        flux_dissipation - how much flux the ship can expel
                           without actively venting
        """
        self.weapons = weapons
        self.armor_grid = armor_grid
        self.hull = hull
        self.flux_capacity = flux_capacity
        self.flux_dissipation = flux_dissipation
        self.hard_flux, self.soft_flux = 0, 0
   
    @property
    def will_overload(self):
        """
        Return whether the ship will now overload.
       
        A ship will overload if soft or hard flux exceeds
        the flux capacity of the ship.
        """
        return (self.hard_flux > self.flux_capacity
                or self.soft_flux > self.flux_capacity)
   
    @property
    def overloaded(self):
        """
        Return whether the ship is in the overloaded state.
       
        Ignores the duration of overloaded.
        -TODO: Implement overload duration
        """
        return self.will_overload
       
    @property
    def shield_up(self):
        """
        Return whether the shield is up or down.
       
        Presumes the shield to be up if the ship is not overloaded,
        ignoring smart tricks the AI can play to take kinetic damage
        on its armor and save its shield for incoming high explosive
        damage.
        """
        return not self.overloaded


class Shot:
    """
    A shot fired at a row of armor cells protected by a shield.

    Calculates the expectation value of the damage of a shot
    with a spread to a ship with a shield, armor grid, and random
    positional deviation.
   
    variables:
    base_damage - amount listed under damage in weapon_data.csv
    base_shield_damage - starting amount of damage to be inflicted
                         on the ship shield
    base_armor_damage - starting amount of damage to be inflicted
                        on the ship armor
    strength - strength against armor for armor damage calculation
    flux_hard: whether the flux damage against shields is hard or not
   
    methods:
    - distribute
    - _damage_distribution
    - damage_armor_grid
    """
    def __init__(
            self,
            base_damage: float,
            base_shield_damage: float,
            base_armor_damage: float,
            strength: float,
            flux_hard: bool):
        """
        base_damage - amount listed under damage in weapon_data.csv
        base_shield_damage - starting amount of damage to be inflicted
                             on the ship shield
        base_armor_damage - starting amount of damage to be inflicted
                            on the ship armor
        strength - strength against armor for armor damage calculation
        flux_hard: whether the flux damage against shields is hard or not
        """
        self.base_damage = base_damage
        self.base_shield_damage = base_shield_damage
        self.base_armor_damage = base_armor_damage
        self.strength = strength
        self.probabilities = None

    def distribute(self, ship: object, distribution: object):
        """
        Spread hit probability over each armor cell of a ship.
       
        Calculate the probability to hit each armor cell of a
        row and save the probabilities for later calculation.
        """
        self.probabilities = np.vectorize(distribution)(ship.armor_grid.bounds)
   
    def _damage_distribution(self, ship: object) -> list:
        """
        Return the expected damage to each cell of the shiped armor
        row times the probability to hit it.
       
        damage - damage against armor after reduction by armor
        """
        return (self.base_armor_damage
                * self.probabilities
                * ship.armor_grid.damage_factors(self.strength))

    def damage_armor_grid(self, ship):
        """
        Reduce the values of the armor grid cells of the ship
        by the expected value of the damage of the shot across them.
        """
        for i, damage in enumerate(self._damage_distribution(ship)):
            ship.armor_grid.cells[0:5,i:i+5] = np.maximum(0,
                ship.armor_grid.cells[0:5,i:i+5]
                - damage
                * ArmorGrid.WEIGHTS
                * ArmorGrid.ARMOR_RATING_PER_CELL_FACTOR)
                       
                       
class Weapon:
    """
    A weapon for a Starsector ship in simulated battle.
   
    Is carried by a Ship instance, contains a shot with some
    distribution, and fires that shot at a target.
    """
    def __init__(self, shot: object, distribution: object):
        """
        shot - projectile, missile, or beam tick of the weapon
        distribution - function returning the probability that
                       of the shot to hit between two bounds
        """
        self.shot = shot
        self.distribution = distribution
       
    def fire(self, ship: object):
        """
        Fire the shot of this weapon at that ship.
       
        ship - a Ship instance
        """
        self.shot.distribute(ship, self.distribution)
        #TODO: implement general hit ship method on Shot
        self.shot.damage_armor_grid(ship)
        return #future code below
        if ship.shield_up:
            pass #TODO: implement shield damage, overloading, etc.
        else: self.shot.damage_armor_grid(ship)
[close]
test.py
Code
import combat_entities
import numpy as np
import json
       
   
def test_Shot():
    def test_damage_armor_grid():
        def hit_probability(bound: float): return 0.1 #dummy for test
       
        with open('data.json') as f:
            data = json.load(f)["Shot.damage_armor_grid"]
       
        decimal_places = data["test_config"]["decimal_places"]
        ship_spec = (
            1_000,#hull
            1_000,#flux_capacity
            100#flux_dissipation
        )
        armor_grid_spec = (
            data["armor_grid_spec"]["armor_rating"],
            data["armor_grid_spec"]["cell_size"],
            data["armor_grid_spec"]["width"]
        )
        weapons = []
        shot_spec = (
            data["shot_spec"]["base_damage"],
            data["shot_spec"]["base_shield_damage"],
            data["shot_spec"]["base_armor_damage"],
            data["shot_spec"]["strength"],
            data["shot_spec"]["flux_hard"]
        )
       
        ship = combat_entities.Ship(weapons,
                                    combat_entities.ArmorGrid(*armor_grid_spec),
                                    *ship_spec)
        weapon = combat_entities.Weapon(combat_entities.Shot(*shot_spec),
                                        hit_probability)
       
        initial_grid_expected = np.array(
            data["armor_grid_cell_values"]["initial"])
        initial_grid = np.round(ship.armor_grid.cells, decimal_places)
        if not (initial_grid == initial_grid_expected).all():
            print("TEST FAILED")
            print("Location: test_Shot.test_damage_armor_grid")
            print("Problem: initial armor grid does not equal expected one.")
            try:
                print("initial_grid - initial_grid_expected")
                print(initial_grid - initial_grid_expected)
            except:
                print("Could not subtract grids")
       
        weapon.fire(ship)
       
        grid_after_shot_expected = np.array(
            data["armor_grid_cell_values"]["after_shot"])
        grid_after_shot = np.round(ship.armor_grid.cells, decimal_places)
        if not (grid_after_shot == grid_after_shot_expected).all():
            print("TEST FAILED")
            print("Location: test_Shot.test_damage_armor_grid")
            print("Problem: armor grid after shot does not equal expected one")
            try:
                print()
                print("grid_after_shot_expected")
                print(grid_after_shot_expected)
                print()
                print("grid_after_shot")
                print(grid_after_shot)
                print()
                print("grid_after_shot - grid_after_shot_expected")
                print(grid_after_shot - grid_after_shot_expected)
            except:
                print("Could not subtract grids")
       
    test_damage_armor_grid()
[close]
main.py
Code
import test

def main():
    test.test_Shot()
main()
[close]
[close]

I have noticed that my result is symmetrical whereas yours is less damaged on the right.  I wonder if we have implemented or applied our probability functions differently.
« Last Edit: December 10, 2022, 02:30:32 PM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #243 on: December 10, 2022, 02:54:37 PM »

Well, if yours is symmetrical and mine is not, then yours would be correct because the damage must be symmetrical, to the extent that you should be able to only calculate it halfway and mirror the rest, since we always target the center and have a symmetrical probability distribution. I'm pretty bad with indexing which has been the bane of my code time and again so it is entirely possibly due to that again. Don't worry about it. I see absolutely no way how you could go from a few integers to exactly the right numbers there other than a bug in mine. I currently can't get to computer so can't debug.

But do try the successive shots and nonuniform dist as those ensure the adr is computed correctly. If the result is the same other than at extreme right where it must be a problem on my end then your code is exactly correct here.

Edit: though looking at it, are you sure it isn't such as a copypaste error? In my first print in first post on this page the result looks symmetrical unlike the one you copied. But it is split across lines due to R and no rounding.

Quote

"New armor matrix:"
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]    [,11]
[1,] 6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524
[2,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[3,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[4,] 6.647619 6.609524 6.571429 6.533333 6.514286 6.514286 6.514286 6.514286 6.514286 6.514286 6.533333
[5,] 6.666667 6.647619 6.628571 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524 6.609524
        [,12]    [,13]    [,14]
[1,] 6.628571 6.647619 6.666667
[2,] 6.571429 6.609524 6.647619
[3,] 6.571429 6.609524 6.647619
[4,] 6.571429 6.609524 6.647619
[5,] 6.628571 6.647619 6.666667

Columns 12, 13 and 14 are on a new line due to not fitting in the R print horizontally.
« Last Edit: December 10, 2022, 03:24:14 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #244 on: December 10, 2022, 03:33:54 PM »

You're right: I had incorrectly copied the expected numbers.  Deleting and re-copying the expected numbers fixed the problem.

My next questions are:
1. The constructor arguments of each combat entity should be the relevant data from the game files.  What numbers shall we need and  formulae would yield them from the ones we have?
2. How many successive shots should we test?
« Last Edit: December 10, 2022, 04:04:54 PM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #245 on: December 10, 2022, 04:25:24 PM »

Well, I think this roadmap is still good, though I added the extra step of simulations to test the model vs itself and the error correction part.

R   Py
[ ] [?] Import data for ships and weapons (ships: hullhp, default flux dissipation, flux capacity, armor, width in px, no. armor cells, shield width in px, shield upkeep)
     (weapons: damage, chargeup, chargedown, burst size, burst delay, ammo capacity, ammo regen, ammo reload size, type (beam or not))
[v] [v] Create probability distribution of hits over ship
[v] [ ] Create sequence describing hits from weapon at timepoint (in whole seconds) during the simulation
[v] [v] Armor damage function to be used during combat simulation (the most complex part of the thing - use intrinsic_parity's)
[v] [ ] Testing function to plot a large number of ships using simulated random hits and plot model prediction vs the average
[v] [ ] Main combat loop (
   flow: 1. check whether using shields to block -> do not block and dissipate soft and then hard flux if blocking would overload you,
   else block and dissipate soft flux only
   2. damage shields by damage to shields * probability to hit shields if blocking with shields
   3. damage armor using the armor damage function
   4. damage hull
   5. repeat until dead, record time to kill)
[v] [ ] Graph combat loop to make sure everything is working as needed
[v] [ ] Build and test error corrected armor damage function using expected ADR
[v] [ ] Put it all together, loop over weapons using a particular algorithm or just brute force, compare times to kill



I think the next logical step would be to run the same 3 shot trials I did above to see results are still the same. Then we know it is accurate for a few shors. Then after that, to test an arbitrary number of shots, you should pit the model vs itself - plot the armor and hull damage to, say, 100 ships using random hits on the armor vs. the model's prediction to yield a residual plot like I did above. If the error is consistently a few % of hull hp at point of kill when killing a Dominator with a 400 damage energy weapon (can check the error's direction and magnitude vs intrinsic_parity's and my plots) then that means you have successfully replicated the uncorrected model. This is also an important function to build for later for quantifying error and if you want to try to implement error correction.

You should get roughly around 60 shots to kill with a relatively wide distribution with those parameters and also find a slight model error. It's hard to be exact about how many shots it should take because unfortunately exact probability distributions used for hits weren't posted at the time, and that affects it, but seems to be about that in our previous models. Getting something in the 50 to 70 range with a relatively wide hit distribution would be a favorable sign but deviation from that doesn't necessarily mean error (as long as numbers aren't absurd) as like said seems we didn't note the dist and also could be hidden bugs in previous code. What is important is that the sim results are reasonable and the model fits them as expected with a small error. Can extend the "dumb" elementary operations model to provide comparison data with an exactly specified probability, too.
« Last Edit: December 10, 2022, 04:46:53 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #246 on: December 10, 2022, 07:19:34 PM »

It passes the three-shot test!  Sounds like I need to code some sort of ship combat logic.  Also, how should I arrange for the extended test?  Perhaps I could have the armor grid states from your test?

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #247 on: December 10, 2022, 10:19:19 PM »

Well, it wouldn't really make sense to use mine, since this is supposed to be a test of the model's own workings, in two ways: 1) that it predicts random results using its own functions correctly, and therefore, if those functions are true to the game, results from the game correctly and 2) it produces the exactly correct result (without model error from the naive model's lack of handling of expected adr values) as a mean of random ships. Note that unlike the naive model's prediction this is expected to be the actual mean from the game as when using random shots we do not hit the expected value from expected value problem. Then later the final step is to show the error correction corrects the prediction to the mean.

For this test you need to be able to calculate hull damage, and should write a function to damage armor with 1 shot at ship cell x. This will be needed later for the star matrices, too, so I suggest a separate function. Then repeat that with random shots until death for 1 ship, repeat 100 times, graph results and compute mean/median (your preference) of hull damage. Compare model prediction to this. The alternative hackish way is to repeatedly pass your current functions prob dists like 0 0 1 0 0 0 ... with a random index set to 1 and 0 otherwise, but since you need a performant version of this for later anyway, might as well do it now. You can check your work by plotting the new vs the old with the hackish probability method with a number of random ships and seeing results are similar.

So the test should run like
- create undamaged ship
- damage it using random shots until it dies, record hull hp at each step
- repeat 100 times, calculate mean/median hull hp (these are expected to converge at the end so shouldn't be a big deal which, the distribution starts skewed when hull hp is only going down for a part of ships but by the end there should be equal deviation both ways from the mean)
- get predictions from model for hull hp vs the same ship with the same weapon with the same prob dist as the random shots
- compare results by graphing or tabling as is your preference

I would not add shields to this test yet, since that should essentially just mean adding a cycle of skipping damage to armor to the model if successful, and should not demonstrate any additional error because shield calculation by the model is exact as there is no consecutive exp values problem when the operations are linear, but is a source of potential errors. Later, though, you can use this test with shields included to demonstrate total error in the model when shields and realistic weapons are in.
« Last Edit: December 10, 2022, 11:06:39 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #248 on: December 11, 2022, 07:55:56 PM »

I have been following the above instructions, decided to compare the average number of simulated firings to destroy the ship to the calculated number before trying the statistics you mentioned, and so suspect my result proves the calculation to accurately represent the simulation that I have stopped working until you review this result and my code.
Number of firings to destory ship
Calculated: 272
Average Simulated: 273.67
Code
Code
import combat_entities
import numpy as np
import pytest
import json

       
def _data():
    with open('test_combat_entities_data.json') as f:
        return json.load(f)["Shot.damage_armor_grid"]


def _decimal_places(data):
    return data["test_config"]["decimal_places"]


def _ship_spec(data):
    return (1_000,#hull
            1_000,#flux_capacity
            100)#flux_dissipation)


def _hit_probability(bound: float): return 0.1#dummy for test


def _armor_grid_spec(data):
    return (data["armor_grid_spec"]["armor_rating"],
            data["armor_grid_spec"]["cell_size"],
            data["armor_grid_spec"]["width"])

def _weapons(data):
    return []


def _shot_spec(data):
    return (data["shot_spec"]["base_damage"],
            data["shot_spec"]["base_shield_damage"],
            data["shot_spec"]["base_armor_damage"],
            data["shot_spec"]["strength"],
            data["shot_spec"]["flux_hard"])


def test_armor_grid_constructor():
    armor_grid = combat_entities.ArmorGrid(*_armor_grid_spec(_data()))
    assert (np.round(armor_grid.cells, _decimal_places(_data()))
            == np.array(_data()["armor_grid_cell_values"]["initial"])).all(), (
            "Initial armor grid does not equal expected one.")

   
def test_damage_armor_grid():
    armor_grid = combat_entities.ArmorGrid(*_armor_grid_spec(_data()))
    ship = combat_entities.Ship(_weapons(_data()), armor_grid,
                                *_ship_spec(_data()))
    shot = combat_entities.Shot(*_shot_spec(_data()))
    shot.distribute(ship, _hit_probability)
    decimal_places = _decimal_places(_data())
    for i, expected_armor_grid in enumerate(
            _data()["armor_grid_cell_values"]["after_shots"]):
        for i, damage in enumerate(shot._damage_distribution(armor_grid)):
            shot.damage_armor_grid(ship.armor_grid, damage, i)
        ship.armor_grid.cells = np.maximum(0, ship.armor_grid.cells)
        assert (np.round(ship.armor_grid.cells, decimal_places) 
                == np.array(expected_armor_grid)).all(), (
                "Armor grid after shot", i, "does not equal expected one.")


def _simulate_hit(shot: object, ship: object) -> object:
    index = np.random.randint(0, len(ship.armor_grid.bounds))
    pooled_armor = max(ship.armor_grid._minimum_armor,
                       ship.armor_grid._pool(index))
    damage_factor = max(combat_entities.ArmorGrid._MINIMUM_DAMAGE_FACTOR,
                        1 / (1 + pooled_armor / shot.strength))
    damage = shot.base_armor_damage * damage_factor
    shot.damage_armor_grid(ship.armor_grid, damage, index)
    middle_cells = ship.armor_grid.cells[2][2:-2]
    hull_damage = np.sum(middle_cells[middle_cells<0])
    ship.hull = max(0, ship.hull + hull_damage)
    ship.armor_grid.cells = np.maximum(0, ship.armor_grid.cells)


def test_calculation_vs_simulation():
    shot = combat_entities.Shot(*_shot_spec(_data()))
    armor_grid_spec = _armor_grid_spec(_data())
    weapons = _weapons(_data())
    ship_spec = _ship_spec(_data())
    calculated_ship = combat_entities.Ship(
        weapons,
        combat_entities.ArmorGrid(*armor_grid_spec),
        *ship_spec)
    shot.distribute(calculated_ship, _hit_probability)
    trials = 100
   
    calculated_firings = 0
    while calculated_ship.hull > 0:
        shot.damage_ship(calculated_ship)
        calculated_firings += 1

    simulated_firings = []
    for trial in range(trials):
        simulated_ship = combat_entities.Ship(
            weapons,
            combat_entities.ArmorGrid(*armor_grid_spec),
            *ship_spec)
        firing = 0
        while simulated_ship.hull > 0:
            _simulate_hit(shot, simulated_ship)
            firing += 1
        simulated_firings.append(firing)

    print()
    print("Number of firings to destory ship")
    print("Calculated:", calculated_firings)
    print("Average Simulated:", np.average(simulated_firings))
[close]
« Last Edit: December 11, 2022, 08:02:36 PM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #249 on: December 11, 2022, 09:33:28 PM »

Well, it does seem right, and the direction of error is right and magnitude seems ok, but I'm unable to comment on whether it should take that many shots without weapon data since I was unable to find it in the code. A couple of things:

Your simulated hit function uses
Code
    index = np.random.randint(0, len(ship.armor_grid.bounds))

I think it would be better to pass the index to the function, and have a separate function that generates indices according to a custom probability distribution. For example, knowing that probabilities sum to 1, you could pass it the hit probability distribution you want to model, calculate the cumulative distribution (ie. for index k, sum of indices up to and inclusive of k) to get upper bounds for boxes, and check which box a random uniform variable from 0 to 1 falls in. This way you can simulate with any probability distribution that you want, which is relevant because we would expect that to affect error in the model, and you can also use the simulated hit function for the hypothetical matrices later where you must pass a predetermined index to it.

The error is eliminated whenever the minimum armor rule or the minimum damage rule is in effect, so that is why it's important to test a weapon that is not strong enough to instantly destroy armor, but also not weak enough to be under the minimum damage rule most of the time to see the greatest possible error. So I'd recommend testing against a Dominator (1500 armor, 14000 hull, 12 cells) with a 400 damage energy weapon, because we have two independent simulations existing of what the shot count should be (roughly, though, because the expected prob dists weren't published except as being wide, but we can also rig the "dumb" version to calculate it if doubts arise).

Looking at firings to kill is a good idea. I'd also be interested in difference in hull - this is because we might combine a variety of weapons so we would want each weapon to be reasonably accurate about hull separately as the weapons will deliver the kill in combination. Print both results?
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #250 on: December 12, 2022, 04:45:27 AM »

Well, it does seem right, and the direction of error is right and magnitude seems ok, but I'm unable to comment on whether it should take that many shots without weapon data since I was unable to find it in the code. A couple of things:

It's the same weapon data as usual.
{
    "Shot.damage_armor_grid":{
        "test_config":{
            "decimal_places":6
        },
        "shot_spec":{
            "base_damage":10.0,
            "base_armor_damage":20.0,
            "base_shield_damage":5.0,
            "strength":40.0,
            "flux_hard":false
        },
        "armor_grid_spec":{
            "armor_rating":100.0,
            "cell_size":10.0,
            "width":10
        },
        "armor_grid_cell_values":{
            "initial":[
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667],
                [6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667,6.666667]
            ],
            "after_shots":[
      [[6.666667,6.647619,6.628571,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.628571,6.647619,6.666667],
                 [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.571429,6.609524,6.647619],
                 [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.571429,6.609524,6.647619],
                 [6.647619,6.609524,6.571429,6.533333,6.514286,6.514286,6.514286,6.514286,6.514286,6.514286,6.533333,6.571429,6.609524,6.647619],
                 [6.666667,6.647619,6.628571,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.609524,6.628571,6.647619,6.666667]],
      [[6.666667,6.628403,6.590086,6.551735,6.551631,6.551577,6.551557,6.551557,6.551577,6.551631,6.551735,6.590086,6.628403,6.666667],
       [6.628403,6.551822,6.475154,6.398435,6.359961,6.359799,6.359728,6.359728,6.359799,6.359961,6.398435,6.475154,6.551822,6.628403],
       [6.628403,6.551822,6.475154,6.398435,6.359961,6.359799,6.359728,6.359728,6.359799,6.359961,6.398435,6.475154,6.551822,6.628403],
       [6.628403,6.551822,6.475154,6.398435,6.359961,6.359799,6.359728,6.359728,6.359799,6.359961,6.398435,6.475154,6.551822,6.628403],
       [6.666667,6.628403,6.590086,6.551735,6.551631,6.551577,6.551557,6.551557,6.551577,6.551631,6.551735,6.590086,6.628403,6.666667]],
      [[6.666667,6.609013,6.551198,6.493276,6.492959,6.492791,6.49273,6.49273,6.492791,6.492959,6.493276,6.551198,6.609013,6.666667],
       [6.609013,6.493544,6.377807,6.261915,6.203615,6.203117,6.20290,6.20290,6.203117,6.203615,6.261915,6.377807,6.493544,6.609013],
       [6.609013,6.493544,6.377807,6.261915,6.203615,6.203117,6.20290,6.20290,6.203117,6.203615,6.261915,6.377807,6.493544,6.609013],
       [6.609013,6.493544,6.377807,6.261915,6.203615,6.203117,6.20290,6.20290,6.203117,6.203615,6.261915,6.377807,6.493544,6.609013],
       [6.666667,6.609013,6.551198,6.493276,6.492959,6.492791,6.49273,6.49273,6.492791,6.492959,6.493276,6.551198,6.609013,6.666667]]
            ]
        }
    }
}


Quote
Your simulated hit function uses
Code
    index = np.random.randint(0, len(ship.armor_grid.bounds))

I think it would be better to pass the index to the function, and have a separate function that generates indices according to a custom probability distribution. For example, knowing that probabilities sum to 1, you could pass it the hit probability distribution you want to model, calculate the cumulative distribution (ie. for index k, sum of indices up to and inclusive of k) to get upper bounds for boxes, and check which box a random uniform variable from 0 to 1 falls in. This way you can simulate with any probability distribution that you want, which is relevant because we would expect that to affect error in the model, and you can also use the simulated hit function for the hypothetical matrices later where you must pass a predetermined index to it.

Sure.
Code
def _simulate_hit(shot: object, ship: object, index: int):
    pooled_armor = max(ship.armor_grid._minimum_armor,
                       ship.armor_grid._pool(index))
    damage_factor = max(combat_entities.ArmorGrid._MINIMUM_DAMAGE_FACTOR,
                        1 / (1 + pooled_armor / shot.strength))
    damage = shot.base_armor_damage * damage_factor
    shot.damage_armor_grid(ship.armor_grid, damage, index)
    middle_cells = ship.armor_grid.cells[2][2:-2]
    hull_damage = np.sum(middle_cells[middle_cells<0])
    ship.hull = max(0, ship.hull + hull_damage)
    ship.armor_grid.cells = np.maximum(0, ship.armor_grid.cells)

Quote
The error is eliminated whenever the minimum armor rule or the minimum damage rule is in effect, so that is why it's important to test a weapon that is not strong enough to instantly destroy armor, but also not weak enough to be under the minimum damage rule most of the time to see the greatest possible error. So I'd recommend testing against a Dominator (1500 armor, 14000 hull, 12 cells) with a 400 damage energy weapon, because we have two independent simulations existing of what the shot count should be (roughly, though, because the expected prob dists weren't published except as being wide, but we can also rig the "dumb" version to calculate it if doubts arise).

Sounds like a good idea.  Would you mind creating a .json for those numbers and the resulting armor grids as shown above?

Quote
Looking at firings to kill is a good idea. I'd also be interested in difference in hull - this is because we might combine a variety of weapons so we would want each weapon to be reasonably accurate about hull separately as the weapons will deliver the kill in combination. Print both results?

Would you please elaborate about what you mean by "difference in hull" because I thought the test would continue until the ship were destroyed?
« Last Edit: December 12, 2022, 04:47:33 AM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #251 on: December 12, 2022, 06:48:42 AM »

Ok let's try this:

Code
       "shot_spec":{
            "base_damage":400.0,
            "base_armor_damage":400.0,
            "base_shield_damage":400.0,
            "strength":400.0,
            "flux_hard":true
        },

Code
        "armor_grid_spec":{
            "armor_rating":1500.0,
            "cell_size":10.0,
            "width":12
        },

I do not know what cell_size means or how you configure hull hp unfortunately. The correct armor grid should be 16 cells wide and have 100 armor hp in each cell

Quote
Would you please elaborate about what you mean by "difference in hull" because I thought the test would continue until the ship were destroyed?


Alright let me fire up MSPaint


Of course with 1 weapon R_hull (mean hull simulated ships have left when model dies) and R_shot (how many more shots it takes to kill the mean simulated ship than the model) are function of each other. But we might prefer knowing the magnitude of R_hull, because what if the weapon runs out of ammo a few shots before kill, and we have to continue with a pea shooter? Then how will model error affect us? (Of course you could still answer "a function of how many big gun shots would be left but I think the hull residual is more intuitive. The shot residual is better for description of single weapon error tho)

Edit to add: the curved line is supposed to represent mean of ships despite not looking like it. Sorry about the level of artwork here, I was a little pressed for time.
« Last Edit: December 12, 2022, 07:05:23 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #252 on: December 12, 2022, 07:32:59 AM »

Ok let's try this:

Code
       "shot_spec":{
            "base_damage":400.0,
            "base_armor_damage":400.0,
            "base_shield_damage":400.0,
            "strength":400.0,
            "flux_hard":true
        },

Code
        "armor_grid_spec":{
            "armor_rating":1500.0,
            "cell_size":10.0,
            "width":12
        },

I do not know what cell_size means or how you configure hull hp unfortunately. The correct armor grid should be 16 cells wide and have 100 armor hp in each cell

Oh, I had left cell_size and hull hardcoded.  I can make those configurable.  I still need those armor grid states I mentioned.

Quote
Alright let me fire up MSPaint


Of course with 1 weapon these two are a function of each other. But we might prefer knowing the magnitude of R_hull, because what if the weapon runs out of ammo a few shots before kill, and we have to continue with a pea shooter? Then how will model error affect us?

Using the same .json as before because I need the new armor grids to make the test pass,
Number of firings to destroy ship
Calculated: 272
Average Simulated: 273

Average hull value
Calculated: 565
Simulated: 566
Code
Code
def test_calculation_vs_simulation():
    shot = combat_entities.Shot(*_shot_spec(_data()))
    armor_grid_spec = _armor_grid_spec(_data())
    weapons = _weapons(_data())
    ship_spec = _ship_spec(_data())
    calculated_ship = combat_entities.Ship(
        weapons,
        combat_entities.ArmorGrid(*armor_grid_spec),
        *ship_spec)
    simulated_ship = combat_entities.Ship(
        weapons,
        combat_entities.ArmorGrid(*armor_grid_spec),
        *ship_spec)
    shot.distribute(calculated_ship, _hit_probability)
    trials = 100
   
    calculated_firings = 0
    calculated_hull = 0
    while calculated_ship.hull > 0:
        shot.damage_ship(calculated_ship)
        calculated_hull += calculated_ship.hull
        calculated_firings += 1
    calculated_hull /= calculated_firings

    simulated_firings = 0
    simulated_hull = 0
    for trial in range(trials):
        firing = 0
        hull = 0
        while simulated_ship.hull > 0:
            index = np.random.randint(0, len(simulated_ship.armor_grid.bounds))
            _simulate_hit(shot, simulated_ship, index)
            hull += simulated_ship.hull
            firing += 1
        simulated_hull += hull / firing
        simulated_ship.armor_grid = combat_entities.ArmorGrid(*armor_grid_spec)
        simulated_ship.hull = 1000
        simulated_firings += firing
    simulated_firings /= trials
    simulated_hull /= trials

    print()
    print("Number of firings to destroy ship")
    print("Calculated:", calculated_firings)
    print("Average Simulated:", round(simulated_firings))
    print()
    print("Average hull value")
    print("Calculated:", round(calculated_hull))
    print("Simulated:", round(simulated_hull))
[close]
« Last Edit: December 12, 2022, 07:34:46 AM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #253 on: December 12, 2022, 07:59:46 AM »

Could you elaborate - what do you mean by armor grid states? A sample shot series from the dumb model?
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Liral

  • Admiral
  • *****
  • Posts: 718
  • Realistic Combat Mod Author
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #254 on: December 12, 2022, 08:07:30 AM »

Could you elaborate - what do you mean by armor grid states? A sample shot series from the dumb model?

What do you mean by the dumb model?  I mean the series of shots we did last time but for these numbers that you have chosen now.
Pages: 1 ... 15 16 [17] 18 19 ... 32