2
\$\begingroup\$

I want to determine a path (x and y coordinates) by using curvature data (radius, with constant distance (length_of_arc) travelled between data points. I want to repeat this calculation every 0.010 seconds and visualize the points in pyhon kivy (at the moment a raw calculation takes 0.006 - 0.014 seconds).

Assume sample data

radius = [68.96551724, 69.73500697, 71.78750897, 75.98784195, 81.96721311, 90.74410163, 102.6694045, 120.0480192,
          146.1988304, 187.9699248, 265.9574468, 458.7155963, 1694.915254, -1020.408163, -393.7007874, -244.4987775]

This is the code (inside a class) that I use to calculate the coordinates. Is there a way to increase performance / readability of the code. It is important to note that performance has higher priority.

EDIT: I already tried small angle approximation (sin x \approx x, cos x \approx 1 - 0.5 x^2) but that did only lead to insignificant changes.

# positive curvature is a right bend, negative curvature is a left bend
# scale_factor: used to scale the values for plotting them
# initial_displacement_x and initial_displacement_y: values are displaced such that the plot is in the Center of the screen.
@staticmethod
def calculate_coordinates_from_length_of_arc_and_radius(_radius, length_of_arc, scale_factor,
                                                    initial_displacement_x, initial_displacement_y):
    phi = 0  # accumulation of angle
    number_of_data_points = len(_radius)
    x_accumulated = number_of_data_points * [0]
    y_accumulated = number_of_data_points * [0]
    track_coordinates = 2 * number_of_data_points * [0] # x,y alternating needed for ploting values as line in python kivy

    for data_point in range(number_of_data_points - 1):
        delta_phi = - length_of_arc / _radius[data_point]
        phi = phi + delta_phi
        dx = - _radius[data_point] * (cos(delta_phi) - 1)
        dy = - _radius[data_point] * sin(delta_phi)

        if data_point != 0:
            dx_rotated = cos(phi) * dx - sin(phi) * dy # apply rotation matrix
            dy_rotated = sin(phi) * dx + cos(phi) * dy # apply rotation matrix
        else:
            dx_rotated = dx  # the displacements are not rotated
            dy_rotated = dy  # for the first data point

        x_accumulated[data_point + 1] = x_accumulated[data_point] + dx_rotated
        y_accumulated[data_point + 1] = y_accumulated[data_point] + dy_rotated
        track_coordinates[2 * data_point + 2] = scale_factor * (x_accumulated[data_point] + dx_rotated)
        track_coordinates[2 * data_point + 3] = scale_factor * (y_accumulated[data_point] + dy_rotated)

    for data_point in range(len(track_coordinates)):
        if data_point % 2:
            track_coordinates[data_point] = track_coordinates[data_point] + initial_displacement_x
        elif not data_point % 2:
            track_coordinates[data_point] = track_coordinates[data_point] + initial_displacement_y
    return track_coordinates
\$\endgroup\$
  • 1
    \$\begingroup\$ Which version of Python are you using? \$\endgroup\$ – Ludisposed Aug 21 '18 at 9:18
  • 1
    \$\begingroup\$ @Ludisposed: I am using Python version 3.7.0 \$\endgroup\$ – MrYouMath Aug 21 '18 at 9:26
  • \$\begingroup\$ @Graipher: You could use random.shuffle(array) from random and shuffle the array that I have given. Would that suffice? \$\endgroup\$ – MrYouMath Aug 21 '18 at 10:20
  • \$\begingroup\$ Opps, did not see the example data...my bad \$\endgroup\$ – Graipher Aug 21 '18 at 10:22
  • 1
    \$\begingroup\$ at the moment I am using length_of_arc = 1, scale_factor=4, initial_displacements are scalars, which are set to 350 and 690 at the moment. \$\endgroup\$ – MrYouMath Aug 21 '18 at 10:26
2
\$\begingroup\$

If you are doing a lot of numerical calculations in Python and want them to be fast, you should use numpy, a package specifically for this purpose.

It allows you to write your code in a vectorized way, making it easer to understand at the same time:

import numpy as np

@staticmethod
def arc_coordinates(_radius, length_of_arc, scale_factor, x0, y0):
    """
    positive curvature is a right bend, negative curvature is a left bend
    scale_factor: used to scale the values for plotting them
    x0 and y0: values are displaced such that the plot is in the center of the screen.
    """
    radius = np.array(_radius)
    delta_phi = - length_of_arc / radius
    phi = np.cumsum(delta_phi)
    dx = - radius * (np.cos(delta_phi) - 1)
    dy = - radius * np.sin(delta_phi)
    track_coordinates = np.vstack([dx, dy]).T
    track_coordinates[1:, 0] = np.cos(phi[1:]) * dx[1:] - np.sin(phi[1:]) * dy[1:]
    track_coordinates[1:, 1] = np.sin(phi[1:]) * dx[1:] + np.cos(phi[1:]) * dy[1:]
    track_coordinates = scale_factor * np.cumsum(track_coordinates, axis=0) + np.array([[y0, x0]])
    track_coordinates = np.concatenate([np.array([y0, x0]), track_coordinates.flatten()[:-2]])
    return track_coordinates

This gives the same results as your code. Note that it (just like your code) uses the weird convention that the output array is flat, alternating between x and y values, starting with a y-value.

On my machine this is not actually faster than your implementation, though:

%timeit arc_coordinates(radius, 1, 1, 0, 0)
39.4 µs ± 849 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit calculate_coordinates_from_length_of_arc_and_radius(radius, 1, 1, 0, 0)
21.5 µs ± 457 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

However, this probably has a few possibilities for improvement left (WIP).

I also renamed your function to a (hopefully) also clear but shorter name as well as the initial displacements. I also made your comment detailing the parameters a docstring.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your answer. But that does not really speed up the calculations for me. \$\endgroup\$ – MrYouMath Aug 21 '18 at 13:05
  • \$\begingroup\$ @MrYouMath For me as well. Currently profiling to see where I can gain some speed. Your radius list might just be too short for this to help a lot, though. \$\endgroup\$ – Graipher Aug 21 '18 at 13:12

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.