diff --git a/README.md b/README.md
index 43e7646b..c531e7ce 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,105 @@ tests/install/test_container_install mdmitry1/python311-dev
Installation instructions for Ubuntu-22.04 can be followed using `homebrew` in place of `apt`
+## Quickstart
+
+### Problem: find minimal distance between point (2,1) and unit circle
+
+
+
+
+ Analytical solution for this problem:
+ `
+f(x*) = 6 - 2√5 ≈ 1.527864`, where `x* = (2/√5,1/√5) ≈ (0.894427, 0.447214)`
+
+ Solution: see `bash` script [quickstart.sh](https://raw.githubusercontent.com/SMLP-Systems/smlp/smlp_quickstart/quickstart/quickstart.sh)
+ The script has 2 steps
+ - Step 1: Create input dataset and visualize the problem
+ - Step 2: Run SMLP
+ SMLP creates polynomial model and finds approximate solution
+ SMLP results
+ `f(x*) = 1.527865, x* = (0.894531, 0.447004)`
+ are within 0.05% accuracy for `f(x*)` and `x*`
+
+Running the script:
+```bash
+smlp_package_path=$(python3.11 -c 'import smlp; from os.path import dirname; print(dirname(smlp.__file__))')
+$smlp_package_path/quickstart/quickstart.sh
+```
+
+
+
+ Test case description
+
+ **1.** *constraint_dora.json* - spec in json format
+
+```
+{
+ "version": "1.2",
+ "variables": [
+ {"label":"X1", "interface":"knob", "type":"real", "range":[-1.5,2.5], "rad-abs": 0.0},
+ {"label":"X2", "interface":"knob", "type":"real", "range":[-1.5,2.0], "rad-abs": 0.0},
+ {"label":"Y1", "interface":"output", "type":"real"}
+ ],
+ "alpha": "X1*X1+X2*X2<=1",
+ "objectives": {"objective1": "-Y1"}
+}
+```
+
+ Legend:
+
+```
+ X1 - first controllable variable
+ X2 - second controlllable variable
+ Y1 - output function
+ rad-abs - sensitivity radius.
+ Zero radius means that solution sensitivity check is skipped
+ alpha - constraint depending on controllable variables
+ objective1 - optimization goal
+```
+
+
+
+ **2.** SMLP command line arguments
+
+ ```
+ -data ${name}.csv.gz # input CSV dataset
+ -spec ${script_path}/${name_lc}.json # JSON spec file
+ -pref ${name} # output file prefix
+ -mode optimize # operation mode
+ -model poly_sklearn # model type
+ -epsilon 0.0000005 # convergence threshold
+```
+
+
+
+### Problem modification in the user area
+
+- Step 1: Copy the problem to the current directory and enter problem work area
+```bash
+smlp_package_path=$(python3.11 -c 'import smlp; from os.path import dirname; print(dirname(smlp.__file__))')
+\cp -rp $smlp_package_path/quickstart .
+cd quickstart
+```
+- Step 2: As an example, change constraint in order to get solution in rational numbers
+ Let's change circle radius to 2/√5, so squared radius will be 4/5
+ In order to do this, edit `constraint_dora.json` file and change right side of the inequality to be 4/5:
+ `"alpha": "X1*X1+X2*X2<=4/5",`
+ [Analytical solution](https://www.wolframalpha.com/input?i=Minimize%3A+f%28x1%2C+x2%29+%3D+%28x1+-+2%29%5E2+%2B+%28x2+-+1%29%5E2+subject+to+x1%5E2+%2B+x2%5E2+-+4%2F5+%3C%3D+0) for modified problem:
+ `
+f(x*) = 9/5 = 1.8`, where `x* = (4/5,2/5) = (0.8, 0.4)`
+- Step 3: Run the script from current directory
+```bash
+./quickstart.sh
+```
+Expected SMLP results are within 0.03% accuracy for `f(x*)` and `x*`:
+```bash
+Working directory: /quickstart/Constraint_dora_results_
+X1 = 0.800048828125
+X2 = 0.3999021053314209
+Y1 = 1.8000002980730385
+```
+
## [Tutorial](https://github.com/SMLP-Systems/smlp/tree/master/tutorial)
- Black-box optimization Eggholder Function
diff --git a/misc/minimal_distance.png b/misc/minimal_distance.png
new file mode 100644
index 00000000..68be010f
Binary files /dev/null and b/misc/minimal_distance.png differ
diff --git a/quickstart/constraint_dora.json b/quickstart/constraint_dora.json
new file mode 100644
index 00000000..3b6f1472
--- /dev/null
+++ b/quickstart/constraint_dora.json
@@ -0,0 +1,12 @@
+{
+ "version": "1.2",
+ "variables": [
+ {"label":"X1", "interface":"knob", "type":"real", "range":[-1.5,2.5], "rad-abs": 0.0},
+ {"label":"X2", "interface":"knob", "type":"real", "range":[-1.5,2.0], "rad-abs": 0.0},
+ {"label":"Y1", "interface":"output", "type":"real"}
+ ],
+ "alpha": "X1*X1+X2*X2<=1",
+ "objectives": {
+ "objective1": "-Y1"
+ }
+}
diff --git a/quickstart/constraint_dora_dataset.py b/quickstart/constraint_dora_dataset.py
new file mode 100755
index 00000000..7248036f
--- /dev/null
+++ b/quickstart/constraint_dora_dataset.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3.11
+"""
+Constrained Optimization Example using SMLP
+Classic Lagrange Multiplier Problem (appears in many optimization textbooks)
+
+Problem:
+ Minimize: f(x1, x2) = (x1 - 2)^2 + (x2 - 1)^2
+ Subject to: x1^2 + x2^2 - 1 <= 0 (inside unit circle)
+
+ Geometrically: Find the point on the unit circle closest to (2, 1)
+
+Analytical Solution (using Lagrange multipliers):
+ Setting ∇f = λ∇g where g(x1,x2) = x1² + x2² - 1:
+ - 2(x1-2) = 2λx1 → x1 = 2/(1+λ)
+ - 2(x2-1) = 2λx2 → x2 = 1/(1+λ)
+ - Substituting into constraint: 5 = (1+λ)²
+ - Solving: λ = √5 - 1
+
+Expected Result:
+ Optimal point: x1 = 2/√5 ≈ 0.894427, x2 = 1/√5 ≈ 0.447214
+ Minimum value: f = (√5 - 1)² ≈ 1.527864
+
+Reference: Wolfram Alpha
+https://www.wolframalpha.com/input?i=Minimize%3A+f%28x1%2C+x2%29+%3D+%28x1+-+2%29%5E2+%2B+%28x2+-+1%29%5E2+subject+to+x1%5E2+%2B+x2%5E2+-+1+%3C%3D+0
+"""
+
+from sys import argv
+from numpy import linspace, meshgrid, cos, sin, pi, sqrt, inf
+from pandas import read_csv, concat
+from gzip import open as gzopen
+from matplotlib import pyplot as plt
+
+def main():
+# Initial guess
+ rng = range(0, 1000)
+ x1_start, x1_stop = (-1.5, 2.5)
+ x2_start, x2_stop = (-1.5, 2.0)
+ x1 = linspace(x1_start, x1_stop, rng.stop)
+ x2 = linspace(x2_start, x2_stop, rng.stop)
+ X1, X2 = meshgrid(x1, x2)
+ Z = (X1 - 2)**2 + (X2 - 1)**2
+ with gzopen('Constraint_dora.csv.gz',"wt") as ds:
+ ds.write("X1,X2,Y1\n")
+ [[ds.write(f"{X1[i][j]},{X2[i][j]},{Z[i][j]}\n") for j in rng] for i in rng]
+
+ # Visualization
+ fig, ax = plt.subplots(figsize=(10, 8))
+
+ # Plot contours of objective function
+ contours = ax.contour(X1, X2, Z, levels=20, cmap='viridis', alpha=0.6)
+ ax.clabel(contours, inline=True, fontsize=8)
+
+# Plot constraint boundary (unit circle)
+ theta = linspace(0, 2*pi, 100)
+ circle_x = cos(theta)
+ circle_y = sin(theta)
+ ax.plot(circle_x, circle_y, 'r-', linewidth=2, label='Constraint boundary')
+ ax.fill(circle_x, circle_y, alpha=0.1, color='red', label='Feasible region')
+
+ # Plot unconstrained optimum
+ ax.plot(2, 1, 'bs', markersize=12, label='Unconstrained optimum (2, 1)')
+
+ ax.set_xlabel('x1', fontsize=12)
+ ax.set_ylabel('x2', fontsize=12)
+ ax.set_title('Constrained Optimization: Minimize f(x1,x2) subject to x1² + x2² ≤ 1',
+ fontsize=14)
+ # Plot constrained optimum
+ ax.plot(2/sqrt(5), 1/sqrt(5), 'go', markersize=12, label=f'Constrained optimum ({2/sqrt(5):.3f}, {1/sqrt(5):.3f})')
+ ax.legend(loc='upper right')
+ ax.grid(True, alpha=0.3)
+ ax.axis('equal')
+ ax.set_ylim(-1.5, 2.0)
+
+ plt.tight_layout()
+ timeout = inf
+ if len(argv) > 2:
+ if '-timeout' == argv[1]:
+ timeout = int(argv[2])
+ if not inf == timeout:
+ timer = fig.canvas.new_timer(interval=timeout*1000, callbacks=[(plt.close, [], {})])
+ timer.start()
+ plt.show()
+
+if __name__ == "__main__":
+ main()
diff --git a/quickstart/constraint_dora_poly_optimization_results_expected.txt b/quickstart/constraint_dora_poly_optimization_results_expected.txt
new file mode 100644
index 00000000..17dc3ca3
--- /dev/null
+++ b/quickstart/constraint_dora_poly_optimization_results_expected.txt
@@ -0,0 +1,3 @@
+X1 = 0.89453125
+X2 = 0.4470043182373047
+Y1 = 1.5278653812779421
diff --git a/quickstart/quickstart.sh b/quickstart/quickstart.sh
new file mode 100755
index 00000000..44778722
--- /dev/null
+++ b/quickstart/quickstart.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+script_path="$(dirname "$(realpath "$0")")"
+name=Constraint_dora
+if [[ $# -gt 0 ]]; then
+ if [[ "-clean" == "$1" ]]; then
+ rm -rf ${name}_results_* 2>/dev/null
+ exit 0
+ fi
+fi
+results_dir=${name}_results_$(date +%s)
+rm -rf $results_dir 2>/dev/null
+mkdir -p $results_dir
+echo "Working directory: $(realpath $results_dir)"
+cd $results_dir
+log=${name}.log
+dataset=${name}.csv.gz
+name_lc="$(echo "$name" | tr '[:upper:]' '[:lower:]')"
+"${script_path}/${name_lc}_dataset.py" #Create dataset and visualize the problem
+results=${name}_poly_optimization_results.txt
+rm -f "$results" 2>/dev/null
+smlp_args=(
+ -data ${name}.csv.gz # input CSV dataset
+ -spec ${script_path}/${name_lc}.json # JSON spec file
+ -pref ${name} # output file prefix
+ -mode optimize # operation mode
+ -model poly_sklearn # model type
+ -epsilon 0.0000005 # convergence threshold
+)
+
+smlp "${smlp_args[@]}" >"$log" 2>&1
+for var in X1 X2 Y1; do
+ echo "$var = $(jq ".${var}.value_in_config" ${name}_${name}_optimization_results.json)" 2>&1 | tee -a "$results"
+done
diff --git a/setup.py b/setup.py
index c7bed053..6efee577 100644
--- a/setup.py
+++ b/setup.py
@@ -780,25 +780,29 @@ def run(self):
shutil.copytree(str(installed_pkg), str(dest))
print(f"[smlp build] smlp extension copied to wheel at {dest}")
- # 5. Copy Python source from src/smlp_py into smlp/smlp_py inside the wheel
- smlp_py_src = REPO_ROOT / "src" / "smlp_py"
- if smlp_py_src.is_dir():
- smlp_py_dest = dest / "smlp_py" # dest is already smlp/
- if smlp_py_dest.exists():
- shutil.rmtree(smlp_py_dest)
- shutil.copytree(str(smlp_py_src), str(smlp_py_dest))
- print(f"[smlp build] smlp_py source copied to wheel at {smlp_py_dest}")
- else:
- print(f"[smlp build] WARNING: src/smlp_py not found at {smlp_py_src}, skipping.")
-
- # 6. Copy src/run_smlp.py into smlp/ inside the wheel
- run_smlp_src = REPO_ROOT / "src" / "run_smlp.py"
- if run_smlp_src.is_file():
- shutil.copy2(str(run_smlp_src), str(dest / "run_smlp.py"))
- print(f"[smlp build] run_smlp.py copied to wheel at {dest / 'run_smlp.py'}")
- else:
- print(f"[smlp build] WARNING: src/run_smlp.py not found at {run_smlp_src}, skipping.")
-
+ # 5. Copy Python sources from src/ into smlp/ inside the wheel
+ sources = [
+ (REPO_ROOT / "src" / "smlp_py", dest / "smlp_py", "dir"),
+ (REPO_ROOT / "src" / "__init__.py", dest / "__init__.py", "file"),
+ (REPO_ROOT / "src" / "run_smlp.py", dest / "run_smlp.py", "file"),
+ (REPO_ROOT / "quickstart", dest / "quickstart", "dir"),
+ ]
+ for src, dst, kind in sources:
+ copy_success = False
+ if kind == "dir":
+ if src.is_dir():
+ if dst.exists():
+ shutil.rmtree(str(dst))
+ shutil.copytree(str(src), str(dst))
+ copy_success = True
+ else:
+ if src.is_file():
+ shutil.copy2(str(src), str(dst))
+ copy_success = True
+ if copy_success:
+ print(f"[smlp build] {src} copied to wheel at {dst}")
+ else:
+ print(f"[smlp build] WARNING: source is not found at {src}, skipping.")
# ---------------------------------------------------------------------------
# setup()
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 00000000..fe8594d4
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: Apache-2.0
+# This file is part of smlp.