4
\$\begingroup\$

I have a physical robot which has a function, turn(degrees). For different robots, there will be small differences in the accuracy of the turn. I have fine turning variables, multiplier and adder. The degrees are multiplied by the multiplier, and then adder is added on.

I have made a program to brute-force the optimal values for fine tuning. The program is very simple, unoptimized, and slow.

The code:

from datetime import datetime

# in the format (request degrees, actual degrees)
real_results = (
(90, 99),
(100, 109),
(110, 121),
(125, 140),
(130, 149),
)
max_adder = 100
max_multiplier = 100
precision = 0.01

#        amount off, adder, multiplier
best_setting = [9999, 0, 0]

start_time = datetime.now()
# this lets me "step" in precision increments, because range only accepts
# integers, I tried making my own function but it was 3 times slower
# stackoverflow.com/questions/7267226
for adder in range(int(max_adder / precision)):
    for multiplier in range(int(max_multiplier / precision)):
        amount_off = 0
        for result in real_results:
            # the * precision turns it from an integer into steps of 0.01
            amount_off += abs(
                result[0] * (multiplier*precision) + (adder*precision) - result[1])
        if amount_off < best_setting[0]:
            best_setting[0] = amount_off
            best_setting[1] = adder
            best_setting[2] = multiplier

print("Best settings are:")
print("adder: {:0.3f}".format(1 / (best_setting[1] * precision) if best_setting[1] else 0))
print("multiplier: {:0.3f}".format(1 / (best_setting[2] * precision) if best_setting[2] else 0))
print("Average amount off: {:.4f}".format(best_setting[0] / len(real_results) if best_setting[0] else 0))
print("That run took:", datetime.now() - start_time)

How can I make my program faster? The idea I currently have is having the second for loop (adder) could start in the middle, and act like a basic P controller. I'll work on this while I wait for replies :)


Clarification:

The data set will be manually created by someone requesting a turn, and recording what the actual turn was. This means the data set will usually be around 4 to 8 results long.

The precision is how accurate we want the result to be. Ideally it would be as precise as possible, but since the robot has a fair amount of unpredictability, 2 d.p is fine.

The program finds the number that the results were multiplied by. In order to find the number that will give the most accurate turn, we must divide 1 by the number.

numpy.arange: yes, I should have used that because that is exactly what my code emulates. I didn't know about that yesterday :P

\$\endgroup\$
  • 1
    \$\begingroup\$ The inverse of \$y = mx + c\$ is \$x = \frac{y-c}{m}\$. There's no sense to reciprocating best_setting[1]. \$\endgroup\$ – Peter Taylor Jun 22 '17 at 10:41
  • \$\begingroup\$ Thank you :) that has been fixed in a newer version of the code using Numpy arange \$\endgroup\$ – DarkMatterMatt Jun 22 '17 at 18:43
3
\$\begingroup\$

Algorithm

You seem to essentially be doing linear regression with a least absolute deviations optimisation criterion. There are various standard approaches, although I'm not entirely sure about their applicability.

Problem: the task implemented by this code is not well specified. The precision variable serves to quantise the gradient and intercept of the line, but it's impossible to tell from the given specification and comments whether this quantisation is inherent to the problem domain or whether it's there to control the runtime of the search.

If the quantisation is inherent then some of the standard techniques will give starting points but you'll need to test the four nearest valid parameters (rounding each parameter up and down).

Given the size of your data, the approach which tries all lines through two points is probably going to be the most efficient, but again I'm not sure whether the real scenario has a larger dataset.


Code

start_time = datetime.now()
...
print("That run took:", datetime.now() - start_time)

I believe that timeit is the standard way of doing this in Python.


for adder in range(int(max_adder / precision)):
    for multiplier in range(int(max_multiplier / precision)):
        ...
                result[0] * (multiplier*precision) + (adder*precision) - result[1])

It took some thought to decipher the intent here, especially since precision's meaning is not documented. It would be clearer to use numpy.arange or to implement something similar.


print("adder: {:0.3f}".format(1 / (best_setting[1] * precision) if best_setting[1] else 0))
print("multipliertiply: {:0.3f}".format(1 / (best_setting[2] * precision) if best_setting[2] else 0))

Huh? Where did those reciprocals come from? Also, you seem to have started changing multiply to multiplier and got distracted part-way through.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.