Parcourir la source

[research/lotterysim] update cascade report with pi vs pid, and pi scripts

ertosns il y a 3 ans
Parent
commit
fc012bb2c4

+ 2 - 2
script/research/lotterysim/acc_vs_staked_ratio.py

@@ -30,12 +30,12 @@ for nodes in numpy.linspace(1,NODES, 10):
         effective_airdrop*=float(staked_ratio)
         stake_portion = effective_airdrop/airdrop*100
         print("network airdrop: {}, staked token: {}/{}% on {} nodes".format(airdrop, effective_airdrop, stake_portion, len(darkies)))
-    avg_acc = sum(accs)/EXPS*100
+    avg_acc = sum(accs)/EXPS
     plot+=[(stake_portion, avg_acc)]
 
 
 plt.plot([x[0] for x in plot], [x[1] for x in plot])
 plt.xlabel('drk staked %')
 plt.ylabel('accuracy %')
-plt.savefig('img'+os.sep+'stake.png')
+plt.savefig('img'+os.sep+'stake_pid.png')
 plt.show()

+ 41 - 0
script/research/lotterysim/acc_vs_staked_ratio_pi.py

@@ -0,0 +1,41 @@
+from core.lottery import *
+import os
+import numpy
+from matplotlib import pyplot as plt
+
+os.system("rm log/f_output.hist; rm log/f_feedback.hist")
+
+RUNNING_TIME = int(input("running time:"))
+ERC20DRK=2.1*10**9
+NODES=1000
+plot = []
+EXPS=10
+for nodes in numpy.concatenate((numpy.array([1,5]), numpy.linspace(10,NODES, 10))):
+    accs = []
+    for _ in range(EXPS):
+        darkies = []
+        egalitarian = ERC20DRK/NODES
+        darkies += [ Darkie(random.gauss(egalitarian, egalitarian*0.1), strategy=random_strategy(EPOCH_LENGTH)) for id in range(int(nodes)) ]
+        #darkies += [Darkie() for _ in range(NODES*2)]
+        airdrop = ERC20DRK
+        dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.0104, ki=-0.0366, kd=0.0384,  r_kp=-2.53, r_ki=29.5, r_kd=53.77)
+        #dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.0104, ki=-0.0366, kd=0,  r_kp=-2.53, r_ki=29.5, r_kd=0)
+        for darkie in darkies:
+            dt.add_darkie(darkie)
+        acc, apy, reward, staked_ratio, apr = dt.background(rand_running_time=False)
+        accs += [acc]
+        effective_airdrop  = 0
+        for darkie in darkies:
+            effective_airdrop+=darkie.stake
+        effective_airdrop*=float(staked_ratio)
+        stake_portion = effective_airdrop/airdrop*100
+        print("network airdrop: {}, staked token: {}/{}% on {} nodes".format(airdrop, effective_airdrop, stake_portion, len(darkies)))
+    avg_acc = sum(accs)/EXPS
+    plot+=[(stake_portion, avg_acc)]
+
+
+plt.plot([x[0] for x in plot], [x[1] for x in plot])
+plt.xlabel('drk staked %')
+plt.ylabel('accuracy %')
+plt.savefig('img'+os.sep+'stake_pi.png')
+plt.show()

+ 2 - 1
script/research/lotterysim/discrete_instance.py

@@ -28,7 +28,8 @@ if __name__ == "__main__":
         effective_airdrop+=darkie.stake
     print("network airdrop: {}, staked token: {}/{}% on {} nodes".format(airdrop, effective_airdrop, effective_airdrop/airdrop*100, len(darkies)))
     #dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0.03840000000000491,  r_kp=-2.53, r_ki=29.5, r_kd=53.77)
-    dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0.03840000000000491,  r_kp=-0.63, r_ki=3.35, r_kd=-1.11)
+    #dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0.03840000000000491,  r_kp=-0.63, r_ki=3.35, r_kd=-1.11)
+    dt = DarkfiTable(airdrop, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0,  r_kp=-0.63, r_ki=3.35, r_kd=0)
     for darkie in darkies:
         dt.add_darkie(darkie)
     acc, avg_apy, avg_reward, stake_ratio, avg_apr = dt.background(rand_running_time=False)

BIN
script/research/lotterysim/img/apr_distribution.png


BIN
script/research/lotterysim/img/feedback_history_processed.png


BIN
script/research/lotterysim/img/output_history_processed.png


BIN
script/research/lotterysim/img/stake.png


BIN
script/research/lotterysim/img/stake_pi.png


+ 163 - 0
script/research/lotterysim/primary_discrete_auto_crawler_pi.py

@@ -0,0 +1,163 @@
+from argparse import ArgumentParser
+from core.lottery import DarkfiTable
+from core.utils import *
+from core.darkie import Darkie
+from tqdm import tqdm
+import os
+from core.strategy import random_strategy
+
+AVG_LEN = 10
+
+KP_STEP=0.01
+KP_SEARCH=-0.63
+
+KI_STEP=0.01
+KI_SEARCH=3.35
+
+RUNNING_TIME=1000
+NODES = 1000
+
+SHIFTING = 0.05
+
+highest_apr = 0.05
+highest_acc = 0.2
+highest_staked = 0.3
+lowest_apr2target_diff = 1
+
+KP='kp'
+KI='ki'
+
+KP_RANGE_MULTIPLIER = 2
+KI_RANGE_MULTIPLIER = 2
+
+
+highest_gain = (KP_SEARCH, KI_SEARCH)
+
+parser = ArgumentParser()
+parser.add_argument('-p', '--high-precision', action='store_false', default=False)
+parser.add_argument('-r', '--randomizenodes', action='store_true', default=True)
+parser.add_argument('-t', '--rand-running-time', action='store_true', default=True)
+parser.add_argument('-d', '--debug', action='store_false')
+args = parser.parse_args()
+high_precision = args.high_precision
+randomize_nodes = args.randomizenodes
+rand_running_time = args.rand_running_time
+debug = args.debug
+
+def experiment(controller_type=CONTROLLER_TYPE_DISCRETE, rkp=0, rki=0, distribution=[], hp=True):
+    dt = DarkfiTable(ERC20DRK, RUNNING_TIME, controller_type, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0, r_kp=rkp, r_ki=rki, r_kd=0)
+    RND_NODES = random.randint(5, NODES) if randomize_nodes else NODES
+    for idx in range(0,RND_NODES):
+        darkie = Darkie(distribution[idx], strategy=random_strategy(EPOCH_LENGTH))
+        dt.add_darkie(darkie)
+    acc, apy, reward, stake_ratio, apr = dt.background(rand_running_time, hp)
+    return acc, apy, reward, stake_ratio, apr
+
+def multi_trial_exp(kp, ki, distribution = [], hp=True):
+    global highest_apr
+    global highest_acc
+    global highest_staked
+    global highest_gain
+    global lowest_apr2target_diff
+    new_record=False
+    accs = []
+    aprs = []
+    rewards = []
+    stakes_ratios = []
+    aprs = []
+    for i in range(0, AVG_LEN):
+        acc, apy, reward, stake_ratio, apr = experiment(CONTROLLER_TYPE_DISCRETE, rkp=kp, rki=ki, distribution=distribution, hp=hp)
+        accs += [acc]
+        rewards += [reward]
+        aprs += [apr]
+        stakes_ratios += [stake_ratio]
+    avg_acc = float(sum(accs))/AVG_LEN
+    avg_reward = float(sum(rewards))/AVG_LEN
+    avg_staked = float(sum(stakes_ratios))/AVG_LEN
+    avg_apr = float(sum(aprs))/AVG_LEN
+    buff = 'avg(acc): {}, avg(apr): {}, avg(reward): {}, avg(stake ratio): {}, kp: {}, ki:{}, '.format(avg_acc, avg_apr, avg_reward, avg_staked, kp, ki)
+    if avg_apr > 0:
+        gain = (kp, ki)
+        acc_gain = (avg_apr, gain)
+        apr2target_diff = math.fabs(avg_apr - float(TARGET_APR))
+        #if  avg_acc > highest_acc and apr2target_diff < 0.08:
+        if  avg_acc > highest_acc:
+            new_record = True
+            highest_apr = avg_apr
+            highest_acc = avg_acc
+            highest_staked = avg_staked
+            highest_gain = (kp, ki)
+            lowest_apr2target_diff = apr2target_diff
+            with open('log'+os.sep+"highest_gain.txt", 'w') as f:
+                f.write(buff)
+    return buff, new_record
+
+def crawler(crawl, range_multiplier, step=0.1):
+    start = None
+    if crawl==KP:
+        start = highest_gain[0]
+    elif crawl==KI:
+        start = highest_gain[1]
+
+    range_start = (start*range_multiplier if start <=0 else -1*start)
+    range_end = (-1*start if start<=0 else range_multiplier*start)
+    # if number of steps under 10 step resize the step to 50
+    while (range_end-range_start)/step < 10:
+        range_start -= SHIFTING
+        range_end += SHIFTING
+        step /= 10
+
+    while True:
+        try:
+            crawl_range = np.arange(range_start, range_end, step)
+            break
+        except Exception as e:
+            print('start: {}, end: {}, step: {}, exp: {}'.format(range_start, rang_end, step, e))
+            step*=10
+    np.random.shuffle(crawl_range)
+    crawl_range = tqdm(crawl_range)
+    distribution = [random.gauss(ERC20DRK/NODES, ERC20DRK/NODES*0.1) for i in range(NODES)]
+    for i in crawl_range:
+        kp = i if crawl==KP else highest_gain[0]
+        ki = i if crawl==KI else highest_gain[1]
+        buff, new_record = multi_trial_exp(kp, ki, distribution, hp=high_precision)
+        crawl_range.set_description('highest:{} / {}'.format(highest_acc, buff))
+        if new_record:
+            break
+
+while True:
+    prev_highest_gain = highest_gain
+    # kp crawl
+    crawler(KP, KP_RANGE_MULTIPLIER, KP_STEP)
+    if highest_gain[0] == prev_highest_gain[0]:
+        KP_RANGE_MULTIPLIER+=1
+        KP_STEP/=10
+    else:
+        start = highest_gain[0]
+        range_start = (start*KP_RANGE_MULTIPLIER if start <=0 else -1*start) - SHIFTING
+        range_end = (-1*start if start<=0 else KP_RANGE_MULTIPLIER*start) + SHIFTING
+        while (range_end - range_start)/KP_STEP >500:
+            #if KP_STEP < 0.1:
+            KP_STEP*=2
+            KP_RANGE_MULTIPLIER-=1
+            #TODO (res) shouldn't the range also shrink?
+            # not always true.
+            # how to distinguish between thrinking range, and large step?
+            # good strategy is step shoudn't > 0.1
+            # range also should be > 0.8
+            # what about range multiplier?
+
+    # ki crawl
+    crawler(KI, KI_RANGE_MULTIPLIER, KI_STEP)
+    if highest_gain[1] == prev_highest_gain[1]:
+        KI_RANGE_MULTIPLIER+=1
+        KI_STEP/=10
+    else:
+        start = highest_gain[1]
+        range_start = (start*KI_RANGE_MULTIPLIER if start <=0 else -1*start) - SHIFTING
+        range_end = (-1*start if start<=0 else KI_RANGE_MULTIPLIER*start) + SHIFTING
+        while (range_end - range_start)/KI_STEP >500:
+            #print('range_end: {}, range_start: {}, ki_step: {}'.format(range_end, range_start, KI_STEP))
+            #if KP_STEP < 1:
+            KI_STEP*=2
+            KI_RANGE_MULTIPLIER-=1

+ 2 - 1
script/research/lotterysim/reports/cascade.aux

@@ -15,5 +15,6 @@
 \@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces cascade PID diagram}}{7}{}\protected@file@percent }
 \@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces with accuracy set point to primary controller}}{8}{}\protected@file@percent }
 \@writefile{lof}{\contentsline {figure}{\numberline {7}{\ignorespaces relation between staked DRK ratio and accuracy of secondary controller, with accuracy as set point to primary controller}}{9}{}\protected@file@percent }
-\@writefile{toc}{\contentsline {section}{\numberline {5}conclusion}{10}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {section}{\numberline {5}PI, or PID \dots  }{9}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {section}{\numberline {6}conclusion}{10}{}\protected@file@percent }
 \gdef \@abspage@last{10}

+ 36 - 19
script/research/lotterysim/reports/cascade.log

@@ -1,4 +1,4 @@
-This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian) (preloaded format=pdflatex 2023.6.2)  22 JUN 2023 02:56
+This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian) (preloaded format=pdflatex 2023.6.2)  4 JUL 2023 22:04
 entering extended mode
  restricted \write18 enabled.
  %&-line parsing enabled.
@@ -546,36 +546,53 @@ Overfull \hbox (117.52686pt too wide) in paragraph at lines 181--182
 [][] 
  []
 
-<acc_stake_1000nodes_10kslots_with_cascade.png, id=44, 462.528pt x 346.896pt>
+
+! LaTeX Error: Lonely \item--perhaps a missing list environment.
+
+See the LaTeX manual or LaTeX Companion for explanation.
+Type  H <return>  for immediate help.
+ ...                                              
+                                                  
+l.187 \item e
+             xperiments done on accuracy of cascade control, and relation be...
+
+? 
+
+Underfull \hbox (badness 10000) detected at line 187
+[][]
+ []
+
+[8 <./acc_setpoint_quarterstake.png>]
+<acc_stake_1000nodes_10kslots_with_cascade.png, id=48, 462.528pt x 346.896pt>
 File: acc_stake_1000nodes_10kslots_with_cascade.png Graphic file (type png)
 <use acc_stake_1000nodes_10kslots_with_cascade.png>
 Package pdftex.def Info: acc_stake_1000nodes_10kslots_with_cascade.png  used on
- input line 189.
+ input line 193.
 (pdftex.def)             Requested size: 462.52686pt x 346.89514pt.
 
-Overfull \hbox (117.52686pt too wide) in paragraph at lines 189--190
+Overfull \hbox (117.52686pt too wide) in paragraph at lines 193--194
 [][] 
  []
 
-[8 <./acc_setpoint_quarterstake.png>] [9 <./acc_stake_1000nodes_10kslots_with_c
-ascade.png>] [10] (./cascade.aux) ) 
+[9 <./acc_stake_1000nodes_10kslots_with_cascade.png>] [10] (./cascade.aux) ) 
 Here is how much of TeX's memory you used:
- 13365 strings out of 480171
- 274482 string characters out of 5894490
- 534248 words of memory out of 5000000
- 31079 multiletter control sequences out of 15000+600000
+ 13366 strings out of 480171
+ 274498 string characters out of 5894490
+ 534249 words of memory out of 5000000
+ 31080 multiletter control sequences out of 15000+600000
  474642 words of font info for 47 fonts, out of 8000000 for 9000
  59 hyphenation exceptions out of 8191
  100i,8n,104p,461b,754s stack positions out of 5000i,500n,10000p,200000b,80000s
-</usr/share/texlive/texmf-dist/fonts/type1/p
-ublic/amsfonts/cm/cmbx12.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/
-amsfonts/cm/cmmi10.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfon
-ts/cm/cmmi7.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/c
-mr10.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr12.pf
-b></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr17.pfb></usr
-/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/share/t
-exlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb>
-Output written on cascade.pdf (10 pages, 188489 bytes).
+</u
+sr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx12.pfb></usr/sha
+re/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/share/tex
+live/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/share/texlive/te
+xmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/share/texlive/texmf-dis
+t/fonts/type1/public/amsfonts/cm/cmr12.pfb></usr/share/texlive/texmf-dist/fonts
+/type1/public/amsfonts/cm/cmr17.pfb></usr/share/texlive/texmf-dist/fonts/type1/
+public/amsfonts/cm/cmr7.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/a
+msfonts/cm/cmsy10.pfb>
+Output written on cascade.pdf (10 pages, 189605 bytes).
 PDF statistics:
  91 PDF objects out of 1000 (max. 8388607)
  52 compressed objects within 1 object stream

BIN
script/research/lotterysim/reports/cascade.pdf


+ 4 - 0
script/research/lotterysim/reports/cascade.tex

@@ -182,6 +182,10 @@ in stake.
     \caption{with accuracy set point to primary controller}
 \end{figure}
 
+\section {PI, or PID \dots}
+
+\item experiments done on accuracy of cascade control, and relation between staked ratio, and accuracy of finalization using PI, PID controllers, using same parameters in experiments above, with $kd=0$ in primary, and secondary controller, although secondary controller exhibit high oscillation, but overall accuracy doesn't change if derivative term is dropped.
+
 \begin{description}
 \item figure 7 shows the relation between staked DRK tokens ratio, and accuracy
 \end{description}

BIN
script/research/lotterysim/reports/stake_pi.png


+ 7 - 7
script/research/lotterysim/secondary_discrete_auto_crawler.py

@@ -8,17 +8,17 @@ import os
 
 AVG_LEN = 5
 
-KP_STEP=0.3
-KP_SEARCH= 0.03
+KP_STEP=0.01
+KP_SEARCH= -0.01
 
-KI_STEP=0.3
-KI_SEARCH=1.95
+KI_STEP=0.01
+KI_SEARCH=-0.036
 
-KD_STEP=0.3
-KD_SEARCH=-0.95
+KD_STEP=0.01
+KD_SEARCH=0.0384
 
 EPSILON=0.0001
-RUNNING_TIME=1000
+RUNNING_TIME=10000
 NODES = 1000
 
 highest_acc = 0

+ 136 - 0
script/research/lotterysim/secondary_discrete_auto_crawler_pi.py

@@ -0,0 +1,136 @@
+from argparse import ArgumentParser
+from core.lottery import DarkfiTable
+from core.utils import *
+from core.darkie import Darkie
+from tqdm import tqdm
+from core.strategy import SigmoidStrategy
+import os
+
+AVG_LEN = 5
+
+KP_STEP=0.01
+KP_SEARCH= -0.01
+
+KI_STEP=0.01
+KI_SEARCH=-0.036
+
+EPSILON=0.0001
+RUNNING_TIME=1000
+NODES = 1000
+
+highest_acc = 0
+
+KP='kp'
+KI='ki'
+
+KP_RANGE_MULTIPLIER = 2
+KI_RANGE_MULTIPLIER = 2
+
+highest_gain = (KP_SEARCH, KI_SEARCH)
+
+parser = ArgumentParser()
+parser.add_argument('-p', '--high-precision', action='store_true')
+parser.add_argument('-r', '--randomize-nodes', action='store_false')
+parser.add_argument('-t', '--rand-running-time', action='store_false')
+parser.add_argument('-d', '--debug', action='store_false')
+args = parser.parse_args()
+high_precision = args.high_precision
+randomize_nodes = args.randomize_nodes
+rand_running_time = args.rand_running_time
+debug = args.debug
+
+def experiment(controller_type=CONTROLLER_TYPE_DISCRETE, kp=0, ki=0, distribution=[], hp=True):
+    dt = DarkfiTable(ERC20DRK, RUNNING_TIME, controller_type, kp=kp, ki=ki, kd=0)
+    RND_NODES = random.randint(5, NODES) if randomize_nodes else NODES
+    for idx in range(0,RND_NODES):
+        darkie = Darkie(distribution[idx], strategy=SigmoidStrategy(EPOCH_LENGTH))
+        dt.add_darkie(darkie)
+    acc, apy, reward, stake_ratio, apr = dt.background(rand_running_time, hp)
+    return acc
+
+def multi_trial_exp(kp, ki, distribution = [], hp=True):
+    global highest_acc
+    global highest_gain
+    new_record=False
+    exp_threads = []
+    accs = []
+    for i in range(0, AVG_LEN):
+        acc = experiment(CONTROLLER_TYPE_DISCRETE, kp=kp, ki=ki, distribution=distribution, hp=hp)
+        accs += [acc]
+    avg_acc = sum(accs)/float(AVG_LEN)
+    buff = 'accuracy:{}, kp: {}, ki:{}'.format(avg_acc, kp, ki)
+    if avg_acc > 0:
+        gain = (kp, ki)
+        acc_gain = (avg_acc, gain)
+        if avg_acc > highest_acc:
+            new_record = True
+            highest_acc = avg_acc
+            highest_gain = (kp, ki)
+            with open('log'+os.sep+"highest_gain.txt", 'w') as f:
+                f.write(buff)
+    return buff, new_record
+
+SHIFTING = 0.05
+
+def crawler(crawl, range_multiplier, step=0.1):
+    start = None
+    if crawl==KP:
+        start = highest_gain[0]
+    elif crawl==KI:
+        start = highest_gain[1]
+
+    range_start = (start*range_multiplier if start <=0 else -1*start)
+    range_end = (-1*start if start<=0 else range_multiplier*start)
+    # if number of steps under 10 step resize the step to 50
+    while (range_end-range_start)/step < 10:
+        range_start -= SHIFTING
+        range_end += SHIFTING
+        step /= 10
+
+    crawl_range = np.arange(range_start, range_end, step)
+    np.random.shuffle(crawl_range)
+    crawl_range = tqdm(crawl_range)
+    distribution = [random.gauss(ERC20DRK/NODES, ERC20DRK/NODES*0.1) for i in range(NODES)]
+    for i in crawl_range:
+        kp = i if crawl==KP else highest_gain[0]
+        ki = i if crawl==KI else highest_gain[1]
+        buff, new_record = multi_trial_exp(kp, ki, distribution, hp=high_precision)
+        crawl_range.set_description('highest:{} / {}'.format(highest_acc, buff))
+        if new_record:
+            break
+
+while True:
+    prev_highest_gain = highest_gain
+    # kp crawl
+    crawler(KP, KP_RANGE_MULTIPLIER, KP_STEP)
+    if highest_gain[0] == prev_highest_gain[0]:
+        KP_RANGE_MULTIPLIER+=1
+        KP_STEP/=10
+    else:
+        start = highest_gain[0]
+        range_start = (start*KP_RANGE_MULTIPLIER if start <=0 else -1*start) - SHIFTING
+        range_end = (-1*start if start<=0 else KP_RANGE_MULTIPLIER*start) + SHIFTING
+        while (range_end - range_start)/KP_STEP >500:
+            if KP_STEP < 0.1:
+                KP_STEP*=10
+            KP_RANGE_MULTIPLIER-=1
+            #TODO (res) shouldn't the range also shrink?
+            # not always true.
+            # how to distinguish between thrinking range, and large step?
+            # good strategy is step shoudn't > 0.1
+            # range also should be > 0.8
+            # what about range multiplier?
+
+    # ki crawl
+    crawler(KI, KI_RANGE_MULTIPLIER, KI_STEP)
+    if highest_gain[1] == prev_highest_gain[1]:
+        KI_RANGE_MULTIPLIER+=1
+        KI_STEP/=10
+    else:
+        start = highest_gain[1]
+        range_start = (start*KI_RANGE_MULTIPLIER if start <=0 else -1*start) - SHIFTING
+        range_end = (-1*start if start<=0 else KI_RANGE_MULTIPLIER*start) + SHIFTING
+        while (range_end - range_start)/KI_STEP >500:
+            if KP_STEP < 0.1:
+                KI_STEP*=10
+            KI_RANGE_MULTIPLIER-=1