diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index 61376b39..68cc3504 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -30,16 +30,16 @@ jobs: compiler: g++ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: sudo apt update; sudo apt install -y ${{ matrix.compiler }} ruby-dev libgsl-dev python3-dev valgrind if: ${{ matrix.os == 'ubuntu-latest' }} - run: brew install gsl automake libtool if: ${{ matrix.os == 'macos-latest' }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' if: ${{ matrix.os == 'macos-latest' }} - - run: gem install --user-install rake ffi ffi-value whittle + - run: gem install --user-install rake ffi ffi-value whittle xmlrpc - run: pip3 install --user parglare - run: ./autogen.sh - run: mkdir -p build @@ -55,7 +55,7 @@ jobs: - run: make -j check-valgrind-helgrind working-directory: build if: ${{ matrix.os == 'ubuntu-latest' }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: failure() with: name: build-and-check @@ -78,11 +78,11 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' }} - run: brew install gsl automake libtool if: ${{ matrix.os == 'macos-latest' }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' if: ${{ matrix.os == 'macos-latest' }} - - run: gem install --user-install rake ffi ffi-value whittle + - run: gem install --user-install rake ffi ffi-value whittle xmlrpc - run: pip3 install --user parglare - run: ./autogen.sh - run: mkdir -p build @@ -106,11 +106,11 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' }} - run: brew install gsl automake libtool if: ${{ matrix.os == 'macos-latest' }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' if: ${{ matrix.os == 'macos-latest' }} - - run: gem install --user-install rake ffi ffi-value whittle + - run: gem install --user-install rake ffi ffi-value whittle xmlrpc - run: pip3 install --user parglare - run: ./autogen.sh - run: mkdir -p build diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am index 11d69202..9fc75576 100644 --- a/bindings/python/Makefile.am +++ b/bindings/python/Makefile.am @@ -39,6 +39,7 @@ EXTRA_DIST = \ test/test_objective_space.py \ test/test_configuration_space.py \ test/test_feature_space.py \ + test/tuner_server.py \ setup.py if ISMACOS diff --git a/bindings/python/cconfigspace/base.py b/bindings/python/cconfigspace/base.py index c8709657..f89a2974 100644 --- a/bindings/python/cconfigspace/base.py +++ b/bindings/python/cconfigspace/base.py @@ -551,50 +551,50 @@ def serialize(self, format = 'binary', path = None, file_descriptor = None, call s = ct.c_size_t(0) res = ccs_object_serialize(self.handle, SerializeFormat.BINARY, SerializeOperation.SIZE, ct.byref(s), *options) Error.check(res) - v = (ct.c_byte * s.value)() + v = ct.create_string_buffer(s.value) res = ccs_object_serialize(self.handle, SerializeFormat.BINARY, SerializeOperation.MEMORY, ct.sizeof(v), v, *options) Error.check(res) - return v + return v.raw @classmethod def deserialize(cls, format = 'binary', handle_map = None, map_handles = False, vector_callback = None, path = None, buffer = None, file_descriptor = None, callback = None): if format != 'binary': raise Error(Result(Result.ERROR_INVALID_VALUE)) mode_count = 0; - if path: + if path is not None: mode_count += 1 - if buffer: + if buffer is not None: mode_count += 1 - if file_descriptor: + if file_descriptor is not None: mode_count += 1 if not mode_count == 1: raise Error(Result(Result.ERROR_INVALID_VALUE)) - if map_handles and not handle_map: + if map_handles and handle_map is None: raise Error(Result(Result.ERROR_INVALID_VALUE)) o = ccs_object(0) options = [DeserializeOption.END] if map_handles: options = [DeserializeOption.MAP_HANDLES] + options - if handle_map: + if handle_map is not None: options = [DeserializeOption.HANDLE_MAP, handle_map.handle] + options - if vector_callback: + if vector_callback is not None: vector_cb_wrapper = _get_deserialize_vector_callback_wrapper(vector_callback) vector_cb_wrapper_func = ccs_object_deserialize_vector_callback_type(vector_cb_wrapper) options = [DeserializeOption.VECTOR_CALLBACK, vector_cb_wrapper_func, ct.py_object()] + options - if callback: + if callback is not None: cb_wrapper = _get_deserialize_data_callback_wrapper(callback) cb_wrapper_func = ccs_object_deserialize_data_callback_type(cb_wrapper) options = [DeserializeOption.DATA_CALLBACK, cb_wrapper_func, ct.py_object()] + options - elif _default_user_data_deserializer: + elif _default_user_data_deserializer is not None: options = [DeserializeOption.DATA_CALLBACK, _default_user_data_deserializer, ct.py_object()] + options - if buffer: - s = ct.c_size_t(ct.sizeof(buffer)) - res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.MEMORY, s, buffer, *options) - elif path: + if buffer is not None: + s = len(buffer) + res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.MEMORY, s, ct.create_string_buffer(buffer, s), *options) + elif path is not None: p = str.encode(path) pp = ct.c_char_p(p) res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.FILE, pp, *options) - elif file_descriptor: + elif file_descriptor is not None: fd = ct.c_int(file_descriptor) res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.FILE_DESCRIPTOR, fd, *options) else: diff --git a/bindings/python/cconfigspace/map.py b/bindings/python/cconfigspace/map.py index e0bb6556..941b88b7 100644 --- a/bindings/python/cconfigspace/map.py +++ b/bindings/python/cconfigspace/map.py @@ -21,6 +21,10 @@ def __init__(self, handle = None, retain = False, auto_release = True): else: super().__init__(handle = handle, retain = retain, auto_release = auto_release) + @classmethod + def from_handle(cls, handle, retain = True, auto_release = True): + return cls(handle = handle, retain = retain, auto_release = auto_release) + def __len__(self): v = ct.c_size_t() res = ccs_map_get_keys(self.handle, 0, None, ct.byref(v)) @@ -73,3 +77,12 @@ def values(self): res = ccs_map_get_values(self.handle, sz, v, None) Error.check(res) return [x.value for x in v] + + def pairs(self): + sz = self.__len__() + if sz == 0: + return [] + v = (Datum * sz)() + k = (Datum * sz)() + res = ccs_map_get_pairs(self.handle, sz, k, v, None) + return [(k[i].value, v[i].value) for i in range(sz)] diff --git a/bindings/python/test/test_tuner.py b/bindings/python/test/test_tuner.py index f65ae2e1..19856cd7 100644 --- a/bindings/python/test/test_tuner.py +++ b/bindings/python/test/test_tuner.py @@ -1,6 +1,13 @@ import unittest import sys import os as _os +import signal +import threading +import subprocess +import datetime +import base64 +import xmlrpc.client +import ctypes as ct from random import choice sys.path.insert(1, '.') sys.path.insert(1, '..') @@ -167,6 +174,104 @@ def get_vector_data(otype, name): self.assertTrue(t_copy.suggest() in [x.configuration for x in optims_2]) _os.remove('tuner.ccs') + class ServerThread(threading.Thread): + def __init__(self): + self.p = None + threading.Thread.__init__(self) + + def run(self): + subprocess.run(['python3', _os.path.join(_os.path.abspath(_os.path.dirname(__file__)), 'tuner_server.py')], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) + + class TunerProxy: + def __init__(self, name = "", objective_space = None): + self.server = xmlrpc.client.ServerProxy("http://localhost:8000/") + connected = False + start = datetime.datetime.now() + while not connected: + try: + connected = self.server.connected() + except Exception as err: + if datetime.datetime.now() - start > datetime.timedelta(seconds = 10): + raise + if objective_space is not None: + self.objective_space = objective_space + buff = objective_space.serialize() + self.id, result = self.server.create(name, buff) + self.handle_map = ccs.deserialize(buffer = result.data) + else: + self.id, result = self.server.load(name) + self.handle_map = ccs.Map() + self.objective_space = ccs.deserialize(buffer = result.data, handle_map = self.handle_map, map_handles = True) + rev_map = ccs.Map() + for (k, v) in self.handle_map.pairs(): + if isinstance(v, ccs.Context) or isinstance(v, ccs.TreeSpace): + rev_map[ccs.Object(v.handle, retain = False, auto_release = False)] = k + self.server.set_handle_map(self.id, rev_map.serialize()) + + def ask(self, count = 1): + return [ccs.deserialize(buffer = conf_str.data, handle_map = self.handle_map) for conf_str in self.server.ask(self.id, count)] + + def tell(self, evals = []): + self.server.tell(self.id, [e.serialize() for e in evals]) + return self + + def history(self): + return [ccs.deserialize(buffer = eval_str.data, handle_map = self.handle_map) for eval_str in self.server.history(self.id)] + + def history_size(self): + return self.server.history_size(self.id) + + def optima(self): + return [ccs.deserialize(buffer = eval_str.data, handle_map = self.handle_map) for eval_str in self.server.optima(self.id)] + + def num_optima(self): + return self.server.num_optima(self.id) + + def suggest(self): + return ccs.deserialize(buffer = self.server.suggest(self.id).data, handle_map = self.handle_map) + + def save(self): + return self.server.save(self.id) + + def kill(self): + return self.server.kill() + + def test_server(self): + thr = self.ServerThread() + thr.start() + try: + + os = self.create_tuning_problem() + t = self.TunerProxy(name = 'my_tuner', objective_space = os) + func = lambda x, y, z: [(x-2)*(x-2), sin(z+y)] + evals = [ccs.Evaluation(objective_space = os, configuration = c, values = func(*(c.values))) for c in t.ask(100)] + t.tell(evals) + hist = t.history() + self.assertEqual(100, len(hist)) + evals = [ccs.Evaluation(objective_space = os, configuration = c, values = func(*(c.values))) for c in t.ask(100)] + t.tell(evals) + self.assertEqual(200, t.history_size()) + optims = t.optima() + objs = [x.objective_values for x in optims] + objs.sort(key = lambda x: x[0]) + # assert pareto front + self.assertTrue(all(objs[i][1] >= objs[i+1][1] for i in range(len(objs)-1))) + self.assertTrue(t.suggest() in [x.configuration for x in optims]) + + t.save() + t_copy = self.TunerProxy(name = "my_tuner") + hist = t_copy.history() + self.assertEqual(200, len(hist)) + optims_2 = t_copy.optima() + self.assertEqual(len(optims), len(optims_2)) + objs = [x.objective_values for x in optims_2] + objs.sort(key = lambda x: x[0]) + self.assertTrue(all(objs[i][1] >= objs[i+1][1] for i in range(len(objs)-1))) + self.assertTrue(t_copy.suggest() in [x.configuration for x in optims_2]) + + finally: + xmlrpc.client.ServerProxy("http://localhost:8000/").kill() + thr.join() if __name__ == '__main__': unittest.main() diff --git a/bindings/python/test/tuner_server.py b/bindings/python/test/tuner_server.py new file mode 100644 index 00000000..18b154e9 --- /dev/null +++ b/bindings/python/test/tuner_server.py @@ -0,0 +1,172 @@ +from xmlrpc.server import SimpleXMLRPCServer +from threading import Lock +from dataclasses import dataclass +import sys +import traceback +import base64 +import ctypes as ct +from random import choice +sys.path.insert(1, '.') +sys.path.insert(1, '..') +import cconfigspace as ccs + +class TunerData: + def __init__(self): + self.history = [] + self.optima = [] + +def deletef(tuner): + return None + +def askf(tuner, features, count): + if count is None: + return (None, 1) + else: + cs = tuner.search_space + return (cs.samples(count), count) + +def tellf(tuner, evaluations): + tuner.tuner_data.history += evaluations + for e in evaluations: + discard = False + new_optima = [] + for o in tuner.tuner_data.optima: + if discard: + new_optima.append(o) + else: + c = e.compare(o) + if c == ccs.Comparison.EQUIVALENT or c == ccs.Comparison.WORSE: + discard = True + new_optima.append(o) + elif c == ccs.Comparison.NOT_COMPARABLE: + new_optima.append(o) + if not discard: + new_optima.append(e) + tuner.tuner_data.optima = new_optima + return None + +def get_historyf(tuner, features): + return tuner.tuner_data.history + +def get_optimaf(tuner, features): + return tuner.tuner_data.optima + +def suggestf(tuner, features): + if not tuner.tuner_data.optima: + return ask(tuner, 1) + else: + return choice(tuner.tuner_data.optima).configuration + +def get_vector_data(otype, name): + return (ccs.UserDefinedTuner.get_vector(delete = deletef, ask = askf, tell = tellf, get_optima = get_optimaf, get_history = get_historyf, suggest = suggestf), TunerData()) + +@dataclass +class TunerStruct: + tuner: list + handle_map: ccs.Map = None + +class MyServer(SimpleXMLRPCServer): + def serve_forever(self): + self.quit = 0 + while not self.quit: + self.handle_request() + +s = MyServer(('localhost', 8000)) +s.register_introspection_functions() + +@s.register_function +def connected(): + return True + +count = 0 +tuners = dict() +tuners_store = dict() +mutex = Lock() + +def get_tstruct(ident): + with mutex: + return tuners[ident] + +@s.register_function +def create(name, os_string): + global count + handle_map = ccs.Map() + os = ccs.deserialize(buffer = os_string.data, handle_map = handle_map, map_handles = True) + t = ccs.UserDefinedTuner(name = name, objective_space = os, delete = deletef, ask = askf, tell = tellf, get_optima = get_optimaf, get_history = get_historyf, suggest = suggestf, tuner_data = TunerData()) + + rev_map = ccs.Map() + for (k, v) in handle_map.pairs(): + if isinstance(v, ccs.Context) or isinstance(v, ccs.TreeSpace): + rev_map[ccs.Object(v.handle, retain = False, auto_release = False)] = k + buff = rev_map.serialize() + tstruct = TunerStruct(t, handle_map) + ident = None + with mutex: + ident = count + count += 1 + tuners[ident] = tstruct + return (ident, buff) + +@s.register_function +def ask(ident, count): + return [c.serialize() for c in get_tstruct(ident).tuner.ask(count)] + +@s.register_function +def tell(ident, evals): + tstruct = get_tstruct(ident) + tstruct.tuner.tell([ccs.deserialize(buffer = e.data, handle_map = tstruct.handle_map) for e in evals]) + return True + +@s.register_function +def history(ident): + return [e.serialize() for e in get_tstruct(ident).tuner.history()] + +@s.register_function +def history_size(ident): + return get_tstruct(ident).tuner.history_size() + +@s.register_function +def optima(ident): + return [e.serialize() for e in get_tstruct(ident).tuner.optima()] + +@s.register_function +def num_optima(ident): + return get_tstruct(ident).tuner.num_optima() + +@s.register_function +def suggest(ident): + return get_tstruct(ident).tuner.suggest().serialize() + +@s.register_function +def save(ident): + tstruct = get_tstruct(ident) + with mutex: + tuners_store[tstruct.tuner.name] = tstruct.tuner.serialize() + return True + +@s.register_function +def load(name): + global count + buff = None + with mutex: + buff = tuners_store[name] + t = ccs.deserialize(buffer = buff, vector_callback = get_vector_data) + tstruct = TunerStruct(t) + ident = None + with mutex: + ident = count + count += 1 + tuners[ident] = tstruct + return (ident, t.objective_space.serialize()) + +@s.register_function +def set_handle_map(ident, map_str): + get_tstruct(ident).handle_map = ccs.deserialize(buffer = map_str.data) + return True + +@s.register_function +def kill(): + s.quit = 1 + return True + +s.serve_forever() diff --git a/bindings/ruby/Makefile.am b/bindings/ruby/Makefile.am index 79586395..db69d249 100644 --- a/bindings/ruby/Makefile.am +++ b/bindings/ruby/Makefile.am @@ -40,6 +40,7 @@ EXTRA_DIST = \ test/test_configuration_space.rb \ test/test_distribution.rb \ test/test_evaluation.rb \ + test/tuner_server.rb \ rakefile \ cconfigspace.gemspec \ LICENSE diff --git a/bindings/ruby/lib/cconfigspace/base.rb b/bindings/ruby/lib/cconfigspace/base.rb index 5caf1571..a52f4415 100644 --- a/bindings/ruby/lib/cconfigspace/base.rb +++ b/bindings/ruby/lib/cconfigspace/base.rb @@ -728,8 +728,9 @@ def serialize(format: :binary, path: nil, file_descriptor: nil, callback: nil) varargs = [:pointer, sz] + options CCS.error_check CCS.ccs_object_serialize(@handle, format, operation, *varargs) operation = :CCS_SERIALIZE_OPERATION_MEMORY - result = MemoryPointer::new(sz.read_size_t) - varargs = [:size_t, sz.read_size_t, :pointer, result] + options + sz = sz.read_size_t + result = String.new("\0", encoding: 'BINARY') * sz + varargs = [:size_t, sz, :pointer, result] + options end CCS.error_check CCS.ccs_object_serialize(@handle, format, operation, *varargs) return result diff --git a/bindings/ruby/test/test_tuner.rb b/bindings/ruby/test/test_tuner.rb index 961cfbb3..73dbb031 100644 --- a/bindings/ruby/test/test_tuner.rb +++ b/bindings/ruby/test/test_tuner.rb @@ -170,5 +170,127 @@ def test_user_defined assert( t_copy.optima.collect(&:configuration).include?(t_copy.suggest) ) File.delete('tuner.ccs') end + + require 'open3' + require 'xmlrpc/client' + require 'base64' + class TunerProxy + attr_reader :server + attr_reader :id + attr_reader :handle_map + attr_reader :objective_space + def initialize(name: "", objective_space: nil) + @server = XMLRPC::Client.new2('http://localhost:8080/RPC2') + connected = false + start = Time.now + while !connected + begin + connected = server.call('connected') + rescue + raise if Time.now - start > 10 + end + end + if objective_space + @objective_space = objective_space + @id, result = server.call('tuner.create', name, Base64.encode64(objective_space.serialize)) + @handle_map = CCS.deserialize(buffer: Base64.decode64(result)) + else + @id, result = server.call('tuner.load', name) + @handle_map = CCS::Map.new + @objective_space = CCS.deserialize(buffer: Base64.decode64(result), handle_map: @handle_map, map_handles: true) + map = CCS::Map.new + @handle_map.pairs.select { |_ , v| + v.is_a?(CCS::Context) || v.is_a?(CCS::TreeSpace) + }.each { |k, v| + map[CCS::Object::new(v.handle, retain: false, auto_release: false)] = k + } + server.call('tuner.set_handle_map', @id, Base64.encode64(map.serialize)) + end + end + + def ask(count = 1) + server.call('tuner.ask', @id, count).collect { |c| + CCS.deserialize(buffer: Base64.decode64(c), handle_map: @handle_map) + } + end + + def tell(evals = []) + evals_serialized = evals.collect { |e| Base64.encode64(e.serialize) } + server.call('tuner.tell', @id, evals_serialized) + self + end + + def history + server.call('tuner.history', @id).collect { |e| + CCS.deserialize(buffer: Base64.decode64(e), handle_map: @handle_map) + } + end + + def history_size + server.call('tuner.history_size', @id) + end + + def optima + server.call('tuner.optima', @id).collect { |e| + CCS.deserialize(buffer: Base64.decode64(e), handle_map: @handle_map) + } + end + + def num_optima + server.call('tuner.num_optima', @id) + end + + def suggest + e = server.call('tuner.suggest', @id) + CCS.deserialize(buffer: Base64.decode64(e), handle_map: @handle_map) + end + + def save + server.call('tuner.save', @id) + end + end + + def test_server + begin + pid = nil + thr = Thread.new do + Open3.popen2e(RbConfig.ruby, File.join(File.dirname(__FILE__), 'tuner_server.rb')) { |stdin, stdout_stderr, wait_thr| + pid = wait_thr.pid + stdout_stderr.read + } + end + os = create_tuning_problem + t = TunerProxy.new(name: "my_tuner", objective_space: os) + func = lambda { |(x, y, z)| + [(x-2)**2, Math.sin(z+y)] + } + evals = t.ask(100).collect { |c| + CCS::Evaluation::new(objective_space: os, configuration: c, values: func[c.values]) + } + t.tell evals + hist = t.history + assert_equal(100, hist.size) + evals = t.ask(100).collect { |c| + CCS::Evaluation::new(objective_space: os, configuration: c, values: func[c.values]) + } + t.tell evals + assert_equal(200, t.history_size) + objs = t.optima.collect(&:objective_values).sort + objs.collect { |(_, v)| v }.each_cons(2) { |v1, v2| assert( (v1 <=> v2) > 0 ) } + assert( t.optima.collect(&:configuration).include?(t.suggest) ) + + t.save + t_copy = TunerProxy.new(name: "my_tuner") + hist = t_copy.history + assert_equal(200, hist.size) + assert_equal(t.num_optima, t_copy.num_optima) + objs = t_copy.optima.collect(&:objective_values).sort + objs.collect { |(_, v)| v }.each_cons(2) { |v1, v2| assert( (v1 <=> v2) > 0 ) } + assert( t_copy.optima.collect(&:configuration).include?(t_copy.suggest) ) + ensure + Process.kill("SIGHUP", pid) + thr.join + end + end end diff --git a/bindings/ruby/test/tuner_server.rb b/bindings/ruby/test/tuner_server.rb new file mode 100644 index 00000000..435658ef --- /dev/null +++ b/bindings/ruby/test/tuner_server.rb @@ -0,0 +1,174 @@ +require_relative '../lib/cconfigspace' +require 'xmlrpc/server' +require 'base64' + +CCS.init + +class TunerData + attr_accessor :history, :optima + def initialize + @history = [] + @optima = [] + end +end + +del = lambda { |tuner| nil } +ask = lambda { |tuner, _, count| + if count + cs = tuner.search_space + [cs.samples(count), count] + else + [nil, 1] + end +} +tell = lambda { |tuner, evaluations| + tuner.tuner_data.history.concat(evaluations) + evaluations.each { |e| + discard = false + tuner.tuner_data.optima = tuner.tuner_data.optima.collect { |o| + unless discard + case e.compare(o) + when :CCS_COMPARISON_EQUIVALENT, :CCS_COMPARISON_WORSE + discard = true + o + when :CCS_COMPARISON_NOT_COMPARABLE + o + else + nil + end + else + o + end + }.compact + tuner.tuner_data.optima.push(e) unless discard + } +} +get_history = lambda { |tuner, _| + tuner.tuner_data.history +} +get_optima = lambda { |tuner, _| + tuner.tuner_data.optima +} +suggest = lambda { |tuner, _| + if tuner.tuner_data.optima.empty? + ask.call(tuner, 1) + else + tuner.tuner_data.optima.sample.configuration + end +} +get_vector_data = lambda { |otype, name| + [CCS::UserDefinedTuner.get_vector(del: del, ask: ask, tell: tell, get_optima: get_optima, get_history: get_history, suggest: suggest), TunerData.new] +} + +s = XMLRPC::Server.new(8080) +count = 0 +tuners = {} +# Could be on disk +tuners_store = {} +mutex = Mutex.new + +tstruct_byid = lambda { |id| + mutex.synchronize { + tuners[id] + } +} + +TunerStruct = Struct.new(:tuner, :handle_map) + +s.add_handler('connected') do + true +end + +s.add_handler('tuner.create') do |name, os_string| + handle_map = CCS::Map.new + os = CCS.deserialize(buffer: Base64.decode64(os_string), handle_map: handle_map, map_handles: true) + t = CCS::UserDefinedTuner::new(name: name, objective_space: os, del: del, ask: ask, tell: tell, get_optima: get_optima, get_history: get_history, suggest: suggest, tuner_data: TunerData.new) + + map = CCS::Map.new + handle_map.pairs.select { |_ , v| + v.is_a?(CCS::Context) || v.is_a?(CCS::TreeSpace) + }.each { |k, v| + map[CCS::Object::new(v.handle, retain: false, auto_release: false)] = k + } + tstruct = TunerStruct.new(t, handle_map) + id = nil + mutex.synchronize { + id = count + count += 1 + tuners[id] = tstruct + } + [id, Base64.encode64(map.serialize)] +end + +s.add_handler('tuner.ask') do |id, count| + tstruct_byid[id].tuner.ask(count).collect { |c| + Base64.encode64(c.serialize) + } +end + +s.add_handler('tuner.tell') do |id, evals| + tstruct = tstruct_byid[id] + evals.collect! { |e| + CCS.deserialize(buffer: Base64.decode64(e), handle_map: tstruct.handle_map) + } + tstruct.tuner.tell evals + true +end + +s.add_handler('tuner.history') do |id| + tstruct_byid[id].tuner.history.collect { |e| + Base64.encode64(e.serialize) + } +end + +s.add_handler('tuner.history_size') do |id| + tstruct_byid[id].tuner.history_size +end + +s.add_handler('tuner.optima') do |id| + tstruct_byid[id].tuner.optima.collect { |e| + Base64.encode64(e.serialize) + } +end + +s.add_handler('tuner.num_optima') do |id| + tstruct_byid[id].tuner.num_optima +end + +s.add_handler('tuner.suggest') do |id| + e = tstruct_byid[id].tuner.suggest + Base64.encode64(e.serialize) +end + +s.add_handler('tuner.save') do |id| + tstruct = tstruct_byid[id] + buff = tstruct.tuner.serialize + mutex.synchronize { + tuners_store[tstruct.tuner.name] = buff + } + true +end + +s.add_handler('tuner.load') do |name| + buff = nil + mutex.synchronize { + buff = tuners_store[name] + } + t = CCS.deserialize(buffer: buff, vector_callback: get_vector_data) + tstruct = TunerStruct.new(t, nil) + id = nil + mutex.synchronize { + id = count + count += 1 + tuners[id] = tstruct + } + [id, Base64.encode64(t.objective_space.serialize)] +end + +s.add_handler('tuner.set_handle_map') do |id, map_str| + tstruct_byid[id].handle_map = CCS.deserialize(buffer: Base64.decode64(map_str)) + true +end + +s.serve +