Jackdaw
Jackdaw

Reputation: 67

Locally bind functions within lambda's

Is there a way to locally bind functions within lambdas? I have a loop, and within each loop, I create an array of functions. I want to create another function which is the sum of all of these functions and store it in another array. This new array should hold the sum of all functions for each loop. Then I want to create another function that is the sum over all the sum of functions.

However, the problem I have is that the original functions keep updating, so I am not getting my desired result. Can I locally bind the functions? Am I approaching this the wrong way?

import numpy as np

lmax = 4
lapprox = []

# Function to estimate
def curve_func(x):
    return np.sin(x*np.pi)*x**2

# Initialise residual function
def residual_func(x):
    return curve_func(x)

# For each l, create 2**l+1 nodes and determine new nodes.
for l in range(lmax):  
    nodes = np.linspace(0, 1, 2**l+1, endpoint = True)
    if (l==0):
        old_nodes = nodes
        new_nodes = nodes
    else:
        old_nodes = np.linspace(0, 1, 2**(l-1)+1, endpoint = True)
        new_nodes = [x for x in nodes if x not in old_nodes]

    # Create basis function corresponding to each new node
    if (l==0):
        phi = [lambda x, i=i: max(1 - abs(2**l * x - i), 0) for i in range(len(new_nodes))]
    else:
        phi = [lambda x, i=i: max(1 - abs(2**l * x - (2*i+1)), 0) for i in range(len(new_nodes))]

    # Calculate hierarchical surpluses
    coeff = [residual_func(n) for n in new_nodes]

    # Array of functions: coeff*phi
    coeff_phi = [lambda x, func=func, alpha=alpha: coeff[alpha]*func(x) for alpha, func in enumerate(phi)]

    # Array of length lmax, where each value is sum of functions in coeff_phi for fixed l       
    lapprox.append(lambda x: sum(f(x) for f in coeff_phi)) 

    # Sum of all functions in lapprox           
    totapprox = lambda x: sum(f(x) for f in lapprox)

    # Compute new residual function
    residual_func = lambda x: curve_func(x) - totapprox(x)

Extra detail on what the code is doing: The code is designed to approximate a function, such as sin(pi*x)*x^2 using hierarchical linear splines. For each level l, there are some basis functions, given by the array phi. The function is approximated using a linear combination of some coefficients multiplied by these basis functions. The approximation is done sequentially, starting from a low-level, with few basis functions, until a high-level, with many basis functions. I need to keep track of the rolling approximation of the function to determine the values of the new coefficients.

Edit 2: I've defined the functions outside the loop. However, I am struggling in working out how to create a function to keep track of the residual_function. I have attached the 'dirty' solution that works as intended for lmax=3, but I would like to generalise it for any lmax. How can I do that?

def curve_func(x):
    return np.sin(x*np.pi)*x**2

def residual_func_0(x):
    return curve_func(x)

# Define nodes function
def make_nodes(l):
    return np.linspace(0, 1, 2**l+1, endpoint = True)
 
# Define new_nodes function
def make_new_nodes(l):
    if (l==0):
        new_nodes = np.linspace(0, 1, 2**l+1, endpoint = True)
    else:
        old_nodes = np.linspace(0, 1, 2**(l-1)+1, endpoint = True)
        new_nodes = [x for x in make_nodes(l) if x not in old_nodes]
    return new_nodes
    
# Define basis functions
def make_basis(l, i):
    if l == 0:
        return lambda x: max(1 - abs(2**l * x - i), 0)
    else:
        return lambda x: max(1 - abs(2**l * x - (2*i+1)), 0)

# Define coeff*basis functions
def make_scaled_basis(alpha, fn):
    return lambda x: alpha * fn(x)

new_nodes_0 = make_new_nodes(0)
new_nodes_1 = make_new_nodes(1)
new_nodes_2 = make_new_nodes(2)
new_nodes_3 = make_new_nodes(3)

phi_0 = [make_basis(0, i) for i in range(len(new_nodes_0))]
phi_1 = [make_basis(1, i) for i in range(len(new_nodes_1))]
phi_2 = [make_basis(2, i) for i in range(len(new_nodes_2))]
phi_3 = [make_basis(3, i) for i in range(len(new_nodes_3))]

coeff_0 = [curve_func(n) for n in new_nodes_0]
coeff_phi_0 = [make_scaled_basis(alpha, fn) for alpha, fn in zip(coeff_0, phi_0)]
residual_func_0 = lambda x: curve_func(x) - sum(f(x) for f in coeff_phi_0)

coeff_1 = [residual_func_0(n) for n in new_nodes_1]
coeff_phi_1 = [make_scaled_basis(alpha, fn) for alpha, fn in zip(coeff_1, phi_1)]
residual_func_1 = lambda x: residual_func_0(x) - sum(f(x) for f in coeff_phi_1)

coeff_2 = [residual_func_1(n) for n in new_nodes_2]
coeff_phi_2 = [make_scaled_basis(alpha, fn) for alpha, fn in zip(coeff_2, phi_2)]
residual_func_2 = lambda x: residual_func_1(x) - sum(f(x) for f in coeff_phi_2)

coeff_3 = [residual_func_2(n) for n in new_nodes_3]
coeff_phi_3 = [make_scaled_basis(alpha, fn) for alpha, fn in zip(coeff_3, phi_3)]
residual_func_3 = lambda x: residual_func_2(x) - sum(f(x) for f in coeff_phi_3)

Upvotes: 0

Views: 105

Answers (1)

chepner
chepner

Reputation: 531275

A simple way to localize both l and i in the body of a function is to create a function that returns a function which closes over the local variables l and i.

For example:

def make_basis(l, i):
    if l == 0:
        return lambda x: max(1 - abs(2**l * x - i), 0)
    else:
        return lambda x: max(1 - abs(2**l * x - (2*i+1)), 0)

...

for l in range(lmax):
    ...

    phi = [make_basis(l, i) for i in range(len(new_nodes))]

Loops do not create new scopes; only function bodies do.

Upvotes: 1

Related Questions