Skip to content

Commit

Permalink
Add optimizer: fides-cpp (#340)
Browse files Browse the repository at this point in the history
Add wrapper and build scripts for https://github.com/dweindl/fides-cpp (C++ rewrite of https://github.com/fides-dev/fides).

So far, only BFGS is supported via HDF5 optimizer options.
  • Loading branch information
dweindl authored Jun 3, 2021
1 parent b1c0eb9 commit d0bd8c1
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 1 deletion.
7 changes: 6 additions & 1 deletion .github/workflows/parpe_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ jobs:
run: |
pip install -r ${PARPE_BASE}/python/requirements.txt
- name: "Install parPE deps: fides"
run: |
sudo apt install libspdlog-dev && ${PARPE_BASE}/ThirdParty/installFides.sh
- name: Configure parPE
run: |
cmake \
Expand All @@ -66,7 +70,8 @@ jobs:
-DIPOPT_INCLUDE_DIRS=/usr/include/coin/ \
-DIPOPT_LIBRARIES=/usr/lib/libipopt.so \
-DGCOV_REPORT=TRUE \
-DBUILD_TESTING=TRUE
-DBUILD_TESTING=TRUE \
-DPARPE_ENABLE_FIDES=TRUE
- name: Build parPE
# with sonar build wrapper
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ set(BUILD_PYTHON_MODULE FALSE CACHE BOOL "Build Python module?")
set(BLAS "CBLAS" CACHE STRING "BLAS library to use")
set_property(CACHE BLAS PROPERTY STRINGS "CBLAS" "MKL")
set(PARPE_ENABLE_MPI TRUE CACHE BOOL "Use MPI?")
set(PARPE_ENABLE_FIDES FALSE CACHE BOOL "Enable fides optimizer?")
set(PARPE_ENABLE_IPOPT TRUE CACHE BOOL "Enable ipopt optimizer?")
set(PARPE_ENABLE_CERES TRUE CACHE BOOL "Enable ceres optimizer?")
set(PARPE_ENABLE_DLIB FALSE CACHE BOOL "Enable dlib optimizers?")
Expand All @@ -68,6 +69,10 @@ if(${PARPE_ENABLE_MPI})
endif(${PARPE_ENABLE_MPI})
# </Build options>

if(${PARPE_ENABLE_FIDES})
find_package(Fides REQUIRED)
endif(${PARPE_ENABLE_FIDES})

find_package(PkgConfig)
pkg_search_module(IPOPT REQUIRED IMPORTED_TARGET GLOBAL ipopt>=3.11.0 )

Expand Down
6 changes: 6 additions & 0 deletions CMakeModules/ParPEConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ include(CMakeFindDependencyMacro)
set(PARPE_ENABLE_MPI @PARPE_ENABLE_MPI@)
set(PARPE_ENABLE_IPOPT @PARPE_ENABLE_IPOPT@)
set(PARPE_ENABLE_CERES @PARPE_ENABLE_CERES@)
set(PARPE_ENABLE_FIDES @PARPE_ENABLE_FIDES@)

if(${PARPE_ENABLE_MPI})
find_dependency(MPI REQUIRED)
Expand All @@ -22,6 +23,11 @@ find_dependency(Ceres COMPONENTS
find_dependency(Eigen3 REQUIRED)
endif()

if(${PARPE_ENABLE_FIDES})
find_dependency(Fides
HINTS "${CMAKE_SOURCE_DIR}/ThirdParty/fides-cpp/build/")
endif()

find_dependency(Amici REQUIRED)
find_dependency(OpenMP)
find_dependency(HDF5 COMPONENTS C CXX HL REQUIRED)
Expand Down
44 changes: 44 additions & 0 deletions ThirdParty/installFides.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Download and build fides-cpp

build_blaze() {
cd "${script_dir}"

if [[ ! -d "${blaze_dir}" ]]; then
if [[ ! -f "blaze-3.8.tar.gz" ]]; then
wget https://bitbucket.org/blaze-lib/blaze/downloads/blaze-3.8.tar.gz
fi
tar -xzf blaze-3.8.tar.gz
cd blaze-3.8
cmake -S . -B build/ -DCMAKE_INSTALL_PREFIX="$(pwd)/build/install"
cd build
make -j2 install
fi
}

build_fides() {
if [ ! -d "${fides_dir}" ]; then
cd "${script_dir}"
git clone https://github.com/dweindl/fides-cpp.git
cd "${fides_dir}"
git checkout 9906bdac6a1966ddd4b37b96f98ad8f89770c128
fi

cd "${fides_dir}"
cmake -S . -B build -DBUILD_TESTING=OFF
cd build
make ${make_opts}
}

set -euo pipefail
script_dir=$(dirname "$0")
script_dir=$(cd "${script_dir}" && pwd)
cd "${script_dir}"

make_opts=${MAKEOPTS-}

fides_dir=${script_dir}/fides-cpp
blaze_dir=${script_dir}/blaze-3.8

build_blaze
build_fides
28 changes: 28 additions & 0 deletions include/parpeoptimization/localOptimizationFides.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef LOCAL_OPTIMIZATION_FIDES_H
#define LOCAL_OPTIMIZATION_FIDES_H

#include <parpeoptimization/optimizationProblem.h>
#include <parpeoptimization/optimizer.h>

namespace parpe {

class OptimizerFides : public Optimizer
{
public:
OptimizerFides() = default;

/**
* @brief Minimize an objective function given as OptimizationProblem using
* fides optimizer
*
* @param problem
* @return .
*/

std::tuple<int, double, std::vector<double>> optimize(
OptimizationProblem* problem) override;
};

} // namespace parpe

#endif
1 change: 1 addition & 0 deletions include/parpeoptimization/optimizationOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class optimizerName {
OPTIMIZER_DLIB,
OPTIMIZER_TOMS611,
OPTIMIZER_FSQP,
OPTIMIZER_FIDES,
OPTIMIZER_MINIBATCH_1 = 10
};

Expand Down
2 changes: 2 additions & 0 deletions src/parpecommon/parpeConfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define PARPE_CONFIG_H

#cmakedefine PARPE_ENABLE_MPI

#cmakedefine PARPE_ENABLE_FIDES
#cmakedefine PARPE_ENABLE_IPOPT
#cmakedefine PARPE_ENABLE_CERES
#cmakedefine PARPE_ENABLE_FSQP
Expand Down
8 changes: 8 additions & 0 deletions src/parpeoptimization/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ set(HEADER_LIST

add_library(${PROJECT_NAME} ${SRC_LIST})

if(${PARPE_ENABLE_FIDES})
target_sources(${PROJECT_NAME}
PRIVATE localOptimizationFides.cpp)
list(APPEND HEADER_LIST
localOptimizationFides.h)
target_link_libraries(${PROJECT_NAME} PUBLIC Fides::fides)
endif(${PARPE_ENABLE_FIDES})

if(${PARPE_ENABLE_IPOPT})
target_sources(${PROJECT_NAME}
PRIVATE localOptimizationIpopt.cpp
Expand Down
162 changes: 162 additions & 0 deletions src/parpeoptimization/localOptimizationFides.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#include <parpeoptimization/localOptimizationFides.h>

#include "parpecommon/logging.h"
#include "parpeoptimization/optimizationOptions.h"
#include "parpeoptimization/optimizationProblem.h"

#include <blaze/math/CustomVector.h>
#include <fides/minimize.hpp>

using blaze::columnVector;
using blaze::CustomVector;
using blaze::DynamicVector;
using blaze::unaligned;
using blaze::unpadded;
using UnalignedUnpadded = CustomVector<int, unaligned, unpadded, columnVector>;

namespace gsl {

template<typename Type, bool TF, typename Alloc, typename Tag>
auto
make_span(DynamicVector<Type, TF, Alloc, Tag>& dv)
{
return gsl::span<Type>(dv.data(), dv.size());
}

template<typename Type, bool TF, typename Alloc, typename Tag>
auto
make_span(DynamicVector<Type, TF, Alloc, Tag> const& dv)
{
return gsl::span<const Type>(dv.data(), dv.size());
}

} // namespace gsl

namespace parpe {

fides::Options
get_optimization_options(OptimizationOptions const& parpe_options)
{
fides::Options fides_options;
fides_options.maxiter = parpe_options.maxOptimizerIterations;

parpe_options.for_each<int>(
[&fides_options](const std::pair<const std::string, const std::string>& pair, int) {
const std::string& key = pair.first;
const std::string& val = pair.second;
auto &options = fides_options;
if (key == "maxiter") {
options.maxiter = std::stoi(val);
} else if (key == "maxtime") {
options.maxtime = std::chrono::seconds(std::stoi(val));
} else if (key == "fatol") {
options.fatol = std::stod(val);
} else if (key == "frtol") {
options.frtol = std::stod(val);
} else if (key == "xtol") {
options.xtol = std::stod(val);
} else if (key == "gatol") {
options.gatol = std::stod(val);
} else if (key == "grtol") {
options.grtol = std::stod(val);
} else if (key == "subspace_solver") {
auto result = std::find_if(
fides::subspace_dim_to_str.cbegin(),
fides::subspace_dim_to_str.cend(),
[key](const auto& kv) { return kv.second == key; });
if (result != fides::subspace_dim_to_str.cend())
options.subspace_solver = result->first;
else
logmessage(LOGLVL_WARNING,
"Invalid value %s provided for option "
"'subspace_solver'. Ignoring.",
val.c_str());
} else if (key == "stepback_strategy") {
auto result = std::find_if(
fides::step_back_strategy_str.cbegin(),
fides::step_back_strategy_str.cend(),
[key](const auto& kv) { return kv.second == key; });
if (result != fides::step_back_strategy_str.cend())
options.stepback_strategy = result->first;
else
logmessage(LOGLVL_WARNING,
"Invalid value %s provided for option "
"'stepback_strategy'. Ignoring.",
val.c_str());
} else if (key == "theta_max") {
options.theta_max = std::stoi(val);
} else if (key == "delta_init") {
options.delta_init = std::stoi(val);
} else if (key == "mu") {
options.mu = std::stoi(val);
} else if (key == "eta") {
options.eta = std::stoi(val);
} else if (key == "gamma1") {
options.gamma1 = std::stoi(val);
} else if (key == "gamma2") {
options.gamma2 = std::stoi(val);
} else if (key == "refine_stepback") {
options.refine_stepback = std::stoi(val);
} else {
logmessage(LOGLVL_WARNING,
"Ignoring unknown optimization option %s.",
key.c_str());
return;
}

logmessage(LOGLVL_DEBUG,
"Set optimization option %s to %s.",
key.c_str(),
val.c_str());
},
0);

return fides_options;
}

std::tuple<int, double, std::vector<double>>
OptimizerFides::optimize(OptimizationProblem* problem)
{
auto reporter = problem->getReporter();
auto numParams =
static_cast<std::size_t>(problem->cost_fun_->numParameters());

DynamicVector<double> x0(numParams);
problem->fillInitialParameters(x0);

DynamicVector<double> lb(numParams);
problem->fillParametersMin(lb);

DynamicVector<double> ub(numParams);
problem->fillParametersMax(ub);

WallTimer optimization_timer;

auto fun = [&problem](DynamicVector<double> x) {
static __thread int numFunctionCalls = 0;
++numFunctionCalls;
DynamicVector<double> g(x.size(), NAN);
double fval = NAN;
problem->cost_fun_->evaluate(
gsl::make_span(x), fval, gsl::make_span(g), nullptr, nullptr);

return std::make_tuple(fval, g, blaze::DynamicMatrix<double>());
};

auto fides_options =
get_optimization_options(problem->getOptimizationOptions());
// TODO to config
fides::BFGS hessian_approximation;
fides::Optimizer optimizer(
fun, lb, ub, fides_options, &hessian_approximation);

reporter->starting(x0);
auto [fval, x, grad, hess] = optimizer.minimize(x0);
reporter->finished(fval, x, static_cast<int>(optimizer.exit_flag_));

return std::make_tuple(static_cast<int>(optimizer.exit_flag_) <= 0,
fval,
std::vector<double>(x.begin(), x.end()));
}

} // namespace parpe
24 changes: 24 additions & 0 deletions src/parpeoptimization/optimizationOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include <parpeoptimization/localOptimizationCeres.h>
#endif

#ifdef PARPE_ENABLE_FIDES
#include <parpeoptimization/localOptimizationFides.h>
#endif

#ifdef PARPE_ENABLE_IPOPT
#include <parpeoptimization/localOptimizationIpopt.h>
#endif
Expand Down Expand Up @@ -127,6 +131,9 @@ std::unique_ptr<OptimizationOptions> OptimizationOptions::fromHDF5(const H5::H5F
std::string optimizerPath;

switch(o->optimizer) {
case optimizerName::OPTIMIZER_FIDES:
optimizerPath = std::string(hdf5path) + "/fides";
break;
case optimizerName::OPTIMIZER_CERES:
optimizerPath = std::string(hdf5path) + "/ceres";
break;
Expand Down Expand Up @@ -261,6 +268,12 @@ void OptimizationOptions::setOption(const std::string& key, std::string value)
std::unique_ptr<Optimizer> optimizerFactory(optimizerName optimizer)
{
switch (optimizer) {
case optimizerName::OPTIMIZER_FIDES:
#ifdef PARPE_ENABLE_FIDES
return std::make_unique<OptimizerFides>();
#else
return nullptr;
#endif
case optimizerName::OPTIMIZER_IPOPT:
#ifdef PARPE_ENABLE_IPOPT
return std::make_unique<OptimizerIpOpt>();
Expand Down Expand Up @@ -307,6 +320,17 @@ void printAvailableOptimizers(std::string const& prefix)
// Note: Keep fall-through switch statement, so compiler will warn us about
// any addition to optimizerName
switch (optimizer) {
case optimizerName::OPTIMIZER_FIDES:
#ifdef PARPE_ENABLE_FIDES
std::cout<<prefix<<std::left<<std::setw(22)<<"OPTIMIZER_FIDES\t"
<<static_cast<int>(optimizerName::OPTIMIZER_FIDES)
<<" enabled\n";
#else
std::cout<<prefix<<std::left<<std::setw(22)<<"OPTIMIZER_FIDES"
<<static_cast<int>(optimizerName::OPTIMIZER_FIDES)
<<" disabled\n";
#endif
[[fallthrough]];
case optimizerName::OPTIMIZER_IPOPT:
#ifdef PARPE_ENABLE_IPOPT
std::cout<<prefix<<std::left<<std::setw(22)<<"OPTIMIZER_IPOPT\t"
Expand Down
5 changes: 5 additions & 0 deletions tests/parpeoptimization/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ target_link_libraries(${PROJECT_NAME}
${GCOV_LIBRARY}
)

if(${PARPE_ENABLE_FIDES})
target_sources(${PROJECT_NAME}
PRIVATE localOptimizationFidesTest.cpp)
endif()

if(${PARPE_ENABLE_IPOPT})
target_sources(${PROJECT_NAME}
PRIVATE localOptimizationIpoptTest.cpp)
Expand Down
Loading

0 comments on commit d0bd8c1

Please sign in to comment.