Optiscope Analysis Examples¶
This notebook demonstrates the analysis capabilities of the optiscope library, including:
- Basic Analysis (Pareto Front, Feasibility)
- Knee Point Detection
- TOPSIS Multi-Criteria Decision Making
- Sensitivity Analysis
In [1]:
Copied!
import numpy as np
import pandas as pd
from optiscope import OptimizationResult, ProblemMetadata
from optiscope.analysis import (
detect_knee_points,
find_knee_region,
sensitivity_analysis_topsis,
topsis_from_result,
)
from optiscope.core import DesignVariable, Objective, OptimizationDirection
from optiscope.plotting import plot_pareto_front_2d
import numpy as np
import pandas as pd
from optiscope import OptimizationResult, ProblemMetadata
from optiscope.analysis import (
detect_knee_points,
find_knee_region,
sensitivity_analysis_topsis,
topsis_from_result,
)
from optiscope.core import DesignVariable, Objective, OptimizationDirection
from optiscope.plotting import plot_pareto_front_2d
Data Generation¶
We generate a synthetic multi-objective optimization problem.
In [2]:
Copied!
def generate_test_problem(n_points: int = 200, seed: int = 42):
"""Generate synthetic multi-objective optimization problem."""
np.random.seed(seed)
# Design variables
x1 = np.random.uniform(0, 10, n_points)
x2 = np.random.uniform(0, 10, n_points)
x3 = np.random.uniform(0, 10, n_points)
x4 = np.random.uniform(0, 10, n_points)
# Objectives (ZDT-like with 3 objectives)
f1 = x1
g = 1 + 9 * (x2 + x3 + x4) / 3
h1 = 1 - np.sqrt(f1 / g)
h2 = 1 - (f1 / g) ** 2
f2 = g * h1
f3 = g * h2
# Add some noise
f2 += np.random.normal(0, 0.1, n_points)
f3 += np.random.normal(0, 0.1, n_points)
# Constraints
g1 = x1 + x2 - 12 # inequality
h1_const = x3 - x4 # equality
# Create result
result = OptimizationResult(
problem_metadata=ProblemMetadata(
name="3-Objective Test Problem",
solver="Random Sampling",
n_design_variables=4,
n_objectives=3,
n_inequality_constraints=1,
n_equality_constraints=1,
n_evaluations=n_points,
),
design_variables=pd.DataFrame(
{"x1_width": x1, "x2_height": x2, "x3_depth": x3, "x4_thickness": x4}
),
objectives=pd.DataFrame({"f1_cost": f1, "f2_weight": f2, "f3_volume": f3}),
inequality_constraints=pd.DataFrame({"g1_size": g1}),
equality_constraints=pd.DataFrame({"h1_balance": h1_const}),
)
# Add metadata
result.add_variable_metadata(
DesignVariable(name="x1_width", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x2_height", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x3_depth", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x4_thickness", units="mm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
Objective(name="f1_cost", units="$", direction=OptimizationDirection.MINIMIZE)
)
result.add_variable_metadata(
Objective(name="f2_weight", units="kg", direction=OptimizationDirection.MINIMIZE)
)
result.add_variable_metadata(
Objective(name="f3_volume", units="cm³", direction=OptimizationDirection.MINIMIZE)
)
return result
print("Generating test problem...")
result = generate_test_problem(n_points=200)
print(f"Created problem: {result.problem_metadata.name}")
def generate_test_problem(n_points: int = 200, seed: int = 42):
"""Generate synthetic multi-objective optimization problem."""
np.random.seed(seed)
# Design variables
x1 = np.random.uniform(0, 10, n_points)
x2 = np.random.uniform(0, 10, n_points)
x3 = np.random.uniform(0, 10, n_points)
x4 = np.random.uniform(0, 10, n_points)
# Objectives (ZDT-like with 3 objectives)
f1 = x1
g = 1 + 9 * (x2 + x3 + x4) / 3
h1 = 1 - np.sqrt(f1 / g)
h2 = 1 - (f1 / g) ** 2
f2 = g * h1
f3 = g * h2
# Add some noise
f2 += np.random.normal(0, 0.1, n_points)
f3 += np.random.normal(0, 0.1, n_points)
# Constraints
g1 = x1 + x2 - 12 # inequality
h1_const = x3 - x4 # equality
# Create result
result = OptimizationResult(
problem_metadata=ProblemMetadata(
name="3-Objective Test Problem",
solver="Random Sampling",
n_design_variables=4,
n_objectives=3,
n_inequality_constraints=1,
n_equality_constraints=1,
n_evaluations=n_points,
),
design_variables=pd.DataFrame(
{"x1_width": x1, "x2_height": x2, "x3_depth": x3, "x4_thickness": x4}
),
objectives=pd.DataFrame({"f1_cost": f1, "f2_weight": f2, "f3_volume": f3}),
inequality_constraints=pd.DataFrame({"g1_size": g1}),
equality_constraints=pd.DataFrame({"h1_balance": h1_const}),
)
# Add metadata
result.add_variable_metadata(
DesignVariable(name="x1_width", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x2_height", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x3_depth", units="cm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
DesignVariable(name="x4_thickness", units="mm", lower_bound=0, upper_bound=10)
)
result.add_variable_metadata(
Objective(name="f1_cost", units="$", direction=OptimizationDirection.MINIMIZE)
)
result.add_variable_metadata(
Objective(name="f2_weight", units="kg", direction=OptimizationDirection.MINIMIZE)
)
result.add_variable_metadata(
Objective(name="f3_volume", units="cm³", direction=OptimizationDirection.MINIMIZE)
)
return result
print("Generating test problem...")
result = generate_test_problem(n_points=200)
print(f"Created problem: {result.problem_metadata.name}")
Generating test problem... Created problem: 3-Objective Test Problem
1. Basic Analysis¶
Identify the Pareto front and check for feasibility.
In [3]:
Copied!
# Find Pareto front
pareto_set = result.find_pareto_front()
print(f"Pareto front: {len(pareto_set)} solutions")
# Check feasibility
feasible_set = result.check_feasibility()
print(f"Feasible solutions: {len(feasible_set)}")
# Feasible Pareto front
feasible_pareto_set = result.find_pareto_front(set_name="feasible_pareto", from_set="feasible")
print(f"Feasible Pareto front: {len(feasible_pareto_set)} solutions")
# Find Pareto front
pareto_set = result.find_pareto_front()
print(f"Pareto front: {len(pareto_set)} solutions")
# Check feasibility
feasible_set = result.check_feasibility()
print(f"Feasible solutions: {len(feasible_set)}")
# Feasible Pareto front
feasible_pareto_set = result.find_pareto_front(set_name="feasible_pareto", from_set="feasible")
print(f"Feasible Pareto front: {len(feasible_pareto_set)} solutions")
Pareto front: 7 solutions Feasible solutions: 200 Feasible Pareto front: 7 solutions
2. Knee Point Detection¶
Identify knee points on the Pareto front, which represent good trade-offs.
In [4]:
Copied!
# Use feasible Pareto front for knee detection
pareto_indices = feasible_pareto_set.indices
pareto_objectives = result.objectives.iloc[pareto_indices][["f1_cost", "f2_weight"]]
# Detect knee points
knee_indices_relative = detect_knee_points(pareto_objectives, method="angle", n_knees=3)
knee_indices = [pareto_indices[i] for i in knee_indices_relative]
result.create_set(
name="knee_points", indices=knee_indices, created_by="knee_detection", color="#FF6B6B"
)
print(f"Detected {len(knee_indices)} knee points")
# Find knee region
region_indices, _, _ = find_knee_region(pareto_objectives, width=0.15, method="angle")
original_region_idx = [pareto_indices[i] for i in region_indices]
result.create_set(
name="knee_region", indices=original_region_idx, created_by="knee_region", color="#FFD93D"
)
print(f"Knee region contains {len(region_indices)} points")
# Visualize
fig = plot_pareto_front_2d(
result,
obj_x="f1_cost",
obj_y="f2_weight",
pareto_set="feasible_pareto",
additional_sets=["knee_points", "knee_region"],
show_dominated=True,
)
fig.show()
# Use feasible Pareto front for knee detection
pareto_indices = feasible_pareto_set.indices
pareto_objectives = result.objectives.iloc[pareto_indices][["f1_cost", "f2_weight"]]
# Detect knee points
knee_indices_relative = detect_knee_points(pareto_objectives, method="angle", n_knees=3)
knee_indices = [pareto_indices[i] for i in knee_indices_relative]
result.create_set(
name="knee_points", indices=knee_indices, created_by="knee_detection", color="#FF6B6B"
)
print(f"Detected {len(knee_indices)} knee points")
# Find knee region
region_indices, _, _ = find_knee_region(pareto_objectives, width=0.15, method="angle")
original_region_idx = [pareto_indices[i] for i in region_indices]
result.create_set(
name="knee_region", indices=original_region_idx, created_by="knee_region", color="#FFD93D"
)
print(f"Knee region contains {len(region_indices)} points")
# Visualize
fig = plot_pareto_front_2d(
result,
obj_x="f1_cost",
obj_y="f2_weight",
pareto_set="feasible_pareto",
additional_sets=["knee_points", "knee_region"],
show_dominated=True,
)
fig.show()
Detected 3 knee points Knee region contains 1 points
3. TOPSIS Analysis¶
Rank solutions using the TOPSIS method with different weight scenarios.
In [5]:
Copied!
# Equal weights
topsis_equal = topsis_from_result(result, weights=None, return_details=True)
print("Top 5 solutions (Equal Weights):")
print(topsis_equal["scores"][:5]) # .sort_values(ascending=False).head(5))
# Cost-focused weights
topsis_cost = topsis_from_result(
result, weights={"f1_cost": 0.6, "f2_weight": 0.25, "f3_volume": 0.15}, return_details=True
)
print("\nTop 5 solutions (Cost Focused):")
print(topsis_cost["scores"][:5]) # .sort_values(ascending=False).head(5))
# Equal weights
topsis_equal = topsis_from_result(result, weights=None, return_details=True)
print("Top 5 solutions (Equal Weights):")
print(topsis_equal["scores"][:5]) # .sort_values(ascending=False).head(5))
# Cost-focused weights
topsis_cost = topsis_from_result(
result, weights={"f1_cost": 0.6, "f2_weight": 0.25, "f3_volume": 0.15}, return_details=True
)
print("\nTop 5 solutions (Cost Focused):")
print(topsis_cost["scores"][:5]) # .sort_values(ascending=False).head(5))
Top 5 solutions (Equal Weights): [0.69808301 0.4767239 0.59936716 0.41680935 0.70530424] Top 5 solutions (Cost Focused): [0.64818053 0.27200421 0.39912171 0.40681625 0.7933383 ]
4. Sensitivity Analysis¶
Analyze how sensitive the rankings are to changes in weights.
In [6]:
Copied!
sensitivity = sensitivity_analysis_topsis(
result.objectives,
base_weights=np.array([1 / 3, 1 / 3, 1 / 3]),
directions=["min", "min", "min"],
perturbation=0.2,
n_samples=100,
)
print("Sensitivity Analysis Results:")
print(f"Most stable solution: {sensitivity['most_stable_solution']}")
print(f"Least stable solution: {sensitivity['least_stable_solution']}")
print(f"Average rank std dev: {sensitivity['avg_rank_std']:.2f}")
sensitivity = sensitivity_analysis_topsis(
result.objectives,
base_weights=np.array([1 / 3, 1 / 3, 1 / 3]),
directions=["min", "min", "min"],
perturbation=0.2,
n_samples=100,
)
print("Sensitivity Analysis Results:")
print(f"Most stable solution: {sensitivity['most_stable_solution']}")
print(f"Least stable solution: {sensitivity['least_stable_solution']}")
print(f"Average rank std dev: {sensitivity['avg_rank_std']:.2f}")
Sensitivity Analysis Results: Most stable solution: 73 Least stable solution: 139 Average rank std dev: 8.33