// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-FileCopyrightText: Copyright 1993,1994,1995 David MacDonald, McConnell Brain Imaging Centre // SPDX-License-Identifier: BSD-3-Clause AND MIT #include "vtkAmoebaMinimizer.h" #include "vtkObjectFactory.h" #include constexpr int N_STEPS_NO_VALUE_IMPROVEMENT = 2; constexpr int N_STEPS_NO_PARAM_IMPROVEMENT = 18; VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkAmoebaMinimizer); //------------------------------------------------------------------------------ vtkAmoebaMinimizer::vtkAmoebaMinimizer() { this->Function = nullptr; this->FunctionArg = nullptr; this->FunctionArgDelete = nullptr; this->NumberOfParameters = 0; this->ParameterNames = nullptr; this->ParameterValues = nullptr; this->ParameterScales = nullptr; this->FunctionValue = 0.0; this->ContractionRatio = 0.5; this->ExpansionRatio = 2.0; this->Tolerance = 1e-4; this->ParameterTolerance = 1e-4; this->MaxIterations = 1000; this->Iterations = 0; this->FunctionEvaluations = 0; // specific to the amoeba this->AmoebaVertices = nullptr; this->AmoebaValues = nullptr; this->AmoebaSum = nullptr; this->AmoebaSize = 0; this->AmoebaHighValue = 0; this->AmoebaNStepsNoImprovement = 0; } //------------------------------------------------------------------------------ vtkAmoebaMinimizer::~vtkAmoebaMinimizer() { this->TerminateAmoeba(); if ((this->FunctionArg) && (this->FunctionArgDelete)) { (*this->FunctionArgDelete)(this->FunctionArg); } this->FunctionArg = nullptr; this->FunctionArgDelete = nullptr; this->Function = nullptr; if (this->ParameterNames) { for (int i = 0; i < this->NumberOfParameters; i++) { delete[] this->ParameterNames[i]; } delete[] this->ParameterNames; this->ParameterNames = nullptr; } delete[] this->ParameterValues; this->ParameterValues = nullptr; delete[] this->ParameterScales; this->ParameterScales = nullptr; this->NumberOfParameters = 0; } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "NumberOfParameters: " << this->GetNumberOfParameters() << "\n"; if (this->NumberOfParameters > 0) { int i; os << indent << "ParameterValues: \n"; for (i = 0; i < this->NumberOfParameters; i++) { const char* name = this->GetParameterName(i); os << indent << " "; if (name) { os << name << ": "; } else { os << i << ": "; } os << this->GetParameterValue(i) << "\n"; } os << indent << "ParameterScales: \n"; for (i = 0; i < this->NumberOfParameters; i++) { const char* name = this->GetParameterName(i); os << indent << " "; if (name) { os << name << ": "; } else { os << i << ": "; } os << this->GetParameterScale(i) << "\n"; } } os << indent << "FunctionValue: " << this->GetFunctionValue() << "\n"; os << indent << "FunctionEvaluations: " << this->GetFunctionEvaluations() << "\n"; os << indent << "Iterations: " << this->GetIterations() << "\n"; os << indent << "MaxIterations: " << this->GetMaxIterations() << "\n"; os << indent << "Tolerance: " << this->GetTolerance() << "\n"; os << indent << "ParameterTolerance: " << this->GetParameterTolerance() << "\n"; os << indent << "ContractionRatio: " << this->GetContractionRatio() << "\n"; os << indent << "ExpansionRatio: " << this->GetExpansionRatio() << "\n"; } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetFunction(void (*f)(void*), void* arg) { if (f != this->Function || arg != this->FunctionArg) { // delete the current arg if there is one and a delete meth if ((this->FunctionArg) && (this->FunctionArgDelete)) { (*this->FunctionArgDelete)(this->FunctionArg); } this->Function = f; this->FunctionArg = arg; this->Modified(); } } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetFunctionArgDelete(void (*f)(void*)) { if (f != this->FunctionArgDelete) { this->FunctionArgDelete = f; this->Modified(); } } //------------------------------------------------------------------------------ double vtkAmoebaMinimizer::GetParameterValue(const char* name) { for (int i = 0; i < this->NumberOfParameters; i++) { if (this->ParameterNames[i] && strcmp(name, this->ParameterNames[i]) == 0) { return this->ParameterValues[i]; } } vtkErrorMacro("GetParameterValue: no parameter named " << name); return 0.0; } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetParameterValue(const char* name, double val) { int i; for (i = 0; i < this->NumberOfParameters; i++) { if (this->ParameterNames[i] && strcmp(name, this->ParameterNames[i]) == 0) { break; } } this->SetParameterValue(i, val); if (!this->ParameterNames[i]) { char* cp = new char[strlen(name) + 8]; strcpy(cp, name); this->ParameterNames[i] = cp; } } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetParameterValue(int i, double val) { if (i < this->NumberOfParameters) { if (this->ParameterValues[i] != val) { this->ParameterValues[i] = val; this->Iterations = 0; // reset to start this->FunctionEvaluations = 0; this->Modified(); } return; } int n = this->NumberOfParameters + 1; char** newParameterNames = new char*[n]; double* newParameterValues = new double[n]; double* newParameterScales = new double[n]; for (int j = 0; j < this->NumberOfParameters; j++) { newParameterNames[j] = this->ParameterNames[j]; this->ParameterNames[j] = nullptr; // or else it will be deleted in Initialize newParameterValues[j] = this->ParameterValues[j]; newParameterScales[j] = this->ParameterScales[j]; } newParameterNames[n - 1] = nullptr; newParameterValues[n - 1] = val; newParameterScales[n - 1] = 1.0; this->Initialize(); this->NumberOfParameters = n; this->ParameterNames = newParameterNames; this->ParameterValues = newParameterValues; this->ParameterScales = newParameterScales; this->Iterations = 0; // reset to start this->FunctionEvaluations = 0; } //------------------------------------------------------------------------------ double vtkAmoebaMinimizer::GetParameterScale(const char* name) { for (int i = 0; i < this->NumberOfParameters; i++) { if (this->ParameterNames[i] && strcmp(name, this->ParameterNames[i]) == 0) { return this->ParameterScales[i]; } } vtkErrorMacro("GetParameterScale: no parameter named " << name); return 1.0; } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetParameterScale(const char* name, double scale) { for (int i = 0; i < this->NumberOfParameters; i++) { if (this->ParameterNames[i] && strcmp(name, this->ParameterNames[i]) == 0) { this->SetParameterScale(i, scale); return; } } vtkErrorMacro("SetParameterScale: no parameter named " << name); } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::SetParameterScale(int i, double scale) { if (i < 0 || i > this->NumberOfParameters) { vtkErrorMacro("SetParameterScale: parameter number out of range: " << i); return; } if (this->ParameterScales[i] != scale) { this->ParameterScales[i] = scale; this->Modified(); } } //------------------------------------------------------------------------------ // reset the number of parameters to zero void vtkAmoebaMinimizer::Initialize() { if (this->ParameterNames) { for (int i = 0; i < this->NumberOfParameters; i++) { delete[] this->ParameterNames[i]; } delete[] this->ParameterNames; this->ParameterNames = nullptr; } delete[] this->ParameterValues; this->ParameterValues = nullptr; delete[] this->ParameterScales; this->ParameterScales = nullptr; this->NumberOfParameters = 0; this->Iterations = 0; this->FunctionEvaluations = 0; this->AmoebaSize = 0; this->Modified(); } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::EvaluateFunction() { if (this->Function) { this->Function(this->FunctionArg); } this->FunctionEvaluations++; } //------------------------------------------------------------------------------ int vtkAmoebaMinimizer::CheckParameterTolerance() { int n = this->NumberOfParameters; double* vertex0 = this->AmoebaVertices[0]; double* scales = this->ParameterScales; double size = 0; for (int i = 1; i <= n; i++) { double* vertex = this->AmoebaVertices[i]; for (int j = 0; j < n; j++) { double d = fabs((vertex[j] - vertex0[j]) / scales[j]); size = ((d < size) ? size : d); } } if (size != this->AmoebaSize) { this->AmoebaNStepsNoImprovement = N_STEPS_NO_VALUE_IMPROVEMENT - 1; } this->AmoebaSize = size; // if amoeba is static, only make a set number of tries if (this->AmoebaNStepsNoImprovement > (N_STEPS_NO_VALUE_IMPROVEMENT + N_STEPS_NO_PARAM_IMPROVEMENT)) { return 1; } return (size <= this->ParameterTolerance); } //------------------------------------------------------------------------------ int vtkAmoebaMinimizer::Iterate() { if (this->Iterations == 0) { if (!this->Function) { vtkErrorMacro("Iterate: Function is nullptr"); return 0; } this->InitializeAmoeba(); } int improved = this->PerformAmoeba(); int paramsWithinTol = 0; if (!improved) { paramsWithinTol = this->CheckParameterTolerance(); } this->GetAmoebaParameterValues(); this->Iterations++; return (improved || !paramsWithinTol); } //------------------------------------------------------------------------------ void vtkAmoebaMinimizer::Minimize() { if (this->Iterations == 0) { if (!this->Function) { vtkErrorMacro("Minimize: Function is nullptr"); return; } this->InitializeAmoeba(); } for (; this->Iterations < this->MaxIterations; this->Iterations++) { int improved = this->PerformAmoeba(); if (!improved) { if (this->CheckParameterTolerance()) { break; } } } this->GetAmoebaParameterValues(); } /* ------------------------------------------------ This code has been modified from the original. Several macros have been expanded, functions have been renamed to match VTK conventions, and the formatting has been changed. */ /* ----------------------------- MNI Header ----------------------------------- @NAME : vtkAmoebaNumericallyClose @INPUT : n1 n2 threshold_ratio @OUTPUT : @RETURNS : true if the numbers are within the threshold ratio @DESCRIPTION: Decides if two numbers are close to each other. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ #define VTK_AMOEBA_SMALLEST 1.0e-20 static int vtkAmoebaNumericallyClose(double n1, double n2, double threshold_ratio) { double avg, diff, abs_n1, abs_n2; diff = n1 - n2; if (diff < 0.0) { diff = -diff; } abs_n1 = (n1 < 0.0 ? -n1 : n1); abs_n2 = (n2 < 0.0 ? -n2 : n2); if (abs_n1 < VTK_AMOEBA_SMALLEST || abs_n2 < VTK_AMOEBA_SMALLEST) { return (abs_n1 < threshold_ratio && abs_n2 < threshold_ratio); } avg = (n1 + n2) / 2.0; if (avg == 0.0) { return (diff <= threshold_ratio); } if (avg < 0.0) { avg = -avg; } return ((diff / avg) <= threshold_ratio); } /* ----------------------------- MNI Header ----------------------------------- @NAME : InitializeAmoeba @INPUT : @OUTPUT : @RETURNS : @DESCRIPTION: Initializes the amoeba structure to minimize the function. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ void vtkAmoebaMinimizer::InitializeAmoeba() { int i, j; this->TerminateAmoeba(); int n_parameters = this->NumberOfParameters; this->AmoebaNStepsNoImprovement = 0; this->AmoebaVertices = new double*[n_parameters + 1]; this->AmoebaVertices[0] = new double[n_parameters * (n_parameters + 1)]; for (i = 1; i < n_parameters + 1; i++) { this->AmoebaVertices[i] = this->AmoebaVertices[i - 1] + n_parameters; } this->AmoebaValues = new double[n_parameters + 1]; this->AmoebaSum = new double[n_parameters]; for (j = 0; j < n_parameters; j++) { this->AmoebaSum[j] = 0.0; } for (i = 0; i < n_parameters + 1; i++) { for (j = 0; j < n_parameters; j++) { this->AmoebaVertices[i][j] = this->ParameterValues[j]; if (i > 0 && j == i - 1) { this->AmoebaVertices[i][j] = this->ParameterValues[j] + this->ParameterScales[j]; } this->AmoebaSum[j] += this->ParameterValues[j]; } } for (i = 0; i < n_parameters + 1; i++) { for (j = 0; j < n_parameters; j++) { this->ParameterValues[j] = this->AmoebaVertices[i][j]; } this->EvaluateFunction(); this->AmoebaValues[i] = this->FunctionValue; } for (j = 0; j < n_parameters; j++) { this->ParameterValues[j] = this->AmoebaVertices[0][j]; } } /* ----------------------------- MNI Header ----------------------------------- @NAME : GetAmoebaParameterValues @INPUT : @OUTPUT : @RETURNS : @DESCRIPTION: Passes back the current position of the amoeba (best value), and returns the function value at that point. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ void vtkAmoebaMinimizer::GetAmoebaParameterValues() { int i, j, low; low = 0; for (i = 1; i < this->NumberOfParameters + 1; i++) { if (this->AmoebaValues[i] < this->AmoebaValues[low]) { low = i; } } for (j = 0; j < this->NumberOfParameters; j++) { this->ParameterValues[j] = this->AmoebaVertices[low][j]; } this->FunctionValue = this->AmoebaValues[low]; } /* ----------------------------- MNI Header ----------------------------------- @NAME : TerminateAmoeba @INPUT : @OUTPUT : @RETURNS : @DESCRIPTION: Frees the amoeba. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ void vtkAmoebaMinimizer::TerminateAmoeba() { if (this->AmoebaVertices) { delete[] this->AmoebaVertices[0]; delete[] this->AmoebaVertices; this->AmoebaVertices = nullptr; } delete[] this->AmoebaValues; this->AmoebaValues = nullptr; delete[] this->AmoebaSum; this->AmoebaSum = nullptr; } /* ----------------------------- MNI Header ----------------------------------- @NAME : TryAmoeba @INPUT : sum high fac @OUTPUT : @RETURNS : value @DESCRIPTION: Does a modification to the high vertex of the amoeba and returns the value of the new point. If the new point is better (smaller value), it replaces the high vertex of the amoeba. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ double vtkAmoebaMinimizer::TryAmoeba(double sum[], int high, double fac) { int j; double y_try, fac1, fac2; double* parameters; parameters = this->ParameterValues; fac1 = (1.0 - fac) / this->NumberOfParameters; fac2 = fac - fac1; for (j = 0; j < this->NumberOfParameters; j++) { parameters[j] = (sum[j] * fac1 + this->AmoebaVertices[high][j] * fac2); } this->EvaluateFunction(); y_try = this->FunctionValue; if (y_try < this->AmoebaValues[high]) { this->AmoebaValues[high] = y_try; for (j = 0; j < this->NumberOfParameters; j++) { sum[j] += parameters[j] - this->AmoebaVertices[high][j]; this->AmoebaVertices[high][j] = parameters[j]; } } return (y_try); } /* ----------------------------- MNI Header ----------------------------------- @NAME : PerformAmoeba @INPUT : @OUTPUT : @RETURNS : true if numerically significant improvement @DESCRIPTION: Performs one iteration of an amoeba, returning true if a numerically significant improvement has been found recently. Even if it returns 0, you can keep calling this function, since it may be contracting with no improvement, but will eventually shrink small enough to get an improvement. @METHOD : @GLOBALS : @CALLS : @CREATED : 1993 David MacDonald @MODIFIED : 2002 David Gobbi ---------------------------------------------------------------------------- */ int vtkAmoebaMinimizer::PerformAmoeba() { int i, j, low, high, next_high; double y_try, y_save; int improvement_found; improvement_found = 1; if (this->AmoebaValues[0] > this->AmoebaValues[1]) { high = 0; next_high = 1; } else { high = 1; next_high = 0; } low = next_high; for (i = 2; i < this->NumberOfParameters + 1; i++) { if (this->AmoebaValues[i] < this->AmoebaValues[low]) { low = i; } else if (this->AmoebaValues[i] > this->AmoebaValues[high]) { next_high = high; high = i; } else if (this->AmoebaValues[i] > this->AmoebaValues[next_high]) { next_high = i; } } if (this->AmoebaValues[high] == this->AmoebaHighValue || vtkAmoebaNumericallyClose(this->AmoebaValues[low], this->AmoebaValues[high], this->Tolerance)) { ++this->AmoebaNStepsNoImprovement; if (this->AmoebaNStepsNoImprovement >= N_STEPS_NO_VALUE_IMPROVEMENT) { improvement_found = 0; } } else { this->AmoebaNStepsNoImprovement = 0; } this->AmoebaHighValue = this->AmoebaValues[high]; y_try = this->TryAmoeba(this->AmoebaSum, high, -1.0); if (y_try <= this->AmoebaValues[low]) { TryAmoeba(this->AmoebaSum, high, this->ExpansionRatio); } else if (y_try >= this->AmoebaValues[next_high]) { y_save = this->AmoebaValues[high]; y_try = TryAmoeba(this->AmoebaSum, high, this->ContractionRatio); if (y_try >= y_save) { for (i = 0; i < this->NumberOfParameters + 1; i++) { if (i != low) { for (j = 0; j < this->NumberOfParameters; j++) { this->ParameterValues[j] = (this->AmoebaVertices[i][j] + this->AmoebaVertices[low][j]) / 2.0; this->AmoebaVertices[i][j] = this->ParameterValues[j]; } this->EvaluateFunction(); this->AmoebaValues[i] = this->FunctionValue; } } for (j = 0; j < this->NumberOfParameters; j++) { this->AmoebaSum[j] = 0.0; for (i = 0; i < this->NumberOfParameters + 1; i++) { this->AmoebaSum[j] += this->AmoebaVertices[i][j]; } } } } return (improvement_found); } VTK_ABI_NAMESPACE_END