Solution of a non-expensive least-square problem based on a scipy implementation

Driver:

ScipyLeastSquares

Download script:

scipy_least_squares.py

The target of the study is to showcase the solution of a non-linear least-squares problem from the NIST statistical reference datasets. As an example, the MGH17 problem is considered which consists of fitting a vectorial model \(\mathbf{f}(\mathbf{b}) \in \mathbb{R}^{33}\) with

\[f_i(\mathbf{b}) = b_1 + b_2 \exp(-i \cdot b_4) + b_3 \exp(-i \cdot b_5),\, i = 0, \dots, 32\]

to a target vector with 33 entries. The certified best-fit values are

\[ \begin{align}\begin{aligned}b_1 &= 0.3754100521 \pm 2.0723153551 \cdot 10^{-3}&\\b_2 &= 1.9358469127 \pm 0.22031669222&\\b_3 &= -1.464687136 \pm 0.22175707739&\\b_4 &= 0.1286753464 \pm 4.4861358114\cdot 10^{-3}&\\b_5 &= 0.2212269966 \pm 8.9471996575 \cdot 10^{-3}&\end{aligned}\end{align} \]
 1import sys,os
 2import numpy as np
 3import time
 4import pandas as pd
 5import torch
 6
 7jcm_optimizer_path = r"<JCM_OPTIMIZER_PATH>"
 8sys.path.insert(0, os.path.join(jcm_optimizer_path, "interface", "python"))
 9from jcmoptimizer import Server, Client, Study, Observation
10server = Server()
11client = Client(server.host)
12
13# Definition of the search domain
14design_space = [
15    {'name': 'b1', 'type': 'continuous', 'domain': (0,10)}, 
16    {'name': 'b2', 'type': 'continuous', 'domain': (0.1,4)},
17    {'name': 'b3', 'type': 'continuous', 'domain': (-4,-0.1)},
18    {'name': 'b4', 'type': 'continuous', 'domain': (0.05,1)},
19    {'name': 'b5', 'type': 'continuous', 'domain': (0.05,1)}
20]
21constraints = [
22    {'name': 'test', 'expression': 'b2 + b3 <= 1.0'}
23]
24
25# Creation of the study object with study_id 'scipy_least_squares'
26study = client.create_study(
27    design_space=design_space,
28    constraints=constraints,
29    driver="ScipyLeastSquares",
30    name="Solution of a non-expensive least-square problem based on a scipy implementation",
31    study_id="scipy_least_squares"
32)
33#The vectorial model function of the MGH17 problem
34def model(x: torch.Tensor) -> torch.Tensor:
35    s = torch.arange(33)
36    return x[0] + x[1]*torch.exp(-s*x[3]) + x[2]*torch.exp(-s*x[4])
37
38#Target vector of the MGH17
39target=torch.tensor([
40    8.44E-01, 9.08E-01, 9.32E-01, 9.36E-01, 9.25E-01,
41    9.08E-01, 8.81E-01, 8.50E-01, 8.18E-01, 7.84E-01,
42    7.51E-01, 7.18E-01, 6.85E-01, 6.58E-01, 6.28E-01,
43    6.03E-01, 5.80E-01, 5.58E-01, 5.38E-01, 5.22E-01,
44    5.06E-01, 4.90E-01, 4.78E-01, 4.67E-01, 4.57E-01,
45    4.48E-01, 4.38E-01, 4.31E-01, 4.24E-01, 4.20E-01,
46    4.14E-01, 4.11E-01, 4.06E-01
47])
48
49study.configure(
50    target_vector=target.tolist(),
51    method="trf",
52    max_iter=150,
53    num_parallel=2,
54    num_initial=2,
55    jac=True
56)
57
58# Evaluation of the black-box function for specified design parameters
59def evaluate(study: Study, b1: float, b2: float, b3: float, b4: float, b5: float) -> Observation:
60
61    observation = study.new_observation()
62    #tensor of design values to reconstruct
63    x = torch.tensor([b1, b2, b3, b4, b5], requires_grad=True)
64    
65    observation.add(model(x).tolist())
66
67    #determine Jacobian matrix
68    jac = torch.autograd.functional.jacobian(
69        func=model,
70        inputs=x
71    )
72
73    for idx, param in enumerate(design_space):
74        observation.add(jac[:, idx].tolist(), derivative=param["name"])
75    return observation
76
77# Run the minimization
78study.set_evaluator(evaluate)
79study.run()
80best_sample = study.driver.best_sample
81uncertainties = study.driver.uncertainties
82print("Reconstructed parameters:")
83for param in design_space:
84    name = param['name']
85    print(f"  {name} = {best_sample[name]:.3f} +/- {uncertainties[name]:.3f}")
86