Refactoring
This commit is contained in:
parent
9a225439cf
commit
8888b28163
|
@ -1,709 +0,0 @@
|
||||||
/* gdcpp.h
|
|
||||||
*
|
|
||||||
* Author: Fabian Meyer
|
|
||||||
* Created On: 12 Jul 2019
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef GDCPP_GDCPP_H_
|
|
||||||
#define GDCPP_GDCPP_H_
|
|
||||||
|
|
||||||
#include <Eigen/Geometry>
|
|
||||||
#include <functional>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
namespace gdc {
|
|
||||||
typedef long int Index;
|
|
||||||
|
|
||||||
/** Functor to compute forward differences.
|
|
||||||
* Computes the gradient of the objective f(x) as follows:
|
|
||||||
*
|
|
||||||
* grad(x) = (f(x + eps) - f(x)) / eps
|
|
||||||
*
|
|
||||||
* The computation requires len(x) evaluations of the objective.
|
|
||||||
*/
|
|
||||||
template <typename Scalar> class ForwardDifferences {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &)> Objective;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scalar eps_;
|
|
||||||
Index threads_;
|
|
||||||
Objective objective_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ForwardDifferences()
|
|
||||||
: ForwardDifferences(std::sqrt(std::numeric_limits<Scalar>::epsilon())) {}
|
|
||||||
|
|
||||||
ForwardDifferences(const Scalar eps) : eps_(eps), threads_(1), objective_() {}
|
|
||||||
|
|
||||||
void setNumericalEpsilon(const Scalar eps) { eps_ = eps; }
|
|
||||||
|
|
||||||
void setThreads(const Index threads) { threads_ = threads; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void operator()(const Vector &xval, const Scalar fval, Vector &gradient) {
|
|
||||||
assert(objective_);
|
|
||||||
|
|
||||||
gradient.resize(xval.size());
|
|
||||||
#pragma omp parallel for num_threads(threads_)
|
|
||||||
for (Index i = 0; i < xval.size(); ++i) {
|
|
||||||
Vector xvalN = xval;
|
|
||||||
xvalN(i) += eps_;
|
|
||||||
Scalar fvalN = objective_(xvalN);
|
|
||||||
|
|
||||||
gradient(i) = (fvalN - fval) / eps_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Functor to compute backward differences.
|
|
||||||
* Computes the gradient of the objective f(x) as follows:
|
|
||||||
*
|
|
||||||
* grad(x) = (f(x) - f(x - eps)) / eps
|
|
||||||
*
|
|
||||||
* The computation requires len(x) evaluations of the objective.
|
|
||||||
*/
|
|
||||||
template <typename Scalar> class BackwardDifferences {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &)> Objective;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scalar eps_;
|
|
||||||
Index threads_;
|
|
||||||
Objective objective_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
BackwardDifferences()
|
|
||||||
: BackwardDifferences(std::sqrt(std::numeric_limits<Scalar>::epsilon())) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BackwardDifferences(const Scalar eps)
|
|
||||||
: eps_(eps), threads_(1), objective_() {}
|
|
||||||
|
|
||||||
void setNumericalEpsilon(const Scalar eps) { eps_ = eps; }
|
|
||||||
|
|
||||||
void setThreads(const Index threads) { threads_ = threads; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void operator()(const Vector &xval, const Scalar fval, Vector &gradient) {
|
|
||||||
assert(objective_);
|
|
||||||
|
|
||||||
gradient.resize(xval.size());
|
|
||||||
#pragma omp parallel for num_threads(threads_)
|
|
||||||
for (Index i = 0; i < xval.size(); ++i) {
|
|
||||||
Vector xvalN = xval;
|
|
||||||
xvalN(i) -= eps_;
|
|
||||||
Scalar fvalN = objective_(xvalN);
|
|
||||||
gradient(i) = (fval - fvalN) / eps_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Functor to compute central differences.
|
|
||||||
* Computes the gradient of the objective f(x) as follows:
|
|
||||||
*
|
|
||||||
* grad(x) = (f(x + 0.5 eps) - f(x - 0.5 eps)) / eps
|
|
||||||
*
|
|
||||||
* The computation requires 2 * len(x) evaluations of the objective.
|
|
||||||
*/
|
|
||||||
template <typename Scalar> struct CentralDifferences {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &)> Objective;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scalar eps_;
|
|
||||||
Index threads_;
|
|
||||||
Objective objective_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CentralDifferences()
|
|
||||||
: CentralDifferences(std::sqrt(std::numeric_limits<Scalar>::epsilon())) {}
|
|
||||||
|
|
||||||
CentralDifferences(const Scalar eps) : eps_(eps), threads_(1), objective_() {}
|
|
||||||
|
|
||||||
void setNumericalEpsilon(const Scalar eps) { eps_ = eps; }
|
|
||||||
|
|
||||||
void setThreads(const Index threads) { threads_ = threads; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void operator()(const Vector &xval, const Scalar, Vector &gradient) {
|
|
||||||
assert(objective_);
|
|
||||||
|
|
||||||
Vector fvals(xval.size() * 2);
|
|
||||||
#pragma omp parallel for num_threads(threads_)
|
|
||||||
for (Index i = 0; i < fvals.size(); ++i) {
|
|
||||||
Index idx = i / 2;
|
|
||||||
Vector xvalN = xval;
|
|
||||||
if (i % 2 == 0)
|
|
||||||
xvalN(idx) += eps_ / 2;
|
|
||||||
else
|
|
||||||
xvalN(idx) -= eps_ / 2;
|
|
||||||
|
|
||||||
fvals(i) = objective_(xvalN);
|
|
||||||
}
|
|
||||||
|
|
||||||
gradient.resize(xval.size());
|
|
||||||
for (Index i = 0; i < xval.size(); ++i)
|
|
||||||
gradient(i) = (fvals(i * 2) - fvals(i * 2 + 1)) / eps_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Dummy callback functor, which does nothing. */
|
|
||||||
template <typename Scalar> struct NoCallback {
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
|
|
||||||
bool operator()(const Index, const Vector &, const Scalar,
|
|
||||||
const Vector &) const {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Step size functor, which returns a constant step size. */
|
|
||||||
template <typename Scalar> class ConstantStepSize {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &, Vector &)> Objective;
|
|
||||||
typedef std::function<void(const Vector &, const Scalar, Vector &)>
|
|
||||||
FiniteDifferences;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scalar stepSize_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ConstantStepSize() : ConstantStepSize(0.000000000000001) {}
|
|
||||||
|
|
||||||
ConstantStepSize(const Scalar stepSize) : stepSize_(stepSize) {}
|
|
||||||
|
|
||||||
/** Set the step size returned by this functor.
|
|
||||||
* @param stepSize step size returned by functor */
|
|
||||||
void setStepSize(const Scalar stepSize) { stepSize_ = stepSize; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &) {}
|
|
||||||
|
|
||||||
void setFiniteDifferences(const FiniteDifferences &) {}
|
|
||||||
|
|
||||||
Scalar operator()(const Vector &, const Scalar, const Vector &) {
|
|
||||||
return stepSize_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Step size functor to compute Barzilai-Borwein (BB) steps.
|
|
||||||
* The functor can either compute the direct or inverse BB step.
|
|
||||||
* The steps are computed as follows:
|
|
||||||
*
|
|
||||||
* s_k = x_k - x_k-1 k >= 1
|
|
||||||
* y_k = grad_k - grad_k-1 k >= 1
|
|
||||||
* Direct: stepSize = (s_k^T * s_k) / (y_k^T * s_k)
|
|
||||||
* Inverse: stepSize = (y_k^T * s_k) / (y_k^T * y_k)
|
|
||||||
*
|
|
||||||
* The very first step is computed as a constant. */
|
|
||||||
template <typename Scalar> class BarzilaiBorwein {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &, Vector &)> Objective;
|
|
||||||
typedef std::function<void(const Vector &, const Scalar, Vector &)>
|
|
||||||
FiniteDifferences;
|
|
||||||
|
|
||||||
enum class Method { Direct, Inverse };
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vector lastXval_;
|
|
||||||
Vector lastGradient_;
|
|
||||||
Method method_;
|
|
||||||
Scalar constStep_;
|
|
||||||
|
|
||||||
Scalar constantStep() const { return constStep_; }
|
|
||||||
|
|
||||||
Scalar directStep(const Vector &xval, const Vector &gradient) {
|
|
||||||
auto sk = xval - lastXval_;
|
|
||||||
auto yk = gradient - lastGradient_;
|
|
||||||
Scalar num = sk.dot(sk);
|
|
||||||
Scalar denom = sk.dot(yk);
|
|
||||||
|
|
||||||
if (denom == 0)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return std::abs(num / denom);
|
|
||||||
}
|
|
||||||
|
|
||||||
Scalar inverseStep(const Vector &xval, const Vector &gradient) {
|
|
||||||
auto sk = xval - lastXval_;
|
|
||||||
auto yk = gradient - lastGradient_;
|
|
||||||
Scalar num = sk.dot(yk);
|
|
||||||
Scalar denom = yk.dot(yk);
|
|
||||||
|
|
||||||
if (denom == 0)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return std::abs(num / denom);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
BarzilaiBorwein() : BarzilaiBorwein(Method::Inverse, 1) {}
|
|
||||||
|
|
||||||
BarzilaiBorwein(const Method method, const Scalar constStep)
|
|
||||||
: lastXval_(), lastGradient_(), method_(method), constStep_(constStep) {}
|
|
||||||
|
|
||||||
void setObjective(const Objective &) {}
|
|
||||||
|
|
||||||
void setFiniteDifferences(const FiniteDifferences &) {}
|
|
||||||
|
|
||||||
void setMethod(const Method method) { method_ = method; }
|
|
||||||
|
|
||||||
void setConstStepSize(const Scalar stepSize) { constStep_ = stepSize; }
|
|
||||||
|
|
||||||
Scalar operator()(const Vector &xval, const Scalar, const Vector &gradient) {
|
|
||||||
Scalar stepSize = 0;
|
|
||||||
if (lastXval_.size() == 0) {
|
|
||||||
stepSize = constStep_;
|
|
||||||
} else {
|
|
||||||
switch (method_) {
|
|
||||||
case Method::Direct:
|
|
||||||
stepSize = directStep(xval, gradient);
|
|
||||||
break;
|
|
||||||
case Method::Inverse:
|
|
||||||
stepSize = inverseStep(xval, gradient);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastGradient_ = gradient;
|
|
||||||
lastXval_ = xval;
|
|
||||||
|
|
||||||
return stepSize;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Step size functor to perform Armijo Linesearch with backtracking.
|
|
||||||
* The functor iteratively decreases the step size until the following
|
|
||||||
* conditions are met:
|
|
||||||
*
|
|
||||||
* Armijo: f(x - stepSize * grad(x)) <= f(x) - cArmijo * stepSize * grad(x)^T *
|
|
||||||
* grad(x)
|
|
||||||
*
|
|
||||||
* If either condition does not hold the step size is decreased:
|
|
||||||
*
|
|
||||||
* stepSize = decrease * stepSize */
|
|
||||||
template <typename Scalar> class ArmijoBacktracking {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &, Vector &)> Objective;
|
|
||||||
typedef std::function<void(const Vector &, const Scalar, Vector &)>
|
|
||||||
FiniteDifferences;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Scalar decrease_;
|
|
||||||
Scalar cArmijo_;
|
|
||||||
Scalar minStep_;
|
|
||||||
Scalar maxStep_;
|
|
||||||
Index maxIt_;
|
|
||||||
Objective objective_;
|
|
||||||
FiniteDifferences finiteDifferences_;
|
|
||||||
|
|
||||||
Scalar evaluateObjective(const Vector &xval, Vector &gradient) {
|
|
||||||
gradient.resize(0);
|
|
||||||
Scalar fval = objective_(xval, gradient);
|
|
||||||
if (gradient.size() == 0)
|
|
||||||
finiteDifferences_(xval, fval, gradient);
|
|
||||||
return fval;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool computeSecondCondition(const Scalar, const Scalar, const Scalar,
|
|
||||||
const Vector &, const Vector &) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
ArmijoBacktracking()
|
|
||||||
: ArmijoBacktracking(0.8, 1e-4, 1e-20, 1, 0) {} // NOTE: maxStep was 1
|
|
||||||
|
|
||||||
ArmijoBacktracking(const Scalar decrease, const Scalar cArmijo,
|
|
||||||
const Scalar minStep, const Scalar maxStep,
|
|
||||||
const Index iterations)
|
|
||||||
: decrease_(decrease), cArmijo_(cArmijo), minStep_(minStep),
|
|
||||||
maxStep_(maxStep), maxIt_(iterations), objective_() {
|
|
||||||
assert(decrease > 0);
|
|
||||||
assert(decrease < 1);
|
|
||||||
assert(cArmijo > 0);
|
|
||||||
assert(cArmijo < 0.5);
|
|
||||||
assert(minStep < maxStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the decreasing factor for backtracking.
|
|
||||||
* Assure that decrease in (0, 1).
|
|
||||||
* @param decrease decreasing factor */
|
|
||||||
void setBacktrackingDecrease(const Scalar decrease) {
|
|
||||||
assert(decrease > 0);
|
|
||||||
assert(decrease < 1);
|
|
||||||
decrease_ = decrease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the relaxation constant for the Armijo condition (see class
|
|
||||||
* description).
|
|
||||||
* Assure cArmijo in (0, 0.5).
|
|
||||||
* @param cArmijo armijo constant */
|
|
||||||
void setArmijoConstant(const Scalar cArmijo) {
|
|
||||||
assert(cArmijo > 0);
|
|
||||||
assert(cArmijo < 0.5);
|
|
||||||
cArmijo_ = cArmijo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the bounds for the step size during linesearch.
|
|
||||||
* The final step size is guaranteed to be in [minStep, maxStep].
|
|
||||||
* @param minStep minimum step size
|
|
||||||
* @param maxStep maximum step size */
|
|
||||||
void setStepBounds(const Scalar minStep, const Scalar maxStep) {
|
|
||||||
assert(minStep < maxStep);
|
|
||||||
minStep_ = minStep;
|
|
||||||
maxStep_ = maxStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the maximum number of iterations.
|
|
||||||
* Set to 0 or negative for infinite iterations.
|
|
||||||
* @param iterations maximum number of iterations */
|
|
||||||
void setMaxIterations(const Index iterations) { maxIt_ = iterations; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void setFiniteDifferences(const FiniteDifferences &finiteDifferences) {
|
|
||||||
finiteDifferences_ = finiteDifferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
Scalar operator()(const Vector &xval, const Scalar fval,
|
|
||||||
const Vector &gradient) {
|
|
||||||
assert(objective_);
|
|
||||||
assert(finiteDifferences_);
|
|
||||||
|
|
||||||
Scalar stepSize = maxStep_ / decrease_;
|
|
||||||
Vector gradientN;
|
|
||||||
Vector xvalN;
|
|
||||||
Scalar fvalN;
|
|
||||||
bool armijoCondition = false;
|
|
||||||
bool secondCondition = false;
|
|
||||||
|
|
||||||
Index iterations = 0;
|
|
||||||
while ((maxIt_ <= 0 || iterations < maxIt_) &&
|
|
||||||
stepSize * decrease_ >= minStep_ &&
|
|
||||||
!(armijoCondition && secondCondition)) {
|
|
||||||
stepSize = decrease_ * stepSize;
|
|
||||||
xvalN = xval - stepSize * gradient;
|
|
||||||
fvalN = evaluateObjective(xvalN, gradientN);
|
|
||||||
|
|
||||||
armijoCondition =
|
|
||||||
fvalN <= fval - cArmijo_ * stepSize * gradient.dot(gradient);
|
|
||||||
secondCondition =
|
|
||||||
computeSecondCondition(stepSize, fval, fvalN, gradient, gradientN);
|
|
||||||
|
|
||||||
++iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stepSize;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Step size functor to perform Wolfe Linesearch with backtracking.
|
|
||||||
* The functor iteratively decreases the step size until the following
|
|
||||||
* conditions are met:
|
|
||||||
*
|
|
||||||
* Armijo: f(x - stepSize * grad(x)) <= f(x) - cArmijo * stepSize * grad(x)^T *
|
|
||||||
* grad(x) Wolfe: grad(x)^T grad(x - stepSize * grad(x)) <= cWolfe * grad(x)^T *
|
|
||||||
* grad(x)
|
|
||||||
*
|
|
||||||
* If either condition does not hold the step size is decreased:
|
|
||||||
*
|
|
||||||
* stepSize = decrease * stepSize */
|
|
||||||
template <typename Scalar>
|
|
||||||
class WolfeBacktracking : public ArmijoBacktracking<Scalar> {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &, Vector &)> Objective;
|
|
||||||
typedef std::function<void(const Vector &, const Scalar, Vector &)>
|
|
||||||
FiniteDifferences;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Scalar cWolfe_;
|
|
||||||
|
|
||||||
virtual bool computeSecondCondition(const Scalar, const Scalar, const Scalar,
|
|
||||||
const Vector &gradient,
|
|
||||||
const Vector &gradientN) {
|
|
||||||
return gradient.dot(gradientN) <= cWolfe_ * gradient.dot(gradient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
WolfeBacktracking() : WolfeBacktracking(0.8, 1e-4, 0.9, 1e-20, 1, 0) {}
|
|
||||||
|
|
||||||
WolfeBacktracking(const Scalar decrease, const Scalar cArmijo,
|
|
||||||
const Scalar cWolfe, const Scalar minStep,
|
|
||||||
const Scalar maxStep, const Index iterations)
|
|
||||||
: ArmijoBacktracking<Scalar>(decrease, cArmijo, minStep, maxStep,
|
|
||||||
iterations),
|
|
||||||
cWolfe_(cWolfe) {
|
|
||||||
assert(cWolfe < 1);
|
|
||||||
assert(cArmijo < cWolfe);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the wolfe constants for Armijo and Wolfe condition (see class
|
|
||||||
* description).
|
|
||||||
* Assure that c1 < c2 < 1 and c1 in (0, 0.5).
|
|
||||||
* @param c1 armijo constant
|
|
||||||
* @param c2 wolfe constant */
|
|
||||||
void setWolfeConstant(const Scalar cWolfe) {
|
|
||||||
assert(cWolfe < 1);
|
|
||||||
cWolfe_ = cWolfe;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Step size functor which searches for a step that reduces the function
|
|
||||||
* value.
|
|
||||||
* The functor iteratively decreases the step size until the following
|
|
||||||
* condition is met:
|
|
||||||
*
|
|
||||||
* f(x - stepSize * grad) < f(x)
|
|
||||||
*
|
|
||||||
* If this condition does not hold the step size is decreased:
|
|
||||||
*
|
|
||||||
* stepSize = decrease * stepSize
|
|
||||||
*
|
|
||||||
* This functor does not require to compute any gradients and does not use
|
|
||||||
* finite differences. */
|
|
||||||
template <typename Scalar> class DecreaseBacktracking {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
typedef std::function<Scalar(const Vector &, Vector &)> Objective;
|
|
||||||
typedef std::function<void(const Vector &, const Scalar, Vector &)>
|
|
||||||
FiniteDifferences;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scalar decrease_;
|
|
||||||
Scalar minStep_;
|
|
||||||
Scalar maxStep_;
|
|
||||||
Index maxIt_;
|
|
||||||
Objective objective_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
DecreaseBacktracking() : DecreaseBacktracking(0.8, 1e-12, 1, 0) {}
|
|
||||||
|
|
||||||
DecreaseBacktracking(const Scalar decrease, const Scalar minStep,
|
|
||||||
const Scalar maxStep, const Index iterations)
|
|
||||||
: decrease_(decrease), minStep_(minStep), maxStep_(maxStep),
|
|
||||||
maxIt_(iterations), objective_() {}
|
|
||||||
|
|
||||||
/** Set the decreasing factor for backtracking.
|
|
||||||
* Assure that decrease in (0, 1).
|
|
||||||
* @param decrease decreasing factor */
|
|
||||||
void setBacktrackingDecrease(const Scalar decrease) { decrease_ = decrease; }
|
|
||||||
|
|
||||||
/** Set the bounds for the step size during linesearch.
|
|
||||||
* The final step size is guaranteed to be in [minStep, maxStep].
|
|
||||||
* @param minStep minimum step size
|
|
||||||
* @param maxStep maximum step size */
|
|
||||||
void setStepBounds(const Scalar minStep, const Scalar maxStep) {
|
|
||||||
assert(minStep < maxStep);
|
|
||||||
minStep_ = minStep;
|
|
||||||
maxStep_ = maxStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the maximum number of iterations.
|
|
||||||
* Set to 0 or negative for infinite iterations.
|
|
||||||
* @param iterations maximum number of iterations */
|
|
||||||
void setMaxIterations(const Index iterations) { maxIt_ = iterations; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void setFiniteDifferences(const FiniteDifferences &) {}
|
|
||||||
|
|
||||||
Scalar operator()(const Vector &xval, const Scalar fval,
|
|
||||||
const Vector &gradient) {
|
|
||||||
assert(objective_);
|
|
||||||
|
|
||||||
Scalar stepSize = maxStep_ / decrease_;
|
|
||||||
Vector xvalN;
|
|
||||||
Vector gradientN;
|
|
||||||
Scalar fvalN;
|
|
||||||
bool improvement = false;
|
|
||||||
|
|
||||||
Index iterations = 0;
|
|
||||||
while ((maxIt_ <= 0 || iterations < maxIt_) &&
|
|
||||||
stepSize * decrease_ >= minStep_ && !improvement) {
|
|
||||||
stepSize = decrease_ * stepSize;
|
|
||||||
xvalN = xval - stepSize * gradient;
|
|
||||||
fvalN = objective_(xvalN, gradientN);
|
|
||||||
|
|
||||||
improvement = fvalN < fval;
|
|
||||||
|
|
||||||
++iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stepSize;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Scalar, typename Objective,
|
|
||||||
typename StepSize = BarzilaiBorwein<Scalar>,
|
|
||||||
typename Callback = NoCallback<Scalar>,
|
|
||||||
typename FiniteDifferences = CentralDifferences<Scalar>>
|
|
||||||
class GradientDescent {
|
|
||||||
public:
|
|
||||||
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
|
|
||||||
|
|
||||||
struct Result {
|
|
||||||
Index iterations;
|
|
||||||
bool converged;
|
|
||||||
Scalar fval;
|
|
||||||
Vector xval;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Index maxIt_;
|
|
||||||
Scalar minGradientLen_;
|
|
||||||
Scalar minStepLen_;
|
|
||||||
Scalar momentum_;
|
|
||||||
Index verbosity_;
|
|
||||||
Objective objective_;
|
|
||||||
StepSize stepSize_;
|
|
||||||
Callback callback_;
|
|
||||||
FiniteDifferences finiteDifferences_;
|
|
||||||
|
|
||||||
Scalar evaluateObjective(const Vector &xval, Vector &gradient) {
|
|
||||||
gradient.resize(0);
|
|
||||||
Scalar fval = objective_(xval, gradient);
|
|
||||||
if (gradient.size() == 0)
|
|
||||||
finiteDifferences_(xval, fval, gradient);
|
|
||||||
return fval;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string vector2str(const Vector &vec) const {
|
|
||||||
std::stringstream ss1;
|
|
||||||
ss1 << std::fixed << std::showpoint << std::setprecision(16);
|
|
||||||
std::stringstream ss2;
|
|
||||||
ss2 << '[';
|
|
||||||
for (Index i = 0; i < vec.size(); ++i) {
|
|
||||||
ss1 << vec(i);
|
|
||||||
ss2 << std::setfill(' ') << std::setw(10) << ss1.str();
|
|
||||||
if (i != vec.size() - 1)
|
|
||||||
ss2 << ' ';
|
|
||||||
ss1.str("");
|
|
||||||
}
|
|
||||||
ss2 << ']';
|
|
||||||
|
|
||||||
return ss2.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
GradientDescent()
|
|
||||||
: maxIt_(0), minGradientLen_(static_cast<Scalar>(1e-2)),
|
|
||||||
minStepLen_(static_cast<Scalar>(1e-6)), momentum_(0), verbosity_(0),
|
|
||||||
objective_(), stepSize_(), callback_(), finiteDifferences_() {}
|
|
||||||
|
|
||||||
~GradientDescent() {}
|
|
||||||
|
|
||||||
void setThreads(const Index threads) {
|
|
||||||
finiteDifferences_.setThreads(threads);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNumericalEpsilon(const Scalar eps) {
|
|
||||||
finiteDifferences_.setNumericalEpsilon(eps);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMaxIterations(const Index iterations) { maxIt_ = iterations; }
|
|
||||||
|
|
||||||
void setObjective(const Objective &objective) { objective_ = objective; }
|
|
||||||
|
|
||||||
void setCallback(const Callback &callback) { callback_ = callback; }
|
|
||||||
|
|
||||||
void setMinGradientLength(const Scalar gradientLen) {
|
|
||||||
minGradientLen_ = gradientLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMinStepLength(const Scalar stepLen) { minStepLen_ = stepLen; }
|
|
||||||
|
|
||||||
void setStepSize(const StepSize stepSize) { stepSize_ = stepSize; }
|
|
||||||
|
|
||||||
void setMomentum(const Scalar momentum) { momentum_ = momentum; }
|
|
||||||
|
|
||||||
void setVerbosity(const Index verbosity) { verbosity_ = verbosity; }
|
|
||||||
|
|
||||||
Result minimize(const Vector &initialGuess) {
|
|
||||||
finiteDifferences_.setObjective([this](const Vector &xval) {
|
|
||||||
Vector tmp;
|
|
||||||
return this->objective_(xval, tmp);
|
|
||||||
});
|
|
||||||
stepSize_.setObjective([this](const Vector &xval, Vector &gradient) {
|
|
||||||
return this->objective_(xval, gradient);
|
|
||||||
});
|
|
||||||
stepSize_.setFiniteDifferences(
|
|
||||||
[this](const Vector &xval, const Scalar fval, Vector &gradient) {
|
|
||||||
this->finiteDifferences_(xval, fval, gradient);
|
|
||||||
});
|
|
||||||
|
|
||||||
Vector xval = initialGuess;
|
|
||||||
Vector gradient;
|
|
||||||
Scalar fval;
|
|
||||||
Scalar gradientLen = minGradientLen_ + 1;
|
|
||||||
Scalar stepSize;
|
|
||||||
Vector step = Vector::Zero(xval.size());
|
|
||||||
Scalar stepLen = minStepLen_ + 1;
|
|
||||||
bool callbackResult = true;
|
|
||||||
|
|
||||||
Index iterations = 0;
|
|
||||||
while ((maxIt_ <= 0 || iterations < maxIt_) &&
|
|
||||||
gradientLen >= minGradientLen_ && stepLen >= minStepLen_ &&
|
|
||||||
callbackResult) {
|
|
||||||
xval -= step;
|
|
||||||
fval = evaluateObjective(xval, gradient);
|
|
||||||
gradientLen = gradient.norm();
|
|
||||||
// update step according to step size and momentum
|
|
||||||
stepSize = stepSize_(xval, fval, gradient);
|
|
||||||
step = momentum_ * step + (1 - momentum_) * stepSize * gradient;
|
|
||||||
stepLen = step.norm();
|
|
||||||
// evaluate callback an save its result
|
|
||||||
callbackResult = callback_(iterations, xval, fval, gradient);
|
|
||||||
|
|
||||||
if (verbosity_ > 0) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "it=" << std::setfill('0') << std::setw(4) << iterations
|
|
||||||
<< std::fixed << std::showpoint << std::setprecision(20)
|
|
||||||
<< " gradlen=" << gradientLen << " stepsize=" << stepSize
|
|
||||||
<< " steplen=" << stepLen;
|
|
||||||
|
|
||||||
if (verbosity_ > 2)
|
|
||||||
ss << " callback=" << (callbackResult ? "true" : "false");
|
|
||||||
|
|
||||||
ss << " fval=" << fval;
|
|
||||||
|
|
||||||
if (verbosity_ > 1)
|
|
||||||
ss << " xval=" << vector2str(xval);
|
|
||||||
if (verbosity_ > 2)
|
|
||||||
ss << " gradient=" << vector2str(gradient);
|
|
||||||
if (verbosity_ > 3)
|
|
||||||
ss << " step=" << vector2str(step);
|
|
||||||
std::cout << ss.str() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
++iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result result;
|
|
||||||
result.xval = xval;
|
|
||||||
result.fval = fval;
|
|
||||||
result.iterations = iterations;
|
|
||||||
result.converged = gradientLen < minGradientLen_ || stepLen < minStepLen_;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace gdc
|
|
||||||
|
|
||||||
#endif
|
|
41
src/main.cpp
41
src/main.cpp
|
@ -1,6 +1,7 @@
|
||||||
#include "beamformfinder.hpp"
|
#include "beamformfinder.hpp"
|
||||||
#include "csvfile.hpp"
|
#include "csvfile.hpp"
|
||||||
#include "edgemesh.hpp"
|
#include "edgemesh.hpp"
|
||||||
|
#include "externvariables.hpp"
|
||||||
#include "flatpattern.hpp"
|
#include "flatpattern.hpp"
|
||||||
#include "polyscope/curve_network.h"
|
#include "polyscope/curve_network.h"
|
||||||
#include "polyscope/point_cloud.h"
|
#include "polyscope/point_cloud.h"
|
||||||
|
@ -16,20 +17,30 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vcg/complex/algorithms/update/position.h>
|
#include <vcg/complex/algorithms/update/position.h>
|
||||||
|
|
||||||
|
bool printDebugInfo;
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
if (argc < 4) {
|
if (argc < 5) {
|
||||||
std::cerr << "Specify the pattern pair to be optimized." << std::endl;
|
std::cerr << "Specify at least if D(ebug) or R(elease) and the pattern "
|
||||||
|
"pair to be optimized."
|
||||||
|
<< std::endl;
|
||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (argv[1] == "D") {
|
||||||
|
printDebugInfo = true;
|
||||||
|
} else {
|
||||||
|
printDebugInfo = false;
|
||||||
|
}
|
||||||
// Populate the pattern pair to be optimized
|
// Populate the pattern pair to be optimized
|
||||||
////Full pattern
|
////Full pattern
|
||||||
const std::string filepath_fullPattern = argv[1];
|
const std::string filepath_fullPattern = argv[2];
|
||||||
FlatPattern fullPattern(filepath_fullPattern);
|
FlatPattern fullPattern(filepath_fullPattern);
|
||||||
fullPattern.setLabel(
|
fullPattern.setLabel(
|
||||||
std::filesystem::path(filepath_fullPattern).stem().string());
|
std::filesystem::path(filepath_fullPattern).stem().string());
|
||||||
fullPattern.scale(0.03);
|
fullPattern.scale(0.03);
|
||||||
////Reduced pattern
|
////Reduced pattern
|
||||||
const std::string filepath_reducedPattern = argv[2];
|
const std::string filepath_reducedPattern = argv[3];
|
||||||
FlatPattern reducedPattern(filepath_reducedPattern);
|
FlatPattern reducedPattern(filepath_reducedPattern);
|
||||||
reducedPattern.setLabel(
|
reducedPattern.setLabel(
|
||||||
std::filesystem::path(filepath_reducedPattern).stem().string());
|
std::filesystem::path(filepath_reducedPattern).stem().string());
|
||||||
|
@ -43,29 +54,33 @@ int main(int argc, char *argv[]) {
|
||||||
ReducedModelOptimizer::Settings settings_optimization;
|
ReducedModelOptimizer::Settings settings_optimization;
|
||||||
settings_optimization.xRanges = {beamWidth, beamDimensionsRatio, beamE,
|
settings_optimization.xRanges = {beamWidth, beamDimensionsRatio, beamE,
|
||||||
innerHexagonSize};
|
innerHexagonSize};
|
||||||
const bool input_numberOfFunctionCallsDefined = argc >= 4;
|
const bool input_numberOfFunctionCallsDefined = argc >= 5;
|
||||||
settings_optimization.numberOfFunctionCalls =
|
settings_optimization.numberOfFunctionCalls =
|
||||||
input_numberOfFunctionCallsDefined ? std::atoi(argv[3]) : 100;
|
input_numberOfFunctionCallsDefined ? std::atoi(argv[4]) : 100;
|
||||||
// Optimize pair
|
// Optimize pair
|
||||||
const std::string pairName =
|
const std::string pairName =
|
||||||
fullPattern.getLabel() + "@" + reducedPattern.getLabel();
|
fullPattern.getLabel() + "@" + reducedPattern.getLabel();
|
||||||
std::cout << "Optimizing " << pairName << std::endl;
|
if (printDebugInfo) {
|
||||||
|
std::cout << "Optimizing " << pairName << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<size_t> numberOfNodesPerSlot{1, 0, 0, 2, 1, 2, 1};
|
const std::vector<size_t> numberOfNodesPerSlot{1, 0, 0, 2, 1, 2, 1};
|
||||||
ReducedModelOptimizer optimizer(numberOfNodesPerSlot);
|
ReducedModelOptimizer optimizer(numberOfNodesPerSlot);
|
||||||
optimizer.initializePatterns(fullPattern, reducedPattern, {});
|
optimizer.initializePatterns(fullPattern, reducedPattern, {});
|
||||||
ReducedModelOptimizer::Results optimizationResults =
|
ReducedModelOptimizer::Results optimizationResults =
|
||||||
optimizer.optimize(settings_optimization);
|
optimizer.optimize(settings_optimization);
|
||||||
// Export results
|
// Export results
|
||||||
const bool input_resultDirectoryDefined = argc >= 5;
|
const bool input_resultDirectoryDefined = argc >= 6;
|
||||||
std::string optimiziationResultsDirectory =
|
std::string optimiziationResultsDirectory =
|
||||||
input_resultDirectoryDefined ? argv[4] : "OptimizationResults";
|
input_resultDirectoryDefined ? argv[5] : "OptimizationResults";
|
||||||
std::filesystem::path dirPath_thisOptimization(
|
std::filesystem::path dirPath_thisOptimization(
|
||||||
std::filesystem::path(optimiziationResultsDirectory).append(pairName));
|
std::filesystem::path(optimiziationResultsDirectory).append(pairName));
|
||||||
std::filesystem::create_directories(dirPath_thisOptimization);
|
std::filesystem::create_directories(dirPath_thisOptimization);
|
||||||
csvFile csv_results(std::filesystem::path(dirPath_thisOptimization)
|
csvFile csv_results({}, false);
|
||||||
.append("results.csv")
|
// csvFile csv_results(std::filesystem::path(dirPath_thisOptimization)
|
||||||
.string(),
|
// .append("results.csv")
|
||||||
false);
|
// .string(),
|
||||||
|
// false);
|
||||||
settings_optimization.writeTo(csv_results);
|
settings_optimization.writeTo(csv_results);
|
||||||
optimizationResults.writeTo(settings_optimization, csv_results);
|
optimizationResults.writeTo(settings_optimization, csv_results);
|
||||||
optimizationResults.save(dirPath_thisOptimization.string());
|
optimizationResults.save(dirPath_thisOptimization.string());
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "reducedmodeloptimizer.hpp"
|
#include "reducedmodeloptimizer.hpp"
|
||||||
#include "flatpattern.hpp"
|
#include "flatpattern.hpp"
|
||||||
#include "gradientDescent.h"
|
|
||||||
#include "simulationhistoryplotter.hpp"
|
#include "simulationhistoryplotter.hpp"
|
||||||
#include "trianglepattterntopology.hpp"
|
#include "trianglepattterntopology.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -740,10 +739,12 @@ ReducedModelOptimizer::runOptimization(const Settings &settings) {
|
||||||
results.x = global.minX;
|
results.x = global.minX;
|
||||||
results.objectiveValue = global.minY;
|
results.objectiveValue = global.minY;
|
||||||
results.time = elapsed.count() / 1000.0;
|
results.time = elapsed.count() / 1000.0;
|
||||||
std::cout << "Finished optimizing." << endl;
|
if (printDebugInfo) {
|
||||||
// std::cout << "Solution x:" << endl;
|
std::cout << "Finished optimizing." << endl;
|
||||||
// std::cout << result.x << endl;
|
// std::cout << "Solution x:" << endl;
|
||||||
std::cout << "Objective value:" << global.minY << endl;
|
// std::cout << result.x << endl;
|
||||||
|
std::cout << "Objective value:" << global.minY << endl;
|
||||||
|
}
|
||||||
// std::cout << result.y << endl;
|
// std::cout << result.y << endl;
|
||||||
// std::cout << minY << endl;
|
// std::cout << minY << endl;
|
||||||
// std::cout << "Time(sec):" << elapsed.count() << std::endl;
|
// std::cout << "Time(sec):" << elapsed.count() << std::endl;
|
||||||
|
|
|
@ -65,29 +65,31 @@ public:
|
||||||
return settingsString;
|
return settingsString;
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeTo(csvFile &csv, const bool writeHeader = true) const {
|
void writeTo(csvFile &os, const bool writeHeader = true) const {
|
||||||
// Create settings csv header
|
// Create settings csv header
|
||||||
if (writeHeader) {
|
if (writeHeader) {
|
||||||
if (!xRanges.empty()) {
|
if (!xRanges.empty()) {
|
||||||
for (const xRange &range : xRanges) {
|
for (const xRange &range : xRanges) {
|
||||||
csv << range.label + " max";
|
os << range.label + " max";
|
||||||
csv << range.label + " min";
|
os << range.label + " min";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
csv << "Function Calls";
|
os << "Function Calls";
|
||||||
csv << "Solution Accuracy";
|
os << "Solution Accuracy";
|
||||||
csv << endrow;
|
// os << std::endl;
|
||||||
|
os << endrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!xRanges.empty()) {
|
if (!xRanges.empty()) {
|
||||||
for (const xRange &range : xRanges) {
|
for (const xRange &range : xRanges) {
|
||||||
csv << range.max;
|
os << range.max;
|
||||||
csv << range.min;
|
os << range.min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
csv << numberOfFunctionCalls;
|
os << numberOfFunctionCalls;
|
||||||
csv << solutionAccuracy;
|
os << solutionAccuracy;
|
||||||
csv << endrow;
|
// os << std::endl;
|
||||||
|
os << endrow;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -295,37 +297,67 @@ struct ReducedModelOptimizer::Results {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void saveMeshFiles() const {
|
||||||
|
const int numberOfSimulationJobs = fullPatternSimulationJobs.size();
|
||||||
|
assert(numberOfSimulationJobs != 0 &&
|
||||||
|
fullPatternSimulationJobs.size() ==
|
||||||
|
reducedPatternSimulationJobs.size());
|
||||||
|
|
||||||
|
fullPatternSimulationJobs[0]->pMesh->savePly("FullPattern_undeformed.ply");
|
||||||
|
reducedPatternSimulationJobs[0]->pMesh->savePly(
|
||||||
|
"ReducedPattern_undeformed.ply");
|
||||||
|
FormFinder simulator;
|
||||||
|
for (int simulationJobIndex = 0;
|
||||||
|
simulationJobIndex < numberOfSimulationJobs; simulationJobIndex++) {
|
||||||
|
// Drawing of full pattern results
|
||||||
|
const std::shared_ptr<SimulationJob> &pFullPatternSimulationJob =
|
||||||
|
fullPatternSimulationJobs[simulationJobIndex];
|
||||||
|
SimulationResults fullModelResults =
|
||||||
|
simulator.executeSimulation(pFullPatternSimulationJob);
|
||||||
|
fullModelResults.saveDeformedModel();
|
||||||
|
|
||||||
|
// Drawing of reduced pattern results
|
||||||
|
const std::shared_ptr<SimulationJob> &pReducedPatternSimulationJob =
|
||||||
|
reducedPatternSimulationJobs[simulationJobIndex];
|
||||||
|
SimulationResults reducedModelResults =
|
||||||
|
simulator.executeSimulation(pReducedPatternSimulationJob);
|
||||||
|
reducedModelResults.saveDeformedModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void writeTo(const ReducedModelOptimizer::Settings &settings_optimization,
|
void writeTo(const ReducedModelOptimizer::Settings &settings_optimization,
|
||||||
csvFile &csv, const bool writeHeader = true) const {
|
csvFile &os, const bool writeHeader = true) const {
|
||||||
if (writeHeader) {
|
if (writeHeader) {
|
||||||
//// Write header to csv
|
//// Write header to csv
|
||||||
csv << "Obj value";
|
os << "Obj value";
|
||||||
for (const ReducedModelOptimizer::xRange &range :
|
for (const ReducedModelOptimizer::xRange &range :
|
||||||
settings_optimization.xRanges) {
|
settings_optimization.xRanges) {
|
||||||
csv << range.label;
|
os << range.label;
|
||||||
}
|
}
|
||||||
csv << "Time(s)";
|
os << "Time(s)";
|
||||||
csv << "#Crashes";
|
os << "#Crashes";
|
||||||
csv << endrow;
|
// os << std::endl;
|
||||||
|
os << endrow;
|
||||||
}
|
}
|
||||||
csv << objectiveValue;
|
os << objectiveValue;
|
||||||
for (const double &optimalX : x) {
|
for (const double &optimalX : x) {
|
||||||
csv << optimalX;
|
os << optimalX;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int unusedXVarCounter = 0;
|
for (int unusedXVarCounter = 0;
|
||||||
unusedXVarCounter < settings_optimization.xRanges.size() - x.size();
|
unusedXVarCounter < settings_optimization.xRanges.size() - x.size();
|
||||||
unusedXVarCounter++) {
|
unusedXVarCounter++) {
|
||||||
csv << "-";
|
os << "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
csv << time;
|
os << time;
|
||||||
if (numberOfSimulationCrashes == 0) {
|
if (numberOfSimulationCrashes == 0) {
|
||||||
csv << "No crashes";
|
os << "-";
|
||||||
} else {
|
} else {
|
||||||
csv << numberOfSimulationCrashes;
|
os << numberOfSimulationCrashes;
|
||||||
}
|
}
|
||||||
csv << endrow;
|
// os << std::endl;
|
||||||
|
os << endrow;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue