Lambda function

Lambda functions in Python are small, anonymous functions defined with the lambda keyword rather than with the standard def statement. They allow you to write functions in a concise way when you don't need to reuse the function multiple times, making your code shorter and often more readable in context.

Key Characteristics of Lambda Functions

  1. Anonymous: Lambda functions do not have a formal name. They are often used inline as an argument to other functions.

  2. Single Expression: A lambda function is restricted to a single expression. The result of that expression is implicitly returned. You cannot include multiple statements or assignments within a lambda.

  3. Usage Context: They are typically used in situations where a full function definition would be unnecessarily verbose, such as in functional programming constructs (e.g., when using map(), filter(), or sorted()), or for quick calculations.

  4. Syntax: The basic syntax for a lambda function is:

    1
    lambda arguments: expression
    • arguments: A comma-separated list of parameters.
    • expression: A single expression that is evaluated and returned.

Examples of Lambda Functions

1. Simple Arithmetic Operation

A basic example of a lambda function is adding 1 to a number:

1
2
3
add_one = lambda x: x + 1
result = add_one(5)
print(result) # Output: 6

In this example: - lambda x: x + 1 defines a lambda function that takes one input x and returns x + 1. - The function is assigned to the variable add_one, which can be used just like any other function.

2. Sorting with a Lambda Function

Lambda functions are commonly used to provide a key function for sorting:

1
2
3
4
5
6
7
# List of tuples representing (name, age)
people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]

# Sort by age (the second element of each tuple)
people_sorted = sorted(people, key=lambda person: person[1])
print(people_sorted)
# Output: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]

In this example: - The sorted() function is used with the key parameter, which expects a function that extracts a comparison key from each list element. - The lambda function lambda person: person[1] picks the second element (age) from each tuple for sorting.

3. Using Lambda with map() and filter()

Lambda functions are also useful for transforming lists: - Using map():

1
2
3
4
numbers = [1, 2, 3, 4, 5]
# Double each number using a lambda function
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # Output: [2, 4, 6, 8, 10]

  • Using filter():
    1
    2
    3
    4
    numbers = [1, 2, 3, 4, 5, 6]
    # Filter out even numbers using a lambda function
    odds = list(filter(lambda x: x % 2 != 0, numbers))
    print(odds) # Output: [1, 3, 5]

In these examples: - map() applies the lambda function to each element, returning a new list with the transformed values. - filter() applies the lambda function as a condition. It returns a new list consisting only of elements for which the function returns True.

4. Lambda in the Context of fsolve

In snippet:

1
beta_sol = fsolve(lambda beta: equation(beta, theta), 0.0)[0]
- The lambda function lambda beta: equation(beta, theta) is used to create an anonymous function that takes only beta as an argument. - This lambda “freezes” theta (provided from the outer loop) so that fsolve only needs to vary beta to find a solution for which equation(beta, theta) = 0. - fsolve then returns an array of solutions. The [0] index extracts the first (and only) solution.

Summary

Lambda functions are a concise way to define small functions on the fly without naming them. They are particularly useful when used as arguments to higher-order functions or in contexts where the functionality is short-lived and does not require a formal definition.

fsolve function

The fsolve function in Python is part of the scipy.optimize module and is used to find the roots of a system of nonlinear equations. It employs numerical methods to solve equations of the form \(F(x) = 0\), where \(F\) can be a scalar or vector function. Below is a detailed explanation of its functionality and parameters.

Key Features of fsolve

  1. Purpose: Solve nonlinear equations (or systems of equations) numerically.
  2. Algorithm: Uses MINPACK’s hybrd algorithm (a modification of Powell’s method) for systems and scalar equations.
  3. Input: Requires an initial guess (x0) for the solution.
  4. Output: Returns the root (or roots) closest to the initial guess.

Function Syntax

1
2
3
4
5
6
7
8
9
10
11
12
from scipy.optimize import fsolve

solution = fsolve(
func, # Function defining the equation(s)
x0, # Initial guess
args=(), # Extra arguments for `func`
fprime=None, # Jacobian of `func` (optional)
full_output=0, # Return additional metadata
xtol=1.49012e-08, # Tolerance for termination
maxfev=0, # Maximum function evaluations
...
)

Parameters Explained

1. func (required)

  • Definition: The function \(F(x)\) whose root is to be found.

  • Input: A vector (for systems) or scalar (for single equations).

  • Output: A vector of residuals (for systems) or a scalar (for single equations).

  • Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Single equation: x^2 - 4 = 0
    def func(x):
    return x**2 - 4

    # System of equations:
    # x + y = 3
    # x - y = 1
    def system_func(vars):
    x, y = vars
    return [x + y - 3, x - y - 1]

2. x0 (required)

  • Definition: Initial guess for the root(s). Critical for convergence!

  • Type: Array-like (even for scalar equations).

  • Example

1
2
3
4
5
# For x^2 - 4 = 0, guess x=3
x0_single = [3]

# For the system, guess x=0, y=0
x0_system = [0, 0]

3. args (optional)

  • Definition: Extra arguments to pass to func (e.g., constants).

  • Type: Tuple.

  • Example

1
2
3
4
5
# Solve f(x, a, b) = x^2 + a*x + b = 0
def func(x, a, b):
return x**2 + a*x + b

solution = fsolve(func, x0=[1], args=(2, 3)) # Solves x² + 2x + 3 = 0

4. fprime (optional)

  • Definition: Jacobian matrix (derivatives) of func. If provided, improves efficiency.

  • Type: Function returning the Jacobian.

  • Example

    1
    2
    3
    4
    5
    6
    7
    8
    # Jacobian for the system:
    # F = [x + y - 3, x - y - 1]
    def jacobian(vars):
    x, y = vars
    return [[1, 1], # dF1/dx, dF1/dy
    [1, -1]] # dF2/dx, dF2/dy

    solution = fsolve(system_func, x0=[0,0], fprime=jacobian)

5. xtol (optional)

  • Definition: Tolerance for termination. The solver stops when the relative error between iterations is below xtol.
  • Default: \(1.49 \times 10^{-8}\).

6. maxfev (optional)

  • Definition: Maximum number of function evaluations. If set to 0, the limit is \(100 \times (n+1)\), where \(n\) is the number of variables.
  • Use Case: Prevents infinite loops for non-convergent problems.

7. full_output (optional)

  • Definition: If True, returns additional metadata (e.g., convergence status).

  • Example

    1
    2
    solution, infodict, ier, msg = fsolve(func, x0=[1], full_output=True)
    print(infodict) # Shows function evaluations, Jacobian, etc.

Examples

Example 1: Solve \(x^2 - 4 = 0\)

1
2
3
4
5
6
7
from scipy.optimize import fsolve

def func(x):
return x**2 - 4

solution = fsolve(func, x0=[3]) # Initial guess: x=3
print(solution) # Output: [2.0]

Example 2: Solve a System of Equations

1
2
3
4
5
6
def system_func(vars):
x, y = vars
return [x + y - 3, x - y - 1]

solution = fsolve(system_func, x0=[0, 0])
print(solution) # Output: [2.0, 1.0]

Example 3: Using args for Parameters

1
2
3
4
5
def func(x, a, b):
return x**3 + a*x + b

solution = fsolve(func, x0=[1], args=(2, 5)) # Solves x³ + 2x + 5 = 0
print(solution)

Common Issues & Tips

  1. Initial Guess (x0):
    • Poor guesses may lead to no convergence. Try different values!
    • Use domain knowledge or plot the function to choose x0.
  2. Convergence Problems:
    • Check if func(x0) is close to zero (e.g., print(func(x0))).
    • Increase maxfev or adjust xtol.
  3. Jacobian:
    • Providing fprime speeds up convergence but requires manual derivative calculations.
  4. Systems of Equations:
    • Ensure func returns an array of residuals with the same length as x0.

Output

  • Returns: A NumPy array containing the root(s).

  • If full_output=True, returns a tuple:

    1
    (solution, infodict, ier, msg)
    • infodict: Dictionary with details (e.g., number of function evaluations).
    • ier: Status (1 = success).
    • msg: Description of termination cause.

fsolve is a powerful tool for solving nonlinear equations, but its success heavily depends on the initial guess (x0) and the behavior of the function. For complex systems, consider using alternative methods (e.g., root with method=lm for least-squares minimization).

Compute the sum of two arrays

We can easily compute a new set of values by summing each \(\theta\) with its corresponding \(\beta\) only if \(\beta\) is a valid number (i.e. not nan). One common approach is to filter out the pairs where \(\beta\) is nan. Here are two approaches you can use:

Approach 1: List Comprehension

Convert your \(\theta\) values (which is already a NumPy array) and your beta_values (a list) into a list of sums for the valid cases:

1
2
3
4
5
6
import numpy as np

# Using list comprehension to filter out pairs where beta is nan.
theta_beta_sum = [theta + beta for theta, beta in zip(theta_values, beta_values) if not np.isnan(beta)]

print("Sum of theta and beta for valid beta values:", theta_beta_sum)

This code uses Python's built-in zip function to iterate over both lists in parallel and np.isnan(beta) to check if beta is not a number. It then only sums those pairs which have a valid \(\beta\).

zip function

The original arrays, theta_values and beta_values, both have the same number of elements regardless of the presence of nan values. The list comprehension with zip pairs them element‐by‐element (by their index). However, the filtering condition (if not np.isnan(beta)) means that if the beta value is nan, the corresponding pair (including the theta value) is skipped when constructing the new list.

What This Means
  • Original Arrays: Both theta_values and beta_values have the same length, say \(N\). Each position \(i\) in both arrays corresponds to a single pair \((\theta_i, \beta_i)\).

  • Filtered List Construction: With the list comprehension:

    1
    theta_beta_sum = [theta + beta for theta, beta in zip(theta_values, beta_values) if not np.isnan(beta)]

    each pair is evaluated. If beta is a valid number (i.e., not nan), the sum \(\theta_i + \beta_i\) is computed and appended to theta_beta_sum. If beta is nan, that pair is omitted. Consequently, the resulting list theta_beta_sum will only include sums where beta is valid, and its length may be less than \(N\).

Example

Assume:

1
2
theta_values = np.array([0, 0.5, 1.0, 1.5])
beta_values = [0.1, np.nan, 0.3, 0.4]
  • The zipped pairs are:
    • (0, 0.1) → valid, so include \(0 + 0.1 = 0.1\)
    • (0.5, np.nan) → beta is nan, so skip this pair.
    • (1.0, 0.3) → valid, so include \(1.0 + 0.3 = 1.3\)
    • (1.5, 0.4) → valid, so include \(1.5 + 0.4 = 1.9\)

The final list becomes:

1
theta_beta_sum = [0.1, 1.3, 1.9]

Notice that the second element was omitted because beta was nan.

Summary
  • Dimensions Remain Unchanged: The original theta_values and beta_values arrays keep their original dimensions.
  • Filtered Sum List: The list comprehension creates a new list that only contains sums from pairs where beta is a valid number, which might result in a list with fewer elements than the original arrays.

This approach ensures you're calculating sums only from the valid pairs while leaving the original data intact.

Approach 2: Using NumPy Arrays

Alternatively, we can convert your beta_values list to a NumPy array and use boolean indexing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

# Convert beta_values to a numpy array if it is not already.
beta_array = np.array(beta_values)

# Create a boolean array that is True where beta is a number (not nan)
valid_idx = ~np.isnan(beta_array)

# Filter theta_values and beta_array and compute the sum for valid indices
theta_valid = theta_values[valid_idx]
beta_valid = beta_array[valid_idx]
theta_beta_sum = theta_valid + beta_valid

print("Sum of theta and beta for valid beta values:", theta_beta_sum)

With boolean indexing, we first create an index (valid_idx) where the \(\beta\) values are valid. Then we use that index to select the corresponding \(\theta\) values and \(\beta\) values before computing the sum.

Both methods will give the sum of \(\theta\) and \(\beta\) only where \(\beta\) is not nan.

write data into csv file

1
2
3
4
import csv
with open ('theta_beta_sum.csv', 'w', newline='') as file
writer = csv.writer(file)
writer.writerows(theta_beta_sum)