Unverified Commit cd3def9c authored by Rohan Budhiraja's avatar Rohan Budhiraja Committed by GitHub
Browse files

Merge pull request #13 from proyan/devel2

expose code-handler, lang-c, varname; update readme; add example cppadcg
parents 6720db1e 6831073a
name: PyCppAD CI for MacOS/Linux
on:
pull_request:
push:
branches:
- master
- devel
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest"]
steps:
- uses: actions/checkout@v2
- name: Checkout submodules
run: |
git submodule update --init
- uses: conda-incubator/setup-miniconda@v2
with:
activate-environment: pycppad
auto-update-conda: true
environment-file: .github/workflows/conda/conda-env.yml
python-version: 3.8
- name: Install cmake and update conda
shell: bash -l {0}
run: |
conda activate pycppad
conda install cmake -c main
- name: Build PyCppAD
shell: bash -l {0}
run: |
conda activate pycppad
echo $CONDA_PREFIX
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DPYTHON_EXECUTABLE=$(which python3) -DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON
make
make build_tests
export CTEST_OUTPUT_ON_FAILURE=1
make test
make install
- name: Uninstall PyCppAD
shell: bash -l {0}
run: |
cd build
make uninstall
\ No newline at end of file
name: PyCppAD CI compilation
on: [push,pull_request]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
sudo apt install libboost-python-dev libeigen3-dev
. /etc/os-release
sudo tee /etc/apt/sources.list.d/robotpkg.list <<EOF
deb [arch=amd64] http://robotpkg.openrobots.org/wip/packages/debian/pub $UBUNTU_CODENAME robotpkg
deb [arch=amd64] http://robotpkg.openrobots.org/packages/debian/pub $UBUNTU_CODENAME robotpkg
EOF
curl http://robotpkg.openrobots.org/packages/debian/robotpkg.key | sudo apt-key add -
sudo apt update
sudo apt install robotpkg-cppad robotpkg-cppadcodegen robotpkg-py38-eigenpy
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: |
git submodule update --init
export PATH=$PATH:/opt/openrobots/bin
export PYTHONPATH=$PYTHONPATH:/opt/openrobots/lib/python3/site-packages:/opt/openrobots/lib/python3/dist-packages
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/openrobots/lib64:/opt/openrobots/lib/x86_64-linux-gnu:/opt/openrobots/lib/plugin:/opt/openrobots/lib
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/openrobots/lib/pkgconfig:/opt/openrobots/share/pkgconfig:/opt/openrobots/lib/x86_64-linux-gnu/pkgconfig:/opt/openrobots/lib64/pkgconfig
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_PREFIX_PATH=/opt/openrobots -DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
# - name: Test
# working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
# run: ctest -C ${{env.BUILD_TYPE}}
name: PyCppAD CI for Windows
on:
pull_request:
push:
branches:
- master
- devel
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
name: [windows-latest-clang-cl]
include:
- name: windows-latest-clang-cl
os: windows-2019
compiler: clang-cl
steps:
- uses: actions/checkout@v2
- name: Checkout submodules
run: |
git submodule update --init
- uses: goanpeca/setup-miniconda@v1
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
with:
activate-environment: pycppad
environment-file: .github/workflows/conda/conda-env.yml
python-version: 3.7
- name: Install cmake and update conda
run: |
conda install cmake -c main
- name: Build PyCppAD
shell: cmd /C CALL {0}
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
run: |
:: unset extra Boost envs
set Boost_ROOT=
set BOOST_ROOT_1_69_0=
set BOOST_ROOT_1_72_0=
set PATH=%PATH:C:\hostedtoolcache\windows\Boost\1.72.0;=%
call "%programfiles(x86)%\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
:: Create build directory
mkdir build
pushd build
:: Configure
cmake ^
-G "Visual Studio 16 2019" -T "ClangCl" -DCMAKE_GENERATOR_PLATFORM=x64 ^
-DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX%\Library ^
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ^
-DPYTHON_SITELIB=%CONDA_PREFIX%\Lib\site-packages ^
-DPYTHON_EXECUTABLE=%CONDA_PREFIX%\python.exe ^
-DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON ^
..
:: Build
cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install
:: Testing
set PATH=%PATH%;%CONDA_PREFIX%\Lib\site-packages\pycppad
ctest --output-on-failure -C Release -V
:: Test Python import
cd ..
python -c "import pycppad"
name: pycppad
channels:
- conda-forge
- nodefaults
dependencies:
- boost
- eigenpy
- python
- cppad
- cppadcodegen
\ No newline at end of file
......@@ -33,3 +33,5 @@
*~
_build*
*.orig
\ No newline at end of file
......@@ -77,6 +77,9 @@ IF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS)
LIST(APPEND ${PROJECT_NAME}_HEADERS
include/${PROJECT_NAME}/codegen/cg.hpp
include/${PROJECT_NAME}/codegen/cppadcg-scalar.hpp
include/${PROJECT_NAME}/codegen/code-handler.hpp
include/${PROJECT_NAME}/codegen/lang/c/language-c.hpp
include/${PROJECT_NAME}/codegen/lang/c/lang-c-default-var-name-gen.hpp
)
ENDIF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS)
......
......@@ -2,12 +2,57 @@ PyCppAD — Python bindings for CppAD Automatic Differentiation library
======
<p align="center">
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/License-BSD%203--Clause-green.svg" alt="License"/></a>
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/License-BSD%203--Clause-green.svg" alt="License"/></a>
<a href="https://anaconda.org/conda-forge/pycppad"><img src="https://img.shields.io/conda/dn/conda-forge/pycppad.svg" alt="Conda Downloads"/></a>
<a href="https://anaconda.org/conda-forge/pycppad"><img src="https://img.shields.io/conda/vn/conda-forge/pycppad.svg" alt="Conda Version"/></a>
<a href="https://conda.anaconda.org/conda-forge"><img src="https://anaconda.org/conda-forge/pycppad/badges/installer/conda.svg" alt="Anaconda-Server Badge"/></a>
</p>
**PyCppAD** is an open source framework which provides bindings for the CppAD Automatic Differentiation([CppAD](https://coin-or.github.io/CppAD/doc/cppad.htm)) C++ library in Python.
**PyCppAD** also includes support for the CppADCodeGen ([CppADCodeGen](https://github.com/joaoleal/CppADCodeGen)), C++ library, which exploits CppAD functionality to perform code generation.
## Installing PyCppAD
### Installation via <img src="https://s3.amazonaws.com/conda-dev/conda_logo.svg" height="18">
As simple as that:
```bash
conda install pycppad -c conda-forge
```
### Installation by source
#### Dependencies
PyCppAD dependes on the following dependencies:
* [Boost.Python](http://boostorg.github.io/python/doc/html/index.html)
* [Eigen3](https://eigen.tuxfamily.org)
* [EigenPy](https://github.com/stack-of-tasks/eigenpy)
* [CppAD](https://coin-or.github.io/CppAD/doc/install.htm)
* [CppADCodeGen](https://github.com/joaoleal/CppADCodeGen) (Optional)
#### Compiling PyCppAD
Once the dependencies have been installed, follow the following commands to compile PyCppAD:
```
cd ${path_to_your_development_source}/
git clone --recursive https://github.com/Simple-Robotics/pycppad.git
cd pycppad
mkdir _build-RELEASE
cd _build-RELEASE
cmake .. -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=${path_to_your_installation}
make install
```
Set up the `path_to_your_installation` and `path_to_your_development_source` based on your personal development environment.
Alternatively, in order to compile with [CppADCodeGen](https://github.com/joaoleal/CppADCodeGen) support, add `-DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON` to the cmake command, and follow the build sequence as before:
```
cmake .. -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=${path_to_your_installation} -DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON
```
## Acknowledgments
The development of **PyCppAD** is supported by the [Willow team](https://www.di.ens.fr/willow/) [@INRIA](http://www.inria.fr).
ADD_PYTHON_UNIT_TEST("add_eq" "example/add_eq.py" "python")
ADD_PYTHON_UNIT_TEST("cppad_type" "example/cppad_type.py" "python")
IF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS)
ADD_PYTHON_UNIT_TEST("cppadcg_c_codegen" "example/cppadcg_c_codegen.py" "python")
ENDIF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS)
from pycppad import AD, Value, Independent
x = AD(2)
print(x)
print(Value(x))
y = AD(3)
z = x+y
print(z)
z2 = x*y
print(z2)
z3 = x-y
print(z3)
z4 = x/y
print(z4)
import numpy as np
dAD = np.dtype(AD)
x = np.array([AD(1), AD(2)], dtype=dAD)
y = np.array([AD(3), AD(4)], dtype=dAD)
Independent(x)
z = x+y
print(z)
z2 = x-y
print(z2)
from pycppad import AD, ADCG, CG, Independent, Value, ADCGFun, CodeHandler, LanguageC, LangCDefaultVariableNameGenerator
import numpy as np
#/***************************************************************************
# the model
#*************************************************************************/
# independent variable vector
n = 2
x = np.array([ADCG(CG(0.)),]*n)
x[0] = ADCG(CG(2.))
x[1] = ADCG(CG(3.))
Independent(x)
# dependent variable vector
m=1
y = np.array([ADCG(CG(0)),]*m)
#the model
a = x[0] / ADCG(CG(1.)) + x[1] * x[1]
y[0] = a / ADCG(CG(2.))
fun = ADCGFun(x, y); # the model tape
# /***************************************************************************
# * Generate the C source code
# **************************************************************************/
# /**
# * start the special steps for source code generation for a Jacobian
# */
handler = CodeHandler(50)
indVars = np.array([CG(0.)]*n)
handler.makeVariables(indVars)
jac = fun.Jacobian(indVars)
langC = LanguageC("double", 3)
nameGen = LangCDefaultVariableNameGenerator("y","x","v","array","sarray")
code = handler.generateCode(langC, jac, nameGen, "source")
print(code)
/*
* Copyright 2021 INRIA
*/
#ifndef __pycppad_codegen_code_handler_hpp__
#define __pycppad_codegen_code_handler_hpp__
#include <cppad/cg/code_handler.hpp>
#include <cppad/cg/lang/c/language_c.hpp>
#include <cppad/cg/lang/c/lang_c_default_var_name_gen.hpp>
namespace pycppad
{
namespace codegen
{
namespace bp = boost::python;
template<typename Scalar>
class CodeHandlerVisitor
: public bp::def_visitor< CodeHandlerVisitor<Scalar> >
{
public:
typedef ::CppAD::cg::CG<Scalar> CG;
typedef ::CppAD::AD<CG> ADCG;
typedef ::CppAD::AD<Scalar> AD;
typedef Eigen::Matrix<ADCG,Eigen::Dynamic,1> VectorADCG;
typedef Eigen::Matrix<AD,Eigen::Dynamic,1> VectorAD;
typedef Eigen::Matrix<ADCG,1,Eigen::Dynamic> RowVectorADCG;
typedef Eigen::Matrix<CG,Eigen::Dynamic,1> VectorCG;
typedef Eigen::Matrix<CG,1,Eigen::Dynamic> RowVectorCG;
typedef ::CppAD::cg::CodeHandler<Scalar> CodeHandler;
typedef ::CppAD::cg::LanguageC<Scalar> LanguageC;
typedef ::CppAD::cg::LangCDefaultVariableNameGenerator<Scalar> LangCDefaultVariableNameGenerator;
protected:
template<typename VectorType>
static void makeVariables(CodeHandler& self, const VectorType& x)
{
VectorType& x_= const_cast<VectorType&>(x);
self.makeVariables(x_);
return;
}
template<typename VectorType, typename LangType, typename NameGenType>
static std::string generateCode(CodeHandler& self,
LangType& lang,
const VectorType& dependent,
NameGenType& nameGen,
const std::string& jobName)
{
std::ostringstream oss;
VectorType& dependent_= const_cast<VectorType&>(dependent);
::CppAD::cg::ArrayView<typename VectorType::Scalar> dependent_av(dependent_.data(), dependent_.size());
self.generateCode(oss, lang, dependent_av, nameGen, jobName);
return oss.str();
}
public:
template<class PyClass>
void visit(PyClass& cl) const
{
cl
.def(bp::init<size_t>(bp::args("self","varCount")))
.def("setReuseVariableIDs", &CodeHandler::setReuseVariableIDs, bp::args("self", "reuse"))
.def("isReuseVariableIDs", &CodeHandler::isReuseVariableIDs, bp::arg("self"))
.def("makeVariable",
static_cast<void (CodeHandler::*)(CG&)>(&CodeHandler::makeVariable),
bp::args("self", "variable"),
"Marks the provided variable as being an independent variable.\n"
"Parameters:\n"
"\tvariable: the variables that will become independent variable")
.def("makeVariable",
static_cast<void (CodeHandler::*)(ADCG&)>(&CodeHandler::makeVariable),
bp::args("self", "variable"),
"Marks the provided variable as being an independent variable.\n"
"Parameters:\n"
"\tvariable: the variables that will become independent variable"
)
.def("makeVariables",
&makeVariables<VectorCG>,
bp::args("self", "variables"),
"Marks the provided variables as being independent variables.\n"
"Parameters:\n"
"\tvariables: the vector of variables that will become independent variables")
.def("makeVariables",
&CodeHandler::template makeVariables<RowVectorADCG>,
bp::args("self", "variables"),
"Marks the provided variables as being independent variables.\n"
"Parameters:\n"
"\tvariables: the vector of variables that will become independent variables")
.def("makeVariables",
&CodeHandler::template makeVariables<VectorCG>,
bp::args("self", "variables"),
"Marks the provided variables as being independent variables.\n"
"Parameters:\n"
"\tvariables: the vector of variables that will become independent variables")
.def("makeVariables",
&CodeHandler::template makeVariables<RowVectorCG>,
bp::args("self", "variables"),
"Marks the provided variables as being independent variables.\n"
"Parameters:\n"
"\tvariables: the vector of variables that will become independent variables")
.def("getIndependentVariableSize", &CodeHandler::getIndependentVariableSize, bp::arg("self"))
.def("getIndependentVariableIndex", &CodeHandler::getIndependentVariableIndex, bp::args("self", "var"))
.def("getMaximumVariableID", &CodeHandler::getMaximumVariableID, bp::arg("self"))
.def("isVerbose", &CodeHandler::isVerbose, bp::arg("self"))
.def("setVerbose", &CodeHandler::setVerbose, bp::arg("self"))
//.def("getJobTimer", &CodeHandler::getJobTimer, bp::arg("self"))
//.def("setJobTimer", &CodeHandler::setJobTimer, bp::args("self", "jobTimer"))
.def("isZeroDependents", &CodeHandler::isZeroDependents, bp::arg("self"))
.def("setZeroDependents", &CodeHandler::setZeroDependents, bp::args("self", "zeroDependents"))
.def("getOperationTreeVisitId", &CodeHandler::getOperationTreeVisitId, bp::arg("self"))
.def("startNewOperationTreeVisit", &CodeHandler::startNewOperationTreeVisit, bp::arg("self"))
.def("isVisited", &CodeHandler::isVisited, bp::args("self", "node"))
.def("markVisited", &CodeHandler::markVisited, bp::args("self", "node"))
.def("getAtomicFunctionName", &CodeHandler::getAtomicFunctionName, bp::args("self", "id"),
"Provides the name used by an atomic function with a given ID.\n"
"Parameters:\n"
"\tid: the atomic function ID.")
//.def("getExternalFuncMaxForwardOrder", &CodeHandler::getExternalFuncMaxForwardOrder, bp::arg("self"))
//.def("getExternalFuncMaxReverseOrder", &CodeHandler::getExternalFuncMaxReverseOrder, bp::arg("self"))
.def("generateCode", &generateCode<VectorCG, LanguageC, LangCDefaultVariableNameGenerator>,
(bp::arg("self"), bp::arg("lang"), bp::arg("dependent"), bp::arg("nameGen"), bp::arg("jobName")="source"),
"Creates the source code from the operations registered so far.\n"
"Parameters:\n"
"\tlang: The targeted language.\n"
"\tdependent: The dependent variables for which the source code\n"
" should be generated. By defining this vector the \n"
" number of operations in the source code can be\n"
" reduced and thus providing a more optimized code.\n"
"\tnameGen: Provides the rules for variable name creation. data related to the model\n"
"\tjobName: Name of this job.")
.def("generateCode", &generateCode<RowVectorCG, LanguageC, LangCDefaultVariableNameGenerator>,
(bp::arg("self"), bp::arg("lang"), bp::arg("dependent"), bp::arg("nameGen"), bp::arg("jobName")="source"),
"Creates the source code from the operations registered so far.\n"
"Parameters:\n"
"\tlang: The targeted language.\n"
"\tdependent: The dependent variables for which the source code\n"
" should be generated. By defining this vector the \n"
" number of operations in the source code can be\n"
" reduced and thus providing a more optimized code.\n"
"\tnameGen: Provides the rules for variable name creation. data related to the model\n"
"\tjobName: Name of this job.")
;
}
static void expose(const std::string & class_name = "CodeHandler")
{
bp::class_<CodeHandler,
boost::noncopyable>(class_name.c_str(),
"Helper class to analyze the operation graph"
"and generate source code for several languages",
bp::no_init)
.def(CodeHandlerVisitor<Scalar>());
}
};
}
}
#endif //#ifndef __pycppad_codegen_code_handler_hpp__
......@@ -9,6 +9,9 @@
#include <eigenpy/eigenpy.hpp>
#include "pycppad/codegen/cg.hpp"
#include "pycppad/codegen/code-handler.hpp"
#include "pycppad/codegen/lang/c/language-c.hpp"
#include "pycppad/codegen/lang/c/lang-c-default-var-name-gen.hpp"
#include "pycppad/ad.hpp"
#include "pycppad/independent.hpp"
#include "pycppad/ad_fun.hpp"
......@@ -38,6 +41,13 @@ namespace pycppad
pycppad::ADFunVisitor<CGScalar>::expose("ADCGFun");
pycppad::IndependentVisitor<VectorADCG>::expose("Independent");
pycppad::IndependentVisitor<RowVectorADCG>::expose("Independent");
CodeHandlerVisitor<Scalar>::expose("CodeHandler");
LanguageCVisitor<Scalar>::expose("LanguageC");
LangCDefaultVariableNameGeneratorVisitor<Scalar>::expose("LangCDefaultVariableNameGenerator");
}
}
}
......
/*
* Copyright 2021 INRIA
*/
#ifndef __pycppad_codegen_lang_c_lang_c_default_var_name_gen_hpp__
#define __pycppad_codegen_lang_c_lang_c_default_var_name_gen_hpp__
#include <cppad/cg/lang/c/lang_c_default_var_name_gen.hpp>
namespace pycppad
{
namespace codegen
{
template<typename Scalar>
class LangCDefaultVariableNameGeneratorVisitor
: public bp::def_visitor< LangCDefaultVariableNameGeneratorVisitor<Scalar> >
{
public:
typedef ::CppAD::cg::LangCDefaultVariableNameGenerator<Scalar> LangCDefaultVariableNameGenerator;
template<class PyClass>
void visit(PyClass& cl) const
{
cl
.def(bp::init<std::string, std::string, std::string, std::string, std::string>
(bp::args("self","y","x","v","array","sarray")))
;
}
static void expose(const std::string & class_name = "LangCDefaultVariableNameGenerator")
{
bp::class_<LangCDefaultVariableNameGenerator,
boost::noncopyable>(class_name.c_str(),
"Creates variables names for the source code",
bp::no_init)
.def(LangCDefaultVariableNameGeneratorVisitor<Scalar>());
}
};
}
}
#endif //#ifndef __pycppad_codegen_lang_c_lang_c_default_var_name_gen_hpp__