diff --git a/pyproject.toml b/pyproject.toml index 1e42913..e6e0317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ scenic = "^2.1.0b4" progressbar2 = "^3.53.1" networkx = "^2.6.3" statsmodels = "^0.13.2" +glis = ">=2" GPy = {version = "^1.9.9", optional = true} GPyOpt = {version = "^1.2.6", optional = true} diff --git a/src/verifai/samplers/feature_sampler.py b/src/verifai/samplers/feature_sampler.py index 5890505..d61b615 100644 --- a/src/verifai/samplers/feature_sampler.py +++ b/src/verifai/samplers/feature_sampler.py @@ -20,6 +20,7 @@ from verifai.samplers.bayesian_optimization import BayesOptSampler from verifai.samplers.simulated_annealing import SimulatedAnnealingSampler from verifai.samplers.grid_sampler import GridSampler +from verifai.samplers.glis_optimization import GLISSampler ### Samplers defined over FeatureSpaces @@ -149,6 +150,22 @@ def makeDomainSampler(domain): makeRandomSampler) return LateFeatureSampler(space, RandomSampler, makeDomainSampler) + def glisSamplerFor(space, params): + """Creates a GLIS Optimization sampler for a given space. + Uses random sampling for lengths of feature lists and any + Domains that are not continous and standardizable. + """ + + def makeDomainSampler(domain): + return SplitSampler.fromPredicate( + domain, + lambda d: d.standardizedDimension > 0, + lambda domain: GLISSampler(domain=domain, + params=params), + makeRandomSampler) + + return LateFeatureSampler(space, RandomSampler, makeDomainSampler) + def getSample(self): """Generate a sample, along with any sampler-specific info. diff --git a/src/verifai/samplers/glis_optimization.py b/src/verifai/samplers/glis_optimization.py new file mode 100644 index 0000000..be7d8a8 --- /dev/null +++ b/src/verifai/samplers/glis_optimization.py @@ -0,0 +1,39 @@ +"""GLIS Optimization sampler : Defined only for continuous domains. +For discrete inputs define another sampler""" + +from glis.solvers import GLIS +from verifai.samplers.domain_sampler import BoxSampler + +class GLISSampler(BoxSampler): + ''' + Integrates the GLIS sampler with VerifAI + --- + Parameters: + domain : FeatureSpace + params : DotMap + --- + Note: see the definition of the GLIS class for the available parameters or the GLIS documentation + https://pypi.org/project/glis/ + ''' + def __init__(self, domain, params): + super().__init__(domain) + from numpy import zeros, ones + + self.rho = None + + dim = domain.flattenedDimension + self.lb = zeros(dim) + self.ub = ones(dim) + self.sampler = GLIS(bounds=(self.lb, self.ub), **params) + + + def getVector(self): + if self.rho is None: + self.x = self.sampler.initialize() + elif not self.rho == int(1): + self.x = self.sampler.update(self.rho) + + return tuple(self.x), None + + def updateVector(self, vector, info, rho): + self.rho = rho \ No newline at end of file diff --git a/src/verifai/server.py b/src/verifai/server.py index 1298dcf..970cd81 100644 --- a/src/verifai/server.py +++ b/src/verifai/server.py @@ -96,6 +96,11 @@ def choose_sampler(sample_space, sampler_type, sample_space, BO_params=bo_params) return 'bo', sampler + if sampler_type == 'glis': + sampler = FeatureSampler.glisSamplerFor( + sample_space, sampler_params) + return 'glis', sampler + raise ValueError(f'unknown sampler type "{sampler_type}"') class Server: diff --git a/tests/test_glisOptimization.py b/tests/test_glisOptimization.py new file mode 100644 index 0000000..df7b1c0 --- /dev/null +++ b/tests/test_glisOptimization.py @@ -0,0 +1,92 @@ +from glis.solvers import GLIS +from verifai.features import * +from verifai.samplers import * +from dotmap import DotMap +from numpy import random as rdm + +import pytest +from tests.utils import sampleWithFeedback, checkSaveRestore + + +def test_glisOptimization(): + carDomain = Struct({ + 'position': Box([-10,10], [-10,10], [0,1]), + 'heading': Box([0, math.pi]), + }) + + space = FeatureSpace({ + 'cars': Feature(Array(carDomain, [2])) + }) + + def f(sample): + sample = sample.cars[0].heading[0] + return abs(sample - 0.75) + + params = DotMap() + params.n_initial_random=10 + sampler = FeatureSampler.glisSamplerFor(space, params) + + sampleWithFeedback(sampler, 10, f) + +def test_save_restore(tmpdir): + space = FeatureSpace({ + 'a': Feature(DiscreteBox([0, 12])), + 'b': Feature(Box((0, 1)), lengthDomain=DiscreteBox((1, 2))) + }) + params = DotMap() + params.n_initial_random=3 + sampler = FeatureSampler.glisSamplerFor(space, params) + + checkSaveRestore(sampler, tmpdir) + +def test_direct_vs_verifai(): + fun = lambda x: ((4.0 - 2.1 * x[0] ** 2 + x[0] ** 4 / 3.0) * + x[0] ** 2 + x[0] * x[1] + (4.0 * x[1] ** 2 - 4.0) * x[1] ** 2) + + p = {'display': 0, 'n_initial_random': 3} + + # Direct GLIS approach + rdm.seed(0) + random.seed(0) + + lb = np.array([-2.0, -1.0]) + ub = np.array([2.0, 1.0]) + + prob = GLIS(bounds=(lb, ub), **p) + + X = [] + F = [] + for i in range(6): + if i == 0: + x = prob.initialize() + X.append(x.tolist()) + else: + f = fun(x) + x = prob.update(f) + X.append(x.tolist()) + F.append(f) + + # VerifAI approach + rdm.seed(0) + random.seed(0) + space = FeatureSpace({ + 'a': Feature(Box([-2.0, 2.0])), + 'b': Feature(Box([-1.0, 1.0])) + }) + + sampler = FeatureSampler.glisSamplerFor(space, p) + + X_prime = [] + F_prime = [] + for i in range(6): + if i == 0: + x_prime = sampler.nextSample(int(1)) + X_prime.append([x_prime[0][0], x_prime[1][0]]) + else: + f_prime = fun([x_prime[0][0], x_prime[1][0]]) + x_prime = sampler.nextSample(feedback=f_prime) + X_prime.append([x_prime[0][0], x_prime[1][0]]) + F_prime.append(f_prime) + + assert X == X_prime + assert F == F_prime \ No newline at end of file