Skip to content

Commit

Permalink
Merge pull request #1833 from numbbo/release-2.2.2
Browse files Browse the repository at this point in the history
merge of development branch into master
  • Loading branch information
brockho committed Jan 11, 2019
2 parents ef9e219 + feea380 commit 76612c1
Show file tree
Hide file tree
Showing 101 changed files with 6,174 additions and 6,198 deletions.
41 changes: 7 additions & 34 deletions .circleci/config.yml
Expand Up @@ -86,6 +86,13 @@ jobs:
name: Run coco post-processing tests
command: python3 do.py test-postprocessing
no_output_timeout: 5400
- run:
name: Run coco C
command: python do.py run-c
- run:
name: Run coco C tests
command: python do.py test-c
no_output_timeout: 1800

test_ubuntu_latest_python2:
docker:
Expand Down Expand Up @@ -166,42 +173,9 @@ jobs:
name: Run coco post-processing tests
command: python do.py test-postprocessing
no_output_timeout: 5400

test_cmocka_ubuntu_latest_python2:
docker:
- image: ubuntu:latest
environment:
CC: gcc
working_directory: ~/coco
steps:
- checkout
- run:
name: Update apt-get
command: apt-get update -qy
- run:
name: Install python-dev with apt-get
command: apt-get install -y python-dev
- run:
name: Install pip with apt-get
command: apt-get install -y python-pip
- run:
name: Install build-essential with apt-get
command: apt-get install -y build-essential
- run:
name: Install git with apt-get
command: apt-get install -y git
- run:
name: Install python packages with pip
command: pip install --upgrade numpy setuptools \
six scipy pytest cython
- run:
name: Run coco C
command: python do.py run-c
- run:
name: Install cmocka and mlocate for C tests
command: |
apt-get install -y mlocate libcmocka-dev
updatedb
- run:
name: Run coco C tests
command: python do.py test-c
Expand All @@ -214,4 +188,3 @@ workflows:
jobs:
- test_ubuntu_rolling_python3
- test_ubuntu_latest_python2
- test_cmocka_ubuntu_latest_python2
5 changes: 4 additions & 1 deletion LICENSE
Expand Up @@ -47,5 +47,8 @@ We acknowledge in particular:
* the hypervolume code of the SMS-EMOA from the Shark library in
``code-experiments/examples/bbob-biobj-matlab-smsemoa/``, distributed
under the terms of the GNU General Public License and the non-dominated
sorting part as written by Y. Cao.
sorting part as written by Y. Cao
* the MinUnit code used as the basis for the implementation in
``code-experiments/test/unit-test/minunit_c89.h``, distributed under
the terms of the MIT Licence.

20 changes: 20 additions & 0 deletions code-experiments/build/python/coco_test.py
Expand Up @@ -79,6 +79,25 @@ def testmod(module):
doctest.testmod(module, # optionflags=doctest.ELLIPSIS,
raise_on_error=True)

def best_parameter(f):
f._best_parameter('print')
with open('._bbob_problem_best_parameter.txt', 'rt') as file_:
return [float(s) for s in file_.read().split()]

def run_constrained_suite_test():
from collections import defaultdict
try:
suite = Suite('bbob-constrained', '', '')
except NameError:
return
counts = defaultdict(int)
for f in suite:
counts[-5] += np.any(f.initial_solution < -5)
counts[5] += np.any(f.initial_solution > 5)
counts['c'] += np.any(f.constraint(f.initial_solution) > 0)
counts['b'] += np.any(f.constraint(best_parameter(f)) > 1e-11) # mac: 6.8361219664552603e-12 is the largest value
assert sum(counts.values()) == 0

def run_doctests():
"""Run doctests on "all" modules.
Expand Down Expand Up @@ -120,6 +139,7 @@ def main(args):
run_doctests()
print('doctests done.\nRunning example_experiment:'), sys.stdout.flush()
example_experiment.main()
# run_constrained_suite_test()
for arg in args if args else default_testcases:
if arg is None or arg == 'None':
break
Expand Down
4,554 changes: 2,653 additions & 1,901 deletions code-experiments/build/python/cython/interface.c

Large diffs are not rendered by default.

194 changes: 43 additions & 151 deletions code-experiments/build/python/cython/interface.pyx
Expand Up @@ -7,8 +7,7 @@ cimport numpy as np

from cocoex.exceptions import InvalidProblemException, NoSuchProblemException, NoSuchSuiteException

# known_suite_names = ["bbob", "bbob-biobj", "bbob-biobj-ext", "bbob-constrained"]
known_suite_names = ["bbob", "bbob-biobj", "bbob-biobj-ext"]
known_suite_names = ["bbob", "bbob-biobj"]
_known_suite_names = ["bbob", "bbob-biobj", "bbob-biobj-ext", "bbob-constrained", "bbob-largescale"]

# _test_assignment = "seems to prevent an 'export' error (i.e. induce export) to make this module known under Linux and Windows (possibly because of the leading underscore of _interface)"
Expand Down Expand Up @@ -77,103 +76,7 @@ cdef bytes _bstring(s):
cdef coco_observer_t* _current_observer

cdef class Suite:
"""Suite of benchmark problems.
Input arguments to `Suite` are `name: str`, `instance: str`, `options: str`,
and passed to the respective C code (see `coco.h`).
>>> import cocoex as ex
>>> suite = ex.Suite("bbob", "", "")
>>> f = suite.next_problem()
>>> assert f.number_of_objectives == 1
>>> print("f([1,2]) = %.11f" % f([1,2]))
f([1,2]) = 90.00369408000
Sweeping through all problems is as simple as::
>>> import cocoex as ex
>>> suite = ex.Suite("bbob-biobj", "", "")
>>> observer = ex.Observer("bbob-biobj", "result_folder:doctest")
>>> for fun in suite:
... if fun.index == 0:
... print("Number of objectives %d, %d, %d" %
... (fun.number_of_objectives,
... suite.number_of_objectives[0],
... suite.number_of_objectives[-1]))
... fun.observe_with(observer)
... assert fun.number_of_objectives == suite.number_of_objectives[0]
... # run run run using fun # doctest: +ELLIPSIS
Number of objectives 2, 2, 2...
In the example, an observer was added to produce output data for the
COCO post-processing.
The following example runs the entire bbob2009 benchmark suite
on random search::
>>> import numpy as np
>>> from cocoex import Suite, Observer
...
>>> MAX_FE = 22 # max f-evaluations
>>> def random_search(f, lb, ub, m): # don't use m >> 1e5 with this implementation
... candidates = lb + (ub - lb) * np.random.rand(m, len(lb))
... return candidates[np.argmin([f(x) for x in candidates])]
...
>>> solver = random_search
>>> suite = Suite("bbob", "year:2009", "")
>>> observer = Observer("bbob",
... "result_folder: %s_on_%s" % (solver.__name__, "bbob2009"))
>>> for fun in suite:
... if fun.dimension > 10:
... break
... print('Current problem index = %d' % fun.index)
... fun.observe_with(observer)
... solver(fun, fun.lower_bounds, fun.upper_bounds, MAX_FE)
... # data should be now in the "exdata/random_search_on_bbob2009" folder
... # doctest: +ELLIPSIS
Current problem index = 0...
>>> #
>>> # Exactly the same using another looping technique:
>>> for id in suite.ids():
... fun = suite.get_problem(id, observer)
... _ = solver(fun, fun.lower_bounds, fun.upper_bounds, MAX_FE)
... print("Evaluations on %s: %d" % (fun.name, fun.evaluations))
... fun.free() # this is absolutely necessary here
... # doctest: +ELLIPSIS
Evaluations on ...
We can select a single function, say BBOB f9 in 20D, of a given suite like::
>>> import cocoex as ex
>>> suite = ex.Suite("bbob", "", "dimensions:20 instance_indices:1")
>>> len(suite)
24
>>> f9 = suite.get_problem(8)
>>> x = f9.initial_solution # a copy of a feasible point
>>> all(x == 0)
True
See module attribute `cocoex.known_suite_names` for known suite names::
>>> import cocoex as ex
>>> for suite_name in ex.known_suite_names:
... suite = ex.Suite(suite_name, "", "")
... print(suite.dimensions)
... for f in suite:
... assert f.dimension in suite.dimensions
... # doctest: +ELLIPSIS
[2, 3, 5, 10, 20, 40]...
See file `example_experiment.py` for a full example use case.
Details: depending on the benchmark suite and observer, only one problem can
be open at a time. Using `get_problem` without `free` or mixing the use of
`next_problem` and `get_problem` may not be possible. For example, in this
case the "bbob" observer is known to lead to a crash of the Python
interpreter.
See also `Observer` and `example_experiment.py`.
"""
"""see __init__.py"""
cdef coco_suite_t* suite # AKA _self
cdef coco_problem_t* _current_problem
cdef bytes _name # used in @property name
Expand Down Expand Up @@ -480,7 +383,7 @@ also report back a missing name to https://github.com/numbbo/coco/issues
return list(self._indices)
@property
def name(self):
"""name of this suite as used to instantiate the suite via `Suite(name, ...)`"""
"""see __init__.py"""
return self._name
@property
def instance(self):
Expand Down Expand Up @@ -523,8 +426,10 @@ also report back a missing name to https://github.com/numbbo/coco/issues
try:
problem = s.next_problem()
if problem is None:
return # StopIteration is deprecated
raise StopIteration
except NoSuchProblemException:
return # StopIteration is deprecated
raise StopIteration
yield problem
except:
Expand All @@ -533,34 +438,7 @@ also report back a missing name to https://github.com/numbbo/coco/issues
s is self or s.free()

cdef class Observer:
"""Observer which can be "attached to" one or several problems, however not
necessarily at the same time.
The typical observer records data to be used in the COCO post-processing
module `cocopp` afterwards.
>>> import cocoex as ex
>>> suite = ex.Suite("bbob", "", "")
>>> assert len(suite) == 2160
>>> f = suite.get_problem(33)
>>> assert f.id.endswith('f003_i04_d02')
>>> observer = ex.Observer("bbob",
... "result_folder: doctest")
>>> f.observe_with(observer) # the same as observer.observe(f) # doctest: +ELLIPSIS
<cocoex...
>>> # work work work with observed f
>>> f.free()
Details:
- `f.free()` in the above example must be called before to observe
another problem with the "bbob" observer. Otherwise the Python
interpreter will crash due to an error raised from the C code.
- Due to technical sublties between Python/Cython/C, the pointer to the
underlying C observer is passed by global assignment with
`_update_current_observer_global()`
"""
"""see __init__.py"""
cdef coco_observer_t* _observer
cdef bytes _name
cdef bytes _options
Expand Down Expand Up @@ -624,11 +502,7 @@ cdef Problem_init(coco_problem_t* problem, free=True, suite_name=None):
res._suite_name = suite_name
return res._initialize(problem, free)
cdef class Problem:
"""`Problem` instances are usually generated using `Suite`.
The main feature of a problem instance is that it is callable, returning the
objective function value when called with a candidate solution as input.
"""
"""see __init__.py"""
cdef coco_problem_t* problem
cdef np.ndarray y_values # argument for coco_evaluate
cdef np.ndarray constraint_values # argument for coco_evaluate
Expand All @@ -643,6 +517,7 @@ cdef class Problem:
cdef _list_of_observers # for the record
cdef _problem_index # for the record, this is not public but used in index property
cdef _do_free
cdef _initial_solution_proposal_calls
cdef initialized
def __cinit__(self):
cdef np.npy_intp shape[1]
Expand All @@ -667,6 +542,7 @@ cdef class Problem:
self.y_values = np.zeros(self._number_of_objectives)
self.constraint_values = np.zeros(self._number_of_constraints)
self.x_initial = np.zeros(self._number_of_variables)
self._initial_solution_proposal_calls = 0
## FIXME: Inefficient because we copy the bounds instead of
## sharing the data.
self._lower_bounds = -np.inf * np.ones(self._number_of_variables)
Expand All @@ -681,10 +557,7 @@ cdef class Problem:
self.initialized = True
return self
def constraint(self, x):
"""return constraint values for `x`.
By convention, constraints with values <= 0 are considered as satisfied.
"""
"""see __init__.py"""
if self.number_of_constraints <= 0:
return # return None, prevent Python kernel from dying
# or should we return `[]` for zero constraints?
Expand Down Expand Up @@ -800,15 +673,15 @@ cdef class Problem:
"""
if restart_number is None:
try:
self._initial_solution_proposal_calls += 1
except AttributeError:
self._initial_solution_proposal_calls = 0
restart_number = self._initial_solution_proposal_calls
if restart_number <= 0 or self.number_of_constraints > 0:
self._initial_solution_proposal_calls += 1 # count calls without explicit argument
if restart_number <= 0:
return self.initial_solution
return self.lower_bounds + (self.upper_bounds - self.lower_bounds) * (
np.random.rand(self.dimension) + np.random.rand(self.dimension)) / 2
rv_triangular = np.random.rand(self.dimension) + np.random.rand(self.dimension)
if self.number_of_constraints > 0:
return self.initial_solution + 1.0 * (rv_triangular - 1)
return self.lower_bounds + rv_triangular * (
self.upper_bounds - self.lower_bounds) / 2
@property
def initial_solution(self):
"""return feasible initial solution"""
Expand Down Expand Up @@ -934,6 +807,31 @@ cdef class Problem:
if self.problem is not NULL:
return coco_problem_get_id(self.problem)

def _parse_id(self, substr):
"search `substr` in `id` and return converted `int` up to '_'"
if self.problem is NULL:
return None
i = self.id.find(substr)
if i < 0:
raise ValueError()
return int(self.id[i + len(substr):].split('_')[0])

@property
def id_function(self):
"see __init__.py"
try:
return self._parse_id('_f')
except ValueError:
raise ValueError("cannot deduce function id from '%s'" % self.id)

@property
def id_instance(self):
"see __init__.py"
try:
return self._parse_id('_i')
except ValueError:
raise ValueError("cannot deduce instance id from '%s'" % self.id)

@property
def name(self):
if self.problem is not NULL:
Expand All @@ -951,13 +849,7 @@ cdef class Problem:

@property
def info(self):
"""human readible info, alias for ``str(self)``.
The format of this info string is not guarantied and may change
in future.
See also: ``repr(self)``
"""
"""see __init__.py"""
return str(self)

def __str__(self):
Expand Down

0 comments on commit 76612c1

Please sign in to comment.