Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding platform specific build for python wheel #7

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Test Wheel Build
on: [push, pull_request]

jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@master
with:
concurrent_skipping: 'same_content'
skip_after_successful_duplicate: 'true'
paths_ignore: '["**/README.md"]'
do_not_skip: '["pull_request"]'

build-and-check:
needs: pre_job
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
name: Build and Check ${{ matrix.os }} ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]

steps:
- uses: actions/checkout@v2
- run: sudo apt update; sudo apt install -y gcc ruby-dev libgsl-dev python3-dev
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: brew install gsl automake
if: ${{ matrix.os == 'macos-latest' }}
# - run: gem install --user-install rake ffi whittle
- run: pip3 install invoke
- run: python3 -m invoke build --install --wheel
working-directory: bindings/python
- run: python3 -m unittest discover -s test/
working-directory: bindings/python

8 changes: 4 additions & 4 deletions .github/workflows/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- run: brew install gsl automake
if: ${{ matrix.os == 'macos-latest' }}
- run: gem install --user-install rake ffi whittle
- run: pip3 install --user parglare==0.12.0
- run: pip3 install --user parglare==0.12.0 packaging
- run: ./autogen.sh
- run: mkdir -p build
- run: ../configure --enable-strict
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
- run: sudo apt-get update
- run: sudo apt-get install -y gcc g++ ruby-dev libgsl-dev python3-dev
- run: gem install --user-install rake ffi whittle
- run: pip3 install --user parglare==0.12.0
- run: pip3 install --user parglare==0.12.0 packaging
- run: ./autogen.sh
- run: mkdir -p build
- run: ../configure --enable-strict CC=g++
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
- run: sudo apt-get update
- run: sudo apt-get install -y clang ruby-dev libgsl-dev python3-dev
- run: gem install --user-install rake ffi whittle
- run: pip3 install --user parglare==0.12.0
- run: pip3 install --user parglare==0.12.0 packaging
- run: ./autogen.sh
- run: mkdir -p build
- run: ../configure CC=clang
Expand Down Expand Up @@ -287,7 +287,7 @@ jobs:
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: brew install gsl automake
if: ${{ matrix.os == 'macos-latest' }}
- run: pip3 install --user parglare==0.12.0
- run: pip3 install --user parglare==0.12.0 packaging
- run: ./autogen.sh
- run: mkdir -p build
- run: ../configure --enable-strict
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ Makefile
#Misc
build*
src/config.h.in
.vscode/settings.json
1 change: 1 addition & 0 deletions bindings/python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__pycache__
cconfigspace.egg-info/
dist
VERSION
3 changes: 2 additions & 1 deletion bindings/python/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
all:

test: FORCE
python3 -m unittest discover -s test/

PYTHONPATH=`pwd`:$$PYTHONPATH python3 -m unittest discover -s test/

FORCE:
39 changes: 39 additions & 0 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# CConfigSpace (Python)

## Installation

### MacOS (x86/arm)

Install the GSL and Automake libraries with Homebrew:

```console
brew install gsl automake
```

<!-- gem install --user-install rake ffi whittle -->

Install `invoke` (Python version of `make`):

```console
pip install invoke
```

Make sure to set up your brew environment correctly to have access to the GSL library:

```console
export LD_LIBRARY_PATH=$(brew --prefix)/lib:$LD_LIBRARY_PATH
export LIBRARY_PATH=$(brew --prefix)/lib:$LIBRARY_PATH
export CPATH=$(brew --prefix)/include:$CPATH
```

Build the CConfigSpace C library:

```console
invoke ccs-build
```

Install the Python binding:

```
pip install -e.
```
64 changes: 57 additions & 7 deletions bindings/python/cconfigspace/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,64 @@
import platform
import ctypes as ct
import os
import platform
import packaging.version
import warnings

from ctypes.util import find_library

# from .__version__ import __version__
__version__ = "0.0.1"


__ccs_versions___ = [
packaging.version.parse(__version__),
packaging.version.parse(__version__)
] # [min, max] ccs versions



if os.environ.get('LIBCCONFIGSPACE_SO_'):
libcconfigspace = ct.cdll.LoadLibrary(os.environ.get('LIBCCONFIGSPACE_SO_'))
# load configspace library

# test input library from the user
libcconfigspace_file = os.environ.get("LIBCCONFIGSPACE_SO_")

if libcconfigspace_file is not None:
# try loading the user specified library
libcconfigspace = ct.cdll.LoadLibrary(libcconfigspace_file)
else:
if platform.uname()[0] == "Linux":
libcconfigspace = ct.cdll.LoadLibrary('libcconfigspace.so.0.0.0')
else:
libcconfigspace = ct.cdll.LoadLibrary('libcconfigspace.dylib')
# test if system library is available (or if the library ws already loaded)
libcconfigspace_file = find_library("libcconfigspace")

# default to package lib
if libcconfigspace_file is None:

# look for the correct lib extension for the current platform
system = platform.uname()[0]
if system == "Linux":
lib_ext = "so.0.0.0"
elif system == "Darwin": # MacOS
lib_ext = "0.dylib"
else: # Windows
lib_ext = "dll"

# create a versioned name for the library (is the library already openened?)
libcconfigspace_file = f"libcconfigspace.{lib_ext}"

# try loading the system library
try:
libcconfigspace = ct.cdll.LoadLibrary(libcconfigspace_file)
except OSError:
# set of path to retrieve build directory
package_dir = os.path.dirname(os.path.abspath(__file__))
libcconfigspace_file = os.path.join(package_dir, f"libcconfigspace.{lib_ext}")
libcconfigspace = ct.cdll.LoadLibrary(libcconfigspace_file)

# check if the ccs library is allowed
from .base import ccs_get_version

libcconfigspace_version = packaging.version.parse(ccs_get_version().short)
if __ccs_versions___[0] > libcconfigspace_version or libcconfigspace_version > __ccs_versions___[1]:
warnings.warn(f"The loaded C-library of CConfigSpace has version {libcconfigspace_version} when it should be >={__ccs_versions___[0]}, <{__ccs_versions___[1]}")

from .base import *
from .rng import *
Expand Down
7 changes: 7 additions & 0 deletions bindings/python/cconfigspace/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os


VERSION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "VERSION")

with open(VERSION_FILE, "r") as f:
__version__ = f.read()
9 changes: 7 additions & 2 deletions bindings/python/cconfigspace/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ class ccs_version(ct.Structure):
("minor", ct.c_ushort),
("major", ct.c_ushort)]

def __str__(self):
return "{}.{}.{}.{}".format(self.major, self.minor, self.patch, self.revision)
def __repr__(self):
return f"{self.major}.{self.minor}.{self.patch}-{self.revision}"

@property
def short(self):
"""Short version of CConfigSpace C-library"""
return f"{self.major}.{self.minor}.{self.patch}"

# Base types
ccs_float = ct.c_double
Expand Down
93 changes: 93 additions & 0 deletions bindings/python/sample/performance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Provides the following type of outputs:

test_linear_space(k=10) -> 0.084 sec ± 0.001
test_linear_space(k=20) -> 0.136 sec ± 0.002
test_hierarchical() -> 0.047 sec ± 0.001
"""
import time
import numpy as np

import cconfigspace as ccs


def repeat(f, *args, repetitions=10, **kwargs):
T = []
for _ in range(repetitions):
t1 = time.time()

f(*args, **kwargs)

duration = time.time() - t1
T.append(duration)

mean_duration = np.mean(T)
std_duration = np.std(T)
return mean_duration, std_duration


def config_to_str(config):
args = ""
args_list = config.get("args", [])
for i, arg in enumerate(args_list):
args += str(arg)
if i < len(args_list) - 1:
args += ", "

kwargs = ""
kwargs_dict = config.get("kwargs", {})
for i, (kw_k, kw_v) in enumerate(kwargs_dict.items()):
kwargs += f"{kw_k}={kw_v}"
if i < len(kwargs_dict) - 1:
kwargs += ", "

if args == "" and kwargs == "":
return f"{config['f'].__name__}()"
elif args != "" and kwargs == "":
return f"{config['f'].__name__}({args})"
elif args == "" and kwargs != "":
return f"{config['f'].__name__}({kwargs})"
else:
return f"{config['f'].__name__}({args}, {kwargs})"


def test_linear_space(k):
"""Test the sampling performance of a configuration space without conditions or forbiddens."""
space_ccs = ccs.ConfigurationSpace()
hps = [
ccs.NumericalHyperparameter(lower=0, upper=1, name=f"x{i}") for i in range(k)
]
space_ccs.add_hyperparameters(hps)

configs = space_ccs.samples(10000)
configs = [c.values for c in configs]


def test_hierarchical():
"""Test the sampling performance of a configuration space conditions."""
space_ccs = ccs.ConfigurationSpace()
x = ccs.OrdinalHyperparameter(values=[0, 1], name="x", default_index=1)
y = ccs.NumericalHyperparameter(lower=0, upper=1, name="y")
z = ccs.CategoricalHyperparameter(values=["0", "1"], name="z")
space_ccs.add_hyperparameters([x, y, z])

space_ccs.set_condition(y, "x == 1")
space_ccs.set_condition(z, "x == 1")

configs = space_ccs.samples(10000)
configs = [c.values for c in configs]


if __name__ == "__main__":

funcs = [
{"f": test_linear_space, "kwargs": {"k": 10}},
{"f": test_linear_space, "kwargs": {"k": 20}},
{"f": test_hierarchical},
]
repetitions = 10

for f_config in funcs:
mean_dur, std_dur = repeat(
f_config["f"], *f_config.get("args", []), **f_config.get("kwargs", {})
)
print(f"{config_to_str(f_config)} -> {mean_dur:.3f} sec ± {std_dur:.3f}")
Loading