Skip to content

Instantly share code, notes, and snippets.

@Alvtron
Last active January 10, 2020 15:23
Show Gist options
  • Save Alvtron/f0acbfa80b6625e0ba91e54b81246059 to your computer and use it in GitHub Desktop.
Save Alvtron/f0acbfa80b6625e0ba91e54b81246059 to your computer and use it in GitHub Desktop.
Reflect values between a minimum and maximum.

Reflect

Reflect values between a minimum and maximum. I have written both an iterative and recursive version of the algorithm.

Iterative (fastest)

import math

def reflect(value, min_value, max_value):
    span = max_value - min_value
    if value < min_value:
        length = min_value - value
        times = math.floor(length / span)
        delta = length - span * times
        return min_value + delta if times % 2 == 0 else max_value - delta
    elif value > max_value:
        length = value - max_value
        times = math.floor(length / span)
        delta = length - span * times
        return max_value - delta if times % 2 == 0 else min_value + delta
    else:
        return value

Recursive

def reflect_recursive(value, min_value, max_value):
    if value < min_value:
        return reflect_recursive(min_value + (min_value - value), min_value, max_value)
    elif value > max_value:
        return reflect_recursive(max_value - (value - max_value), min_value, max_value)
    else:
        return value

Description

This algorithm is useful for situations where one or multiple values need to be constrained within a predefined space.

While a simple clamp-function will do this just fine, the values might end up residing along the minimum or maximum, which leads to less variance in the data.

This algorithm may be useful in constrained search space exploration in multiple dimensions and was originally created for that purpose. Such searches are common in global optimization tasks, or hyper-parameter optimization algorithms for black-box functions.

Recursive or iterative?

The recursive function is more compact, but it also requires more operations; the number of operations scale linearly with how many times the value exceeds the span between the minimum and maximum. It is therefore recommended to use the iterative function as it requires the same amount of operations regardless of how large the value is.

Examples

1D list

import random

min_value = 0
max_value = 100

for value in [random.uniform(-250., 250.) for _ in range(10)]:
  print(value, "-->", reflect(value, min_value, max_value))

Output:

35.76567245051814 --> 35.76567245051814
-65.8385756123771 --> 65.8385756123771
-44.858018802589896 --> 44.858018802589896
-243.09102707381268 --> 43.09102707381268
9.921358638726929 --> 9.921358638726929
-7.940068972499944 --> 7.940068972499944
151.39851126754178 --> 48.60148873245822
-177.72874419074253 --> 22.271255809257468
89.64493033145743 --> 89.64493033145743
-40.733802405349905 --> 40.733802405349905

2D coordinates plot

import matplotlib.pyplot as plt

# define constraint space
min_value = -50
max_value = 50
# generate points
n_points = 20
points = [(random.randint(-250, 250), random.randint(-250, 250)) for _ in range(n_points)]
# define figure
plt.figure(figsize=(8, 8))
plt.xlabel("x")
plt.ylabel("y")
plt.axvline(x=min_value, color='r', linestyle='-', linewidth=0.4)
plt.axvline(x=max_value, color='r', linestyle='-', linewidth=0.4)
plt.axhline(y=min_value, color='r', linestyle='-', linewidth=0.4)
plt.axhline(y=max_value, color='r', linestyle='-', linewidth=0.4)
for i, (x, y) in enumerate(points):
    new_x = reflect(x, min_value, max_value)
    new_y = reflect(y, min_value, max_value)
    plt.scatter(x, y, marker="x", color='g', label="original" if i == 0 else "")
    plt.scatter(new_x, new_y, marker="x", color='b', label="reflected" if i == 0 else "")
    plt.arrow(x=x, y=y, dx=new_x-x, dy=new_y-y)
plt.legend()
plt.savefig(fname="test/plot.png", format='png', transparent=False)
plt.show()

plot

import math
def reflect(value, min_value, max_value):
span = max_value - min_value
if value < min_value:
length = min_value - value
times = math.floor(length / span)
delta = length - span * times
return min_value + delta if times % 2 == 0 else max_value - delta
elif value > max_value:
length = value - max_value
times = math.floor(length / span)
delta = length - span * times
return max_value - delta if times % 2 == 0 else min_value + delta
else:
return value
def reflect_recursive(value, min_value, max_value):
if value < min_value:
return reflect_recursive(min_value + (min_value - value), min_value, max_value)
elif value > max_value:
return reflect_recursive(max_value - (value - max_value), min_value, max_value)
else:
return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment