Tensor Product Function

The tensor product function is defined as:

$f(x) = \prod_{i=1}^{d} \cos(a\pi x_i)$

where:

  • (d): Represents the dimensionality of the input vector (x).
  • (x_i): Represents the (i)-th component of the input vector.
  • (a): A constant parameter.

Package Imports

using Surrogates
using SurrogatesRandomForest
using Plots
using Statistics
using PrettyTables
using BenchmarkTools

## Python SMT
using PythonCall
using CondaPkg
smt = pyimport("smt.surrogate_models")
np = pyimport("numpy")
Transaction

  Prefix: /cache/build/exclusive-amdci3-0/julialang/scimlbenchmarks-dot-jl/
benchmarks/Surrogates/.CondaPkg/env

  Updating specs:

   - libstdcxx-ng[version='>=3.4,<13.0']
   - numpy=*
   - conda-forge::python[version='>=3.7,<4',build=*cpython*]
   - smt=*


  Package              Version  Build                Channel           Size
───────────────────────────────────────────────────────────────────────────
──
  Install:
───────────────────────────────────────────────────────────────────────────
──

  + libstdcxx-ng        12.3.0  h0f45ef3_5           conda-forge        3MB
  + _libgcc_mutex          0.1  conda_forge          conda-forge     Cached
  + python_abi            3.11  4_cp311              conda-forge        6kB
  + ld_impl_linux-64      2.40  h41732ed_0           conda-forge     Cached
  + ca-certificates   2024.2.2  hbcca054_0           conda-forge      155kB
  + libgomp             13.2.0  h807b86a_5           conda-forge      420kB
  + _openmp_mutex          4.5  2_gnu                conda-forge     Cached
  + libgcc-ng           13.2.0  h807b86a_5           conda-forge      771kB
  + xorg-libxdmcp        1.1.3  h7f98852_0           conda-forge       19kB
  + pthread-stubs          0.4  h36c2ea0_1001        conda-forge        6kB
  + libbrotlicommon      1.1.0  hd590300_1           conda-forge       69kB
  + libdeflate            1.19  hd590300_0           conda-forge       67kB
  + lerc                 4.0.0  h27087fc_0           conda-forge      282kB
  + libjpeg-turbo        3.0.0  hd590300_1           conda-forge      619kB
  + xorg-libxau         1.0.11  hd590300_0           conda-forge       14kB
  + libwebp-base         1.3.2  hd590300_0           conda-forge      402kB
  + openssl              3.2.1  hd590300_0           conda-forge        3MB
  + libxcrypt           4.4.36  hd590300_1           conda-forge     Cached
  + libzlib             1.2.13  hd590300_5           conda-forge     Cached
  + libffi               3.4.2  h7f98852_5           conda-forge     Cached
  + bzip2                1.0.8  hd590300_5           conda-forge     Cached
  + ncurses                6.4  h59595ed_2           conda-forge     Cached
  + libgfortran5        13.2.0  ha4646dd_5           conda-forge        1MB
  + libuuid             2.38.1  h0b41bf4_0           conda-forge     Cached
  + libnsl               2.0.1  hd590300_0           conda-forge     Cached
  + libexpat             2.5.0  hcb278e6_1           conda-forge     Cached
  + xz                   5.2.6  h166bdaf_0           conda-forge     Cached
  + libbrotlienc         1.1.0  hd590300_1           conda-forge      283kB
  + libbrotlidec         1.1.0  hd590300_1           conda-forge       33kB
  + libxcb                1.15  h0b41bf4_0           conda-forge      384kB
  + libpng              1.6.42  h2797004_0           conda-forge      289kB
  + zstd                 1.5.5  hfc55251_0           conda-forge      545kB
  + tk                  8.6.13  noxft_h4845f30_101   conda-forge     Cached
  + libsqlite           3.45.1  h2797004_0           conda-forge      859kB
  + readline               8.2  h8228510_1           conda-forge     Cached
  + libgfortran-ng      13.2.0  h69a702a_5           conda-forge       24kB
  + brotli-bin           1.1.0  hd590300_1           conda-forge       19kB
  + freetype            2.12.1  h267a509_2           conda-forge      635kB
  + libtiff              4.6.0  ha9c0a0a_2           conda-forge      283kB
  + libopenblas         0.3.26  pthreads_h413a1c8_0  conda-forge        6MB
  + brotli               1.1.0  hd590300_1           conda-forge       19kB
  + openjpeg             2.5.0  h488ebb8_3           conda-forge      357kB
  + lcms2                 2.16  hb7c19ff_0           conda-forge      245kB
  + libblas              3.9.0  21_linux64_openblas  conda-forge       15kB
  + libcblas             3.9.0  21_linux64_openblas  conda-forge       15kB
  + liblapack            3.9.0  21_linux64_openblas  conda-forge       15kB
  + tzdata               2024a  h0c530f3_0           conda-forge      120kB
  + python              3.11.7  hab00c5b_1_cpython   conda-forge       31MB
  + wheel               0.42.0  pyhd8ed1ab_0         conda-forge     Cached
  + setuptools          69.0.3  pyhd8ed1ab_0         conda-forge     Cached
  + pip                   24.0  pyhd8ed1ab_0         conda-forge        1MB
  + six                 1.16.0  pyh6c4a22f_0         conda-forge       14kB
  + munkres              1.1.4  pyh9f0ad1d_0         conda-forge       12kB
  + pyparsing            3.1.1  pyhd8ed1ab_0         conda-forge       90kB
  + packaging             23.2  pyhd8ed1ab_0         conda-forge       49kB
  + cycler              0.12.1  pyhd8ed1ab_0         conda-forge       13kB
  + certifi           2024.2.2  pyhd8ed1ab_0         conda-forge      161kB
  + threadpoolctl        3.2.0  pyha21a80b_0         conda-forge       21kB
  + joblib               1.3.2  pyhd8ed1ab_0         conda-forge      221kB
  + python-dateutil      2.8.2  pyhd8ed1ab_0         conda-forge      246kB
  + pillow              10.2.0  py311ha6c5da5_0      conda-forge       42MB
  + kiwisolver           1.4.5  py311h9547e67_1      conda-forge       73kB
  + numpy               1.26.4  py311h64a7726_0      conda-forge        8MB
  + fonttools           4.48.1  py311h459d7ec_0      conda-forge        3MB
  + contourpy            1.2.0  py311h9547e67_0      conda-forge      256kB
  + scipy               1.12.0  py311h64a7726_2      conda-forge       17MB
  + matplotlib-base      3.8.2  py311h54ef318_0      conda-forge        8MB
  + scikit-learn         1.4.0  py311hc009520_0      conda-forge       10MB
  + pydoe3               1.0.1  pyhd8ed1ab_0         conda-forge       24kB
  + smt                  2.3.0  py311h320fe9a_1      conda-forge      407kB

  Summary:

  Install: 70 packages

  Total download: 142MB

───────────────────────────────────────────────────────────────────────────
──



Transaction starting
Linking libstdcxx-ng-12.3.0-h0f45ef3_5
Linking _libgcc_mutex-0.1-conda_forge
Linking python_abi-3.11-4_cp311
Linking ld_impl_linux-64-2.40-h41732ed_0
Linking ca-certificates-2024.2.2-hbcca054_0
Linking libgomp-13.2.0-h807b86a_5
Linking _openmp_mutex-4.5-2_gnu
Linking libgcc-ng-13.2.0-h807b86a_5
Linking xorg-libxdmcp-1.1.3-h7f98852_0
Linking pthread-stubs-0.4-h36c2ea0_1001
Linking libbrotlicommon-1.1.0-hd590300_1
Linking libdeflate-1.19-hd590300_0
Linking lerc-4.0.0-h27087fc_0
Linking libjpeg-turbo-3.0.0-hd590300_1
Linking xorg-libxau-1.0.11-hd590300_0
Linking libwebp-base-1.3.2-hd590300_0
Linking openssl-3.2.1-hd590300_0
Linking libxcrypt-4.4.36-hd590300_1
Linking libzlib-1.2.13-hd590300_5
Linking libffi-3.4.2-h7f98852_5
Linking bzip2-1.0.8-hd590300_5
Linking ncurses-6.4-h59595ed_2
Linking libgfortran5-13.2.0-ha4646dd_5
Linking libuuid-2.38.1-h0b41bf4_0
Linking libnsl-2.0.1-hd590300_0
Linking libexpat-2.5.0-hcb278e6_1
Linking xz-5.2.6-h166bdaf_0
Linking libbrotlienc-1.1.0-hd590300_1
Linking libbrotlidec-1.1.0-hd590300_1
Linking libxcb-1.15-h0b41bf4_0
Linking libpng-1.6.42-h2797004_0
Linking zstd-1.5.5-hfc55251_0
Linking tk-8.6.13-noxft_h4845f30_101
Linking libsqlite-3.45.1-h2797004_0
Linking readline-8.2-h8228510_1
Linking libgfortran-ng-13.2.0-h69a702a_5
Linking brotli-bin-1.1.0-hd590300_1
Linking freetype-2.12.1-h267a509_2
Linking libtiff-4.6.0-ha9c0a0a_2
Linking libopenblas-0.3.26-pthreads_h413a1c8_0
Linking brotli-1.1.0-hd590300_1
Linking openjpeg-2.5.0-h488ebb8_3
Linking lcms2-2.16-hb7c19ff_0
Linking libblas-3.9.0-21_linux64_openblas
Linking libcblas-3.9.0-21_linux64_openblas
Linking liblapack-3.9.0-21_linux64_openblas
Linking tzdata-2024a-h0c530f3_0
Linking python-3.11.7-hab00c5b_1_cpython
Linking wheel-0.42.0-pyhd8ed1ab_0
Linking setuptools-69.0.3-pyhd8ed1ab_0
Linking pip-24.0-pyhd8ed1ab_0
Linking six-1.16.0-pyh6c4a22f_0
Linking munkres-1.1.4-pyh9f0ad1d_0
Linking pyparsing-3.1.1-pyhd8ed1ab_0
Linking packaging-23.2-pyhd8ed1ab_0
Linking cycler-0.12.1-pyhd8ed1ab_0
Linking certifi-2024.2.2-pyhd8ed1ab_0
Linking threadpoolctl-3.2.0-pyha21a80b_0
Linking joblib-1.3.2-pyhd8ed1ab_0
Linking python-dateutil-2.8.2-pyhd8ed1ab_0
Linking pillow-10.2.0-py311ha6c5da5_0
Linking kiwisolver-1.4.5-py311h9547e67_1
Linking numpy-1.26.4-py311h64a7726_0
Linking fonttools-4.48.1-py311h459d7ec_0
Linking contourpy-1.2.0-py311h9547e67_0
Linking scipy-1.12.0-py311h64a7726_2
Linking matplotlib-base-3.8.2-py311h54ef318_0
Linking scikit-learn-1.4.0-py311hc009520_0
Linking pydoe3-1.0.1-pyhd8ed1ab_0
Linking smt-2.3.0-py311h320fe9a_1

Transaction finished

To activate this environment, use:

    micromamba activate /cache/build/exclusive-amdci3-0/julialang/scimlbenc
hmarks-dot-jl/benchmarks/Surrogates/.CondaPkg/env

Or to execute a single command in this environment, use:

    micromamba run -p /cache/build/exclusive-amdci3-0/julialang/scimlbenchm
arks-dot-jl/benchmarks/Surrogates/.CondaPkg/env mycommand

Python: <module 'numpy' from '/cache/build/exclusive-amdci3-0/julialang/sci
mlbenchmarks-dot-jl/benchmarks/Surrogates/.CondaPkg/env/lib/python3.11/site
-packages/numpy/__init__.py'>

Define the function

function tensor_product_function(x)
    a = 0.5
    return prod(cos.(a * π * x))
end
tensor_product_function (generic function with 1 method)

Define parameters for training and test data

lb = -5.0 # Lower bound of sampling range
ub = 5.0  # Upper bound of sampling range
n_train = 100 # Number of training points
n_test = 150 # Number of testing points
150

Sample training and test data points

x_train = sample(n_train, lb, ub, SobolSample())  # Sample training data points
y_train = tensor_product_function.(x_train)  # Calculate corresponding function values
x_test = sample(n_test, lb, ub, RandomSample())  # Sample larger test data set
y_test = tensor_product_function.(x_test)  # Calculate corresponding true function values
150-element Vector{Float64}:
  0.606472051232782
  0.8445649479493853
  0.9131793130470989
 -0.028393077060722024
 -0.3293969012812371
  0.9669155136758031
 -0.9221683292020114
 -0.9885137048149184
 -0.29596163748466425
  0.8648440331841805
  ⋮
 -0.9322612730628108
 -0.9196897406947262
  0.9354958997878758
 -0.8524139987629917
  0.3335106580561711
 -0.8514051030428336
  0.8603290525250558
  0.022396723288478432
 -0.1804008609222902

Plot training and test points

scatter(x_train, y_train, label="Training Points", xlabel="X-axis", ylabel="Y-axis", legend=:topright)
scatter!(x_test, y_test, label="Testing Points")

Fit surrogate models

# SMT
radial_surrogate_smt = smt.RBF(d0=1.0, poly_degree=1.0, print_global=Py(false))
radial_surrogate_smt.set_training_values(np.array(x_train), np.array(y_train))
radial_surrogate_smt.train()

theta = 0.5 / max(1e-6 * abs(ub - lb), std(x_train))^2.0
kriging_surrogate_smt = smt.KRG(theta0=np.array([theta]), poly="quadratic", print_global=Py(false))
kriging_surrogate_smt.set_training_values(np.array(x_train), np.array(y_train))
kriging_surrogate_smt.train()

# Julia
randomforest_surrogate = RandomForestSurrogate(x_train, y_train, lb, ub, num_round = 10)
radial_surrogate = RadialBasis(x_train, y_train, lb, ub)
kriging_surrogate = Kriging(x_train, y_train, lb, ub)
loba_surrogate = LobachevskySurrogate(x_train, y_train, lb, ub, alpha = 2.0, n = 6)
(::Surrogates.LobachevskySurrogate{Vector{Float64}, Vector{Float64}, Float6
4, Int64, Float64, Float64, Vector{Float64}, Bool}) (generic function with 
2 methods)

Predictions on training and test data

## Training data
radial_train_pred_smt = radial_surrogate_smt.predict_values(np.array(x_train))
radial_train_pred_smt = pyconvert(Matrix{Float64}, radial_train_pred_smt)[:, 1]
kriging_train_pred_smt = kriging_surrogate_smt.predict_values(np.array(x_train))
kriging_train_pred_smt = pyconvert(Matrix{Float64}, kriging_train_pred_smt)[:, 1]
random_forest_train_pred = randomforest_surrogate.(x_train)
radial_train_pred = radial_surrogate.(x_train)
kriging_train_pred = kriging_surrogate.(x_train)
loba_train_pred = loba_surrogate.(x_train)

## Test data
radial_test_pred_smt = radial_surrogate_smt.predict_values(np.array(x_test))
radial_test_pred_smt = pyconvert(Matrix{Float64}, radial_test_pred_smt)[:, 1]
kriging_test_pred_smt = kriging_surrogate_smt.predict_values(np.array(x_test))
kriging_test_pred_smt = pyconvert(Matrix{Float64}, kriging_test_pred_smt)[:, 1]
random_forest_test_pred = randomforest_surrogate.(x_test)
radial_test_pred = radial_surrogate.(x_test)
kriging_test_pred = kriging_surrogate.(x_test)
loba_test_pred = loba_surrogate.(x_test)
150-element Vector{Float64}:
  0.6064854155242003
  0.8445809069866606
  0.9131776378609109
 -0.028394296598705394
 -0.329412569853211
  0.9669139233465368
 -0.9221601919602044
 -0.9885160746667232
 -0.29596807024134275
  0.864845560831596
  ⋮
 -0.9322425791311004
 -0.9196968503484708
  0.9355000237327307
 -0.8523880646904418
  0.3335066218647656
 -0.8513857339829656
  0.8603690252830117
  0.024292827885731682
 -0.18039978911447344

Define the MSE function

function calculate_mse(predictions, true_values)
    return mean((predictions .- true_values).^2)  # Calculate mean of squared errors
end
calculate_mse (generic function with 1 method)

Calculate MSE for the models

## Training MSE
mse_radial_train_smt = calculate_mse(radial_train_pred_smt, y_train)
mse_krig_train_smt = calculate_mse(kriging_train_pred_smt, y_train)
mse_rf_train = calculate_mse(random_forest_train_pred, y_train)
mse_radial_train = calculate_mse(radial_train_pred, y_train)
mse_krig_train = calculate_mse(kriging_train_pred, y_train)
mse_loba_train = calculate_mse(loba_train_pred, y_train)

## Test MSE
mse_radial_test_smt = calculate_mse(radial_test_pred_smt, y_test)
mse_krig_test_smt = calculate_mse(kriging_test_pred_smt, y_test)
mse_rf_test = calculate_mse(random_forest_test_pred, y_test)
mse_radial_test = calculate_mse(radial_test_pred, y_test)
mse_krig_test = calculate_mse(kriging_test_pred, y_test)
mse_loba_test = calculate_mse(loba_test_pred, y_test)
5.888472293149944e-8

Compare MSE

models = ["Random Forest", "Radial Basis", "Kriging", "Lobachevsky", "Radial Basis (SMT)", "Kriging (SMT)"]
train_mses = [mse_rf_train, mse_radial_train, mse_krig_train, mse_loba_train, mse_radial_train_smt, mse_krig_train_smt]
test_mses = [mse_rf_test, mse_radial_test, mse_krig_test, mse_loba_test, mse_radial_test_smt, mse_krig_test_smt]
mses = sort(collect(zip(test_mses, train_mses, models)))
pretty_table(hcat(getindex.(mses, 3), getindex.(mses, 2), getindex.(mses, 1)), header=["Model", "Training MSE", "Test MSE"])
┌────────────────────┬──────────────┬─────────────┐
│              Model │ Training MSE │    Test MSE │
├────────────────────┼──────────────┼─────────────┤
│      Kriging (SMT) │  3.60838e-18 │ 6.52071e-18 │
│ Radial Basis (SMT) │  2.34051e-14 │ 4.58259e-12 │
│        Lobachevsky │   3.7375e-19 │  5.88847e-8 │
│            Kriging │   1.16824e-5 │  1.61868e-5 │
│       Radial Basis │  1.74579e-30 │ 0.000201267 │
│      Random Forest │   0.00176894 │  0.00880474 │
└────────────────────┴──────────────┴─────────────┘

Plot predictions

xs = -5:0.01:5

radial_pred_smt = radial_surrogate_smt.predict_values(np.array(xs))
radial_pred_smt = pyconvert(Matrix{Float64}, radial_pred_smt)[:, 1]
kriging_pred_smt = kriging_surrogate_smt.predict_values(np.array(xs))
kriging_pred_smt = pyconvert(Matrix{Float64}, kriging_pred_smt)[:, 1]

plot(xs, radial_pred_smt, label="Radial Basis (SMT)", legend=:top, color=:cyan)
plot!(xs, kriging_surrogate.(xs), label="Kriging (SMT)", legend=:top, color=:magenta)
plot!(xs, tensor_product_function.(xs), label="True function", legend=:top, color=:black)
plot!(xs, randomforest_surrogate.(xs), label="Random Forest", legend=:top, color=:green)
plot!(xs, radial_surrogate.(xs), label="Radial Basis", legend=:top, color=:red)
plot!(xs, kriging_surrogate.(xs), label="Kriging", legend=:top, color=:blue)
plot!(xs, loba_surrogate.(xs), label="Lobachevsky", legend=:top, color=:purple)

Time evaluation

time_original = @belapsed tensor_product_function.(x_test)
time_radial_smt = @belapsed radial_surrogate_smt.predict_values(np.array(x_test))
time_krig_smt = @belapsed kriging_surrogate_smt.predict_values(np.array(x_test))
time_rf = @belapsed randomforest_surrogate.(x_test)
time_radial = @belapsed radial_surrogate.(x_test)
time_krig = @belapsed kriging_surrogate.(x_test)
time_loba = @belapsed loba_surrogate.(x_test)
0.001347901

Compare time performance

times = ["Random Forest" => time_rf, "Radial Basis" => time_radial, "Kriging" => time_krig, "Lobachevsky" => time_loba, "Radial Basis (SMT)" => time_radial_smt, "Kriging (SMT)" => time_krig_smt, "Original Function" => time_original]
sorted_times = sort(times, by=x->x[2])
pretty_table(hcat(first.(sorted_times), last.(sorted_times)), header=["Model", "Time(s)"])
┌────────────────────┬─────────────┐
│              Model │     Time(s) │
├────────────────────┼─────────────┤
│  Original Function │    1.549e-6 │
│            Kriging │ 0.000336707 │
│ Radial Basis (SMT) │ 0.000357148 │
│        Lobachevsky │   0.0013479 │
│       Radial Basis │  0.00188254 │
│      Random Forest │  0.00408928 │
│      Kriging (SMT) │   0.0322772 │
└────────────────────┴─────────────┘