Skip to content

Add a constrained parameter to map_range() #2

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

Merged
merged 6 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 81 additions & 16 deletions adafruit_simplemath.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2021 Dan Halbert for Adafruit Industries LLC
# SPDX-FileCopyrightText: 2021 James Carr
#
# SPDX-License-Identifier: MIT
"""
Expand All @@ -8,7 +9,7 @@
Math utility functions


* Author(s): Adafruit Industries
* Author(s): Dan Halbert, James Carr

Implementation Notes
--------------------
Expand All @@ -27,12 +28,72 @@ def map_range(
x: float, in_min: float, in_max: float, out_min: float, out_max: float
) -> float:
"""
Maps a number from one range to another. Somewhat similar to the Arduino ``map()`` function,
but returns a floating point result, and constrains the output value to be between
``out_min`` and ``out_max``.
If ``in_min`` is greater than ``in_max`` or ``out_min`` is greater than ``out_max``,
the corresponding range is reversed, allowing, for example, mapping a range of 0-10 to 50-0.
Maps a number from one range to another. Somewhat similar to the Arduino
:attr:`map()` function, but returns a floating point result, and
constrains the output value to be between :attr:`out_min` and
:attr:`out_max`. If :attr:`in_min` is greater than :attr:`in_max` or
:attr:`out_min` is greater than :attr:`out_max`, the corresponding range
is reversed, allowing, for example, mapping a range of 0-10 to 50-0.

See also :py:func:`unconstrained_map_range`

.. code-block::

from adafruit_simplemath import map_range

percent = 23
screen_width = 320 # or board.DISPLAY.width
x = map_range(percent, 0, 100, 0, screen_width - 1)
print("X position", percent, "% from the left of screen is", x)

:param float x: Value to convert
:param float in_min: Start value of input range.
:param float in_max: End value of input range.
:param float out_min: Start value of output range.
:param float out_max: End value of output range.
:return: Returns value mapped to new range.
:rtype: float
"""
# in_range = in_max - in_min
# in_delta = x - in_min
# if in_range != 0:
# mapped = in_delta / in_range
# elif in_delta != 0:
# mapped = in_delta
# else:
# mapped = 0.5
# mapped *= out_max - out_min
# mapped += out_min

mapped = unconstrained_map_range(x, in_min, in_max, out_min, out_max)

if out_min <= out_max:
return max(min(mapped, out_max), out_min)
return min(max(mapped, out_max), out_min)


def unconstrained_map_range(
x: float, in_min: float, in_max: float, out_min: float, out_max: float
) -> float:
"""
Maps a number from one range to another. Somewhat similar to the Arduino
:attr:`map()` function, but returns a floating point result, and
does not constrain the output value to be between :attr:`out_min` and
:attr:`out_max`. If :attr:`in_min` is greater than :attr:`in_max` or
:attr:`out_min` is greater than :attr:`out_max`, the corresponding range
is reversed, allowing, for example, mapping a range of 0-10 to 50-0.

See also :py:func:`map_range`

.. code-block::

from adafruit_simplemath import unconstrained_map_range

celsius = -20
fahrenheit = unconstrained_map_range(celsius, 0, 100, 32, 212)
print(celsius, "degress Celsius =", fahrenheit, "degrees Fahrenheit")

:param float x: Value to convert
:param float in_min: Start value of input range.
:param float in_max: End value of input range.
:param float out_min: Start value of output range.
Expand All @@ -50,22 +111,26 @@ def map_range(
mapped = 0.5
mapped *= out_max - out_min
mapped += out_min
if out_min <= out_max:
return max(min(mapped, out_max), out_min)
return min(max(mapped, out_max), out_min)

return mapped


def constrain(x: float, out_min: float, out_max: float) -> float:
"""Constrains ``x`` to be within the inclusive range [``out_min``, ``out_max``].
Sometimes called ``clip`` or ``clamp`` in other libraries.
``out_min`` should be less than or equal to ``out_max``.
If ``x`` is less than ``out_min``, return ``out_min``.
If ``x`` is greater than ``out_max``, return ``out_max``.
Otherwise just return ``x``.
"""Constrains :attr:`x` to be within the inclusive range
[:attr:`out_min`, :attr:`out_max`]. Sometimes called :attr:`clip` or
:attr:`clamp` in other libraries. :attr:`out_min` should be less than or
equal to :attr:`out_max`.
If :attr:`x` is less than :attr:`out_min`, return :attr:`out_min`.
If :attr:`x` is greater than :attr:`out_max`, return :attr:`out_max`.
Otherwise just return :attr:`x`.
If :attr:`max_value` is less than :attr:`min_value`, they will be swapped.

:param float x: Value to constrain
:param float out_min: Lower bound of output range.
:param float out_max: Upper bound of output range.
:return: Returns value constrained to given range.
:rtype: float
"""
return max(out_min, min(x, out_max))
if out_min <= out_max:
return max(min(x, out_max), out_min)
return min(max(x, out_max), out_min)
53 changes: 47 additions & 6 deletions examples/simplemath_simpletest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,54 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Dan Halbert for Adafruit Industries
# SPDX-FileCopyrightText: 2021 James Carr
#
# SPDX-License-Identifier: Unlicense

from adafruit_simplemath import map_range, constrain
from adafruit_simplemath import map_range, unconstrained_map_range, constrain

print("map_range() examples")
# Map, say, a sensor value, from a range of 0-255 to 0-1023.
print(map_range(30, 0, 255, 0, 1023))
sensor_input_value = 30
sensor_converted_value = map_range(sensor_input_value, 0, 255, 0, 1023)
print(
"Sensor input value:",
sensor_input_value,
"Converted value:",
sensor_converted_value,
)

percent = 23
screen_width = 320 # or board.DISPLAY.width
x = map_range(percent, 0, 100, 0, screen_width - 1)
print("X position", percent, "% from the left of screen is", x)

print("\nunconstrained_map_range() examples")
celsius = 20
fahrenheit = unconstrained_map_range(celsius, 0, 100, 32, 212)
print(celsius, "degress Celsius =", fahrenheit, "degrees Fahrenheit")

celsius = -20
fahrenheit = unconstrained_map_range(celsius, 0, 100, 32, 212)
print(celsius, "degress Celsius =", fahrenheit, "degrees Fahrenheit")

print("\nconstrain() examples")
# Constrain a value to a range.
print(constrain(0, 1, 3)) # prints 1
print(constrain(4, 1, 3)) # prints 3
print(constrain(2, 2, 3)) # prints 2
def constrain_example(value, min_value, max_value):
constrained_value = constrain(value, min_value, max_value)
print(
"Constrain",
value,
"between [",
min_value,
"and",
max_value,
"] gives",
constrained_value,
)


constrain_example(0, 1, 3) # expects 1
constrain_example(0, 3, 1) # expects 1
constrain_example(4, 1, 3) # expects 3
constrain_example(4, 3, 1) # expects 3
constrain_example(2, 2, 3) # expects 2
constrain_example(2, 3, 2) # expects 2
6 changes: 6 additions & 0 deletions tests/constrain_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2021 Dan Halbert for Adafruit Industries
# SPDX-FileCopyrightText: 2021 James Carr
#
# SPDX-License-Identifier: Unlicense

Expand All @@ -10,3 +11,8 @@ def test_constrain():
assert constrain(10, 1, 10) == 10
assert constrain(0, 1, 10) == 1
assert constrain(11, 1, 10) == 10

# Check out_min > out_max
assert constrain(5, 10, 0) == 5
assert constrain(-5, 10, 0) == 0
assert constrain(15, 10, 0) == 10
11 changes: 11 additions & 0 deletions tests/unconstrained_map_range_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2021 James Carr
#
# SPDX-License-Identifier: Unlicense

from adafruit_simplemath import unconstrained_map_range


def test_unconstrained_map_range():
assert unconstrained_map_range(-40, 32, 212, 0, 100) == -40.0
assert unconstrained_map_range(50, 32, 212, 0, 100) == 10.0
assert unconstrained_map_range(392, 32, 212, 0, 100) == 200.0