Commit bbc258cb authored by Lionel's avatar Lionel

Internal/wheels with act rebased

parent afd6eaca
stages:
- build
- test
- package
cache:
untracked: true
untracked: false
################################################################################
# TEMPLATES
......@@ -12,6 +17,10 @@ cache:
variables:
CC: "/usr/bin/gcc-5"
CXX: "/usr/bin/g++-5"
cache:
key: gcc5
paths:
- build/
.clang-3_8: &clang-3_8
tags:
......@@ -20,6 +29,10 @@ cache:
variables:
CC: "/usr/bin/clang"
CXX: "/usr/bin/clang++"
cache:
key: clang38
paths:
- build/
.clang-llvm-8_0: &clang-llvm-8_0
tags:
......@@ -28,6 +41,10 @@ cache:
variables:
CC: "/usr/bin/clang"
CXX: "/usr/bin/clang++"
cache:
key: llvm80
paths:
- build/
.mvsc: &mvsc
tags:
......@@ -36,6 +53,22 @@ cache:
variables:
PATH: "C:\\Program Files\\Git\\bin;C:\\ProgramData\\Anaconda3;C:\\ProgramData\\Anaconda3\\Library\\mingw-w64\\bin;C:\\ProgramData\\Anaconda3\\Library\\usr\\bin;C:\\ProgramData\\Anaconda3\\Library\\bin;C:\\ProgramData\\Anaconda3\\Scripts;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--mvsc -m BASE+BN+FMDP+ID+LEARNING+PRM -d build"
cache:
key: mvsc
paths:
- build/
.mvsc32: &mvsc32
tags:
- windows
- anaconda
variables:
PATH: "C:\\Program Files\\Git\\bin;C:\\ProgramData\\Anaconda3;C:\\ProgramData\\Anaconda3\\Library\\mingw-w64\\bin;C:\\ProgramData\\Anaconda3\\Library\\usr\\bin;C:\\ProgramData\\Anaconda3\\Library\\bin;C:\\ProgramData\\Anaconda3\\Scripts;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--mvsc32 -m BASE+BN+FMDP+ID+LEARNING+PRM"
cache:
key: mvsc32
paths:
- build/
.agrum_sh: &agrum_sh
script:
......@@ -53,6 +86,27 @@ cache:
script:
- python act --no-fun test release pyAgrum -d build %ACT_OPTIONS% || python act --no-fun clean && python act --no-fun test release pyAgrum %ACT_OPTIONS%
.wheel_py2_sh: &wheel_py2_sh
script:
- mkdir -p wheel
- virtualenv py2 --python=$(which python2) || rm -rf py2 && virtualenv py2 --python=$(which python2)
- source py2/bin/activate
- python act --no-fun wheel release pyAgrum -d $(pwd)/wheel --python=2 || python act --no-fun clean && python act --no-fun wheel release pyAgrum -d $(pwd)/wheel --python=2
- deactivate
.wheel_py3_sh: &wheel_py3_sh
script:
- mkdir -p wheel
- virtualenv py3 --python=$(which python3) || rm -rf py3 && virtualenv py3 --python=$(which python3)
- source py3/bin/activate
- python act --no-fun wheel release pyAgrum -d $(pwd)/wheel --python=3 || python act --no-fun clean && python act --no-fun wheel release pyAgrum -d $(pwd)/wheel --python=3
- deactivate
.wheel_cmd: &wheel_cmd
script:
- mkdir -p wheel
- python act --no-fun wheel release pyAgrum -d %cd%/wheel %ACT_OPTIONS% || python act --no-fun clean && python act --no-fun wheel release pyAgrum -d %cd%/wheel %ACT_OPTIONS%
################################################################################
# BUILD JOBS
################################################################################
......@@ -132,4 +186,109 @@ macos_clang_pyagrum:
################################################################################
# DEPLOY JOBS
################################################################################
.wheel_artifact: &wheel_artifact
artifacts:
when: on_success
expire_in: 1 week
paths:
- wheel/*.whl
.pyAgrum_wheel_template: &pyAgrum_wheel_template
except:
- /^doc\/.*$/
only:
- [email protected]/aGrUM
stage: package
linux_wheel_pyagrum2:
tags:
- python2
- linux
<<: *pyAgrum_wheel_template
<<: *wheel_py2_sh
<<: *wheel_artifact
linux_wheel_pyagrum3:
tags:
- python3
- linux
<<: *pyAgrum_wheel_template
<<: *wheel_py3_sh
<<: *wheel_artifact
macos_wheel_pyagrum2:
tags:
- python2
- macos
<<: *pyAgrum_wheel_template
<<: *pyAgrum_wheel_template
<<: *wheel_py2_sh
<<: *wheel_artifact
macos_wheel_pyagrum3:
tags:
- python3
- macos
<<: *pyAgrum_wheel_template
<<: *wheel_py3_sh
<<: *wheel_artifact
windows_wheel_32_py27:
tags:
- conda32
- windows
variables:
PATH: "C:\\Users\\conda32\\Anaconda3\\envs\\py27;C:\\Users\\conda32\\Anaconda3\\envs\\py27\\Scripts;C:\\Program Files\\Git\\bin;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--python=2 --mvsc32"
cache:
key: py27_x86
paths:
- build/
<<: *pyAgrum_wheel_template
<<: *wheel_cmd
<<: *wheel_artifact
windows_wheel_32_py36:
tags:
- conda32
- windows
variables:
PATH: "C:\\Users\\conda32\\Anaconda3\\envs\\py36;C:\\Users\\conda32\\Anaconda3\\envs\\py36\\Scripts;C:\\Program Files\\Git\\bin;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--python=3 --mvsc32"
cache:
key: py36_x86
paths:
- build/
<<: *pyAgrum_wheel_template
<<: *wheel_cmd
<<: *wheel_artifact
windows_wheel_64_py27:
tags:
- conda64
- windows
variables:
PATH: "C:\\Users\\conda64\\Anaconda3\\envs\\py27;C:\\Users\\conda64\\Anaconda3\\envs\\py27\\Scripts;C:\\Program Files\\Git\\bin;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--python=2 --mvsc"
cache:
key: py27_AMD64
paths:
- build/
<<: *pyAgrum_wheel_template
<<: *wheel_cmd
<<: *wheel_artifact
windows_wheel_64_py36:
tags:
- conda64
- windows
variables:
PATH: "C:\\Users\\conda64\\Anaconda3\\envs\\py36;C:\\Users\\conda64\\Anaconda3\\envs\\py36\\Scripts;C:\\Program Files\\Git\\bin;C:\\Program Files\\CMake\\bin;C:\\Program Files (x86)\\MSBuild\\14.0\\Bin;%PATH%;"
ACT_OPTIONS: "--python=3 --mvsc"
cache:
key: py36_AMD64
paths:
- build/
<<: *pyAgrum_wheel_template
<<: *wheel_cmd
<<: *wheel_artifact
set(AGRUM_VERSION_MAJOR "0")
set(AGRUM_VERSION_MINOR "11")
set(AGRUM_VERSION_PATCH "1.9")
set(ACT_VERSION_MAJOR "2")
set(ACT_VERSION_MINOR "0")
......@@ -79,7 +79,7 @@ def initParams():
cfg.default['build'] = "all"
cfg.default['noSaveParams'] = False
cfg.actions = set("lib test install doc clean show uninstall package autoindent".split())
cfg.actions = set("lib test install doc clean show uninstall package autoindent wheel".split())
cfg.modes = set("debug release".split())
cfg.targets = set("aGrUM pyAgrum jAgrum".split())
cfg.moduleLabels = parseModulesTxt()
......
......@@ -33,6 +33,7 @@ from .oneByOne import checkAgrumMemoryLeaks
from .stats import profileAgrum
from .utils import trace, notif, critic
from .callSphinx import callSphinx
from .wheel_builder import wheel
def isSpecialAction(current):
......@@ -67,6 +68,10 @@ def specialActions(current):
print("")
return True
if current["action"] == "wheel":
wheel(current)
return True
if current["oneByOne"] == True:
trace(current, "Special action [oneByOne]")
checkAgrumMemoryLeaks(current)
......
#!/usr/bin/python
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (C) 2015 by Pierre-Henri WUILLEMIN *
# * {prenom.nom}_at_lip6.fr *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation; either version 2 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program; if not, write to the *
# * Free Software Foundation, Inc., *
# * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
# ***************************************************************************
import sys
import tempfile
import shutil
import fileinput
import warnings
import re
import hashlib
import zipfile
import platform
import os
from os.path import isfile, isdir, join, relpath
from subprocess import check_call, CalledProcessError, PIPE, Popen, STDOUT
from .utils import notif, warn, critic
FOUND_WHEEL=True
try:
import wheel.pep425tags as pep425
except ImportError:
FOUND_WHEEL=False
def wheel(current):
"""If the current Python version used differs from the one asked, fork into
the proper Python interpreter."""
if FOUND_WHEEL:
this = sys.version_info[0]
target = current["python"]
if str(this) != str(target):
warn("Cannot build pyAgrum's wheel for Python{0} when invoking act with Python{1}.".format(target, this))
critic("Please call act with Python{0} to build pyAgrum under Python{0}.".format(target))
go(current)
else:
critic("Please install package wheel to build wheels using act (pip install wheel).")
def go(current):
"""Get a temporary directory to build the wheel and cal sequentially all steps
to build the wheel."""
tmp = tempfile.mkdtemp(prefix='act')
notif('Building wheel in {0}'.format(tmp))
try:
prepare(current, tmp)
notif("Finished building pyAgrum.")
install_dir, version = build_wheel(tmp)
notif("Finished building wheel directory.")
zip_file = zip_wheel(tmp, install_dir, version)
notif("Finished zipping wheel.")
shutil.move(join(tmp, zip_file), join(current['destination'], zip_file))
notif("Wheel moved to: {0}.".format(join(current['destination'], zip_file)))
except CalledProcessError as err:
critic("Failed building pyAgrum", rc=err.returncode)
finally:
print("shutil.rmtree(tmp, True)")
def prepare(current, tmp):
"""Prepare step for building the wheel: builds and install pyAgrum in the temporary
directory and check that this script was called with the same version of Python used
to build pyAgrum."""
version = sys.version_info
this_version = "{0}.{1}.{2}".format(version[0], version[1], version[2])
gum_version = install_pyAgrum(current, tmp)
if gum_version.count('.') == 1:
this_version = "{0}.{1}".format(version[0], version[1])
if this_version != gum_version:
warn("You MUST build wheel with the same Python version used to build pyAgrum.")
warn("Python version used to build the wheel: {0}".format(this_version))
critic("Python version used to build pyAgrum: {0}".format(gum_version))
def safe_windows_path(path):
path = path.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"').replace("(", "\\(").replace(")", "\\)").replace(" ", "\\ ")
return path
def install_pyAgrum(current, tmp):
"""Instals pyAgrum in tmp and return the Python version used to build it."""
targets = 'install release pyAgrum'
version = sys.version_info[0]
options = '--no-fun --withoutSQL -m all -d {0} --python={1}'.format(safe_windows_path(tmp), version)
if platform.system() == "Windows":
cmd = "python"
if current['mvsc']:
options = "{0} --mvsc".format(options)
elif current['mvsc32']:
options = "{0} --mvsc32".format(options)
else:
cmd = sys.executable
#cmd = sys.executable.replace("'", "\\'").replace('"', '\\"')
#cmd = cmd.replace("(", "\\(").replace(")", "\\)").replace(" ", "\\ ")
cmd = '{0} act {1} {2}'.format(cmd, targets, options)
proc = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
out = proc.stdout.readlines()
return get_python_version(out)
def get_python_version(out):
"""Retrieves the Python version from act's output when building pyAgrum."""
version = None
for line in out:
m = ""
encoding = sys.stdout.encoding if sys.stdout.encoding else 'utf-8'
try:
m = re.match('^-- python version : ([23]\.[0-9]+(\.[0-9]+)*).*$', line.decode(encoding))
except UnicodeDecodeError:
# Windows may use latin-1 without saying it
m = re.match('^-- python version : ([23]\.[0-9]+(\.[0-9]+)*).*$', line.decode('latin-1'))
if m:
version = m.group(1)
if version == None:
major = sys.version_info[0]
minor = sys.version_info[1]
micro = sys.version_info[2]
version = "{0}.{1}.{2}".format(major, minor, micro)
notif("Could not find Python version, opting for current Python version: {0})".format(version))
return version
def build_wheel(tmp):
"""Update the WHEEL file with the proper Python version, remove unnecessary
files and generated the RECORD file. Returns the root of the wheel's
directory."""
install_dir = get_base_dir(tmp)
version = get_pyAgrum_version(install_dir)
dist_info = join(install_dir, "pyAgrum-{0}.dist-info".format(version))
write_tag_to_wheel_file(dist_info)
clean_up(install_dir)
write_record_file(install_dir, version)
return install_dir, version
def get_base_dir(tmp):
"""Find the proper directory where pyAgrum is installed (normaly
tmp/lib/pythonX.Y/sites-packages where X.Y is the Python version used to
build pyAgrum)."""
if platform.system() == "Windows":
return join(tmp, "lib", "site-packages")
else:
major = sys.version_info[0]
minor = sys.version_info[1]
return join(tmp, "lib", "python{0}.{1}".format(major,minor), "site-packages")
def get_pyAgrum_version(path):
"""Look up the egg-file in the directory path generated by act when
installing pyAgrum to get pyAgrum's version."""
pattern = '^pyAgrum-([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)-.*$'
files = [f for f in os.listdir(path) if isfile(join(path,f))]
for f in files:
m = re.match(pattern, f)
if m != None:
return m.group(1)
warn("Could not retrieve pyAgrum version.")
return ""
def write_tag_to_wheel_file(dist_info):
"""Adds proper tags using wheel's package implementation of PEP427."""
path = join(dist_info, "WHEEL")
tags = get_tags()
lines = []
try :
with open(path) as f:
lines = [ l.replace("#PYAGRUM_WHEEL_TAGS#", tags) for l in f.readlines() ]
with open(path, "wt") as f:
for line in lines:
f.write(line)
except:
critic("Could not update WHEEL file: {0}".format(path))
def get_tags():
"""Get proper tags using wheel's package implementation of PEP427."""
impl = pep425.get_abbr_impl() + pep425.get_impl_ver()
abi = pep425.get_abi_tag()
arch = pep425.get_platform()
tags = '{0}-{1}-{2}'.format(impl, abi, arch)
return tags
def clean_up(install_dir):
"""Remone unescessary files in isntall_dir (for now, only th egg-info
file)."""
filelist = [ f for f in os.listdir(install_dir) if f.endswith("egg-info") ]
for f in filelist:
try:
os.remove(join(install_dir, f))
except:
warn("Could not remove dir: {0}".format(join(install_dir, f)))
def write_record_file(install_dir, version):
"""Writes the record file."""
files_hash = []
for root, dirs, files in os.walk(install_dir):
for f in files:
try:
path = join(root, f)
sha = sha256_checksum(path)
path = path[len(install_dir):]
files_hash.append("{0},sha256={1}\n".format(path, sha))
except:
critic("Could not compute sha256 for file: {0}".format(join(root, f)))
try:
with open(join(install_dir, "pyAgrum-{0}.dist-info".format(version), "RECORD"), 'w') as f:
for l in files_hash:
f.write(l)
except:
critic("Could not write RECORD file.")
def sha256_checksum(filename, block_size=65536):
"""Returns the sha256 checksum of filename."""
sha256 = hashlib.sha256()
with open(filename, 'rb') as f:
for block in iter(lambda: f.read(block_size), b''):
sha256.update(block)
return sha256.hexdigest()
def zip_wheel(tmp, install_dir, version):
"""Zip all files in install_dir."""
zip_name = "pyAgrum-{0}-{1}.whl".format(version, get_tags())
zipf = zipfile.ZipFile(join(tmp, zip_name), 'w', zipfile.ZIP_DEFLATED)
for root, dirs, files in os.walk(install_dir):
for f in files:
try:
zipf.write(join(install_dir, root, f), relpath(os.path.join(root, f), install_dir))
except:
critic("Could not archive file: {0}".format(join(install_dir, root, f)))
return zip_name
......@@ -130,18 +130,24 @@ file(GLOB GUMLIB_UTILS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/lib/_utils/*.py")
#include("${CMAKE_CURRENT_SOURCE_DIR}/../../VERSION.txt")
set(PYAGRUM_VERSION "${AGRUM_VERSION_MAJOR}.${AGRUM_VERSION_MINOR}.${AGRUM_VERSION_PATCH}")
set(PYAGRUM_EGGFILE "pyAgrum-${PYAGRUM_VERSION}-py${PYTHON_VERSION_MAJOR}.egg-info")
set(PYAGRUM_DISTDIR "pyAgrum-${PYAGRUM_VERSION}.dist-info")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/__init__.in.py" "${CMAKE_CURRENT_BINARY_DIR}/__init__.py")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/functions.in.py" "${CMAKE_CURRENT_BINARY_DIR}/functions.py")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/egg-info.in" "${CMAKE_CURRENT_BINARY_DIR}/${PYAGRUM_EGGFILE}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/METADATA.in" "${CMAKE_CURRENT_BINARY_DIR}/METADATA")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/WHEEL.in" "${CMAKE_CURRENT_BINARY_DIR}/WHEEL")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/__init__.py DESTINATION ${PYTHON_INSTALL}/pyAgrum)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/functions.py DESTINATION ${PYTHON_INSTALL}/pyAgrum)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PYAGRUM_EGGFILE} DESTINATION ${PYTHON_INSTALL})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/METADATA DESTINATION ${PYTHON_INSTALL}/${PYAGRUM_DISTDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/WHEEL DESTINATION ${PYTHON_INSTALL}/${PYAGRUM_DISTDIR})
install(FILES ${GENERATED_PYTHON}/pyAgrum.py DESTINATION ${PYTHON_INSTALL}/pyAgrum)
install(TARGETS ${_PYAGRUMLIB} DESTINATION ${PYTHON_INSTALL}/pyAgrum)
install(FILES ${GUMLIB_FILES} DESTINATION ${PYTHON_INSTALL}/pyAgrum/lib)
install(FILES ${GUMLIB_UTILS_FILES} DESTINATION ${PYTHON_INSTALL}/pyAgrum/lib/_utils)
......
Metadata-Version: 2.0
Name: pyagrum
Version: @[email protected]
Summary: pyAgrum is a Python wrapper for the C++ aGrUM library
Home-page: https://pyagrum.lip6.fr
Author: Pierre-Henri Wuillemin and Christophe Gonzales
Author-email: [email protected]
License: GPL
Keywords: probabilities probabilistic-graphical-models inference diagnosis
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Science/Research
Requires-Dist: numpy (>=1.8.1)
pyAgrum
=======
``pyAgrum`` is a Python wrapper for the Agrum library, to make flexible and
scalable probabilistic graphical models for inference and diagnosis.
Sample code:
.. code:: python
import pyAgrum as gum
bn=gum.BayesNet('WaterSprinkler')
print(bn)
Example
=======
.. code:: python
import pyAgrum as gum
# Creating BayesNet with 4 variables
bn=gum.BayesNet('WaterSprinkler')
print(bn)
# Adding nodes the long way
c=bn.add(gum.LabelizedVariable('c','cloudy ?',2))
print(c)
# Adding nodes the short way
s, r, w = [ bn.add(name, 2) for name in "srw" ]
print (s,r,w)
print (bn)
# Addings arcs c -> s, c -> r, s -> w, r -> w
bn.addArc(c,s)
for link in [(c,r),(s,w),(r,w)]:
bn.addArc(*link)
print(bn)
# Filling CPTs
bn.cpt(c).fillWith([0.5,0.5])
bn.cpt(s)[0,:]=0.5 # equivalent to [0.5,0.5]
bn.cpt(s)[1,:]=[0.9,0.1]
bn.cpt(w)[0,0,:] = [1, 0] # r=0,s=0
bn.cpt(w)[0,1,:] = [0.1, 0.9] # r=0,s=1
bn.cpt(w)[1,0,:] = [0.1, 0.9] # r=1,s=0
bn.cpt(w)[1,1,:] = [0.01, 0.99] # r=1,s=1
bn.cpt(r)[{'c':0}]=[0.8,0.2]
bn.cpt(r)[{'c':1}]=[0.2,0.8]
# Saving BN as a BIF file
gum.saveBN(bn,"WaterSprinkler.bif")
# Loading BN from a BIF file
bn2=gum.loadBN("WaterSprinkler.bif")
# Inference
ie=gum.LazyPropagation(bn)
ie.makeInference()
print (ie.posterior(w))
# Adding evidence
ie.setEvidence({'s': 1, 'c': 0})
ie.makeInference()
print(ie.posterior(w))
ie.setEvidence({'s': [0, 1], 'c': [1, 0]})
ie.makeInference()
print(ie.posterior(w))
LICENSE
=======
Copyright (C) 2005 by Pierre-Henri WUILLEMIN et Christophe GONZALES
{prenom.nom}_at_lip6.fr
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.
Authors
=======
- Pierre-Henri Wuillemin
- Christophe Gonzales