Nonlinear regression#

In this example, we use the PANTR solver to fit a nonlinear model to some noisy data. The model is given by \(y = p_1\ \sin(p_2 x) + p_3\), and the goal is to estimate the parameters \(p_1\), \(p_2\) and \(p_3\) by minimizing the squared error between the model and the data.

True model, noisy data and estimated model.
 1# %% alpaqa nonlinear regression example
 2
 3import alpaqa as pa
 4import casadi as cs
 5import numpy as np
 6from pprint import pprint
 7
 8# %% Build the problem (CasADi code, independent of alpaqa)
 9
10# Symbolic model parameters
11p = cs.MX.sym("params", 3)
12# Symbolic data vectors
13N_data = 50
14data_x = cs.MX.sym("data_x", N_data)
15data_y = cs.MX.sym("data_y", N_data)
16data = cs.horzcat(data_x, data_y)
17
18# Objective function is the squared error between the model and the data
19x, y = cs.MX.sym("x"), cs.MX.sym("y")
20model = cs.Function("model", [x, p], [p[0] * cs.sin(p[1] * x) + p[2]])
21sample_error = cs.Function("err", [x, y, p], [y - model(x, p)])
22sum_sq_error = cs.sumsqr(sample_error.map(N_data)(data_x.T, data_y.T, p))
23
24# %% Generate and compile C code for the objective and constraints using alpaqa
25
26# Compile and load the problem (without general constraints)
27problem = (
28    pa.minimize(sum_sq_error, p)
29    .with_param(cs.vec(data))
30).compile(second_order="psi_prod", sym=cs.SX.sym)
31# Optionally, add constraints on the parameters
32problem.C.lowerbound[1] = 3
33problem.C.upperbound[1] = 7
34
35# %% Generate some data
36
37true_params = [6, 5, 4]
38true_model = np.vectorize(lambda x: model(x, true_params))
39rng = np.random.default_rng(12345)
40data_x = np.linspace(-1, +1, N_data, endpoint=True)
41data_y = true_model(data_x) + rng.standard_normal(N_data)
42
43# Add data to the problem
44problem.param = np.concatenate((data_x, data_y))
45
46# %% Solve the problem using alpaqa's PANTR solver
47
48solver = pa.PANTRSolver({"print_interval": 1})
49# Add evaluation counters to the problem
50cnt = pa.problem_with_counters(problem)
51sol_params, stats = solver(cnt.problem, {"tolerance": 1e-10})
52
53# %% Print the results
54
55print(f"\nSolution:      {sol_params}")
56print(stats["status"])
57pprint(stats)
58print("\nEvaluations:")
59print(cnt.evaluations)
60
61# %% Plot the results
62
63import matplotlib.pyplot as plt
64
65model_str = r"${:.2f}\ \sin({:.2f} x) + {:.2f}$"
66sol_model = np.vectorize(lambda x: model(x, sol_params))
67plt.figure(figsize=(8, 5))
68x_fine = np.linspace(-1, 1, 256)
69plt.plot(data_x, data_y, "x", label="Samples")
70plt.plot(x_fine, true_model(x_fine),
71         label="True model\n" + model_str.format(*true_params))
72plt.plot(x_fine, sol_model(x_fine), "--",
73         label="Est. model\n" + model_str.format(*sol_params))
74plt.legend(loc='upper right')
75plt.title("PANTR nonlinear regression example")
76plt.tight_layout()
77plt.show()
78
79# %%