Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated RAH to version 2. Thank you @ccplarrikin #688

Closed
wants to merge 15 commits into from

Conversation

Ebag333
Copy link
Contributor

@Ebag333 Ebag333 commented Jul 29, 2016

For wall of text regarding the history of this read #680

@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 12, 2016

All right. So I completely rewrote the functionality.

Instead of using multiple tuples, using a single one. Little simpler and easier (and probably ever so slightly faster).

Then what we do is rather simple.

  1. Sort the tuple by modified damage taken (damage profile modified by the resist).
  2. Steal resists from the two that took the least damage.
  3. Add it to the highest two.

Lather, rinse, and repeat.

If we run out of resists to steal, we stop. If the resist that has taken the least amount of damage is at 0 over 15 loops, we stop.

Most of the time it'll be done in 5 loops.

Testing against various profiles shows that it's taking about 180-250 MS to run, so nice and quick. We will need to test against more complex damage profiles to see if that changes any.

@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 12, 2016

Paging @MrNukealizer and @blitzmann for testing.

@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 12, 2016

As a reminder to myself (and a heads up to others), because the merge from Pyfa/Master broke, it created changes for all the diffs, which is why there's 100+ files modified.

Once we get the functionality and bugs worked out, I'll prune this branch and submit a new (clean) PR with just the changes we want to the RAH.

to better show how the multiple damage profile handling works.
@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 12, 2016

Removed as this was inaccurate.

And now updates the armor and modified damage done per cycle
but still not calculating correctly. :(
@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 14, 2016

Okay. Updates made, looks (to me) like it's calculating properly.

It probably won't fully calculate properly with the DCU...but...

So, some benchmarks. All tests done with restarting Pyfa and loading the fit for the first time.

Vengeance with 3 boosters, multiple projected, and damage profile with 3 types:

Start: 15:26:50,664 
End: 15:26:56,112

Vengeance with multiple projected, and damage profile with 3 types:

Start: 15:34:03,881
End: 15:34:08,815

Vengeance with damage profile with 3 types:

Start: 15:35:54,687
End: 15:35:56,881

Gnosis with uniform resists and uniform damage profile with 3 types: (hits cycle limit)

Start: 15:37:55,928
End: 15:38:25,555

The cycle limit is 15 cycles after one of the resists hits 0. We can probably shorten that up.

The start/end times are a wee bit deceptive, even for the Gnosis the actual RAH cycling time is only about 100ms.

I don't think we can make it much faster if we want to keep it fully adaptive...

@MrNukealizer
Copy link
Contributor

MrNukealizer commented Sep 15, 2016

That could definitely be a more efficient way to run it, but the current code gives incorrect results. You seem to be hard coding some values that should be fetched from the module attributes. For example:

if damagePattern.emAmount == damagePattern.thermalAmount == damagePattern.kineticAmount == damagePattern.explosiveAmount:

                # If damage pattern is even across the board, we "reset" back to default resists.
                logger.debug("Setting adaptivearmorhardener resists to uniform profile.")
                damagePattern_tuple[0][4]=.85
                damagePattern_tuple[1][4]=.85
                damagePattern_tuple[2][4]=.85
                damagePattern_tuple[3][4]=.85
                runLoop = 0
            elif damagePattern_tuple[2][2] == 0:
                # If damage pattern is a single source, we set all resists to one damage profile.
                logger.debug("Setting adaptivearmorhardener resists to single damage profile.")
                damagePattern_tuple[0][4]=1
                damagePattern_tuple[1][4]=1
                damagePattern_tuple[2][4]=1
                damagePattern_tuple[3][4]=.4
                runLoop = 0

assumes the starting resonances are all 0.85, but what if that's changed or a T2 version is added? I got around that by grabbing the resonances before changing anything and using that as a base.

if damagePattern_tuple[i][4] < .97:
                        # If there is more than 3% to steal, let's steal 3% and reduce the resit by that amount.
vampDmg[i] = .03

This seems to be the source of inaccuracy. I don't know where you got 3% from, but it's the resistanceShiftAmount attribute divided by 100, which is currently 6%.

After changing that to 6% the algorithm matches most of my tests. The two exceptions were when two or more types of damage did exactly the same amount, i.e. attacking a Gnosis with a Gecko or a T1 Gallente ship with Null and explosive drones, and another scenario with a much worse result. To fix the even damage into even resistances problem, the initial tuple order should be sorted alphabetically: EM, Explosive, Kinetic, Thermal instead of EM, Thermal, Kinetic, Explosive. That causes the sorting to give equal results the same order the game uses.

Now for the test that went horribly wrong:

[Paladin, Paladin]

Reactive Armor Hardener
Core X-Type Armor Kinetic Hardener
Core X-Type Armor Thermal Hardener

with a damage profile of 1/2/2/1 (sentry guns in lowsec) will never cause any of the RAH resistances to reach 0, and thus the loop will never stop.

Aside from those details there's one other accuracy issue. If the RAH never stops changing the code will break out of the loop and give the result from the last cycle. When the RAH loops like that, no single cycle accurately represents the effective resistances over the course of the loop. The effective resistances will be the average of all the cycles in the loop. That's why my code saves the results of previous cycles, identifies a loop, then gives the average of all the cycles in the loop.

One last thing: it's more of a design choice than an accuracy issue, but I still think the code should handle uniform damage rather than staying at the default resistances. There's the specific example of seeing how a ship fares against Geckos, but more than that people use uniform damage to show how the ship handles a "general" situation where they get shot by any and all types of damage. In such a situation the starting resistances of the RAH are meaningless whereas the profile it settles on can give a good idea of the strengths and weaknesses of the armor and how well the RAH will compensate.

@MrNukealizer
Copy link
Contributor

MrNukealizer commented Sep 15, 2016

I still don't understand why my code has performance problems when you run it but this doesn't. When I test the timing my code takes about half as long as this does when the RAH loops, and about the same time when the RAH stops on one profile. Both versions also seem to run 5-20 times faster in my tests than in yours, which seems like more than hardware differences should account for.

@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 15, 2016

assumes the starting resonances are all 0.85, but what if that's changed or a T2 version is added? I got around that by grabbing the resonances before changing anything and using that as a base.

If CCP adds a T2 version or changes skills related to this or anything, really, it'll probably go to hell in a hand basket.

We can store the original resistance and use that, that's not a big deal.

This seems to be the source of inaccuracy. I don't know where you got 3% from, but it's the resistanceShiftAmount attribute divided by 100, which is currently 6%.

Easily changed. Now that I'm looking at it, there's even a value for how much gets transferred.

To fix the even damage into even resistances problem, the initial tuple order should be sorted alphabetically: EM, Explosive, Kinetic, Thermal instead of EM, Thermal, Kinetic, Explosive. That causes the sorting to give equal results the same order the game uses.

I'll poke around and see how to sort multiple columns. If it easy, then I'll add it. It's a pretty extreme edge case to be honest, as the vast majority of scenarios won't have even damage.

Aside from those details there's one other accuracy issue. If the RAH never stops changing the code will break out of the loop and give the result from the last cycle. When the RAH loops like that, no single cycle accurately represents the effective resistances over the course of the loop. The effective resistances will be the average of all the cycles in the loop. That's why my code saves the results of previous cycles, identifies a loop, then gives the average of all the cycles in the loop.

First of all, never ending RAH loops are going to be an edge case. There's no real great way to handle them, as by it's very nature_anything_ we display will be wrong.

Loops inside loops can get expensive, code wise, which is why I avoided that as a solution. I understand that grabbing the last one is fairly arbitrary.

Hmm, a thought occurs to me that might be a solution. We can lower how much it transfers by .01 each loop, which should cause it to basically average out without having to store and loop through the last few resist profiles. I'll test that.

I still don't understand why my code has performance problems when you run it but this doesn't. When I test the timing my code takes about half as long as this does when the RAH loops, and about the same time when the RAH stops on one profile. Both versions also seem to run 5-20 times faster in my tests than in yours, which seems like more than hardware differences should account for.

I'm not super thrilled with the performance of this either when you hit the max number of runs. But as soon as we open the door to something outside of the simple 30/30 resists, performance takes a huge hit.

I can't really explain why my environment runs slower than yours, but I CAN safely say that my environment definitely won't be the slowest potential environment that Pyfa runs on. So it's only downhill from here.

I did submit a PR to potentially break the refresh calcs out, which would dramatically help with times (not so much the individual RAH times as the overall times).

I will also rerun your version and do some benchmarking, once I update this version.

One last thing: it's more of a design choice than an accuracy issue, but I still think the code should handle uniform damage rather than staying at the default resistances.

Noted and I half agree with you. We could key off the resist profile name, but I really dislike hard coding that. There isn't a good answer here, because to the end user any even profile looks the exact same (all 25 percent).

@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 15, 2016

So one edge case that this setup covers that others might not...

2016-09-15 08:12:37,651 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.625000 | 0.820000 | 0.955000 || 0.203507 | 0.142296 | 0.184500 | 0.114600 || 0.000000 | 3.936933 | 3.009600 | 3.120480
2016-09-15 08:12:37,651 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.602500 | 0.797500 | 1.000000 || 0.203507 | 0.137173 | 0.179438 | 0.120000 || 0.000000 | 3.756616 | 3.247200 | 3.025440
2016-09-15 08:12:37,651 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.572500 | 0.857500 | 0.970000 || 0.203507 | 0.130343 | 0.192938 | 0.116400 || 0.000000 | 3.621378 | 3.158100 | 3.168000

I actually didn't plan to cover this, but glad it doesn't break out as soon as the resist are fully allocated.

We can store the original resistance and use that, that's not a big deal.

Done.

I'll poke around and see how to sort multiple columns. If it easy, then I'll add it. It's a pretty extreme edge case to be honest, as the vast majority of scenarios won't have even damage.

Done, easy peasy.

Easily changed. Now that I'm looking at it, there's even a value for how much gets transferred.
Hmm, a thought occurs to me that might be a solution. We can lower how much it transfers by .01 each loop, which should cause it to basically average out without having to store and loop through the last few resist profiles. I'll test that.

Done and done. This actually works very nicely. We could actually go lower than .01, but then (potentially, if we go too low) it shows in the fitting window weird due to rounding issues.

Just in case CCP mucks with values, it also will populate the affected
by tab in a fashion more similar to how the multiple damage type
profiles work.
@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 15, 2016

All righty, some profiling.

Gnosis with nothing but a RAH, damage profile of 0 | 10 | 10 | 10.

v2 code (updated)

2016-09-15 08:26:39,753 service.fit              DEBUG    ==========recalc==========
2016-09-15 08:26:39,756 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=649, ship=Gnosis, name=RAH Test) at 0x709e1b0, withBoosters: True
2016-09-15 08:26:39,766 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: early - 10.00ms (10.00ms elapsed)
2016-09-15 08:26:39,825 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: normal - 60.00ms (70.00ms elapsed)
2016-09-15 08:26:39,835 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 0.910000 | 0.790000 | 0.790000 | 0.910000 || 0.614250 | 0.533250 | 0.533250 | 0.614250 || 0.000000 | 6.750000 | 6.750000 | 6.750000
2016-09-15 08:26:39,835 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 0.970000 | 0.730000 | 0.850000 | 0.850000 || 0.654750 | 0.492750 | 0.573750 | 0.573750 || 0.000000 | 5.332500 | 5.332500 | 6.142500
2016-09-15 08:26:39,836 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 4.927500 | 5.737500 | 5.737500
2016-09-15 08:26:39,838 eos.effects.adaptivearmorhardener DEBUG    Reducing resistance shift amount to 0.050000
2016-09-15 08:26:39,838 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.840000 | 0.780000 | 0.780000 || 0.675000 | 0.567000 | 0.526500 | 0.526500 || 0.000000 | 5.332500 | 5.433750 | 5.433750
2016-09-15 08:26:39,838 eos.effects.adaptivearmorhardener DEBUG    Reducing resistance shift amount to 0.040000
2016-09-15 08:26:39,839 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.820000 | 0.760000 | 0.820000 || 0.675000 | 0.553500 | 0.513000 | 0.553500 || 0.000000 | 5.670000 | 5.265000 | 5.265000
2016-09-15 08:26:39,839 eos.effects.adaptivearmorhardener DEBUG    Reducing resistance shift amount to 0.030000
2016-09-15 08:26:39,841 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.805000 | 0.790000 | 0.805000 || 0.675000 | 0.543375 | 0.533250 | 0.543375 || 0.000000 | 5.535000 | 5.130000 | 5.535000
2016-09-15 08:26:39,841 eos.effects.adaptivearmorhardener DEBUG    Reducing resistance shift amount to 0.020000
2016-09-15 08:26:39,842 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.795000 | 0.810000 | 0.795000 || 0.675000 | 0.536625 | 0.546750 | 0.536625 || 0.000000 | 5.433750 | 5.332500 | 5.433750
2016-09-15 08:26:39,842 eos.effects.adaptivearmorhardener DEBUG    Reducing resistance shift amount to 0.010000
2016-09-15 08:26:39,842 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 5.366250 | 5.467500 | 5.366250
2016-09-15 08:26:39,844 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.800000 | 0.800000 | 0.800000 || 0.675000 | 0.540000 | 0.540000 | 0.540000 || 0.000000 | 5.332500 | 5.433750 | 5.433750
2016-09-15 08:26:39,845 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.795000 | 0.795000 | 0.810000 || 0.675000 | 0.536625 | 0.536625 | 0.546750 || 0.000000 | 5.400000 | 5.400000 | 5.400000
2016-09-15 08:26:39,845 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 5.366250 | 5.366250 | 5.467500
2016-09-15 08:26:39,845 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.800000 | 0.800000 | 0.800000 || 0.675000 | 0.540000 | 0.540000 | 0.540000 || 0.000000 | 5.332500 | 5.433750 | 5.433750
2016-09-15 08:26:39,846 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.795000 | 0.795000 | 0.810000 || 0.675000 | 0.536625 | 0.536625 | 0.546750 || 0.000000 | 5.400000 | 5.400000 | 5.400000
2016-09-15 08:26:39,848 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 5.366250 | 5.366250 | 5.467500
2016-09-15 08:26:39,848 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.800000 | 0.800000 | 0.800000 || 0.675000 | 0.540000 | 0.540000 | 0.540000 || 0.000000 | 5.332500 | 5.433750 | 5.433750
2016-09-15 08:26:39,848 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.795000 | 0.795000 | 0.810000 || 0.675000 | 0.536625 | 0.536625 | 0.546750 || 0.000000 | 5.400000 | 5.400000 | 5.400000
2016-09-15 08:26:39,849 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 5.366250 | 5.366250 | 5.467500
2016-09-15 08:26:39,851 eos.effects.adaptivearmorhardener DEBUG    Looped 15.000000 times. Most likely the RAH is cycling between two different profiles and is in an infinite loop. Breaking out of RAH cycle.
2016-09-15 08:26:39,851 eos.effects.adaptivearmorhardener DEBUG    Adaptive Resists, Ship Resists, Modified Damage (EM|The|Kin|Exp) : 1.000000 | 0.790000 | 0.805000 | 0.805000 || 0.675000 | 0.533250 | 0.543375 | 0.543375 || 0.000000 | 5.332500 | 5.433750 | 5.433750
2016-09-15 08:26:39,852 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: late - 26.00ms (96.00ms elapsed)
2016-09-15 08:26:39,852 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with fit calculation - 1.00ms (97.00ms elapsed)

v3 code

2016-09-15 08:38:58,038 service.fit              DEBUG    ==========recalc==========
2016-09-15 08:38:58,039 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=649, ship=Gnosis, name=RAH Test) at 0xaec07f0, withBoosters: True
2016-09-15 08:38:58,049 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: early - 9.00ms (9.00ms elapsed)
2016-09-15 08:38:58,117 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: normal - 68.00ms (77.00ms elapsed)
2016-09-15 08:38:58,125 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 0.910000/0.790000/0.790000/0.910000
2016-09-15 08:38:58,127 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 0.970000/0.730000/0.850000/0.850000
2016-09-15 08:38:58,128 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 1.000000/0.790000/0.805000/0.805000
2016-09-15 08:38:58,128 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 1.000000/0.850000/0.775000/0.775000
2016-09-15 08:38:58,130 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 1.000000/0.820000/0.745000/0.835000
2016-09-15 08:38:58,130 eos.effects.adaptivearmorhardener DEBUG    Resistances shifted to 1.000000/0.790000/0.805000/0.805000
2016-09-15 08:38:58,131 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with runtime: late - 15.00ms (92.00ms elapsed)
2016-09-15 08:38:58,131 eos.saveddata.fit        DEBUG    Timer - Fit: 649, RAH Test - Done with fit calculation - 0.00ms (92.00ms elapsed)

So a few things.

One, the times are pretty close to the same (4-5 ms difference depending on where you look), but v2 loops 18 times while v3 loops 6 times.

Additionally, v3 should fall into an infinite loop. It doesn't, which means that there's something not quite right there.

End Result v2: 0 | 21 | 19.5 | 19.5
End Result v3: 0 | 18 | 22.5 | 19.5

You can see that the resist end results aren't quite what they should be.

We can probably knock off a few loops off v2 in the never ending cycle scenario (which would bring down the total execution time some), but 15 seems a reasonable compromise. Additionally there's more debug logging on v2, which slows things down a bit further.

Using a single tuple and eliminating a lot of the extra loops seems to have made a significant difference in execution time. I'm still not thrilled with it, but I don't see any ways of further improving that without cutting functionality.

It bugs me a bunch that this doesn't handle stacking penalties very well, but my first attempt at a rewrite tried updating the ship stats to handle it (which it did!), the problem was that there's currently no clean way of updating an existing modification or removing it. You can remove all modifications (which puts the ship back to base stats), and you can soft set the stat, but stacking penalties kick in multiple times and the values go off the rails.

This is probably as close as we're going to get. At this point in time, unless someone else ( @MrNukealizer or @blitzmann ) see further improvements that can be made to the RAH code to speed it up

Now will handle cases where you could enter an infinite loop.  Should be
impossible to cycle forever now, will break out after 100 total loops or
5 loops after the resist transfer amount is lowered to .01.

Another advantage is that it can start reducing the resist transfer
amount in fewer loops, so for many fits will end up taking fewer loops
to run.
@Ebag333 Ebag333 mentioned this pull request Sep 15, 2016
@Ebag333
Copy link
Contributor Author

Ebag333 commented Sep 15, 2016

Closing this as it has been replaced with #737

@Ebag333 Ebag333 closed this Sep 15, 2016
@Ebag333 Ebag333 deleted the RAH_v2 branch October 17, 2016 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants