Skip to content

Commit

Permalink
Expressions in configuration and objective spaces can now depend on p…
Browse files Browse the repository at this point in the history
…arameters from dependent contexts.
  • Loading branch information
Kerilk committed May 21, 2024
1 parent 36d35b2 commit 9d14e43
Show file tree
Hide file tree
Showing 19 changed files with 239 additions and 41 deletions.
12 changes: 8 additions & 4 deletions bindings/python/cconfigspace/configuration_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .parameter import Parameter
from .expression import Expression
from .expression_parser import parser
from .feature_space import FeatureSpace
from .rng import Rng

ccs_create_configuration_space = _ccs_get_function("ccs_create_configuration_space", [ct.c_char_p, ct.c_size_t, ct.POINTER(ccs_parameter), ct.POINTER(ccs_expression), ct.c_size_t, ct.POINTER(ccs_expression), ccs_feature_space, ccs_rng, ct.POINTER(ccs_configuration_space)])
Expand All @@ -25,10 +26,14 @@ def __init__(self, handle = None, retain = False, auto_release = True,
if handle is None:
count = len(parameters)

ctx_params = parameters
if feature_space is not None:
ctx_params = ctx_params + list(feature_space.parameters)
ctx = dict(zip([x.name for x in ctx_params], ctx_params))

if forbidden_clauses is not None:
numfc = len(forbidden_clauses)
if numfc > 0:
ctx = dict(zip([x.name for x in parameters], parameters))
forbidden_clauses = [ parser.parse(fc, extra = ctx) if isinstance(fc, str) else fc for fc in forbidden_clauses ]
fcv = (ccs_expression * numfc)(*[x.handle.value for x in forbidden_clauses])
else:
Expand All @@ -38,10 +43,9 @@ def __init__(self, handle = None, retain = False, auto_release = True,
fcv = None

if conditions is not None:
namedict = dict(zip([x.name for x in parameters], parameters))
indexdict = dict(reversed(ele) for ele in enumerate(parameters))
cv = (ccs_expression * count)()
conditions = dict( (k, parser.parse(v, extra = namedict) if isinstance(v, str) else v) for (k, v) in conditions.items() )
conditions = dict( (k, parser.parse(v, extra = ctx) if isinstance(v, str) else v) for (k, v) in conditions.items() )
for (k, v) in conditions.items():
if isinstance(k, Parameter):
cv[indexdict[k]] = v.handle.value
Expand Down Expand Up @@ -88,7 +92,7 @@ def feature_space(self):
res = ccs_configuration_space_get_feature_space(self.handle, ct.byref(v))
Error.check(res)
if bool(v):
self._feature_space = Rng.from_handle(v)
self._feature_space = FeatureSpace.from_handle(v)
else:
self._feature_space = None
return self._feature_space
Expand Down
9 changes: 8 additions & 1 deletion bindings/python/cconfigspace/objective_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ def __init__(self, handle = None, retain = False, auto_release = True,
name = "", search_space = None, parameters = [], objectives = [], types = None):
if handle is None:
count = len(parameters)
ctx = dict(zip([x.name for x in parameters], parameters))

ctx_params = parameters
if isinstance(search_space, ConfigurationSpace):
ctx_params = ctx_params + list(search_space.parameters)
if search_space.feature_space is not None:
ctx_params = ctx_params + list(search_space.feature_space.parameters)
ctx = dict(zip([x.name for x in ctx_params], ctx_params))

if isinstance(objectives, dict):
types = objectives.values()
objectives = objectives.keys()
Expand Down
30 changes: 30 additions & 0 deletions bindings/python/test/test_configuration_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def test_create(self):
self.assertEqual( (h1, h2, h3), cs.parameters )
self.assertEqual( h2, cs.parameter_by_name(h2.name) )
self.assertTrue( cs.check(cs.default_configuration()) )
self.assertIsNone( cs.feature_space )
c = cs.sample()
self.assertTrue( cs.check(c) )
self.assertEqual( cs.handle.value, c.configuration_space.handle.value )
Expand Down Expand Up @@ -79,6 +80,35 @@ def test_conditions(self):
self.assertEqual( 1, len(forbidden_clauses) )
self.assertEqual( f1.handle.value, forbidden_clauses[0].handle.value )

def test_features(self):
fe1 = ccs.CategoricalParameter(values = ["on", "off"])
fs = ccs.FeatureSpace(parameters = [fe1])
h1 = ccs.NumericalParameter.Float(lower = -1.0, upper = 1.0, default = 0.0)
h2 = ccs.NumericalParameter.Float(lower = -1.0, upper = 1.0)
h3 = ccs.NumericalParameter.Float(lower = -1.0, upper = 1.0)
h4 = ccs.NumericalParameter.Float(lower = -1.0, upper = 1.0)
e1 = ccs.Expression.Less(left = h2, right = 0.0)
e2 = ccs.Expression.Less(left = h3, right = 0.0)
e3 = ccs.Expression.Equal(left = fe1, right = "on")
f1 = ccs.Expression.Less(left = h1, right = 0.0)
cs = ccs.ConfigurationSpace(name = "space", parameters = [h1, h2, h3, h4], conditions = {h1: e2, h3: e1, h4: e3}, forbidden_clauses = [f1], feature_space = fs)
features_on = ccs.Features(feature_space = fs, values = ["on"])
features_off = ccs.Features(feature_space = fs, values = ["off"])
s = cs.sample(features = features_on)
self.assertIsInstance(s.value(3), float)
s = cs.sample(features = features_off)
self.assertEqual(ccs.inactive, s.value(3))

buff = cs.serialize()
cs_copy = ccs.Object.deserialize(buffer = buff)

features_on = ccs.Features(feature_space = cs_copy.feature_space, values = ["on"])
features_off = ccs.Features(feature_space = cs_copy.feature_space, values = ["off"])
s = cs_copy.sample(features = features_on)
self.assertIsInstance(s.value(3), float)
s = cs_copy.sample(features = features_off)
self.assertEqual(ccs.inactive, s.value(3))

def extract_active_parameters(self, values):
res = ['p1']
for v in values:
Expand Down
23 changes: 23 additions & 0 deletions bindings/python/test/test_objective_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ def test_create(self):
self.assertEqual( e2.handle.value, objs[1][0].handle.value )
self.assertEqual( ccs.ObjectiveType.MAXIMIZE, objs[1][1] )

def test_features(self):
f = ccs.NumericalParameter.Float()
fs = ccs.FeatureSpace(parameters = [f])
p = ccs.NumericalParameter.Float()
cs = ccs.ConfigurationSpace(name = "cs", parameters = [p], feature_space = fs)
h = ccs.NumericalParameter.Float()
e1 = ccs.Expression.Add(left = f, right = p)
e2 = ccs.Expression.Variable(parameter = h)
os = ccs.ObjectiveSpace(name = "space", search_space = cs, parameters = [h], objectives = [e1, e2], types = [ccs.ObjectiveType.MINIMIZE, ccs.ObjectiveType.MAXIMIZE])

features = ccs.Features(feature_space = fs, values = [0.5])
configuration = ccs.Configuration(configuration_space = cs, values = [0.25], features = features)
evaluation = ccs.Evaluation(objective_space = os, values = [-0.25], configuration = configuration)
self.assertEqual(tuple([0.25+0.5, -0.25]), evaluation.objective_values)
buff = os.serialize()
os = ccs.deserialize(buffer = buff)
cs = os.search_space
fs = cs.feature_space
features = ccs.Features(feature_space = fs, values = [0.5])
configuration = ccs.Configuration(configuration_space = cs, values = [0.25], features = features)
evaluation = ccs.Evaluation(objective_space = os, values = [-0.25], configuration = configuration)
self.assertEqual(tuple([0.25+0.5, -0.25]), evaluation.objective_values)


if __name__ == '__main__':
unittest.main()
7 changes: 6 additions & 1 deletion bindings/ruby/lib/cconfigspace/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,12 @@ def self.add_optional_handle_property(name, type, accessor, memoize: false)
src << " @#{name} = begin\n" if memoize
src << " ptr = MemoryPointer::new(:#{type})\n"
src << " CCS.error_check CCS.#{accessor}(@handle, ptr)\n"
src << " Object::from_handle(ptr.read_#{type})\n"
src << " h = ptr.read_#{type}\n"
src << " if h.null?\n"
src << " nil\n"
src << " else\n"
src << " Object::from_handle(h)\n"
src << " end\n"
src << " end\n" if memoize
src << "end\n"
class_eval src
Expand Down
6 changes: 4 additions & 2 deletions bindings/ruby/lib/cconfigspace/configuration_space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ def initialize(handle = nil, retain: false, auto_release: true,
p_parameters.write_array_of_pointer(parameters.collect(&:handle))
ptr = MemoryPointer::new(:ccs_configuration_space_t)

ctx_params = parameters
ctx_params += feature_space.parameters if feature_space
ctx = ctx_params.map { |p| [p.name, p] }.to_h

if forbidden_clauses
ctx = parameters.map { |p| [p.name, p] }.to_h
p = ExpressionParser::new(ctx)
forbidden_clauses = forbidden_clauses.collect { |e| e.kind_of?(String) ? p.parse(e) : e }
fccount = forbidden_clauses.size
Expand All @@ -38,7 +41,6 @@ def initialize(handle = nil, retain: false, auto_release: true,
end

if conditions
ctx = parameters.map { |p| [p.name, p] }.to_h
indexdict = parameters.each_with_index.to_h
p = ExpressionParser::new(ctx)
conditions = conditions.transform_values { |v| v.kind_of?(String) ? p.parse(v) : v }
Expand Down
5 changes: 4 additions & 1 deletion bindings/ruby/lib/cconfigspace/objective_space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def initialize(handle = nil, retain: false, auto_release: true,
super(handle, retain: retain, auto_release: auto_release)
else
count = parameters.size
ctx = parameters.map { |p| [p.name, p] }.to_h
ctx_params = parameters
ctx_params += search_space.parameters if search_space.is_a?(ConfigurationSpace)
ctx_params += search_space.feature_space.parameters if search_space.feature_space
ctx = ctx_params.map { |p| [p.name, p] }.to_h
if objectives.kind_of? Hash
types = objectives.values
objectives = objectives.keys
Expand Down
32 changes: 32 additions & 0 deletions bindings/ruby/test/test_configuration_space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def test_create
assert_equal( [h1, h2, h3], cs.parameters )
assert_equal( h2, cs.parameter_by_name(h2.name) )
assert( cs.check(cs.default_configuration) )
assert_nil( cs.feature_space )
c = cs.sample
assert( cs.check(c) )
assert_equal( cs.handle, c.configuration_space.handle )
Expand Down Expand Up @@ -80,6 +81,37 @@ def test_conditions
assert_equal( f1.handle, forbidden_clauses[0].handle )
end

def test_features
fe1 = CCS::CategoricalParameter::new(values: ["on", "off"])
fs = CCS::FeatureSpace::new(parameters: [fe1])
h1 = CCS::NumericalParameter::Float.new(lower: -1.0, upper: 1.0, default: 0.0)
h2 = CCS::NumericalParameter::Float.new(lower: -1.0, upper: 1.0)
h3 = CCS::NumericalParameter::Float.new(lower: -1.0, upper: 1.0)
h4 = CCS::NumericalParameter::Float.new(lower: -1.0, upper: 1.0)
e1 = CCS::Expression::Less.new(left: h2, right: 0.0)
e2 = CCS::Expression::Less.new(left: h3, right: 0.0)
e3 = CCS::Expression::Equal.new(left: fe1, right: "on")
f1 = CCS::Expression::Less.new(left: h1, right: 0.0)
cs = CCS::ConfigurationSpace::new(name: "space", parameters: [h1, h2, h3, h4], conditions: {h1 => e2, h3 => e1, h4 => e3}, forbidden_clauses: [f1], feature_space: fs)

features_on = CCS::Features::new(feature_space: fs, values: ["on"])
features_off = CCS::Features::new(feature_space: fs, values: ["off"])
s = cs.sample(features: features_on)
assert(s.value(3).kind_of?(Float))
s = cs.sample(features: features_off)
assert_equal(CCS::Inactive, s.value(3))

buff = cs.serialize
cs_copy = CCS::deserialize(buffer: buff)

features_on = CCS::Features::new(feature_space: cs_copy.feature_space, values: ["on"])
features_off = CCS::Features::new(feature_space: cs_copy.feature_space, values: ["off"])
s = cs_copy.sample(features: features_on)
assert(s.value(3).kind_of?(Float))
s = cs_copy.sample(features: features_off)
assert_equal(CCS::Inactive, s.value(3))
end

def extract_active_parameters(values)
['p1'] + values.select { |v|
v != CCS::Inactive
Expand Down
25 changes: 25 additions & 0 deletions bindings/ruby/test/test_objective_space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,29 @@ def test_create
assert_equal( :CCS_OBJECTIVE_TYPE_MAXIMIZE, objs[1][1] )
end

def test_features
f = CCS::NumericalParameter::Float.new
fs = CCS::FeatureSpace::new(parameters: [f])
p = CCS::NumericalParameter::Float.new
cs = CCS::ConfigurationSpace::new(name: "cs", parameters: [p], feature_space: fs)
h = CCS::NumericalParameter::Float.new
e1 = CCS::Expression::Add.new(left: f, right: p)
e2 = CCS::Expression::Variable.new(parameter: h)
os = CCS::ObjectiveSpace::new(name: "space", search_space: cs, parameters: [h], objectives: [e1, e2], types: [:CCS_OBJECTIVE_TYPE_MINIMIZE, :CCS_OBJECTIVE_TYPE_MAXIMIZE])

features = CCS::Features::new(feature_space: fs, values: [0.5])
configuration = CCS::Configuration::new(configuration_space: cs, values: [0.25], features: features)
evaluation = CCS::Evaluation::new(objective_space: os, values: [-0.25], configuration: configuration)
assert_equal([0.25+0.5, -0.25], evaluation.objective_values)

buff = os.serialize
os = CCS::deserialize(buffer: buff)
cs = os.search_space
fs = cs.feature_space
features = CCS::Features::new(feature_space: fs, values: [0.5])
configuration = CCS::Configuration::new(configuration_space: cs, values: [0.25], features: features)
evaluation = CCS::Evaluation::new(objective_space: os, values: [-0.25], configuration: configuration)
assert_equal([0.25+0.5, -0.25], evaluation.objective_values)
end

end
2 changes: 1 addition & 1 deletion include/cconfigspace/configuration_space.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extern "C" {
* CCS_PARAMETER_TYPE_STRING; or if a parameter appears more than once in \p
* parameters; or if two or more parameters share the same name; or if a
* paramater is already part of another context; or if an expression references
* a parameter that is not in the configuration space
* a parameter that is not in \p parameters or in \p feature_space
* @return #CCS_RESULT_ERROR_INVALID_CONFIGURATION if adding one of the
* provided forbidden clause would render the default configuration invalid
* @return #CCS_RESULT_ERROR_INVALID_GRAPH if the addition of the conditions
Expand Down
3 changes: 2 additions & 1 deletion include/cconfigspace/objective_space.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ typedef enum ccs_objective_type_e ccs_objective_type_t;
* @return #CCS_RESULT_ERROR_INVALID_PARAMETER if a parameter appears more than
* once in \p parameters; or if two or more parameters share the same name; or
* if a paramater is already part of another context; or if an expression
* references a parameter that is not in \p parameters
* references a parameter that is not in \p parameters or in \p search_space or
* in \p search_space feature space if it exists
* @return #CCS_RESULT_ERROR_OUT_OF_MEMORY if there was a lack of memory to
* allocate the new objective space
* @remarks
Expand Down
7 changes: 7 additions & 0 deletions src/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ _ccs_create_configuration(
CCS_VALIDATE_ERR_GOTO(
err, ccs_retain_object(configuration_space), errinit);
config->data->configuration_space = configuration_space;
config->data->bindings[0] = (ccs_binding_t)config;
config->data->num_bindings = 1;
if (features)
CCS_VALIDATE_ERR_GOTO(
err, ccs_retain_object(features), errinit);
Expand All @@ -191,6 +193,11 @@ _ccs_create_configuration(
&features),
errinit);
config->data->features = features;
if (features) {
config->data->bindings[config->data->num_bindings] =
(ccs_binding_t)features;
config->data->num_bindings++;
}
if (values) {
memcpy(config->data->values, values,
num_parameters * sizeof(ccs_datum_t));
Expand Down
2 changes: 2 additions & 0 deletions src/configuration_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct _ccs_configuration_data_s {
size_t num_values;
ccs_datum_t *values;
ccs_features_t features;
size_t num_bindings;
ccs_binding_t bindings[2];
};

ccs_result_t
Expand Down
Loading

0 comments on commit 9d14e43

Please sign in to comment.