Files
o3de/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h
#	Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h
#	Code/Editor/Plugins/EditorCommon/stdafx.cpp
#	Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h
#	Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h
#	Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h
#	Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h
#	Code/Tools/AssetProcessor/native/precompiled.h
#	Code/Tools/Standalone/StandaloneTools_precompiled.h
#	Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h
#	Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h
#	Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h
#	Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake
#	Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h
#	Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h
#	Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp
#	Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake
#	Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h
#	Gems/Blast/Code/Source/StdAfx.cpp
#	Gems/Camera/Code/Source/Camera_precompiled.h
#	Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h
#	Gems/FastNoise/Code/Source/FastNoise_precompiled.h
#	Gems/Gestures/Code/Source/Gestures_precompiled.h
#	Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h
#	Gems/GraphCanvas/Code/precompiled.h
#	Gems/ImGui/Code/Source/ImGui_precompiled.h
#	Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h
#	Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h
#	Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp
#	Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h
#	Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h
#	Gems/LyShine/Code/Source/LyShine_precompiled.h
#	Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h
#	Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h
#	Gems/Maestro/Code/Source/Maestro_precompiled.h
#	Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h
#	Gems/Metastream/Code/Source/Metastream_precompiled.h
#	Gems/Microphone/Code/Source/Microphone_precompiled.h
#	Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h
#	Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h
#	Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h
#	Gems/PhysX/Code/Source/PhysX_precompiled.h
#	Gems/PhysX/Code/physx_unsupported_files.cmake
#	Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h
#	Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h
#	Gems/ScriptCanvas/Code/Editor/precompiled.h
#	Gems/ScriptCanvas/Code/Source/precompiled.h
#	Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h
#	Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h
#	Gems/ScriptEvents/Code/Source/precompiled.h
#	Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp
#	Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h
#	Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h
#	Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h
#	Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h
#	Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h
#	Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h
#	Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h
#	Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h
#	Gems/Twitch/Code/Source/Twitch_precompiled.h
#	Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
2021-07-16 15:42:37 -07:00

202 lines
8.7 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Optimization/LineSearch.h>
#include <Optimization/Utilities.h>
#include <Optimization/Constants.h>
#include <float.h>
#include <math.h>
namespace NumericalMethods::Optimization
{
bool IsFailure(const LineSearchResult& result)
{
return result.m_outcome >= LineSearchOutcome::FailureExceededIterations;
}
ScalarVariable CubicMinimum(const double a, const double f_a, const double df_a, const double b, const double f_b,
const double c, const double f_c)
{
double coefficients[4] = {};
coefficients[1] = df_a;
const double db = b - a;
const double dc = c - a;
const double denominator = (db * db * dc * dc) * (db - dc);
double e[2];
e[0] = f_b - f_a - coefficients[1] * db;
e[1] = f_c - f_a - coefficients[1] * dc;
coefficients[3] = (dc * dc * e[0] - db * db * e[1]) / denominator;
coefficients[2] = (-dc * dc * dc * e[0] + db * db * db * e[1]) / denominator;
const double radical = coefficients[2] * coefficients[2] - 3.0 * coefficients[3] * coefficients[1];
return a + (-coefficients[2] + sqrt(radical)) / (3.0 * coefficients[3]);
}
ScalarVariable QuadraticMinimum(const double a, const double f_a, const double df_a, const double b, const double f_b)
{
double coefficients[3] = {};
const double db = b - a;
coefficients[1] = df_a;
coefficients[2] = (f_b - f_a - coefficients[1] * db) / (db * db);
return a - coefficients[1] / (2.0 * coefficients[2]);
}
bool ValidateStepSize(const ScalarVariable alphaNew, const double alpha0, const double alpha1, const double edgeThreshold)
{
const double alphaMin = AZStd::GetMin(alpha0, alpha1);
const double alphaMax = AZStd::GetMax(alpha0, alpha1);
const double range = alphaMax - alphaMin;
return (azisfinite(alphaNew) && (alphaNew > alphaMin + edgeThreshold * range) &&
(alphaNew < alphaMax - edgeThreshold * range));
}
LineSearchResult SelectStepSizeFromInterval(double alpha0, double alpha1, double f_alpha0, double f_alpha1,
double df_alpha0, const Function& f, const VectorVariable& x0, const VectorVariable& searchDirection,
const double f_x0, const double df_x0, const double c1, const double c2)
{
const double cubicEdgeThreshold = 0.2;
const double quadraticEdgeThreshold = 0.1;
double alphaLast = 0.0;
double f_alphaLast = f_x0;
LineSearchResult result;
for (AZ::u32 iteration = 0; iteration < lineSearchIterations; iteration++)
{
ScalarVariable alphaNew = 0.0;
if (iteration > 0)
{
// first try selecting a new alpha value based on cubic interpolation through the most recent points
alphaNew = CubicMinimum(alpha0, f_alpha0, df_alpha0, alpha1, f_alpha1, alphaLast, f_alphaLast);
}
// if this is the first iteration, or the cubic method failed or is invalid, try a quadratic
if (iteration == 0 || !ValidateStepSize(alphaNew, alpha0, alpha1, cubicEdgeThreshold))
{
alphaNew = QuadraticMinimum(alpha0, f_alpha0, df_alpha0, alpha1, f_alpha1);
// if the quadratic is invalid, use bisection
if (!ValidateStepSize(alphaNew, alpha0, alpha1, quadraticEdgeThreshold))
{
alphaNew = ScalarVariable(0.5 * (alpha0 + alpha1));
}
}
// Check if alphaNew satisfies the Wolfe conditions
// First the sufficient decrease condition
const double f_alphaNew = FunctionValue(f, x0 + alphaNew * searchDirection);
if ((f_alphaNew > f_x0 + c1 * alphaNew * df_x0) || (f_alphaNew >= f_alpha0))
{
// The decrease is not sufficient, so set up the parameters for the next iteration
f_alphaLast = f_alpha1;
alphaLast = alpha1;
alpha1 = alphaNew;
f_alpha1 = f_alphaNew;
}
else
{
// There is sufficient decrease, so test the second Wolfe condition i.e. whether the derivative
// corresponding to alphaNew is shallower than the derivative at x0
double df_alphaNew = DirectionalDerivative(f, x0 + alphaNew * searchDirection, searchDirection);
if (fabs(df_alphaNew) <= -c2 * df_x0)
{
// alphaNew satisfies the Wolfe conditions, so return it
result.m_outcome = LineSearchOutcome::Success;
result.m_stepSize = alphaNew;
result.m_functionValue = f_alphaNew;
result.m_derivativeValue = df_alphaNew;
return result;
}
if (df_alphaNew * (alpha1 - alpha0) >= 0.0)
{
f_alphaLast = f_alpha1;
alphaLast = alpha1;
alpha1 = alpha0;
f_alpha1 = f_alpha0;
}
else
{
f_alphaLast = f_alpha0;
alphaLast = alpha0;
}
alpha0 = alphaNew;
f_alpha0 = f_alphaNew;
df_alpha0 = df_alphaNew;
}
}
// Failed to find a conforming step size
result.m_stepSize = 0.0;
result.m_functionValue = 0.0;
result.m_derivativeValue = 0.0;
result.m_outcome = LineSearchOutcome::FailureExceededIterations;
return result;
}
LineSearchResult LineSearchWolfe(const Function& f, const VectorVariable& x0, double f_x0,
const VectorVariable& searchDirection)
{
// uses the notation from Nocedal and Wright, where alpha represents the step size
// alpha0 and alpha1 are the lower and upper bounds of an interval which brackets the final value of alpha
// initial step size of 1 is recommended for quasi-Newton methods (Nocedal and Wright)
double alpha0 = 0.0;
double alpha1 = 1.0;
double f_alpha1 = FunctionValue(f, x0 + alpha1 * searchDirection);
double f_alpha0 = f_x0;
const double df_x0 = DirectionalDerivative(f, x0, searchDirection);
double df_alpha0 = df_x0;
for (AZ::u32 iteration = 0; iteration < lineSearchIterations; iteration++)
{
// if the value of f corresponding to alpha1 isn't sufficiently small compared to f at x0,
// then the interval [alpha0 ... alpha1] must bracket a suitable point.
if ((f_alpha1 > f_x0 + WolfeConditionsC1 * alpha1 * df_x0) || (iteration > 0 && f_alpha1 > f_alpha0))
{
return SelectStepSizeFromInterval(alpha0, alpha1, f_alpha0, f_alpha1, df_alpha0,
f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2);
}
// otherwise, if the derivative corresponding to alpha1 is large enough, alpha1 already
// satisfies the Wolfe conditions and so return alpha1.
double df_alpha1 = DirectionalDerivative(f, x0 + alpha1 * searchDirection, searchDirection);
if (fabs(df_alpha1) <= -WolfeConditionsC2 * df_x0)
{
LineSearchResult result;
result.m_outcome = LineSearchOutcome::Success;
result.m_stepSize = alpha1;
result.m_functionValue = f_alpha1;
result.m_derivativeValue = df_alpha1;
return result;
}
if (df_alpha1 >= 0.0)
{
return SelectStepSizeFromInterval(alpha1, alpha0, f_alpha1, f_alpha0, df_alpha1,
f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2);
}
// haven't found an interval which is guaranteed to bracket a suitable point,
// so expand the search region for the next iteration
alpha0 = alpha1;
f_alpha0 = f_alpha1;
alpha1 = 2.0 * alpha1;
f_alpha1 = FunctionValue(f, x0 + alpha1 * searchDirection);
df_alpha0 = df_alpha1;
}
LineSearchResult result;
result.m_outcome = LineSearchOutcome::BestEffort;
result.m_stepSize = alpha1;
result.m_functionValue = f_alpha1;
result.m_derivativeValue = DirectionalDerivative(f, x0 + alpha1 * searchDirection, searchDirection);
return result;
}
} // namespace NumericalMethods::Optimization