-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from TobyBoyne/feature/maximisationobjective
Create maximisation objective
- Loading branch information
Showing
4 changed files
with
262 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"# Objective Maximisation\n", | ||
"\n", | ||
"ENTMOOT supports both minimisation and maximisation of objective functions. This notebook defines a concave function, that has a maximum at (1, 1)." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 1, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from entmoot import Enting, ProblemConfig, PyomoOptimizer\n", | ||
"import numpy as np" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 2, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# define a maximisation problem\n", | ||
"def eval_simple_max_testfunc(X):\n", | ||
" x = np.array(X)\n", | ||
" y = - np.sum((x - np.ones_like(x)) ** 2, axis=1)\n", | ||
" return y.reshape(-1, 1)\n", | ||
"\n", | ||
"def build_simple_max_problem(problem_config: ProblemConfig):\n", | ||
" problem_config.add_feature(\"real\", (0.0, 2.0), name=\"x1\")\n", | ||
" problem_config.add_feature(\"real\", (0.0, 2.0), name=\"x2\")\n", | ||
" problem_config.add_max_objective()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 3, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stderr", | ||
"output_type": "stream", | ||
"text": [ | ||
"c:\\users\\tobyb\\phd\\entmoot\\entmoot\\models\\mean_models\\tree_ensemble.py:23: UserWarning: No 'train_params' for tree ensemble training specified. Switch training to default params!\n", | ||
" warnings.warn(\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# define problem\n", | ||
"problem_config = ProblemConfig(rnd_seed=73)\n", | ||
"# number of objectives\n", | ||
"build_simple_max_problem(problem_config)\n", | ||
"# sample data\n", | ||
"rnd_sample = problem_config.get_rnd_sample_list(num_samples=200)\n", | ||
"testfunc_evals = eval_simple_max_testfunc(rnd_sample)\n", | ||
"\n", | ||
"params = {\"unc_params\": {\"dist_metric\": \"l1\", \"acq_sense\": \"penalty\"}}\n", | ||
"enting = Enting(problem_config, params=params)\n", | ||
"enting.fit(rnd_sample, testfunc_evals)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 4, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"Set parameter Username\n", | ||
"Academic license - for non-commercial use only - expires 2024-09-06\n", | ||
"Read LP format model from file C:\\Users\\tobyb\\AppData\\Local\\Temp\\tmp89rvgogt.pyomo.lp\n", | ||
"Reading time = 0.02 seconds\n", | ||
"x1: 3828 rows, 2714 columns, 12045 nonzeros\n", | ||
"Gurobi Optimizer version 10.0.2 build v10.0.2rc0 (win64)\n", | ||
"\n", | ||
"CPU model: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz, instruction set [SSE2|AVX|AVX2|AVX512]\n", | ||
"Thread count: 4 physical cores, 8 logical processors, using up to 8 threads\n", | ||
"\n", | ||
"Optimize a model with 3828 rows, 2714 columns and 12045 nonzeros\n", | ||
"Model fingerprint: 0xb8904322\n", | ||
"Variable types: 1604 continuous, 1110 integer (1110 binary)\n", | ||
"Coefficient statistics:\n", | ||
" Matrix range [2e-06, 2e+00]\n", | ||
" Objective range [1e+00, 2e+00]\n", | ||
" Bounds range [1e+00, 2e+00]\n", | ||
" RHS range [1e-04, 2e+00]\n", | ||
"Presolve removed 435 rows and 419 columns\n", | ||
"Presolve time: 0.08s\n", | ||
"Presolved: 3393 rows, 2295 columns, 10942 nonzeros\n", | ||
"Variable types: 1585 continuous, 710 integer (710 binary)\n", | ||
"Found heuristic solution: objective 1.4333792\n", | ||
"Found heuristic solution: objective 1.3753556\n", | ||
"\n", | ||
"Root relaxation: objective 3.952765e-02, 617 iterations, 0.00 seconds (0.01 work units)\n", | ||
"\n", | ||
" Nodes | Current Node | Objective Bounds | Work\n", | ||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", | ||
"\n", | ||
" 0 0 0.03953 0 4 1.37536 0.03953 97.1% - 0s\n", | ||
"H 0 0 0.0886752 0.03953 55.4% - 0s\n", | ||
" 0 0 0.03953 0 2 0.08868 0.03953 55.4% - 0s\n", | ||
"H 0 0 0.0395276 0.03953 0.00% - 0s\n", | ||
" 0 0 0.03953 0 2 0.03953 0.03953 0.00% - 0s\n", | ||
"\n", | ||
"Cutting planes:\n", | ||
" Cover: 43\n", | ||
" Implied bound: 3\n", | ||
" Clique: 5\n", | ||
" Flow cover: 7\n", | ||
" Relax-and-lift: 3\n", | ||
"\n", | ||
"Explored 1 nodes (633 simplex iterations) in 0.21 seconds (0.19 work units)\n", | ||
"Thread count was 8 (of 8 available processors)\n", | ||
"\n", | ||
"Solution count 4: 0.0395276 0.0886752 1.37536 1.43338 \n", | ||
"\n", | ||
"Optimal solution found (tolerance 1.00e-04)\n", | ||
"Best objective 3.952764801939e-02, best bound 3.952764801939e-02, gap 0.0000%\n" | ||
] | ||
}, | ||
{ | ||
"data": { | ||
"text/plain": [ | ||
"OptResult(opt_point=[1.1282956329455374, 0.9219237163314549], opt_val=0.039527648019385436, mu_unscaled=[0.039527648019385436], unc_unscaled=0.0, active_leaf_enc=[[(0, '010'), (1, '010'), (2, '011'), (3, '101'), (4, '010'), (5, '101'), (6, '011'), (7, '001'), (8, '011'), (9, '011'), (10, '011'), (11, '011'), (12, '011'), (13, '010'), (14, '010'), (15, '010'), (16, '011'), (17, '011'), (18, '010'), (19, '011'), (20, '011'), (21, '010'), (22, '010'), (23, '010'), (24, '100'), (25, '100'), (26, '101'), (27, '100'), (28, '101'), (29, '101'), (30, '010'), (31, '101'), (32, '010'), (33, '011'), (34, '010'), (35, '010'), (36, '011'), (37, '011'), (38, '010'), (39, '010'), (40, '011'), (41, '001'), (42, '010'), (43, '011'), (44, '010'), (45, '100'), (46, '100'), (47, '101'), (48, '010'), (49, '011'), (50, '010'), (51, '010'), (52, '101'), (53, '010'), (54, '101'), (55, '101'), (56, '101'), (57, '100'), (58, '101'), (59, '010'), (60, '101'), (61, '010'), (62, '101'), (63, '010'), (64, '011'), (65, '011'), (66, '010'), (67, '100'), (68, '010'), (69, '101'), (70, '010'), (71, '100'), (72, '100'), (73, '101'), (74, '011'), (75, '010'), (76, '010'), (77, '010'), (78, '100'), (79, '101'), (80, '110'), (81, '101'), (82, '101'), (83, '010'), (84, '101'), (85, '101'), (86, '100'), (87, '110'), (88, '100'), (89, '101'), (90, '100'), (91, '100'), (92, '100'), (93, '101'), (94, '101'), (95, '010'), (96, '101'), (97, '100'), (98, '100'), (99, '101')]])" | ||
] | ||
}, | ||
"execution_count": 4, | ||
"metadata": {}, | ||
"output_type": "execute_result" | ||
} | ||
], | ||
"source": [ | ||
"params_pyomo = {\"solver_name\": \"gurobi\"}\n", | ||
"opt_pyo = PyomoOptimizer(problem_config, params=params_pyomo)\n", | ||
"\n", | ||
"res_pyo = opt_pyo.solve(enting)\n", | ||
"res_pyo" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "enttest", | ||
"language": "python", | ||
"name": "enttest" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.9.9" | ||
}, | ||
"orig_nbformat": 4 | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from entmoot import Enting, ProblemConfig, PyomoOptimizer | ||
from entmoot.benchmarks import ( | ||
build_multi_obj_categorical_problem, | ||
eval_multi_obj_cat_testfunc, | ||
) | ||
from pytest import approx | ||
|
||
def test_max_predictions_equal_min_predictions(): | ||
"""The sign of the predicted objective is independent of max/min.""" | ||
problem_config = ProblemConfig(rnd_seed=73) | ||
build_multi_obj_categorical_problem(problem_config, n_obj=1) | ||
problem_config.add_min_objective() | ||
|
||
problem_config_max = ProblemConfig(rnd_seed=73) | ||
build_multi_obj_categorical_problem(problem_config_max, n_obj=1) | ||
problem_config_max.add_max_objective() | ||
|
||
rnd_sample = problem_config.get_rnd_sample_list(num_samples=20) | ||
testfunc_evals = eval_multi_obj_cat_testfunc(rnd_sample, n_obj=2) | ||
|
||
params = {"unc_params": {"dist_metric": "l1", "acq_sense": "exploration"}} | ||
enting = Enting(problem_config, params=params) | ||
enting.fit(rnd_sample, testfunc_evals) | ||
|
||
enting_max = Enting(problem_config_max, params=params) | ||
enting_max.fit(rnd_sample, testfunc_evals) | ||
|
||
sample = problem_config.get_rnd_sample_list(num_samples=3) | ||
pred = enting.predict(sample) | ||
pred_max = enting_max.predict(sample) | ||
|
||
for ((m1, u1), (m2, u2)) in zip(pred, pred_max): | ||
print(">", m1, m2) | ||
assert m1 == approx(m2, rel=1e-5) | ||
assert u1 == approx(u2, rel=1e-5) | ||
|
||
def test_max_objective_equals_minus_min_objective(): | ||
"""Assert that the solution found by the minimiser is the same as that of the maximiser for the negative objective function""" | ||
problem_config = ProblemConfig(rnd_seed=73) | ||
build_multi_obj_categorical_problem(problem_config, n_obj=1) | ||
problem_config.add_min_objective() | ||
|
||
problem_config_max = ProblemConfig(rnd_seed=73) | ||
build_multi_obj_categorical_problem(problem_config_max, n_obj=0) | ||
problem_config_max.add_max_objective() | ||
problem_config_max.add_max_objective() | ||
|
||
rnd_sample = problem_config.get_rnd_sample_list(num_samples=20) | ||
testfunc_evals = eval_multi_obj_cat_testfunc(rnd_sample, n_obj=2) | ||
|
||
params = {"unc_params": {"dist_metric": "l1", "acq_sense": "penalty"}} | ||
enting = Enting(problem_config, params=params) | ||
enting.fit(rnd_sample, testfunc_evals) | ||
# pass negative test evaluations to the maximiser | ||
enting_max = Enting(problem_config, params=params) | ||
enting_max.fit(rnd_sample, -testfunc_evals) | ||
|
||
params_pyomo = {"solver_name": "gurobi"} | ||
res = PyomoOptimizer(problem_config, params=params_pyomo).solve(enting) | ||
res_max = PyomoOptimizer(problem_config_max, params=params_pyomo).solve(enting) | ||
|
||
assert res.opt_point == approx(res_max.opt_point, rel=1e-5) | ||
|
||
|
||
|