"""
This module contains functions that are used to create other functions.
Clamping and mixing functions are provided, as are transition functions and
piecewise function assembly.
"""
import math
from codeviking.math.primitives import smooth010
[docs]def make_clamp(x0, x1):
"""
Create a clamping function that clhtamps input values to the range [x0,x1].
"""
return lambda x: max(x0, min(x, x1))
# []()∈∉
[docs]def make_mix(y0, y1):
"""
Create a mix function that mixes between y0 and y1 as x varies between 0
and 1.
* y0 for x0 ≤ 0
* y1 for 1 ≤ x
* y0 + x*(y1-y0) for x ∈[0,1]
"""
def _(x):
if x <= 0.0:
return y0
elif x >= 1.0:
return y1
return y0 * (1.0 - x) + y1 * x
return _
[docs]def piecewise_func1(x0, f0, f1):
"""
Create a piecewise function with one knot:
* f0(x) for x < x0
* f1(x) for x0 ≤ x
:param x0: knot between f0 and f1
:type x0: float
:param f0: function to use for x < x0
:type f0: (float) -> object
:param f1: function to use for x0 ≤ x
:type f1: (float) -> object
:return: piecewise function
:rtype: (float) -> float
"""
return lambda x: f0(x) if x < x0 else f1(x)
[docs]def piecewise_func2(x0, x1, f0, f1, f2):
"""
Create a piecewise function with two knots:
* f0(x) for x < x0
* f1(x) for x0 ≤ x < x1
* f2(x) for x1 ≤ x
:param x0: knot between f0 and f1
:type x0: float
:param x1: knot between f1 and f2
:type x1: float
:param f0: function to use for x < x0
:type f0: (float) -> object
:param f1: function to use for x0 ≤ x < x1
:type f1: (float) -> object
:param f2: function to use for x1 ≤ x
:type f2: (float) -> object
:return: piecewise function
:rtype: (float) -> float
"""
def _(x):
if x < x0:
return f0(x)
if x < x1:
return f1(x)
return f2(x)
return _
[docs]def make_interval_func(x0, x1, y0, f, y1):
"""
Create function on the interval [x0,x1] with constant value outside that
interval:
* y0 for x < x0
* f(x) for x0 ≤ x < x1
* y1 for x1 ≤ x
:type x0 x1 y0 y1: float
:type f: (float) -> float
"""
def _(x):
if x < x0:
return y0
if x < x1:
return f(x)
return y1
return _
[docs]def make_triangle_func(x0, x1, x2, y0, y1, y2):
"""
Create triangle function ( ╱╲ ) on the interval [x0,x2]:
* y0 for x < x0
* linear slope from (x0,y0) to (x1,y1) for x0 ≤ x < x1
* linear slope from (x1,y1) to (x2,y2) for x1 ≤ x < x2
* y2 for x2 ≤ x
:type x0 x1 x2 y0 y1 y2: float
"""
m0 = (y1 - y0) / (x1 - x0)
b0 = -(x1 * y0 - x0 * y1) / (x0 - x1)
m1 = (y2 - y1) / (x2 - x1)
b1 = -(x2 * y1 - x1 * y2) / (x1 - x2)
def _(x):
if x < x0:
return y0
if x < x1:
return m0 * x + b0
if x < x2:
return m1 * x + b1
return y2
return _
[docs]def make_step(x0, y0=0.0, y1=1.0):
"""
create a step function:
* f(x) = y0 for x<x0
* f(x) = y1 for x0≤x
"""
def _(x):
return y0 if (x < x0) else y1
return _
[docs]def make_rect_func(x0, x1, y0, y1, y2):
"""
Create rect function ( ┌─┐ )on the interval [x0,x1]:
* y0 for x < x0
* y1 for x0 ≤ x ≤ x1
* y2 for x1 < x
"""
def _(x):
if x < x0:
return y0
if x <= x1:
return y1
return y2
return _
[docs]def make_smooth_step(x0,
x1,
y0=0.0,
y1=1.0):
"""
Creates a smooth step function f with the following properties:
* f(x)=y0 for x ≤ x0
* f(x)=y1 for x1 ≤ x
* f'(x) = 0 for x=x0 and x=x1.
* in the interval [x0,x1], the function smoothly transitions between
y0 and y1. We use a monotonically increasing sin function on this
interval.
"""
height = y1 - y0
s = math.pi / (x1 - x0)
shift = math.pi / 2.0
def _(x):
if x <= x0:
return y0
if x >= x1:
return y1
return height * 0.5 * (math.sin(s * (x - x0) - shift) + 1.0) + y0
return _
[docs]def make_bump_func(x0, x1, x2,
y0, y1, y2):
"""
Create a bump function with the following properties:
* f(x)=y0 for x≤x0
* f(x1)=y1
* f(x)=y2 for x2≤x
* f'(x) = 0 for x=x0, x=x1, x=x2.
* in the interval [x0,x1], smoothly and monotonically transition
between y0 and y1.
* in the interval [x1,x2], smoothly and monotonically transition
between y1 and y2.
"""
ah = y1 - y0
bh = y2 - y1
aw = x1 - x0
bw = x2 - x1
def _(x):
if x <= x0:
return y0
if x <= x1:
return y0 + ah * smooth010((x - x1) / aw)
if x <= x2:
return y2 - bh * smooth010((x - x1) / bw)
return y2
return _
[docs]def make_map_to_01(t0, t1):
"""
Create a function that linearly maps t0->0.0 and t1->1.0. This function
is undefined when t0==t1. Nevertheless, we return a function that always
returns 0.0 when t0==t1. This means that if you construct a forword map
f= make_map_to_01(t0,t0) and a reverse map g = make_map_from_01(t0, t0),
then their composition will behave sensibly: g(f(t)) == t0.
:param t0: value that maps to 0.0
:type t0: float
:param t1: value that maps to 1.0
:type t1: float
:rtype: (float) -> float
"""
if t0 == t1:
# map is constant
return lambda t: 0.0
scale = 1.0 / (t1 - t0)
return lambda t: (t - t0) * scale
[docs]def make_map_from_01(t0, t1):
"""
Create a function that linearly maps 0.0->t1 and 1.0->t1.
:param t0: value that 0.0 maps to
:type t0: float
:param t1: value that 1.0 maps to
:type t1: float
:rtype: (float) -> float
"""
if t0 == t1:
# map is constant
return lambda t: t0
scale = t1 - t0
return lambda t: t0 + t * scale
[docs]def min_max(a, b):
"""
Return (a, b) in order: ( min(a,b), max(a,b) )
:type a: float
:type b: float
:return: min(a,b), max(a,b)
:rtype: float, float
"""
return min(a, b), max(a, b)
[docs]def make_map_to_01c(t0, t1):
"""
Clamped version of make_map_to_01. The resulting function will always
return a value in [0.0, 1.0].
:param t0: value that maps to 0.0
:type t0: float
:param t1: value that maps to 1.0
:type t1: float
:rtype: (float) -> float
"""
if t0 == t1:
# map is constant
return lambda t: 0.5
scale = 1.0 / (t1 - t0)
return lambda t: min(max((t - t0) * scale, 0.0), 1.0)
[docs]def make_map_from_01c(t0, t1):
"""
Clamped version of make_map_from_01. The resulting function will always
return a value in [t0, t1].
:param t0: value that 0.0 maps to
:type t0: float
:param t1: value that 1.0 maps to
:type t1: float
:rtype: (float) -> float
"""
if t0 == t1:
# map is constant
return lambda t: t0
scale = t1 - t0
a, b = min_max(t0, t1)
return lambda t: min(max(t0 + t * scale, t0), t1)