Question

I need to build a piecewise function with an arbitrary number of intervals and functions, able to operate on a numpy array of inputs.

I can do it with a for loop and indicator arrays, as exemplified in the snippet of code below, but is there is a more pythonic way to do it?

I tried to use numpy.piecewise but, as far as I can tell, the number of segments and functions needs to be statically defined in the source code.

import numpy as np
import matplotlib.pyplot as plt 

# inputs:
#    -xs: points in which to compute the piecewise function
#    -segments: the extremes of the piecewise intervals (as a list)
#    -funcs: the functions (as a list; len(funcs)==len(segments)-1 )
def calc_piecewise(xs, segments, funcs):
    # prepare indicators and results arrays
    indaseg = np.zeros(len(xs), np.bool)
    ys = np.zeros_like(xs)

    # loop through intervals and compute the ys
    for ii in range(len(funcs)):
        indaseg = np.logical_and(xs>=segments[ii], xs<=segments[ii+1])
        ys[indaseg] = funcs[ii](xs[indaseg])

    return ys

def test_calc_piecewise():
    segments = [0.0, 1.0, 2.5, 4.0, 5.0]
    def f0(xs):
        return xs
    def f1(xs):
        return xs*xs
    def f2(xs):
        return 12.5-xs*xs
    def f3(xs):
        return 4.0*xs-19.5
    funcs = [f0, f1, f2, f3]

    xs = np.linspace(0.0, 5.0, 500)
    ys = calc_piecewise(xs, segments, funcs)

    plt.figure()
    title = "calc_piecewise"
    plt.title(title)
    plt.plot(xs, ys, 'r-')
    plt.show()

    return 


test_calc_piecewise()
Was it helpful?

Solution

You can do it with np.piecewise as follows (apologies for formatting!):

ys = np.piecewise(
        xs,
        [(xs >= segments[i]) & (xs <= segments[i+1]) for i in range(len(segments)-1)],
        funcs)

The results are the same.

Essentially your loop and the test equivalent to your line indaseg = np.logical_and(xs>=segments[ii], xs<=segments[ii+1]) is moved into the calling code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top