Fractal Softworks Forum

Please login or register.

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

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

CapnHector

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

You see, the goal is to express expected ADR according to probability distribution of hits. So if the shot misses, then the expected ADR for that probability is just the same as before, as nothing changes.

In terse notation we are computing

E(ADR) = sum ( ADR given hit * P(hit) ) over all hits that can affect ADR and for each central cell. The hit here refers to the next hit. Then in the next step we discard the hypotheticals and use what we calculated for E(ADR) to compute the expected damage from that hit and finally subtract that from our non-hypothetical armor state. (ADR given miss) is just the same ADR as before, since nothing changes for the armor when a shot misses.

But to avoid infinite loops we refer to the previous expected armor state to compute ADR given hit. We know the previous expected armor state is the true expected armor state if we have computed expected ADR correctly at every point, which is why we can do so (and also why we are not allowed to mix models or ever compute expected ADR directly from the armor after step 0).

There is one "easy" extra optimization. When the pooled armor gets below minimum armor threshold, you can drop this model and then just use the minimum ADR for that cell, since then there is no non-linearity anymore. This is the horizontal part in plots above.
« Last Edit: December 02, 2022, 10:29:39 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

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

If the shot misses, it does no damage, so the damage reduction is zero? Unless you have some weird definition of ADR that you haven't defined anywhere?
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #212 on: December 02, 2022, 11:14:12 PM »

Okay so when I say ADR I mean chi but I'm trying not to use my cryptic notation. That is, hit strength/ (hit strength+ pooled armor). But I'm just going to say "chi" (from "function based on h") then. Chi means that. So given the armor does not change in a miss, chi at cell k given miss = chi at cell k. Remember we are looking at the probability of chi from the point of view of the armor cell, not from the shot's point of view. Here is a concrete example of the correct calculation. Say we have a gun and a laser alternating fire and the gun fires first.

Step 0 (preliminaries). Compute  expected chi from hit strength/(hit strength+ pooled armor) for each central cell.
Step 1. Gun shot 1. Compute expected chi after step 1 (expected chi for step 2) using (probability of misses)*(chi at step 0)+ sum(probability of gun hit*chi at cell given gun hit) as described above. Deal damage to armor using expected chi at step 0.
Step 2. Laser shot 1. Compute expected chi after step 2 (expected chi for step 3) using (probability of misses)*(chi at step 1)+ sum(probability of laser hit*chi at cell given laser hit) as described above. Deal damage to armor using expected chi from step 1.
Step 3. Gun shot 2. Compute expected chi after step 3 (expected chi for step 4) using (probability of misses)*(chi at step 2)+ sum(probability of gun hit*chi at cell given gun hit) as described above. Deal damage to armor using expected chi from step 2.

I think I got the timepoints wrong in my algorithm description but I hope this clarifies what should happen. In the algorithm it should be that you compute hypotheticals before applying damage to get the correct order, I think. I'm sure you guys are better at formulating it for the computer to understand though.
« Last Edit: December 02, 2022, 11:30:16 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #213 on: December 02, 2022, 11:50:10 PM »

Oh, are you doing something like saving the chi value from two shots ago and trying to avoid recomputing the value if the armor values didn't change because the last shot didn't hit in the area?
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #214 on: December 03, 2022, 12:31:32 AM »

Yeah that's the mathematical trick I was talking about earlier. The oversimplified summary is that in the uncorrected model we look at expected chi and damage both using the shot's probability distribution ("shot's point of view"). In the corrected we look at the chi from the armor's probability distribution and damage from the shot's. In principle the armor's probability distribution is a function of all possible shots so far. We avoid this by referring to a previous value we know to be correct and updating it iteratively

Because this is pretty hard (conceptually, not computationally, in fact I am still not 100% either of the plaintext summaries is perfect, I just know it can be done mathematically using approximately this algorithm and it did work in my model) it is absolutely necessary to also have a function to generate comparison data using a large number of simulations with random shots to make sure the code is right.
« Last Edit: December 03, 2022, 12:34:12 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 #215 on: December 03, 2022, 01:34:54 AM »

Great! But with some errors. Specifically you do not always set it to same as naive, only once.

Because you said to do that and left it out of the loop, as you have below.

Quote
I am not sure what you mean by skip first and last reductions. Let me try to be more specific. Also, this is if you index ship cells from 1 to n. So if they are in a vector from 0 to n-1, then subtract 1 from each index. You do not need to compute expected ADR for the padding cells beyond hittable cells because "hitting" those directly must count as a miss and not do damage, even though they can be damaged indirectly by hitting the first and last armor cells.

Because I understood your previous specification to imply that the expected armor damage reductions should be iterated from second to second-last rather than from first to last.

Quote

Step 0. For the very first step of dealing damage, you apply the naive armor damage reduction value based on the starting armor only.
  Armor = starting armor
  That is also the starting value for the expected armor damage reductions.
   Expected adr= adr calculated from starting armor (a vector containing a separate value for each of the central cells)
Loop:
Step 1. Deal damage to armor and hull based on the previously calculated (at step 3) expected armor damage reduction values for each central cell.
  Keep the expected armor damage reductions saved in a variable that is separate from the armor matrix as they are updated, not re-calculated.

damage distributed over armor around middle cell due to hit at middle cell = damage*adjusted ADR for that middle cell
Damage over armor is calculated as usual, but using the expected adr rather than naive adr. However for step 0 they are the same, hence the special case above and the order in the loop.

Step 2. Compute, based on current armor state, the armor states that result from
  Substep 1. A hit to cell 1...
  Substep n. A hit to cell n, using armor damage reduction computed based on current armor. Save these armor states somewhere.


We use the hypothetical armor states here:
Step 3. Compute expected armor damage reductions for the next step, using the hypothetical armor states computed at step 2 as:
  Substep 1. At cell 1, the expected armor damage reduction is:
  The previous armor damage reduction, times probability that the shot does not land in cells 1 to 5, plus:
    Probability that the shot does land in cell 1, times armor damage reduction at cell 1 if shot lands in cell 1,
    plus probability that the shot lands in cell 2, times armor damage reduction at cell 1 if shot lands in cell 2,
    plus probability that the shot lands in cell 3, times armor damage reduction at cell 1 if the shot lands in cell 3,
    plus probability that the shot lands in cell 4, times armor damage reduction at cell 1 if the shot lands in cell 4.
    plus probability that the shot lands in cell 5, times armor damage reduction at cell 1 if the shot lands in cell 5.

   Compute:
naive ADR 1= hit strength/(hit strength+ pooled armor at cell 1 in hypothetical armor state where cell 1 was hit)
naive ADR 2= hit strength/(hit strength+ pooled armor at cell 1 in hypothetical armor state where cell 2 was hit)
...
naive ADR 5 = hit strength/(hit strength+ pooled armor at cell 1 in hypothetical armor state where cell 5 was hit)
expected ADR= expected ADR*(1-p1-p2-p3-p4-p5)+p1*naive ADR 1+p2*naive ADR2+...+p5*naive ADR 5
Where the p:s are hit probabilities for those cells (ie probability to hit cell 1, probability to hit cell 2, ...)

  Substep k: For a cell in the middle of the armor, it is the analogous calculation as at substep 1
    but you look at the possibilities and armor damage reductions for if the shot were to land in cells
    k-4 to k+4 as those are the cells that would affect armor damage reduction at cell k.
  Substep n: Same, but the cells are n-4 to n.

Alternative: same as above, but to save resources, only compute to middle cell m. Then for rest of the armor mirror expected ADR, so exp ADR n = exp ADR 1, exp ADR n-1 = exp ADR 2 etc.


At no point after step 0 are you allowed to compute expected ADR directly from the armor but instead it must be updated at each step as above

Therein lies the problem because this code is incorrectly indented, from the loop body to the substeps of the "Compute" statement, which itself seems to contradict the preceding one.  I need you to clarify the difference between Step 3 and "Compute" before I can write Python code.  Also, writing for i in range(5): do_this_thing would more clearly and simply express repetition than repeating statements five times.
« Last Edit: December 03, 2022, 09:53:30 AM by Liral »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #216 on: December 03, 2022, 01:41:34 AM »

Alright it looks like i'll be unable to get to the computer or latex it for a bit but here's a very rough sketch of the proof. Denote pool(A,k) = pooled armor of armor matrix A at cell k using the pooling method we use. We will ignore the minimum armor and minimum damage functions here due to time limits but they can be applied as usual.

Assume for argument's sake we know chi(t), the vector of armor damage reductions after shots at timepoint t, and E(A_t), the expected state of the armor at timepoint t. We know the probability distribution of shots p where p_i is probability to hit cell i and it is constant. We do not know the probability distribution of armor.

Then, to compute chi_t+1, note expected value of pooled damage (that we will then distribute) at a middle cell k of armor of  E(A_(t+2)) is damage*E(chi_k_t+1) where chi is the ADR function. Per LOTUS we can express this using p as sum of chi_k_t+1 given hit at cell i at timepoint t * p_i. We note that in case of misses this is equal to E(chi_k_t)*probability of miss at timepoint t. And in case of a hit it is equal to hit strength/(hit strength+pool(A_t*i,k))*p_i, where i is the cell hit, and A_t*i is the hypothetical amor state if cell i is hit at timepoint t with certainty which we can compute because we know yhe expected armor state E(A_t) per the assumption.

Now, to compute EA_t+1 means just applying damage to E(A_t) using armor damage reductions E(chi_t) which we know by assumption
.so we can compute E A_t+1 and E chi_t+1 if we know E A_t and E chi_t.

Now note that we know these at step 1, so method follows.

Sorry I know this is not very good. Do latex and explanations later as needed but i thought a quick draft would be helpful. Edit: fix timepoints for chi, clarify. Know it's still pretty bad. Explanatory edit: exprcting family emergency which may distract from Starsector. See if it actualizes.
« Last Edit: December 03, 2022, 02:16:40 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 #217 on: December 03, 2022, 03:29:58 AM »

Therein lies the problem because this code is incorrectly indented, from the loop body to the substeps of the "Compute" statement, which itself seems to contradict the preceding one.  I need you to clarify the difference between Step 3 and "Compute" before I can write Python code.  Also, writing for i in range(5): do_this_thing would more clearly and simply express repetition than repeating statements five times.

Oh sorry, I misread what you were doing with the code. Here is an attempt at a better formatted pseudocode courtesy of notepad++. (I did get a chance to get back to the computer momentarily)


Code
# corrected armor damage reduction code

function pool (armor matrix, cell){
   return sum (j from -2 to 2) (i from -2 to 2) armor matrix cell i_j times weight_i_j
}
# function to compute ADR_vector

function ADR (armor matrix, cell) {
return max(minimum damage, hit strength / (hit strength + max(minimum armor fraction * a_0, pool(armor matrix, cell)))
}

damage (armor matrix, cell) {
    loop over armor matrix
deal damage in 5x5 area around cell distributed according to weights / 15 times ADR(armor matrix, cell)
return damaged armor matrix
}

weight =
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

#hit probability distribution for central cells, in reality this will also include misses and be padded but here we will just index from 1 to n
p = vector(probability_mass_at_cell_i, length = n)


# at start, armor is set based on ship starting armor armor_0. In addition there are 2 rows of padding at each end. The actual ship armor cells are numbered from 1 to n.

armor_matrix = matrix(armor_0/15, rows = 5, columns = n+4)

# at start, armor damage reduction is calculated based on armor at start

ADR_vector = vector(hit strength/(hit strength + a_0), length = n)

# to compute next expected ADR we must know previous expected armor state (armor_matrix) and previous expected ADR_vector
function armordamage (armor_matrix, ADR_vector, damage, hit strength, probability vector){
#compute hypotheticals
for (index i over actual ship armor cells from 1 to n)
spawn hypothetical armor matrix armor_star_i = damage( armor matrix, i)
#update armor matrix
armor_matrix = {calculate damage to armor matrix here as usual, hitting each central cell with probability p[i], and use ADR_vector[i] for armor damage reduction}
#update ADR estimates
for (i from 1 to n){
     pooled probability = 0
probability weighted ADR = 0
for (j in (loop over all columns where a hit would affect the ADR; for edges this is 1 to 5 and n-4 to 4; for central cells this is cell-4 to cell +4){
    pooled probability = pooled probability + p[j]
probability weighted ADR = probability weighted ADR + ADR(armor_star_j, at cell i)*p[j]
}
ADR_vector[i] = ADR_vector[i]*(1-pooled_probability) + probability weighted ADR
}
return(armor_matrix, ADR_vector)
}


Edit: fixed error on line 50
« Last Edit: December 03, 2022, 03:43:42 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 #218 on: December 03, 2022, 05:01:14 AM »

And here is a proof sketch.



Edit to add: missing, but note w_i near the end of page 1 is ith row vector of W
« Last Edit: December 03, 2022, 05:58:47 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 #219 on: December 03, 2022, 01:17:41 PM »

Oh sorry, I misread what you were doing with the code. Here is an attempt at a better formatted pseudocode courtesy of notepad++. (I did get a chance to get back to the computer momentarily)

Edit: fixed error on line 50

You might be surprised, but you have now almost written the Python code yourself.  :)  Here is my partly-implemented and heavily marked-up version of your specification.  Also, please note that to specify an iteration clearly-enough for someone else to implement is to almost write it in Python, for which one would only need to use the following syntax to type valid code:
for element in iterable_object:
    do_stuff
    do_other_stuff
    do_yet_other_stuff

or if you want to work with indices,
for index, element in enumerate(iterable_obejct):
    call_some_function(index)
    call_another_function(element, index)
    call_some_other_function(element)

So you might as well—just remember the 0-indexing.  ;D

You can't escape the Pythooooonnnnn hissssss :p
   
Code
"""
Damage computing module.
"""

WEIGHTS = [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]

MINIMUM_ARMOR_FACTOR = 0.05


def pool(armor_grid: list, index: int) -> float:
    """
    Return the pooled armor for the cell at this index of an armor
    row.
    """
    return sum([[armor_grid[i][j] * weight[i][j] for i in
                range(index - 2, index + 3)] for j in range(-3, 2)])
   
   
def armor_damage_factor(armor_grid: list, index: int) -> float:
    """
    Return the armor damage factor for the cell at this index
    of an armor row.
    """
    #CONCERN: The minimum armor factor in this function affects both armor
    #         and hull damage although I remember reading that this factor
    #         affects only the latter
    armor = max(MINIMUM_ARMOR_FACTOR * armor_rating, pool(armor_grid, index))
return max(minimum_damage, hit_strength / (hit_strength + armor))


def armor_damage(armor_grid, cell):
    """
    loop over armor_grid:
    in 5x5 area around cell:
        deal damage distributed according to weights / 15 times ADR(armor matrix, cell)
    """
    #QUESTION: How do you mean to loop over the armor_grid and 5x5 area?
    return armor_grid


def update_armor(
    #QUESTION: what about the armor row, which is needed, or is it
    #          implicitly the cells after the first two and before
    #          the last two of the third row of armor_grid?
    armor_grid: list,
    armor_damage_factors: list,
    damage: float, #QUESTION: What damage, exactly?  To armor, shields, hull?
    hit_strength: float,
    probabilities: list) -> tuple:
    """
    Update the armor grid and armor damage factors.
    """
    #QUESTION: What variable contains the 'actual ship armor cells'—
    #          are they perhaps the armor row?
    #CONCERN: The armor grid (rather than row) index passed to the
    #         armor damage function was i rather than the padding-
    #         adjusted value i + 2.
    virtual_armor_grids = [armor_damage(armor_grid, i + 2) for i, _ in
                           enumerate(armor_grid[2][-2:2])]
   
    armor_grid = #calculate damage to armor matrix here as usual,
    #hitting each central cell with probability p[i], and use
    #armor_damage_factors[i] for armor damage reduction
   
    #re-estimate the armor damage factors
    for i, _ in enumerate(armor_grid[2][-2:2]):
        pooled_probability = 0
        probable_armor_damage_factor = 0
        #QUESTION: What columns are those?
    for j in #loop over all columns where a hit would affect the ADR:
        #QUESTION: Why each entire edge rather than the 3-long
        #          fringe beside each one?
        #CONCERN: I think you want nested iteration, but how,
        #         exactly?
        for #edges this is 1 to 5 and n-4 to 4:
            for #central cells this is cell-4 to cell+4:
                    pooled_probability += probabilities[j]
            probable_armor_damage_factor += armor_damage_factor(
                armor_star_j, at cell i) * probabilities[j]
    armor_damage_factors[i] = armor_damage_factors[i]
                              * (1 - pooled_probability)
                              + probable_armor_damage_factor
    #COMMENT: No need for a return statement in the Python version
    #         of this code because modifying the elements of a list
    #         by index in the body of a function wherein the list is
    #         an argument, as your specification does, modifies
    #         the original list in-place.  We can do the same for
    #         the armor grid.
    #         
    #         The statefulness of our system and side-effects of
    #         this function on both current and expected values make
    #         me think of applying some kind of Markov-like thinking
    #         to this math and an object-oriented approach to the
    #         code implementing it.


def main():
    #QUESTION: What do you mean by central cells and "in reality",
    #          and why does this code not do what would be done
    #          "in reality"?
    #CONCERN: This variable was just named 'p' rather than a word.
    #         Even in code specifications, single-letter variable
    #         names can confuse readers, so please use descriptive
    #         variable names when specifying code.
    #CONCERN: What is 'probability_mass_at_cell_i', and whence does
    #         it come; e.g., an argument or function?
    #hit probability distribution for central cells. In reality this
    #will also include misses and be padded, but here we will just
    #index from 1 to n.
    probabilities = [probability_mass(cell) for cell in armor_row]
   
    #Initial armor is based on ship armor_rating
    #In addition there are 2 rows of padding at each end. The actual
    #ship armor cells are numbered from 1 to n.
    armor_grid = [[armor_rating / 15 for _ in range(len(armor_row) + 4)] for
                  _ in range(5)]
   
    #calculate armor damage reduction
    armor_damage_factors = [hit strength / (hit strength + armor_rating) for _
                            in armor_row]

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #220 on: December 03, 2022, 04:13:34 PM »

Well, family thing is happening now with a slight delay, so expect me to be absent for a few days. Quick comments

    #CONCERN: The minimum armor factor in this function affects both armor
    #         and hull damage although I remember reading that this factor
    #         affects only the latter

Per the wiki, the 5% minimum is used in the ADR calculation. However, if Vanshilar or intrinsic_parity could confirm whether it is, that'd be great.

  #QUESTION: How do you mean to loop over the armor_grid and 5x5 area?

In my version of the code, the idea was that this would return a damaged armor matrix that has taken 1 hit.


   #QUESTION: what about the armor row, which is needed, or is it
    #          implicitly the cells after the first two and before
    #          the last two of the third row of armor_grid?

We always calculate armor damage at the middle cell, then distribute. If the dmg function distributes then don't need to loop over rows here. If handled cell by cell then loop over rows too to distribute damage, but the adr calculation is always pooled at the hittable cell.

damage: float, #QUESTION: What damage, exactly?  To armor, shields, hull?

Shot damage to armor.


QUESTION: What variable contains the 'actual ship armor cells'%u2014
    #          are they perhaps the armor row?
    #CONCERN: The armor grid (rather than row) index passed to the
    #         armor damage function was i rather than the padding-


Index as appropriate in the real version to also comtain padding.

By actual ship armor cells I meant the hittable middle cells (not the padding). Only those should have an expected adr value since only they can be hit, and adr calculation happens there.

      #QUESTION: What columns are those?


We only need to calculate adr for the hittable middle cells, however they are indexed in practice, since only those can be hit. The padding cannot be hit directly.

  #QUESTION: Why each entire edge rather than the 3-long
           #          fringe beside each one?

I'm not sure what you mean, but hits up to 5 cells away must be considered because they would affect ADR at the cell we are examining. The rule is consider all hits that would affect pooled armor at the cell. Padding can't be hit directly so a hit there (at the middle cell) doesn't affect adr as it is a miss.

CONCERN: What is 'probability_mass_at_cell_i', and whence does
    #         it come; e.g., an argument or function?

Just probability to hit cell from hit distribution function.

Note that this does not consider hull damage. Get that from the "normal damage" part, no need to compute it for the ADR adjustment calculations.

Also, up to you whether you want to prototype it first without optimizations, but consider including
- an if condition to only update ADR expectation if pooled armor around middle cell is above minimum armor
- compute only to middle cell, get rest by mirroring
As those will make this actually usable in practice and superior over simulating.

Sorry for any errors here, bit of a hurry now. If anything here in these quick comments seems wrong it may be since I just threwe them together. Trust your judgement, I think you got this. If you understand the principle by now (the idea of updating adr expectations by pooling armor from the hypotheticals) then there are many different ways of actually doing that correctly just like there are for the normal armor damage calculation.
« Last Edit: December 03, 2022, 04:55: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 #221 on: December 04, 2022, 03:06:54 PM »

Code
import copy

"""
Damage computing module.
"""

class ArmorGrid:
    """
    A Starsector ship armor grid.
    """
    _ARMOR_RATING_PER_CELL_FACTOR = 1 / 15
    _MINIMUM_ARMOR_FACTOR = 0.05
    _MINIMUM_DAMAGE_FACTOR = 0.15
    WEIGHTS = [[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):
        self._minimum_armor = ArmorGrid._MINIMUM_ARMOR_FACTOR * armor_rating
        self._cells = [[armor_rating * ArmorGrid._ARMOR_RATING_PER_CELL_FACTOR
                       for _ in range(width + 4)] for _ in range(5)]
        self._bounds = [i * cell_size for i, _ in enumerate(self[2][2:-2])]
   
    def __getitem__(self, row) -> float:
        """
        Return the armor value of the jth cell of the ith row.
        """
        return self._cells[row]

    def _pool(self, index: int) -> float:
        """
        Return the pooled armor for the cell at this index of an armor
        row.
        """
        return sum([[self[i][j] * ArmorGrid._WEIGHTS[i][j] for i in
                    range(index - 2, index + 3)] for j in range(-3, 2)])
 
    def _effective_armor(self, index: int) -> float:
        """
        Return the effective armor for the cell at this index
        of an armor grid.
        """
        return max(self._minimum_armor, self._pool(index))
       
    def damage_factor(self, hit_strength: float, index: int) -> float:
        """
        Return the armor damage factor for a hit to the cell at
        this index of an armor grid.
        """
        return max(ArmorGrid._MINIMUM_DAMAGE_FACTOR,
               1 / (1 + self._effective_armor(index) / hit_strength))
               
    @property
    def bounds(self):
        """
        The right bound of each cell in the middle row, except the two
        padding cells on both sides.
        """
        return self._bounds


class Hit:
    def __init__(
            self,
            probabilities: list,
            base_damage: float,
            shield_damage_factor: float,
            is_beam: bool):
        self._probabilities = probabilities
        self.base_armor_damage = base_damage / shield_damage_factor
        self.strength = self.base_armor_damage / (2 if is_beam else 1)
       
    def _expected_armor_damage_distribution(self, damage: float) -> list:
        """
        Return the expected damage to each cell of the targeted armor
        row times the probability to hit it.
       
        damage - damage against armor after reduction by armor
        """
        return [damage * probability for probability in self._probabilities]
       
    def weighted_expected_armor_damage_distributions(
            self,
            damage: float) -> list:
        """
        Return the weighted distribution across the surrounding armor grid
        of the expected damage to each cell of the targeted armor row.
       
        damage - damage against armor after reduction by armor
        """
        return [
            [[damage * weight for weight in row] for row in ArmorGrid.WEIGHTS]
             for damage in self._expected_armor_damage_distribution(damage)
        ]
                   
    def damage_armor_grid(self, armor_grid: object, armor_damage_factors: list):
        for i, distribution in enumerate(
            self.weighted_expected_armor_damage_distributions(
                self.base_armor_damage)):
            for j, row in enumerate(distribution):
                for k, damage in enumerate(row):
                    armor_grid[j][i+k] = max(0, armor_grid[j][i+k] - damage)


def armor_damage_factors(armor_grid: object, hit_strength: float) -> list:
    """
    Return the armor grid and armor damage factors.
    """
    return [armor_grid.damage_factor(hit_strength, i + 2) for i, _ in
            enumerate(armor_grid[-2:2])]


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


def main():
    armor_rating, cell_size, width = 100, 10, 10
    base_damage, shield_damage_factor, is_beam = 10, 1, False
   
    armor_grid = ArmorGrid(armor_rating, cell_size, width)
    probabilities = [hit_probability(bound) for bound in armor_grid.bounds]
    hit = Hit(probabilities, base_damage, shield_damage_factor, is_beam)
    damage_factors = [hit.strength / (hit.strength + armor_rating) for _ in
                      armor_grid[2:-2]]
   
    for row in armor_grid: print([round(x) for x in row])
   
    hit.damage_armor_grid(armor_grid, damage_factors)
    damage_factors = armor_damage_factors(armor_grid, hit.strength)
   
    print()
   
    for row in armor_grid: print([round(x) for x in row])
main()
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]

[7, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 7]
[6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6]
[6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6]
[6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6]
[7, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 7]

CapnHector

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

All right, seems like that is a basic armor damage calculation right? Good to have it working.

This line seems incorrect
Code
        self.strength = self.base_armor_damage / (2 if is_beam else 1)
since for beams, the damage might be e.g. 1 if we happen to have just 1 tick of a charging down beam about to vanish hitting the armor during the second, but hit strength will still be fixed DPS/2. So for beams a hit strength parameter needs to be passed down separately (or calculated from the fact that DPS = beam tick damage * 10).

I had some problems understanding the rest of it. Does it implement the error correction? I do not think I see the infrastructure for it.
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 #223 on: December 07, 2022, 11:59:40 AM »

Code
import copy

"""
Damage computing module.
"""

class ArmorGrid:
    """
    A Starsector ship armor grid.
    """
    _ARMOR_RATING_PER_CELL_FACTOR = 1 / 15
    _MINIMUM_ARMOR_FACTOR = 0.05
    _MINIMUM_DAMAGE_FACTOR = 0.15
    WEIGHTS = [[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):
        self._minimum_armor = ArmorGrid._MINIMUM_ARMOR_FACTOR * armor_rating
        self._cells = [[armor_rating * ArmorGrid._ARMOR_RATING_PER_CELL_FACTOR
                       for _ in range(width + 4)] for _ in range(5)]
        self._bounds = [i * cell_size for i, _ in enumerate(self[2][2:-2])]
   
    def __getitem__(self, row) -> float:
        """
        Return the armor value of the jth cell of the ith row.
        """
        return self._cells[row]

    def _pool(self, index: int) -> float:
        """
        Return the pooled armor for the cell at this index of an armor
        row.
        """
        return sum([sum([self[j][i + index - 3] * ArmorGrid.WEIGHTS[j][i]
                         for i in range(0, 5)]) for j in range(0, 5)])
 
    def _effective_armor(self, index: int) -> float:
        """
        Return the effective armor for the cell at this index
        of an armor grid.
        """
        return max(self._minimum_armor, self._pool(index))
       
    def damage_factor(self, hit_strength: float, index: int) -> float:
        """
        Return the armor damage factor for a hit to the cell at
        this index of an armor grid.
        """
        return max(ArmorGrid._MINIMUM_DAMAGE_FACTOR,
               1 / (1 + self._effective_armor(index) / hit_strength))
               
    @property
    def bounds(self):
        """
        The right bound of each cell in the middle row, except the two
        padding cells on both sides.
        """
        return self._bounds
       
       
class Target:
    """
    Holds an armor grid and potentially a shield and hull.
    """
    def __init__(self, armor_grid):
        self.armor_grid = armor_grid


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

    base_shield_damage - starting amount of damage to be
                         inflicted on the target shield
    base_armor_damage - starting amount of damage to be
                        inflicted on the target armor
    strength - strength against armor for armor damage
               calculation
    """
    def __init__(
            self,
            base_shield_damage: float,
            base_armor_damage: float,
            strength: float):
        self.base_shield_damage = base_shield_damage
        self.base_armor_damage = base_armor_damage
        self.strength = strength


class DamageExpectation:
    """
    Expected damage to a target by a shot.

    Calculates the expectation value of the damage of a shot
    with a spread to a target with a shield, armor grid, and random
    positional deviation. 

    shot - what is being fired, whether a projectile, missile, or beam tick
    target - what is being hit
    distribution - spread of shots across a horizontal distance
    """
    def __init__(self, target: object, shot: object, distribution: object):
        self.shot = shot
        self.target = target
        self.probabilities = [distribution(bound) for bound in
                              target.armor_grid.bounds]
   
    def _expected_armor_damage_distribution(self) -> list:
        """
        Return the expected damage to each cell of the targeted armor
        row times the probability to hit it.
       
        damage - damage against armor after reduction by armor
        """
       
        return [self.shot.base_armor_damage
                * self.target.armor_grid.damage_factor(self.shot.strength, i+2)
                * self.probabilities[i]
                for i, _ in enumerate(self.probabilities)]
       
    def _weighted_expected_armor_damage_distributions(self) -> list:
        """
        Return the weighted distribution across the surrounding armor grid
        of the expected damage to each cell of the targeted armor row.
       
        damage - damage against armor after reduction by armor
        """
        return [
            [[damage * weight for weight in row] for row in ArmorGrid.WEIGHTS]
             for damage in self._expected_armor_damage_distribution()
        ]

    def damage_armor_grid(self):
        """
        Reduce the values of the armor grid cells of the target
        by the expected value of the damage of the shot across them.
        """
        for i, distribution in enumerate(
            self._weighted_expected_armor_damage_distributions()):
            for j, row in enumerate(distribution):
                for k, damage in enumerate(row):
                    self.target.armor_grid[j][i+k] = (
                        max(0, self.target.armor_grid[j][i+k] - damage))


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


def main():
    armor_rating, cell_size, width = 100, 10, 10
    base_armor_damage, base_shield_damage, hit_strength = 10, 40, 10
   
    target = Target(ArmorGrid(armor_rating, cell_size, width))
    shot = Shot(base_armor_damage, base_shield_damage, hit_strength)
    expectation = DamageExpectation(target, shot, hit_probability)
   
    for row in expectation.target.armor_grid: print([round(x) for x in row])
   
    expectation.damage_armor_grid()
   
    print()
   
    for row in expectation.target.armor_grid: print([round(x) for x in row])
main()
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]

[7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7]
[6, 6, 5, 5, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6]
[6, 6, 5, 5, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6]
[6, 6, 5, 5, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6]
[7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7]


I have discovered that the code was not even calling the right methods and have refactored it, even removing the armor_damage_factors.
« Last Edit: December 07, 2022, 03:18:53 PM by Liral »
Logged

pponmypupu

  • Ensign
  • *
  • Posts: 19
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #224 on: December 08, 2022, 10:48:36 AM »

It is interesting to note that among medium guns, older Collapse-era technology has a surprising advantage, as the Arbalest resulted in slightly faster kills than newer weapons, despite accuracy problems that were included in the model. However, we note that this comes at a range disadvantage that may not be justified by the slightly faster TTK.

this is really interesting and surprising to me. the arbalest to me has always been a weapon of efficiency but not effectiveness. when testing mediums out was there anything in the large slots or how was the ttk measured as the arbalest/heavy needler are kinetics only? what do you think is the reason behind the arbalest's performance?
Logged
Pages: 1 ... 13 14 [15] 16 17 ... 32