Commit de245d6f authored by Brice Videau's avatar Brice Videau
Browse files

Started Ruby bindings.

parent 240a027d
require 'ffi'
require_relative 'cconfigspace/base'
require_relative 'cconfigspace/interval'
require_relative 'cconfigspace/rng'
require_relative 'cconfigspace/distribution'
module CCS
extend FFI::Library
if ENV["LIBCCONFIGSPACE_SO"]
ffi_lib ENV["LIBCCONFIGSPACE_SO"]
else
ffi_lib "cconfigspace"
end
class Pointer < FFI::Pointer
def initialize(*args)
if args.length == 2 then
super(CCS::find_type(args[0]), args[1])
else
super(*args)
end
end
end
class MemoryPointer < FFI::MemoryPointer
def initialize(size, count = 1, clear = true)
if size.is_a?(Symbol)
size = CCS::find_type(size)
end
super(size, count, clear)
end
end
typedef :double, :ccs_float_t
typedef :int64, :ccs_int_t
typedef :int32, :ccs_bool_t
typedef :int32, :ccs_result_t
typedef :uint32, :ccs_hash_t
class MemoryPointer
alias read_ccs_float_t read_double
alias read_array_of_ccs_float_t read_array_of_double
alias read_ccs_int_t read_int64
alias read_array_of_ccs_int_t read_array_of_int64
alias read_ccs_bool_t read_int32
alias read_ccs_result_t read_int32
alias read_ccs_hash_t read_uint32
if FFI.find_type(:size_t).size == 8
alias read_size_t read_uint64
else
alias read_size_t read_uint32
end
end
class Version < FFI::Struct
layout :revision, :uint16,
:patch, :uint16,
:minor, :uint16,
:major, :uint16
end
typedef Version.by_value, :ccs_version_t
TRUE = 1
FALSE = 0
typedef :pointer, :ccs_rng_t
typedef :pointer, :ccs_distribution_t
typedef :pointer, :ccs_hyperparameter_t
typedef :pointer, :ccs_expression_t
typedef :pointer, :ccs_context_t
typedef :pointer, :ccs_configuration_space_t
typedef :pointer, :ccs_configuration_t
typedef :pointer, :ccs_objective_space_t
typedef :pointer, :ccs_evaluation_t
typedef :pointer, :ccs_tuner_t
typedef :pointer, :ccs_object_t
Error = enum FFI::Type::INT32, :ccs_error_t, [
:CCS_SUCCESS,
:CCS_INVALID_OBJECT,
:CCS_INVALID_VALUE,
:CCS_INVALID_TYPE,
:CCS_INVALID_SCALE,
:CCS_INVALID_DISTRIBUTION,
:CCS_INVALID_HYPERPARAMETER,
:CCS_INVALID_CONFIGURATION,
:CCS_INVALID_NAME,
:CCS_INVALID_CONDITION,
:CCS_INVALID_GRAPH,
:CCS_TYPE_NOT_COMPARABLE,
:CCS_INVALID_BOUNDS,
:CCS_OUT_OF_BOUNDS,
:CCS_SAMPLING_UNSUCCESSFUL,
:CCS_INACTIVE_HYPERPARAMETER,
:CCS_OUT_OF_MEMORY,
:CCS_UNSUPPORTED_OPERATION ]
ObjectType = enum FFI::Type::INT32, :ccs_object_type_t, [
:CCS_RNG,
:CCS_DISTRIBUTION,
:CCS_HYPERPARAMETER,
:CCS_EXPRESSION,
:CCS_CONFIGURATION_SPACE,
:CCS_CONFIGURATION,
:CCS_OBJECTIVE_SPACE,
:CCS_EVALUATION,
:CCS_TUNER ]
class MemoryPointer
def read_ccs_object_type_t
ObjectType.from_native(read_int32, nil)
end
end
DataType = enum FFI::Type::INT64, :ccs_data_type_t, [
:CCS_NONE,
:CCS_INTEGER,
:CCS_FLOAT,
:CCS_BOOLEAN,
:CCS_STRING,
:CCS_INACTIVE,
:CCS_OBJECT ]
NumericType = enum FFI::Type::INT64, :ccs_numeric_type_t, [
:CCS_NUM_INTEGER, DataType.to_native(:CCS_INTEGER, nil),
:CCS_NUM_FLOAT, DataType.to_native(:CCS_FLOAT, nil) ]
class MemoryPointer
def read_ccs_numeric_type_t
NumericType.from_native(read_int32, nil)
end
end
class Numeric < FFI::Union
layout :f, :ccs_float_t,
:i, :ccs_int_t
end
typedef Numeric.by_value, :ccs_numeric_t
class Value < FFI::Union
layout :f, :ccs_float_t,
:i, :ccs_int_t,
:s, :string,
:o, :ccs_object_t
end
typedef Value.by_value, :ccs_value_t
class Datum < FFI::Struct
layout :value, :ccs_value_t,
:type, :ccs_data_type_t
end
typedef Datum.by_value, :ccs_datum_t
attach_function :ccs_init, [], :ccs_result_t
attach_function :ccs_get_version, [], :ccs_version_t
attach_function :ccs_retain_object, [:ccs_object_t], :ccs_result_t
attach_function :ccs_release_object, [:ccs_object_t], :ccs_result_t
attach_function :ccs_object_get_type, [:ccs_object_t, :pointer], :ccs_result_t
attach_function :ccs_object_get_refcount, [:ccs_object_t, :pointer], :ccs_result_t
class << self
alias version ccs_get_version
end
def self.error_check(result)
if result < 0
raise StandardError, Error.from_native(-result, nil)
end
end
def self.init
res = ccs_init
error_check(res)
self
end
class Object
class Releaser
def initialize(handle)
@handle = handle
end
def call(id)
CCS.ccs_release_object(@handle)
end
end
attr_reader :handle
def initialize(handle, retain: false)
@handle = handle
if retain
res = CCS.ccs_retain_object(handle)
CCS.error_check(res)
end
ObjectSpace.define_finalizer(self, Releaser::new(handle))
end
def object_type
ptr = MemoryPointer::new(:ccs_object_type_t)
res = CCS.ccs_object_get_type(@handle, ptr)
CCS.error_check(res)
return ptr.read_ccs_object_type_t
end
def refcount
ptr = MemoryPointer::new(:int32)
res = CCS.ccs_object_get_refcount(@handle, ptr)
CCS.error_check(res)
return ptr.read_int32
end
def to_ptr
@handle
end
def self.add_property(name, type, accessor, memoize: false)
src = ""
src << "def #{name}\n"
src << " @#{name} ||= begin\n" if memoize
src << " ptr = MemoryPointer::new(:#{type})\n"
src << " res = CCS.#{accessor}(@handle, ptr)\n"
src << " CCS.error_check(res)\n"
src << " ptr.read_#{type}\n"
src << " end\n" if memoize
src << "end\n"
class_eval src
end
end
end
module CCS
DistributionType = enum FFI::Type::INT32, :ccs_distribution_type_t, [
:CCS_UNIFORM,
:CCS_NORMAL,
:CCS_ROULETTE
]
class MemoryPointer
def read_ccs_distribution_type_t
DistributionType.from_native(read_int32, nil)
end
end
ScaleType = enum FFI::Type::INT32, :ccs_scale_type_t, [
:CCS_LINEAR,
:CCS_LOGARITHMIC
]
class MemoryPointer
def read_ccs_scale_type_t
ScaleType.from_native(read_int32, nil)
end
end
attach_function :ccs_distribution_get_type, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_get_data_type, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_get_dimension, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_get_scale_type, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_get_quantization, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_get_bounds, [:ccs_distribution_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_check_oversampling, [:ccs_distribution_t, Interval.by_ref], :ccs_result_t
attach_function :ccs_distribution_sample, [:ccs_distribution_t, :ccs_rng_t, :pointer], :ccs_result_t
attach_function :ccs_distribution_samples, [:ccs_distribution_t, :ccs_rng_t, :size_t, :pointer], :ccs_result_t
class Distribution < Object
add_property :type, :ccs_distribution_type_t, :ccs_distribution_get_type, memoize: true
add_property :data_type, :ccs_numeric_type_t, :ccs_distribution_get_data_type, memoize: true
add_property :dimension, :size_t, :ccs_distribution_get_dimension, memoize: true
add_property :scale_type, :ccs_scale_type_t, :ccs_distribution_get_scale_type, memoize: true
def initialize(handle, retain: false)
if !handle
raise StandardError, :CCS_INVALID_OBJECT
end
super
end
def quantization
@quantization ||= begin
ptr = MemoryPointer::new(:ccs_numeric_t)
res = CCS.ccs_distribution_get_quantization(@handle, ptr)
CCS.error_check(res)
if data_type == :CCS_NUM_FLOAT
ptr.read_ccs_float_t
else
ptr.read_ccs_int_t
end
end
end
def bounds
@bounds ||= begin
interval = Interval::new(type: :CCS_NUM_FLOAT)
res = CCS.ccs_distribution_get_bounds(@handle, interval)
CCS.error_check(res)
interval
end
end
def oversampling?(interval)
ptr = MemoryPointer::new(:ccs_bool_t)
res = CCS.ccs_distribution_check_oversampling(@handle, interval, ptr)
CCS.error_check(res)
ptr.read_ccs_bool_t == CCS::FALSE ? false : true
end
def sample(rng)
ptr = MemoryPointer::new(:ccs_numeric_t)
res = CCS.ccs_distribution_sample(@handle, rng, ptr)
CCS.error_check(res)
if data_type == :CCS_NUM_FLOAT
ptr.read_ccs_float_t
else
ptr.read_ccs_int_t
end
end
def samples(rng, count)
ptr = MemoryPointer::new(:ccs_numeric_t, count)
res = CCS.ccs_distribution_samples(@handle, rng, count, ptr)
CCS.error_check(res)
if data_type == :CCS_NUM_FLOAT
ptr.read_array_of_ccs_float_t(count)
else
ptr.read_array_of_ccs_int_t(count)
end
end
end
attach_function :ccs_create_uniform_distribution, [:ccs_numeric_type_t, :ccs_numeric_t, :ccs_numeric_t, :ccs_scale_type_t, :ccs_numeric_t, :pointer], :ccs_result_t
attach_function :ccs_create_uniform_int_distribution, [:ccs_int_t, :ccs_int_t, :ccs_scale_type_t, :ccs_int_t, :pointer], :ccs_result_t
attach_function :ccs_create_uniform_float_distribution, [:ccs_float_t, :ccs_float_t, :ccs_scale_type_t, :ccs_float_t, :pointer], :ccs_result_t
class UniformDistribution < Distribution
def initialize(handle = nil, retain: false, data_type: :CCS_NUM_FLOAT, lower: 0.0, upper: 1.0, scale_type: :CCS_LINEAR, quantization: 0.0)
if handle
super(handle, retain: retian)
else
ptr = MemoryPointer::new(:ccs_distribution_t)
res = if data_type == :CCS_NUM_FLOAT
CCS.ccs_create_uniform_float_distribution(lower, upper, scale_type, quantization, ptr)
else
CCS.ccs_create_uniform_int_distribution(lower, upper, scale_type, quantization, ptr)
end
CCS.error_check(res)
super(ptr.read_pointer, retain: false)
end
end
end
end
module CCS
class Interval < FFI::Struct
layout :type, :ccs_numeric_type_t,
:lower, :ccs_numeric_t,
:upper, :ccs_numeric_t,
:lower_included, :ccs_bool_t,
:upper_included, :ccs_bool_t
def initialize(*args, type:, lower: nil, upper: nil, lower_included: true, upper_included: false)
unless [:CCS_NUM_FLOAT, :CCS_NUM_INTEGER].include?(type)
raise StandardError, :CCS_INVALID_TYPE
end
super(*args)
self[:type] = type
if lower
if type == :CCS_NUM_FLOAT
self[:lower][:f] = lower
else
self[:lower][:i] = lower
end
end
if upper
if type == :CCS_NUM_FLOAT
self[:upper][:f] = upper
else
self[:lower][:i] = upper
end
end
self[:lower_included] = lower_included ? CCS::TRUE : CCS::FALSE
self[:upper_included] = upper_included ? CCS::TRUE : CCS::FALSE
end
def type
return self[:type]
end
def lower
case self[:type]
when :CCS_NUM_FLOAT
self[:lower][:f]
when :CCS_NUM_INTEGER
self[:lower][:i]
else
raise StandardError, :CCS_INVALID_TYPE
end
end
def lower=(v)
case self[:type]
when :CCS_NUM_FLOAT
self[:lower][:f] = v
when :CCS_NUM_INTEGER
self[:lower][:i] = v
else
raise StandardError, :CCS_INVALID_TYPE
end
end
def upper
case self[:type]
when :CCS_NUM_FLOAT
self[:upper][:f]
when :CCS_NUM_INTEGER
self[:upper][:i]
else
raise StandardError, :CCS_INVALID_TYPE
end
end
def upper=(v)
case self[:type]
when :CCS_NUM_FLOAT
self[:upper][:f] = v
when :CCS_NUM_INTEGER
self[:upper][:i] = v
else
raise StandardError, :CCS_INVALID_TYPE
end
end
def lower_included?
self[:lower_included] == CCS::FALSE ? false : true
end
def upper_included?
self[:upper_included] == CCS::FALSE ? false : true
end
def lower_included=(v)
self[:lower_included] = v ? CCS::TRUE : CCS::FALSE
end
def upper_included=(v)
self[:upper_included] = v ? CCS::TRUE : CCS::FALSE
end
def empty?
ptr = MemoryPointer::new(:ccs_bool_t)
res = CCS.ccs_interval_empty(self, ptr)
CCS.error_check(res)
ptr.read_ccs_bool_t == CCS::FALSE ? false : true
end
def intersect(other)
intersection = Interval::new(type: :CCS_NUM_FLOAT)
res = CCS.ccs_interval_intersect(self, other, intersection)
CCS.error_check(res)
return intersection
end
def ==(other)
ptr = MemoryPointer::new(:ccs_bool_t)
res = CCS.ccs_interval_equal(self, other, ptr)
CCS.error_check(res)
ptr.read_ccs_bool_t == CCS::FALSE ? false : true
end
def include?(v)
n = Numeric::new
case self[:type]
when :CCS_NUM_FLOAT
n[:f] = v
when :CCS_NUM_INTEGER
n[:i] = v
else
raise StandardError, :CCS_INVALID_TYPE
end
res = CCS.ccs_interval_include(self, n)
res == CCS::FALSE ? false : true
end
end
typedef Interval.by_value, :ccs_interval_t
attach_function :ccs_interval_empty, [Interval.by_ref, :pointer], :ccs_result_t
attach_function :ccs_interval_intersect, [Interval.by_ref, Interval.by_ref, Interval.by_ref], :ccs_result_t
attach_function :ccs_interval_equal, [Interval.by_ref, Interval.by_ref, :pointer], :ccs_result_t
attach_function :ccs_interval_include, [Interval.by_ref, :ccs_numeric_t], :ccs_bool_t
end
module CCS
attach_function :ccs_rng_create, [:pointer], :ccs_result_t
attach_function :ccs_rng_set_seed, [:ccs_rng_t, :ulong], :ccs_result_t
attach_function :ccs_rng_get, [:ccs_rng_t, :pointer], :ccs_result_t
attach_function :ccs_rng_min, [:ccs_rng_t, :pointer], :ccs_result_t
attach_function :ccs_rng_max, [:ccs_rng_t, :pointer], :ccs_result_t
class Rng < Object
add_property :min, :ulong, :ccs_rng_min, memoize: true
add_property :max, :ulong, :ccs_rng_max, memoize: true
def initialize(handle = nil, retain: false)
if handle
super
else
ptr = MemoryPointer::new(:ccs_rng_t)
res = CCS.ccs_rng_create(ptr)
CCS.error_check(res)
super(ptr.read_pointer, retain: false)
end
end
def seed=(s)
res = CCS.ccs_rng_set_seed(@handle, s)
CCS.error_check(res)
s
end
def get
ptr = MemoryPointer::new(:ulong)
res = CCS.ccs_rng_get(@handle, ptr)
CCS.error_check(res)
ptr.read_ulong
end
def uniform
ptr = MemoryPointer::new(:ccs_float_t)
res = CCS.ccs_rng_get(@handle, ptr)
CCS.error_check(res)
ptr.read_ccs_float_t
end
end
end
require 'rake/testtask'
Rake::TestTask.new do |t|
t.pattern = "test/test*.rb"
end
[ '../lib', 'lib' ].each { |d| $:.unshift(d) if File::directory?(d) }
require 'minitest/autorun'
require 'cconfigspace'
class CConfigSpaceTest < Minitest::Test
def setup
CCS.init
end
def test_version
ver = CCS.version
assert(ver.kind_of?(CCS::Version))
end
end
[ '../lib', 'lib' ].each { |d| $:.unshift(d) if File::directory?(d) }
require 'minitest/autorun'
require 'cconfigspace'
class CConfigSpaceTestDistribution < Minitest::Test
def setup
CCS.init
end
def test_create_uniform
d = CCS::UniformDistribution::new
assert( d.object_type == :CCS_DISTRIBUTION )
assert( d.type == :CCS_UNIFORM )
assert( d.data_type == :CCS_NUM_FLOAT )
assert( d.scale_type == :CCS_LINEAR )
assert( d.dimension == 1 )
i = d.bounds
assert_equal( :CCS_NUM_FLOAT, i.type)
assert_equal( 0.0, i.lower)
assert_equal( 1.0, i.upper)
assert( i.lower_included? )
refute( i.upper_included? )
end
def test_sample_uniform
rng = CCS::Rng::new
d = CCS::UniformDistribution::new
i = d.bounds
v = d.sample(rng)
assert( i.include?(v) )
a = d.samples(rng, 100)
assert_equal(100, a.size)
a.each { |w|
assert( i.include?(w) )
}
end
end
[ '../lib', 'lib' ].each { |d| $:.unshift(d) if File::directory?(d) }
require 'minitest/autorun'