diff --git a/.cirrus.yml b/.cirrus.yml
new file mode 100644
index 0000000000..5036dafdc8
--- /dev/null
+++ b/.cirrus.yml
@@ -0,0 +1,201 @@
+task:
+
+ name: "Cirrus tests"
+
+ # Circus CI will shut down on Monday, June 1, 2026. See https://cirruslabs.org/ for details.
+
+
+ #
+ # Status as of 20260226:
+ #
+ # - All tests pass on Debian and Fedora, including both AMD64 and ARM64.
+ #
+ # - The tests pass on AMD64 FreeBSD. However, cmake_test_script with LLVM 22 needs the following:
+ # export LDFLAGS="-Wl,-z,execstack"
+ # Otherwise, the error message is like
+ # "ld: error: CMakeFiles/primac.dir/uobyqa_c.f90.o: requires an executable stack, but -z execstack is not specified"
+ # In contrast, cmake_test_script passes on Linux with gcc with
+ # export LDFLAGS="-Wl,-z,noexecstack"
+ # if FFLAGS contains --ftrampoline-impl=heap.
+ # This means that an executable stack is not generated on Linux with gcc, but is generated on FreeBSD with LLVM. Why?
+ #
+ # - cmake_test_script fails on AMD64 Alpine when testing example_SOLVER_c and SOLVER_data_c.
+ # The error message is like
+ # ```
+ # Program received signal SIGABRT, Aborted.
+ # __restore_sigs (set=set@entry=0x7fffffffd8a0) at ./arch/x86_64/syscall_arch.h:40
+ # warning: 40 ./arch/x86_64/syscall_arch.h: No such file or directory
+ # ```
+ # On the other hand, the test works fine on ARM64 Alpine.
+ #
+
+
+ # Perform the task only if the folders or files specified below are changed
+ # See https://cirrus-ci.org/guide/writing-tasks/#conditional-task-execution
+ ## skip: the task will be created / triggered, but its execution will be skipped, and it will be marked as successful.
+ ##skip: "!changesInclude('.cirrus.yml', 'fortran/**', 'c/**', 'python/**')"
+ ## only_if: the task will not be created / triggered.
+ ##only_if: "changesInclude('.cirrus.yml', 'CMakeLists.txt', 'fortran/**', 'c/**', 'python/**')"
+
+ timeout_in: 120m # There is a hard limit of 2 hours for free tasks.
+
+ matrix: # We test the platforms not available on GitHub Actions.
+ - name: AMD64 Debian
+ container:
+ image: debian:latest
+
+ - name: ARM64 Debian
+ arm_container:
+ image: debian:latest
+
+ - name: AMD64 Fedora
+ container:
+ image: fedora:latest
+
+ - name: ARM64 Fedora
+ arm_container:
+ image: fedora:latest
+
+ - name: AMD64 Alpine
+ container:
+ image: alpine:latest
+
+ - name: ARM64 Alpine
+ arm_container:
+ image: alpine:latest
+
+ - name: AMD64 FreeBSD
+ freebsd_instance:
+ #image_family: freebsd-16-0-snap # INVALID_ARGUMENT: Snap images are not supported due to boot stability.
+ image_family: freebsd-15-0-amd64-ufs
+ architecture: amd64
+
+ # The following seems not available as of 20260222. See
+ # https://github.com/cirruslabs/cirrus-ci-docs/issues/906
+ # https://cirrus-ci.com/task/6720657520590848
+ # - name: ARM64 FreeBSD
+ # freebsd_instance:
+ # image_family: freebsd-14-0
+ # architecture: arm64
+
+ dependencies_script: |
+ set -euo pipefail
+
+ echo "MK=make" >> $CIRRUS_ENV
+
+ uname -a
+ cat /etc/os-release || true
+
+ if [ "$(uname)" = FreeBSD ] ; then
+ pkg update && pkg upgrade -y && pkg install -y bash gcc git cmake devel/gmake devel/gdb llvm-devel
+ echo "MK=gmake" >> $CIRRUS_ENV
+
+ export PATH=/usr/local/llvm-devel/bin:$PATH
+ echo "PATH=$PATH" >> $CIRRUS_ENV
+ type clang && clang --version
+ type flang && flang --version
+ elif grep -qi "fedora" /etc/os-release ; then
+ dnf upgrade -y && dnf install -y gcc git make cmake gfortran gdb libasan libubsan
+ elif grep -qi "alpine" /etc/os-release ; then
+ apk update && apk upgrade && apk add musl-dev gcc git make cmake gfortran gdb bash musl-dbg
+ else
+ apt update && apt upgrade -y && apt install -y gcc git make cmake gfortran gdb
+ fi
+
+ type gcc && gcc --version
+ type gfortran && gfortran --version
+ type cmake && cmake --version
+ type gdb && gdb --version
+
+
+ fortran_example_script: |
+ ROOT_DIR=$(git rev-parse --show-toplevel)
+ for SOLVER in uobyqa newuoa bobyqa lincoa cobyla ; do
+ cd $ROOT_DIR/fortran/examples/$SOLVER
+ export EXAMPLE_NUM=1 && $MK gtest
+ export EXAMPLE_NUM=2 && $MK gtest
+ if type flang &> /dev/null ; then
+ export EXAMPLE_NUM=1 && $MK ftest
+ export EXAMPLE_NUM=2 && $MK ftest
+ fi
+ done
+
+
+ cmake_test_script: |
+
+ if [ "$(uname -m)" = x86_64 ] && grep -qi "alpine" /etc/os-release ; then
+ echo "WARNING: CMake test is skipped on AMD64 Alpine." >&2
+ echo "The current CMake test fails on AMD64 Alpine; should investigate the issue and fix it." >&2
+ exit 0
+ fi
+
+ ROOT_DIR=$(git rev-parse --show-toplevel)
+ cd $ROOT_DIR
+
+ if [ "$(uname)" = FreeBSD ] ; then
+ # See https://github.com/libprima/prima/issues/275 for why we use clang and flang on FreeBSD,
+ # and gcc and gfortran on Linux.
+ export CC=clang
+ export CFLAGS="-Wall -Werror"
+ export FC=flang
+ export FFLAGS="-Werror -fimplicit-none -pedantic"
+ export LDFLAGS="-Wl,-z,execstack" # Executable stack is necessary. This is needed as of LLVM 22
+ else
+ export CC=gcc
+ export CFLAGS="-Wall -Wextra -Wpedantic -Werror"
+ export FC=gfortran
+ export FFLAGS="-Wall -Wextra -Wpedantic -Werror -fimplicit-none -ftrampoline-impl=heap -frecursive -fcheck=all -fstack-check -Wno-function-elimination"
+ export LDFLAGS="-Wl,-z,noexecstack" # No executable stack is needed. This is possible with gcc 14+
+ fi
+
+ $FC --version
+ $CC --version
+ cmake --version
+
+ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${CFLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+ ctest --output-on-failure -V -E stress
+
+
+ fortran_test_script: |
+ ROOT_DIR=$(git rev-parse --show-toplevel)
+ cd $ROOT_DIR/fortran/tests
+
+ # Decide the solver to test by $(date + %N)
+ NS=$(date +%N)
+ NS=${NS#"${NS%%[!0]*}"} # Strip leading zeros, or the shell will interpret the number as octal and fail if it contains digits 8 or 9.
+ SOLVER_NUM=$((${NS:-0} % 5)) # Use 0 if completely empty
+ if [ $SOLVER_NUM -eq 0 ] ; then
+ SOLVER=uobyqa
+ elif [ $SOLVER_NUM -eq 1 ] ; then
+ SOLVER=newuoa
+ elif [ $SOLVER_NUM -eq 2 ] ; then
+ SOLVER=bobyqa
+ elif [ $SOLVER_NUM -eq 3 ] ; then
+ SOLVER=lincoa
+ else
+ SOLVER=cobyla
+ fi
+ echo $SOLVER_NUM $SOLVER
+
+ # Decide the integer kind to test by $(date +%N)
+ NS=$(date +%N)
+ NS=${NS#"${NS%%[!0]*}"} # Strip leading zeros, or the shell will interpret the number as octal and fail if it contains digits 8 or 9.
+ IK=$((2**((${NS:-0} % 3) + 1))) # Use 0 if completely empty
+ echo $IK
+
+ # Decide the real kind to test by $(date +%N)
+ NS=$(date +%N)
+ NS=${NS#"${NS%%[!0]*}"} # Strip leading zeros, or the shell will interpret the number as octal and fail if it contains digits 8 or 9.
+ RK=$((2**((${NS:-0} % 3) + 2))) # Use 0 if completely empty
+ echo $RK
+
+ $MK clean && $MK gtest_i${IK}_r${RK}_d1_tst.$SOLVER
+ if type flang &> /dev/null ; then
+ RK=$((2**((${NS:-0} % 2) + 2))) # Use 0 if completely empty; LLVM flang does not support RK=16 as of LLVM 22.1.0
+ echo $RK
+ $MK clean && $MK ftest_i${IK}_r${RK}_d1_tst.$SOLVER
+ fi
+
+ on_failure:
diff --git a/.development b/.development
index d1ff5dc176..3062e0970a 160000
--- a/.development
+++ b/.development
@@ -1 +1 @@
-Subproject commit d1ff5dc1766b35edb99222caa0563cfc85f0f761
+Subproject commit 3062e0970a766959a87795f7b682ef8a65a924d5
diff --git a/.git-archival.txt b/.git-archival.txt
new file mode 100644
index 0000000000..7f30cc476a
--- /dev/null
+++ b/.git-archival.txt
@@ -0,0 +1 @@
+$Format:%(describe:tags=true,match=v[0-9]*)$
diff --git a/.gitattributes b/.gitattributes
index 1faff63707..8a2f16468f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -70,3 +70,6 @@
*.fits binary
*.npy binary
*.npz binary
+
+# Version files for git archive
+.git-archival.txt export-subst
diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml
new file mode 100644
index 0000000000..4a6f8f1363
--- /dev/null
+++ b/.github/actionlint.yaml
@@ -0,0 +1,6 @@
+# Configuration related to self-hosted runner.
+self-hosted-runner:
+ # Labels of self-hosted runner in array of strings.
+ labels:
+ - nagfor
+ - cmake_pi
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 52cea00000..1bd7ad590a 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -45,6 +45,7 @@ BQPGASIM
BROWNAL
Bindel
Broyden
+CCALLBACK
CGRAD
CHANDHEQ
CHCKTST
@@ -897,7 +898,7 @@ funx
fval
fwrite
fvdsrc
-frdsrc
+rdsrc
gaussian
gca
gcc
@@ -2048,11 +2049,10 @@ COBJCON
cobjfun
cobjfuncon
constrc
-evalcobj
-evalcobjcon
execstack
FUNPTR
PROCPOINTER
+PYTHONPATH
cfun
cobj
NVHPC
@@ -2066,4 +2066,505 @@ julia
libclang
dumpbin
objdump
-
+rfun
+cfiles
+ifiles
+parcluster
+ERRINROSNE
+FLG
+gsrc
+ifd
+INCLUDEDIR
+nbut
+ncharacter
+nchist
+nconstrviolation
+nds
+nexitflag
+nfhist
+nfintrf
+nfrac
+ngredsq
+nif
+ninteger
+nmex
+nnf
+nrestmp
+nreturn
+nshs
+nsolver
+nwhich
+Olimit
+tfopt
+tfun
+tfx
+vlagc
+gcp
+whos
+fortlab
+LEVYMONT
+BEALE
+BOXBODLS
+GMNCASE
+ZECEVIC
+BROWNBS
+HALDMADS
+CANTILVR
+WATSONNE
+GBRAIN
+GOULDQP
+MEYER
+NYSTROM
+TRUSPYR
+WACHBIEG
+WAYSEA
+ZANGWIL
+CHWIRUT
+LOTSCHD
+HYDROELS
+JENSMP
+KSIP
+MARATOS
+MEXHAT
+OPTCNTRL
+OPTPRLOC
+OSCIPANE
+TWOBARS
+lang
+archnorma
+orthtol
+nouninit
+libgfortran
+chocolatey
+fcn
+BINDIR
+cmdfile
+macports
+nwrite
+stardev
+noutput
+nprobinfo
+dble
+interm
+procpointer
+METHANB
+nprob
+VANDANMSLS
+ODFITS
+HATFLDGLS
+FCCU
+HIMMELBK
+LDFLAGSVER
+LINKFLAGS
+FFINC
+FFLIB
+METHANB
+TENBARS
+OET
+TRIGON
+setxor
+HP
+MAXPOW
+minfloat
+maxpow
+QCNEW
+apk
+cirruslabs
+dnf
+freebsd
+sonoma
+randi
+libasan
+libubsan
+JDORP
+hpa
+CERI
+HIMMELP
+nuse
+nxbdi
+amd
+mmlir
+nojvm
+nodesktop
+nodisplay
+nosplash
+noopengl
+ogfile
+nend
+capfd
+cibuildwheel
+dtype
+dummybaseobject
+GIL
+maxcv
+myargs
+ndarray
+newfun
+NEWPYTHON
+nfev
+nlconstrlist
+nlcs
+pybind
+pypa
+pystr
+rtol
+scikit
+ucrt
+whl
+xlist
+ARCHS
+CIBW
+cibw
+rtools
+amd
+edgeitems
+printoptions
+maxfev
+testname
+skipif
+outerr
+libprimac
+libprimaf
+libprimafc
+htmlcov
+delocate
+broadcastable
+autoselection
+auditwheel
+Cbuild
+Ceditable
+asarray
+jac
+lstsq
+nanmax
+nanmin
+prepdfo
+rcond
+subcases
+newconstraint
+inear
+PYCUTEST
+pycutest
+excludelist
+slsqp
+optiprofiler
+manylinux
+Opti
+pathspec
+itemsize
+tcpclient
+tomemoryview
+scm
+nuj
+PARKCH
+TESTQUAD
+WAITALL
+xcodebuild
+ICX
+Belakovski
+allclose
+argmin
+biggs
+CHEC
+checkbreak
+chisti
+codebases
+cov
+cqai
+cqi
+degenlpb
+errinbar
+fhisti
+hstack
+isneginf
+isposinf
+joptcandidate
+jth
+Kbreak
+mgh
+mmm
+nanargmax
+tenbars
+tfi
+pyprima
+primasum
+primapow
+autouse
+SYNTHES
+pytestmark
+misra
+polak
+loadgroup
+rosen
+newx
+COBYQA
+sustainability
+Bmine
+Bnumpy
+btest
+byteorder
+cstrerror
+debugpy
+floatmode
+funerror
+jankiprofiler
+mysum
+NSFC
+numrows
+presult
+speedtest
+tempsum
+simulink
+COOLHANSLS
+DEGENQP
+dotglob
+EQC
+HILBERTB
+ORTHREGB
+VESUVIA
+VESUVIOULS
+AIRCRFTB
+ALLINIT
+ALLINITU
+BIGGSC
+BRANIN
+BRKMCC
+BROWNDEN
+CLUSTERLS
+DANIWOODLS
+DEMBO
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA
+DGOSPEC
+DJTL
+ECKERLE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL
+ENSOLS
+errid
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+FBRAINLS
+GBRAINLS
+GENHS
+GROWTHLS
+HAHN
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFLS
+HATFLDH
+HILBERTA
+HIMMELBA
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HUBFIT
+INTRISIC
+iseed
+iswritable
+JUDGEB
+KOWOSB
+LANCZOS
+libsvml
+LOGHAIRY
+LOGROS
+LRIJCNN
+MARATOSB
+matprodxy
+MDHOLE
+MINSTD
+nelse
+NELSONLS
+nerror
+noexecstack
+OSBORNE
+OSBORNEA
+OSBORNEB
+OSLBQP
+PFIT
+plocate
+PORTFL
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PSPDOC
+QINGB
+rco
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN
+sco
+SCW
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SISSER
+SOLVERF
+SPECAN
+STANCMIN
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+SUPERSIM
+TESTSEED
+THURBERLS
+VESUVIALS
+VESUVIOLS
+VIBRBEAM
+vsrc
+YFIT
+AIRCRFTA
+alh
+ALLINITA
+ALLINITC
+ALSOTAME
+ARGAUSS
+BARDNE
+BEALENE
+BOXBOD
+BROWNBSNE
+BROWNDENE
+BYRDSPHR
+CHACONN
+CONCON
+CONGIGMZ
+COOLHANS
+CSFI
+CUBENE
+DANIWOOD
+DANWOOD
+DEMYMALO
+DENSCHNBNE
+DENSCHNCNE
+DENSCHNDNE
+DENSCHNENE
+DENSCHNFNE
+DIPIGRI
+DIXCHLNG
+EGGCRATENE
+ELATTAR
+ELATVIDUNE
+ENSO
+EXPFITNE
+FBRAINNE
+Fresult
+FREURONE
+GETMEXLIBGCC
+GIGOMEZ
+GOTTFR
+GULFNE
+HAIFAS
+HATFLDANE
+HATFLDBNE
+HATFLDDNE
+HATFLDENE
+HATFLDF
+HATFLDFLNE
+HELIXNE
+HIMMELBC
+HIMMELBD
+HIMMELBE
+HIMMELBFNE
+HYPCIR
+INTEQNE
+JENSMPNE
+JUDGENE
+KIWCRESC
+KOEBHELBNE
+KOWOSBNE
+ldd
+LEWISPOL
+LNP
+LOOTSMA
+MADSEN
+MCONCON
+mexfun
+MINMAXBD
+MOREBVNE
+MWRIGHT
+ncommon
+PENLT
+POWELLBS
+POWELLSE
+POWELLSQ
+POWERSUMNE
+readelf
+ROSENMMX
+RSNBRNE
+SINVALNE
+soname
+SPECANNE
+SSINE
+STREGNE
+STRTCHDVNE
+tempname
+THURBER
+VARDIMNE
+VIBRBEAMNE
+WEEDSNE
+WOMFLET
+YFITNE
+Qoro
+HHmm
+MMdd
+ZAMB
+excinfo
+HNN
+Neurosolver
+amdflang
+aomp
+libstdcxx
+MFF
+MFORT
+Urayasu
+acfl
+armpl
+modulefiles
+nyes
+iex
+atfl
+gsed
+SDKROOT
+xcrun
+dsrc
+ncall
+ccc
+AMOP
+ATf
+emax
+emin
+scalmax
+scalmin
+sumx
+cebb
+ebab
+fdcc
+sumxs
+aoccflang
+actionlint
+timelimit
+euo
+ftrampoline
+CLANGRT
+dumpmachine
+DIXMAANA
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+libm
+FCFLAGS
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
index dc74921747..86028d2588 100644
--- a/.github/actions/spelling/excludes.txt
+++ b/.github/actions/spelling/excludes.txt
@@ -70,9 +70,5 @@ ignore$
\.pdf$
^\.development/archiva/
^\.development/norma/
-rescue_idz/norma/
+^benchmark/
fortran/original/
-^\Qbenchmark/rescue_idz/archiva/230305/fortran/.keep\E$
-^\Qbenchmark/rescue_idz/archiva/230305/norma/fortran/.keep\E$
-^\Qbenchmark/rescue_idz/archiva/230305/norma/matlab/.keep\E$
-^\Qbenchmark/rescue_idz/archiva/230305/matlab/.keep\E$
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
index c9e9c4e362..9cde5b02ba 100644
--- a/.github/actions/spelling/patterns.txt
+++ b/.github/actions/spelling/patterns.txt
@@ -53,3 +53,9 @@
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
+
+# uuid:
+\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
+
+# Non-English
+[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
diff --git a/.github/scripts b/.github/scripts
index de1b45b9fd..b5bdb489d9 160000
--- a/.github/scripts
+++ b/.github/scripts
@@ -1 +1 @@
-Subproject commit de1b45b9fd667a9fe42cd7d5ecec4318f60d9117
+Subproject commit b5bdb489d90bb4338d6ba072907a4b026ff32891
diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml
new file mode 100644
index 0000000000..f09bee394a
--- /dev/null
+++ b/.github/workflows/actionlint.yml
@@ -0,0 +1,42 @@
+name: Lint the workflows
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+
+
+jobs:
+
+ actionlint:
+ name: actionlint
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v6.0.2
+
+ - name: actionlint
+ id: actionlint #optional, id required only when outputs are used in the workflow steps later
+ uses: raven-actions/actionlint@v2
+ with:
+ matcher: false # optional
+ cache: false # optional
+ fail-on-error: false # optional
+ files: ".github/workflows/*.yml"
+ flags: "-ignore SC1090 -ignore SC2015 -ignore SC2028 -ignore SC2035 -ignore SC2046 -ignore SC2086 -ignore SC2155 -ignore SC2193 -ignore SC2242"
+
+ - name: actionlint Summary
+ if: ${{ steps.actionlint.outputs.exit-code != 0 }} # example usage, do echo only when actionlint action failed
+ run: |
+ echo "Used actionlint version ${{ steps.actionlint.outputs.version-semver }}"
+ echo "Used actionlint release ${{ steps.actionlint.outputs.version-tag }}"
+ echo "actionlint ended with ${{ steps.actionlint.outputs.exit-code }} exit code"
+ echo "actionlint ended because '${{ steps.actionlint.outputs.exit-message }}'"
+ echo "actionlint found ${{ steps.actionlint.outputs.total-errors }} errors"
+ echo "actionlint checked ${{ steps.actionlint.outputs.total-files }} files"
+ echo "actionlint cache used: ${{ steps.actionlint.outputs.cache-hit }}"
+ exit ${{ steps.actionlint.outputs.exit-code }}
diff --git a/.github/workflows/build_python.yml b/.github/workflows/build_python.yml
new file mode 100644
index 0000000000..bb7f2fbe29
--- /dev/null
+++ b/.github/workflows/build_python.yml
@@ -0,0 +1,110 @@
+name: Build Python wheels
+
+on:
+ # Trigger the workflow on push or pull request
+ push:
+ pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ #schedule:
+ # - cron: '0 16 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ build_wheels:
+ name: Build wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-22.04, ubuntu-latest, windows-2022, windows-latest, macos-26-intel, macos-latest]
+
+ steps:
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ fetch-depth: 0 # Get tags for use with git describe
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ fetch-depth: 0 # Get tags for use with git describe
+
+ - name: Checkout pybind11 submodule
+ run: git submodule update --init python/pybind11
+
+
+ - name: Set the MACOSX_DEPLOYMENT_TARGET
+ if: ${{ runner.os == 'macOS' }}
+ run: |
+ MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion | cut -d'.' -f 1)
+ echo "MACOSX_DEPLOYMENT_TARGET is $MACOSX_DEPLOYMENT_TARGET"
+ echo "MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET" >> $GITHUB_ENV
+
+
+ - name: Set up Fortran
+ uses: fortran-lang/setup-fortran@main
+ if: ${{ runner.os == 'macOS' }}
+ with:
+ compiler: gcc
+ version: 12 # Zaikun 20260125: 12 is a quite old version already. Why not the latest?
+
+ # Copied from https://github.com/scipy/scipy/blob/main/.github/workflows/wheels.yml
+ # For rtools, see https://github.com/r-windows/rtools-installer/releases, which has been
+ # archived since 20231027.
+ - name: win_amd64 - install rtools
+ if: ${{ runner.os == 'Windows' }}
+ run: |
+ # mingw-w64
+ choco install rtools -y --no-progress --force --version=4.0.0.20220206
+ echo "c:\rtools40\ucrt64\bin;" >> $env:GITHUB_PATH
+
+ - name: Check the versions of tools
+ shell: bash
+ run: |
+ which cmake && cmake --version
+ which gcc && gcc --version
+ which gfortran && gfortran --version
+ if [[ $(uname) == "Darwin" ]]; then
+ xcodebuild -version
+ fi
+
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v3.4.1
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
+ path: ./wheelhouse/*.whl
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: coverage-report-${{ matrix.os }}-${{ strategy.job-index }}
+ path: ./prima_htmlcov
+
+
+ build_sdist:
+ name: Build source distribution
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6.0.2
+
+ - name: Build sdist
+ run: pipx run build --sdist
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: cibw-sdist
+ path: dist/*.tar.gz
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
index bcbc01c3fa..64a37d8faf 100644
--- a/.github/workflows/cmake.yml
+++ b/.github/workflows/cmake.yml
@@ -10,9 +10,15 @@ on:
git-ref:
description: Git Ref (Optional)
required: false
+ stress-test:
+ description: Stress Test (Optional, true or false)
+ required: false
+ verbose-makefile:
+ description: Verbose Makefile (Optional, true or false)
+ required: false
# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , {1}, {2}', inputs.git-ref, inputs.stress-test, inputs.verbose-makefile) || '' }}
permissions:
@@ -20,140 +26,227 @@ permissions:
jobs:
+ # N.B.: It is tempting to use environment variables to define the compiler flags to avoid
+ # repeating them, but this is not supported as of 20240501. See
+ # https://stackoverflow.com/questions/74072206/github-actions-use-variables-in-matrix-definition
cmake-main:
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest, macos-latest]
+ # See https://github.com/fortran-lang/setup-fortran?tab=readme-ov-file#runner-compatibility
+ # for the toolchains provided by fortran-lang/setup-fortran. We test the latest three on each OS.
+ # First define the toolchains on Linux and macOS.
+ os: [ubuntu-22.04, ubuntu-latest, macos-26-intel, macos-latest]
toolchain:
- - {compiler: gcc, version: 11, cflags: '-Wall -Wextra -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -pedantic -fimplicit-none -frecursive -fcheck=all -fstack-check -Wno-function-elimination'}
- - {compiler: intel-classic, version: '2021.9', cflags: '-diag-disable=10441', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics -assume recursion'}
+ - {compiler: gcc, version: 11, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ - {compiler: gcc, version: 12, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ # As of 20240616 gcc13 has issues importing math.h on macOS.
+ # - {compiler: gcc, version: 13, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ - {compiler: intel-classic, version: '2021.8', cflags: '-diag-disable=10441 -Wall -w3 -Werror-all', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - {compiler: intel-classic, version: '2021.9', cflags: '-diag-disable=10441 -Wall -w3 -Werror-all', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - {compiler: intel-classic, version: '2021.10', cflags: '-diag-disable=10441 -Wall -w3 -Werror-all', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+
include:
+ # intel compiler (ifx) does not support macOS. So they are not included above but below.
+ # Zaikun 20240423
+ # On ubuntu-latest, we want to use '-Wall -w3 -Werror-all' as what we do for the intel-classic
+ # compiler, but the (new) intel c compiler does not recognize '-w3 -Werror-all', even though the
+ # official documentation of the compiler mentions them. Why?
+ - os: ubuntu-22.04
+ toolchain: {compiler: intel, version: '2023.2', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - os: ubuntu-22.04
+ toolchain: {compiler: intel, version: '2024.0', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - os: ubuntu-22.04
+ toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - os: ubuntu-latest
+ toolchain: {compiler: intel, version: '2023.2', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+ - os: ubuntu-latest
+ toolchain: {compiler: intel, version: '2024.0', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
- os: ubuntu-latest
- toolchain: {compiler: intel, version: '2023.1', cflags: '', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics -assume recursion'}
+ toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -Werror', fflags: '-warn all -debug extended -fimplicit-none -standard-semantics'}
+
+ # What follows contains the toolchains for Windows, including gcc, intel-classic, and intel.
+ - os: windows-latest
+ toolchain: {compiler: gcc, version: 11, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ - os: windows-latest
+ toolchain: {compiler: gcc, version: 12, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ - os: windows-latest
+ toolchain: {compiler: gcc, version: 13, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ # Zaikun 20240423:
+ # 1. On windows-latest, the cflags will not be recognized correctly if we start them with `/` instead
+ # of `-`, even though the former aligns with the official documentation of the compilers. Why?
+ # 2. On windows-latest, we want to use '-Wall -W5 -Werror-all' as what we do for the intel-classic
+ # compiler, but the (new) intel c compiler does not recognize '-W5 -Werror-all', even though the
+ # official documentation of the compiler mentions them. Why?
- os: windows-latest
- toolchain: {compiler: gcc, version: 12, cflags: '-Wall -Wextra -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -pedantic -fimplicit-none -frecursive -fcheck=all -fstack-check -Wno-function-elimination'}
+ toolchain: {compiler: intel, version: '2023.2', cflags: '-Wall -Werror', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'}
- os: windows-latest
- toolchain: {compiler: intel, version: '2023.1', cflags: '', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics /assume:recursion'}
+ toolchain: {compiler: intel, version: '2024.0', cflags: '-Wall -Werror', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'}
- os: windows-latest
- toolchain: {compiler: intel-classic, version: '2021.9', cflags: '/Qdiag-disable:10441', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics /assume:recursion'}
+ toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -Werror', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'}
+ - os: windows-latest
+ toolchain: {compiler: intel, version: '2024.1', cflags: '-Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics', cc: cl}
+ # N.B.: As of 20240401, setup-fortran fails constantly with windows-latest and intel-classic
+ # 2021.8. Thus this combination is not included.
+ - os: windows-latest
+ toolchain: {compiler: intel-classic, version: '2021.9', cflags: '-Qdiag-disable:10441 -Wall -W5 -Werror-all', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'}
+ - os: windows-latest
+ toolchain: {compiler: intel-classic, version: '2021.10', cflags: '-Qdiag-disable:10441 -Wall -W5 -Werror-all', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics'}
+ - os: windows-latest
+ toolchain: {compiler: intel-classic, version: '2021.10', cflags: '-Wall -W4 -WX', fflags: '/warn:all /debug:extended /Z7 /fimplicit-none /standard-semantics', cc: cl}
steps:
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
+
+
+ - name: Miscellaneous setup
+ shell: bash
+ run: |
+ # Get the latest version of github_actions_scripts. Needed if the last step did not clone the latest version of the repository.
+ rm -rf .github/scripts && git clone https://github.com/equipez/github_actions_scripts.git .github/scripts && ls -al .github/scripts
+ bash .github/scripts/misc_setup
+
+ # Revise string.f90 in order to print the value of `x` in case segmentation fault happens
+ # again as https://github.com/libprima/prima/actions/runs/7599777476/job/20697191410
+ - name: Revise string.f90
+ if: ${{ matrix.os == 'windows-latest' }}
+ shell: bash
+ run: |
+ cd fortran/common/ || exit 42
+ $SEDI "s|\(write (str, '(I0)') x\)|write (*, *) '---> x = ', x, '<---'\n\1|" string.f90
+ cat string.f90
- - name: Install Ninja / Ubuntu
- if: ${{ matrix.os == 'ubuntu-latest' }}
- run: sudo apt update && sudo apt install ninja-build
- - name: Install Ninja / MacOS
- if: ${{ matrix.os == 'macos-latest' }}
- run: brew install ninja
- - uses: awvwgk/setup-fortran@main
+ - name: Set up Fortran
+ uses: fortran-lang/setup-fortran@main
id: setup-fortran
with:
compiler: ${{ matrix.toolchain.compiler }}
version: ${{ matrix.toolchain.version }}
- - name: Build
+ - name: Override C compiler
+ if: ${{ matrix.toolchain.cc }}
+ run: echo "CC=${{ matrix.toolchain.cc }}" >> $env:GITHUB_ENV
+
+ - name: Build and test
run: |
cmake --version
- cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- ctest --output-on-failure -V -j4 -R example
+
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
+
+ cmake -G Ninja -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+ ctest --output-on-failure -V -E stress
env:
FC: ${{ steps.setup-fortran.outputs.fc }}
+ shell: bash
- name: Stress test
- if: ${{ github.event_name == 'schedule' }}
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
run: |
- cmake --build . --target tests --parallel 4
- ctest --output-on-failure -V -j4 -R large
+ ctest --output-on-failure -V -R stress
+ shell: bash
cmake-other:
runs-on: ubuntu-latest
- continue-on-error: true
strategy:
fail-fast: false
matrix:
toolchain:
- - {compiler: aflang, cflags: '-Wall', fflags: '-pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mrecursive'}
- - {compiler: nvfortran, cflags: '-Wall', fflags: '-C -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mbounds -Mchkstk -Mchkptr'}
- - {compiler: flang, cflags: '-Wall', fflags: '-pedantic -Weverything -Wall -Wextra'}
+ # Flang family with -Mchkptr may fail. See https://forums.developer.nvidia.com/t/bug-in-nvfortran-with-mchkptr-for-unallocated-optional-arguments/223220
+ # As of 20240220, aoccflang with -Mbounds would fail due to the bug at https://github.com/flang-compiler/flang/issues/1238
+ - {compiler: nvfortran, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-Wall -Wextra -Minform=warn -Mstandard -Mchkstk'}
+ - {compiler: flang, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-std=f2018 -pedantic -fimplicit-none -Werror'}
+ - {compiler: aoccflang, cflags: '-Wall -Wextra -Wpedantic -Werror', fflags: '-pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard'}
steps:
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
- name: Install AOCC
- if: ${{ matrix.toolchain.compiler == 'aflang' }}
+ if: ${{ matrix.toolchain.compiler == 'aoccflang' }}
run: bash .github/scripts/install_aocc
- name: Install nvfortran
if: ${{ matrix.toolchain.compiler == 'nvfortran' }}
run: bash .github/scripts/install_nvfortran
+ # Install Flang after AOCC, to make sure that flang is this one, while AOCC flang will be
+ # known as aoccflang.
- name: Install Flang
if: ${{ matrix.toolchain.compiler == 'flang' }}
- run: bash .github/scripts/install_flang
+ run: bash .github/scripts/install_llvm
- - name: Build
+ - name: Build and test
run: |
- cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- # cobyla test does not pass on AOCC: https://github.com/libprima/prima/issues/41
- ctest --output-on-failure -V -j4 -R example -E cobyla
+ $FC --version
+ #$CC --version
+ cmake --version
+
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
+
+ cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+
+ # As of 20240316, CMake test fails on cobyla with the AOCC flang and nvfortran.
+ # See https://github.com/libprima/prima/issues/165
+ ctest --output-on-failure -V -E "stress|cobyla"
+ shell: bash
env:
FC: ${{ matrix.toolchain.compiler }}
- name: Stress test
- if: ${{ github.event_name == 'schedule' }}
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
run: |
- cmake --build . --target tests --parallel 4
- ctest --output-on-failure -V -j4 -R large -E cobyla
-
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: [cmake-main, cmake-other]
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
+ ctest --output-on-failure -V -R stress -E cobyla
+ shell: bash
diff --git a/.github/workflows/cmake_kunpeng b/.github/workflows/cmake_kunpeng
deleted file mode 100644
index 2f6cca504d..0000000000
--- a/.github/workflows/cmake_kunpeng
+++ /dev/null
@@ -1,90 +0,0 @@
-name: CMake build on Kunpeng
-
-on:
- #push:
- schedule:
- - cron: '0 18 * * 6' # 16h Saturday
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-permissions:
- contents: read
-
-jobs:
-
- cmake-main:
- runs-on: [self-hosted, kp]
- strategy:
- fail-fast: false
- matrix:
- toolchain:
- - {compiler: gcc, cflags: '-Wall -Wextra -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -pedantic -fimplicit-none -frecursive -fcheck=all -fstack-check -Wno-function-elimination'}
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Build
- run: |
- cmake --version
- cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- ctest --output-on-failure -V -j4 -R example
- env:
- FC: ${{ steps.setup-fortran.outputs.fc }}
-
-
- cmake-other:
- runs-on: [self-hosted, kp]
- strategy:
- fail-fast: false
- matrix:
- toolchain:
- - {compiler: nvfortran, cflags: '-Wall', fflags: '-C -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mbounds -Mchkstk -Mchkptr'}
- - {compiler: flang, cflags: '-Wall', fflags: '-pedantic -Weverything -Wall -Wextra'}
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Build
- run: |
- cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- # cobyla test does not pass on AOCC: https://github.com/libprima/prima/issues/41
- ctest --output-on-failure -V -j4 -R example -E cobyla
- env:
- FC: ${{ matrix.toolchain.compiler }}
diff --git a/.github/workflows/cmake_mac_nagfor.yml b/.github/workflows/cmake_mac_nagfor.yml
new file mode 100644
index 0000000000..d919a4cc2e
--- /dev/null
+++ b/.github/workflows/cmake_mac_nagfor.yml
@@ -0,0 +1,95 @@
+name: CMake build, macOS ARM64, nagfor
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 18 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ stress-test:
+ description: Stress Test (Optional, true or false)
+ required: false
+ verbose-makefile:
+ description: Verbose Makefile (Optional, true or false)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , {1}, {2}', inputs.git-ref, inputs.stress-test, inputs.verbose-makefile) || '' }}
+
+
+jobs:
+
+ cmake:
+ runs-on: [self-hosted, macOS, ARM64]
+ strategy:
+ fail-fast: false
+ matrix:
+ cc: [gcc, clang]
+
+
+ steps:
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Build and test
+ shell: bash
+ run: |
+ export CC=${{ matrix.cc }}
+ export CFLAGS='-Wall -Wextra -Wpedantic -Werror'
+
+ export FC="nagfor"
+ source ~/local/bin/nag_licensing || echo "\n\nNAG licensing failed.\n\n"
+ export FFLAGS='-fpp -nan -ieee=stop -gline -u -C -C=alias -C=dangling -C=intovf -kind=unique -Warn=constant_coindexing -Warn=subnormal'
+ # Zaikun 20240121: With gcc 13.2 or AppleClang 1.5.0 and nagfor 7.1.7143, if '-C=undefined'
+ # is included in FFLAGS, then the C tests will encounter a segmentation fault, although
+ # the Fortran tests work correctly.
+ #export FFLAGS='-fpp -nan -ieee=stop -gline -u -C -C=alias -C=dangling -C=intovf -C=undefined -kind=unique -Warn=constant_coindexing -Warn=subnormal'
+
+ $FC -V
+ $CC --version
+ cmake --version
+
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
+
+ cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${CFLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+ ctest --output-on-failure -V -E stress
+
+ - name: Stress test
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
+ shell: bash
+ run: |
+ ctest --output-on-failure -V -R stress
diff --git a/.github/workflows/cmake_nagfor.yml b/.github/workflows/cmake_nagfor.yml
index aeb42afcfd..f93fa5bd14 100644
--- a/.github/workflows/cmake_nagfor.yml
+++ b/.github/workflows/cmake_nagfor.yml
@@ -1,8 +1,8 @@
-name: CMake build / nagfor
+name: CMake build, nagfor
on:
# Trigger the workflow on push or pull request
- #push:
+ push:
#pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
@@ -13,66 +13,80 @@ on:
git-ref:
description: Git Ref (Optional)
required: false
+ stress-test:
+ description: Stress Test (Optional, true or false)
+ required: false
+ fflags:
+ description: FFLAGS
+ required: false
+ verbose-makefile:
+ description: Verbose Makefile (Optional, true or false)
+ required: false
# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , {1}, {2}', inputs.git-ref, inputs.stress-test, inputs.verbose-makefile) || '' }}
+permissions:
+ contents: read
+
jobs:
- cmake-nagfor:
+ cmake:
name: CMake build with nagfor
runs-on: [self-hosted, nagfor]
- continue-on-error: true
steps:
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- - name: Conduct the test
+ - name: Build and test
run: |
- export PATH=$PATH:"~/local/bin"
source ~/local/bin/nag_licensing || echo "\n\nNAG licensing failed.\n\n"
- # Use $(( )) rather than $(expr ). See https://unix.stackexchange.com/questions/63166/bash-e-exits-when-let-or-expr-evaluates-to-0
- FFLAGS=-O$(($(date +%-d) % 5))
- FFLAGS=${FFLAGS/O0/g}
- FFLAGS=${FFLAGS/O4/fast}
- export FFLAGS
+ export CC=gcc
+ export CFLAGS="-Wall -Wextra -Wpedantic -Werror"
export FC=nagfor
- cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- ctest --output-on-failure -V
+ if [[ -n "${{ github.event.inputs.fflags }}" ]]; then
+ export FFLAGS="${{ github.event.inputs.fflags }}"
+ else
+ export FFLAGS='-fpp -nan -ieee=stop -gline -u -C -C=alias -C=dangling -C=intovf -kind=unique -Warn=constant_coindexing -Warn=subnormal'
+ # Zaikun 20240121: With gcc 13.2 or AppleClang 1.5.0 and nagfor 7.1.7143, if '-C=undefined'
+ # is included in FFLAGS, then the C tests will encounter a segmentation fault, although
+ # the Fortran tests work correctly. According to NAG support, -C=undefined "is not compatible with calling C code via a BIND(C) interface".
+ fi
+ $FC -V
+ $CC --version
+ cmake --version
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: cmake-nagfor
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
+ cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${CFLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+ ctest --output-on-failure -V -E stress
+
+ - name: Stress test
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
+ shell: bash
+ run: |
+ ctest --output-on-failure -V -R stress
diff --git a/.github/workflows/cmake_pi b/.github/workflows/cmake_pi
deleted file mode 100644
index 88d4763664..0000000000
--- a/.github/workflows/cmake_pi
+++ /dev/null
@@ -1,90 +0,0 @@
-name: CMake build on Raspberry Pi
-
-on:
- #push:
- schedule:
- - cron: '0 16 * * 6' # 16h Saturday
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-permissions:
- contents: read
-
-jobs:
-
- cmake-main:
- runs-on: [self-hosted, pi]
- strategy:
- fail-fast: false
- matrix:
- toolchain:
- - {compiler: gcc, cflags: '-Wall -Wextra -Werror', fflags: '-Wall -Wextra -Wpedantic -Werror -pedantic -fimplicit-none -frecursive -fcheck=all -fstack-check -Wno-function-elimination'}
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Build
- run: |
- cmake --version
- cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- ctest --output-on-failure -V -j4 -R example
- env:
- FC: ${{ steps.setup-fortran.outputs.fc }}
-
-
- cmake-other:
- runs-on: [self-hosted, ARM64, pi64]
- strategy:
- fail-fast: false
- matrix:
- toolchain:
- - {compiler: nvfortran, cflags: '-Wall', fflags: '-C -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mbounds -Mchkstk -Mchkptr'}
- - {compiler: flang, cflags: '-Wall', fflags: '-pedantic -Weverything -Wall -Wextra'}
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Build
- run: |
- cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${{ matrix.toolchain.cflags }}" -DCMAKE_Fortran_FLAGS="${{ matrix.toolchain.fflags }}" .
- cmake --build . --target install --parallel 4
- cmake --build . --target examples --parallel 4
- # cobyla test does not pass on AOCC: https://github.com/libprima/prima/issues/41
- ctest --output-on-failure -V -j4 -R example -E cobyla
- env:
- FC: ${{ matrix.toolchain.compiler }}
diff --git a/.github/workflows/compile_mex.yml b/.github/workflows/compile_mex.yml
new file mode 100644
index 0000000000..8a06aa1e60
--- /dev/null
+++ b/.github/workflows/compile_mex.yml
@@ -0,0 +1,241 @@
+name: Compile MEX
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 0 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+env:
+ MATLAB_VERSION: R2023b
+ MATLAB: /Applications/MATLAB_R2023b.app/bin/matlab
+
+jobs:
+
+ mex:
+ name: Compile MEX
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+
+ # The matrix is the same as that of test_matlab.yml
+ matrix:
+ # As of 20260507 and MATLAB R2026a, MEX does not work on windows-latest due to the version
+ # of MS Visual Studio, which is Visual Studio 2026 on windows-latest as of 20260507.
+ os: [ubuntu-22.04, ubuntu-latest, macos-15-intel, macos-26-intel, windows-2022]
+ matlab: [R2020b, R2021a, R2021b, R2022a, R2022b, R2023a, R2023b, R2024a, R2024b, R2025a, R2025b, R2026a, latest]
+
+ exclude:
+
+ # Below R2022a, MEX does not work on windows-2022 due to the version of MS Visual Studio;
+ # R2022a/b should work according to the documentation of MathWorks and GitHub Actions, but
+ # they do not as of July 2025.
+ - os: windows-2022
+ matlab: R2020b
+ - os: windows-2022
+ matlab: R2021a
+ - os: windows-2022
+ matlab: R2021b
+ - os: windows-2022
+ matlab: R2022a
+ - os: windows-2022
+ matlab: R2022b
+
+ # MATLAB drops support for macOS on Intel starting from R2026a.
+ - os: macos-15-intel
+ matlab: R2026a
+ - os: macos-15-intel
+ matlab: latest
+ - os: macos-26-intel
+ matlab: R2026a
+ - os: macos-26-intel
+ matlab: latest
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Decide gfortran version for MATLAB on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: |
+ GFVER=latest
+ # MATLAB R2021b- supports gfortran 9-
+ MATLAB_VERSION_NUMBER=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ if [[ "${MATLAB_VERSION_NUMBER}" != "latest" && "${MATLAB_VERSION_NUMBER}" -le 2021 ]]; then
+ GFVER=9
+ fi
+ echo "GFVER=$GFVER"
+ echo "GFVER=$GFVER" >> $GITHUB_ENV
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: ${{ env.GFVER }}
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows')
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+
+ - name: Compile the MEX functions
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ startup-options: -nojvm -noopengl -logfile matlab/interfaces/private/build.log
+ command: |
+ ver;
+ options.half = false;
+ options.single = true;
+ options.quadruple = true;
+ options.debug = true;
+ options.classical = false;
+ options.verbose = true;
+ options
+ setup(options);
+ testprima;
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ with:
+ name: prima-${{ matrix.os }}-${{ matrix.matlab }}
+ path: |
+ ./*
+ !./benchmark
+ !./.*
+ !./*.log
+ !./*.dot
+ !./*.json
+
+
+ # mex_mac_silicon:
+ # name: Compile MEX, macOS ARM64
+ # runs-on: [self-hosted, macOS, ARM64]
+ # strategy:
+ # fail-fast: false
+
+ # steps:
+ # - name: Clone Repository (Latest)
+ # uses: actions/checkout@v6.0.2
+ # if: github.event.inputs.git-ref == ''
+ # with:
+ # submodules: recursive
+ # - name: Clone Repository (Custom Ref)
+ # uses: actions/checkout@v6.0.2
+ # if: github.event.inputs.git-ref != ''
+ # with:
+ # ref: ${{ github.event.inputs.git-ref }}
+ # submodules: recursive
+
+ # - name: Miscellaneous setup
+ # run: |
+ # bash .github/scripts/misc_setup
+ # echo "PATH=$PATH:$(dirname ${{ env.MATLAB }})" >> $GITHUB_ENV
+
+ # - name: Check whether matlab is on the path
+ # run: |
+ # echo "$PATH"
+ # which matlab
+
+ # - name: Conduct the test
+ # uses: matlab-actions/run-command@v3.1.1
+ # with:
+ # startup-options: -nojvm -noopengl -logfile matlab/interfaces/private/build.log
+ # command: |
+ # ver;
+ # options.half = true;
+ # options.single = true;
+ # options.quadruple = true;
+ # options.debug = true;
+ # options.classical = false;
+ # options.verbose = true;
+ # options
+ # setup(options);
+ # testprima;
+
+ # - name: Store artifacts
+ # uses: actions/upload-artifact@v7
+ # with:
+ # name: prima-macos-arm64-${{ env.MATLAB_VERSION }}
+ # path: |
+ # ./*
+ # !./benchmark
+ # !./.*
+ # !./*.log
+ # !./*.dot
+ # !./*.json
diff --git a/.github/workflows/lint_hosted.yml b/.github/workflows/lint_hosted.yml
index cc87643f1a..f363ed250a 100644
--- a/.github/workflows/lint_hosted.yml
+++ b/.github/workflows/lint_hosted.yml
@@ -23,7 +23,6 @@ jobs:
test:
name: Lint the Fortran code
runs-on: ubuntu-latest
- continue-on-error: true
strategy:
fail-fast: false
matrix:
@@ -33,7 +32,7 @@ jobs:
steps:
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -47,28 +46,31 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
+ run: export SWAP_SIZE=6 && bash .github/scripts/misc_setup
- name: Set up MATLAB
id: set-up-matlab
if: ${{ matrix.linter == 'mlint' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: latest
+ cache: false # We should not cache MATLAB; otherwise, an empty directory will be cached since we remove $MATLABROOT in the next step
+ products: Parallel_Computing_Toolbox
- name: Get fintrf.h and remove MATLAB
if: ${{ matrix.linter == 'mlint' }}
@@ -81,18 +83,27 @@ jobs:
# Remove MATLAB, or the runner will run out of space.
sudo rm -rf "$MATLABROOT"/* || exit 4
# mlint uses `locate` to find fintrf.h
- sudo apt update && sudo apt install mlocate -y && sudo updatedb
+ sudo apt update || true
+ sudo apt install plocate -y && sudo updatedb
locate '/extern/include/fintrf.h' || exit 5
- name: Install AOCC
run: bash .github/scripts/install_aocc
+ - name: Install AOMP
+ # Retry in case of failure due to transient network issues. If the installation still fails
+ # after all retries, we let it fail silently and continue with the test. This is fine since
+ # there are many tests with other compilers, and since the AOMP compiler is tested in
+ # test_aflang.html. If we try too many times, there may not be enough time left for the test
+ # to run anyway!
+ run: bash .github/scripts/install_aomp 3 1800 || true
+
- name: Install gfortran
- uses: awvwgk/setup-fortran@main
+ uses: fortran-lang/setup-fortran@main
id: setup-fortran
with:
compiler: gcc
- version: ${{ env.GFORTRAN_VERSION }}
+ version: latest
- name: Install Intel oneAPI
run: bash .github/scripts/install_oneapi_linux.sh
@@ -106,33 +117,25 @@ jobs:
- name: Install Oracle sunf95
run: bash .github/scripts/install_sunf95
- # Install Flang after AOCC, to make sure that flang is this one, while AOCC flang will be
- # known as aflang.
- name: Install Flang
- run: bash .github/scripts/install_flang
+ run: bash .github/scripts/install_llvm
- name: Conduct the test
+ # Check the compilers before the test. We continue with the test even if some compilers
+ # are not found successfully due to unsuccessful installation that were let unresolved in
+ # the previous steps, e.g., installation of AOMP. This is fine since there are many tests
+ # with other compilers, and since there is a dedicated workflow to test each compiler.
run: |
- type gfortran ifort ifx aflang g95 nvfortran sunf95 flang
+ type aoccflang && aoccflang --version || true # AOCC Flang, known as "aoccflang" after install_aocc, to be distinguished from LLVM Flang
+ type amdflang && amdflang --version || true
+ type flang && flang --version || true
+ type g95 && g95 --version || true
+ type gfortran && gfortran --version || true
+ type ifx && ifx --version || true
+ type nvfortran && nvfortran --version || true
+ type sunf95 && sunf95 -V || true
cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./${{ matrix.linter }} --all
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/lint_nagfor.yml b/.github/workflows/lint_nagfor.yml
index 1e05eb107a..75e1dcf2f3 100644
--- a/.github/workflows/lint_nagfor.yml
+++ b/.github/workflows/lint_nagfor.yml
@@ -23,7 +23,6 @@ jobs:
test:
name: Lint the Fortran code and the MEX gateways with nagfor
runs-on: [self-hosted, nagfor]
- continue-on-error: true
strategy:
fail-fast: false
matrix:
@@ -31,19 +30,20 @@ jobs:
steps:
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
@@ -63,19 +63,3 @@ jobs:
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/parallel_test_matlab.yml b/.github/workflows/parallel_test_matlab.yml
new file mode 100644
index 0000000000..dcad978d65
--- /dev/null
+++ b/.github/workflows/parallel_test_matlab.yml
@@ -0,0 +1,225 @@
+name: Parallel test, MATLAB
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 14 2-31/2 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+ dimension:
+ description: Dimension (Optional)
+ required: false
+ np:
+ description: Number of Parallel Calls (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}, dimension {2}, {3} parallel calls', inputs.git-ref, inputs.random-seed, inputs.dimension, inputs.np) || '' }}
+
+jobs:
+ test:
+ name: Parallel test of PRIMA
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+
+ # The matrix is the same as that of stress_test_matlab.yml
+ matrix:
+ os: [ubuntu-latest, macos-26-intel, windows-latest]
+ matlab: [R2023a, latest]
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ exclude:
+ # MATLAB drops support for macOS on Intel starting from R2026a. We simply exclude the
+ # corresponding tests, as we have done extensive tests previously.
+ - os: macos-26-intel
+ matlab: latest
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows')
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ if ~isempty('${{ inputs.random-seed }}')
+ options.seed = str2num('${{ inputs.random-seed }}');
+ end
+ if ~isempty('${{ inputs.dimension }}')
+ options.n = str2num('${{ inputs.dimension }}');
+ end
+ if ~isempty('${{ inputs.np }}')
+ options.np = str2num('${{ inputs.np }}');
+ end
+ options
+
+ % Conduct the test multiple times. Some errors may not occur during the first test.
+ try
+ parallel('${{ matrix.solver }}', options);
+ options.compile = false;
+ parallel('${{ matrix.solver }}', options);
+ parallel('${{ matrix.solver }}', options);
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
+
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.solver }}
+ path: |
+ matlab_crash_dump*
diff --git a/.github/workflows/parallel_test_matlab_mac.yml b/.github/workflows/parallel_test_matlab_mac.yml
new file mode 100644
index 0000000000..cb8c82eee5
--- /dev/null
+++ b/.github/workflows/parallel_test_matlab_mac.yml
@@ -0,0 +1,71 @@
+name: Parallel test, MATLAB, macOS ARM64
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 2-31/3 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+ dimension:
+ description: Dimension (Optional)
+ required: false
+ np:
+ description: Number of Parallel Calls (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}, dimension {2}, {3} parallel calls', inputs.git-ref, inputs.random-seed, inputs.dimension, inputs.np) || '' }}
+
+env:
+ MATLAB: /Applications/MATLAB_R2023b.app/bin/matlab
+
+jobs:
+ test:
+ name: Parallel test of PRIMA
+ runs-on: [self-hosted, macOS, ARM64]
+ strategy:
+ fail-fast: false
+ matrix:
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+
+ steps:
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+
+
+ - name: Conduct the test # We do not use matlab-actions/run-command, which is not supported on macOS ARM64 as of 20240119
+ run: ${{ env.MATLAB }} -nojvm -batch "ver; root_dir = pwd(); cd(fullfile(root_dir, 'matlab/tests')); parallel('${{ matrix.solver }}'); copy_crash_dump_files(root_dir, true);"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.solver }}
+ path: |
+ matlab_crash_dump*
+
+
+
diff --git a/.github/workflows/profile_all.yml b/.github/workflows/profile_all.yml
index 5c698a6b16..1cb3139f39 100644
--- a/.github/workflows/profile_all.yml
+++ b/.github/workflows/profile_all.yml
@@ -29,9 +29,11 @@ jobs:
matlab: [latest]
dim: [all]
solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
with_optim_toolbox: [yes, no]
ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
exclude:
- solver: cobylan
with_optim_toolbox: yes
@@ -53,13 +55,20 @@ jobs:
ctol_indicator: 1
- solver: bobyqa
ctol_indicator: 2
+ - solver: uobyqa
+ test_feasibility_problems: true
+ - solver: newuoa
+ test_feasibility_problems: true
+ - solver: bobyqa
+ test_feasibility_problems: true
+
steps:
- name: Get the solver name
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -73,62 +82,121 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-${{ matrix.with_optim_toolbox }}
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox == 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox == 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
- name: Set up MATLAB without optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox != 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox != 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -140,6 +208,7 @@ jobs:
options = struct();
options.nr = 4; % 4 random runs for each problem
options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
if strcmp('${{ matrix.solver }}', 'cobylal')
%options.nr = 3;
@@ -176,16 +245,46 @@ jobs:
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.with_optim_toolbox }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
path: |
/tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
/tmp/${{ env.SOLNAME }}_profile_prima/*.txt
- /tmp/${{ env.SOLNAME }}_profile_prima/*start*
- /tmp/${{ env.SOLNAME }}_profile_prima/*end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_all_sq.yml b/.github/workflows/profile_all_sq.yml
new file mode 100644
index 0000000000..4930c5e818
--- /dev/null
+++ b/.github/workflows/profile_all_sq.yml
@@ -0,0 +1,293 @@
+name: Plot performance profiles for all, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 17 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [all]
+ solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ precision: [single, quadruple]
+ ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
+ exclude:
+ - solver: uobyqa
+ ctol_indicator: 1
+ - solver: uobyqa
+ ctol_indicator: 2
+ - solver: newuoa
+ ctol_indicator: 1
+ - solver: newuoa
+ ctol_indicator: 2
+ - solver: bobyqa
+ ctol_indicator: 1
+ - solver: bobyqa
+ ctol_indicator: 2
+ - solver: uobyqa # See https://github.com/libprima/prima/issues/98
+ competitor: classical
+ precision: single
+ - solver: uobyqa
+ test_feasibility_problems: true
+ - solver: newuoa
+ test_feasibility_problems: true
+ - solver: bobyqa
+ test_feasibility_problems: true
+
+
+ steps:
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ blacklist = {};
+ blacklist = [blacklist, {'HYDCAR6LS', 'JENSMP', 'METHANL8LS', 'MEXHAT', 'TOINTQOR'}]; % uobyqa
+ blacklist = [blacklist, {'ARGLINA', 'ARGLINB', 'ARGLINC', 'BA-L1SPLS', 'CHNRSNBM', 'CHNROSNB', 'CHWIRUT1LS', 'ERRINROS', 'LSC1LS', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN16LS', 'METHANB8LS', 'QING', 'SPIN2LS', 'TOINTPSP', 'TRIGON2'}]; % newuoa
+ blacklist = [blacklist, {'CHEBYQAD', 'DECONVU', 'HOLMES'}]; % bobyqa
+ blacklist = [blacklist, {'AGG', 'ARGLALE', 'AVION2', 'CVXQP1', 'DALLASS', 'DUALC1', 'DUAL1', 'DUAL3', 'DUAL4', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'HIMMELBI', 'HYDROELS', 'KSIP', 'QPNBLEND', 'SMBANK', 'SSEBLIN', 'ZECEVIC2'}]; % lincoa
+ blacklist = [blacklist, {'EQC'}]; % Classical LINCOA segfaults when the precision is single
+ blacklist = [blacklist, {'ACOPP14', 'ACOPR14', 'ANTWERP', 'CANTILVR', 'DEGENLPA', 'DEGENLPB', 'DNIEPER', 'ERRINROSNE', 'FCCU', 'GBRAIN', 'GOULDQP1', 'GROUPING', 'HIMMELBK', 'HS33', 'HS102', 'HS103', 'HS105', 'LOTSCHD', 'LAUNCH', 'LIN', 'LOADBAL', 'LSNNODOC', 'MARATOS', 'MEYER3NE', 'NET1', 'NYSTROM5', 'HALDMADS', 'OET2', 'OPTCNTRL', 'OPTPRLOC', 'OSCIPANE', 'PALMER4ANE', 'POLAK2', 'PRODPL0', 'PRODPL1', 'QCNEW', 'QPNBLEND', 'RAT42', 'RK23', 'TFI2', 'TWOBARS', 'TRIGON1NE', 'TRO6X2', 'TRO3X3', 'TRUSPYR1', 'WACHBIEG', 'WATER', 'WATSONNE', 'WAYSEA1NE', 'ZANGWIL3', 'ZECEVIC2', 'ZECEVIC4', 'ZY2', 'BA-L1SP','DISCS', 'CERI651E'}]; % cobyla
+
+ ver;
+
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = blacklist;
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+ options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
+
+ try
+ if strcmp('${{ matrix.solver }}', 'cobylal')
+ prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'cobylan')
+ prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
+ else
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ end
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = '${{ env.SOLNAME }}';
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
+ path: |
+ matlab_crash_dump*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_bobyqa_small.yml b/.github/workflows/profile_bobyqa_small.yml
index f54f188d45..0b993ef7d9 100644
--- a/.github/workflows/profile_bobyqa_small.yml
+++ b/.github/workflows/profile_bobyqa_small.yml
@@ -30,60 +30,133 @@ jobs:
matlab: [latest]
dim: [small]
solver: [bobyqa]
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -103,16 +176,46 @@ jobs:
movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
/tmp/${{ matrix.solver }}_profile_prima/*.txt
- /tmp/${{ matrix.solver }}_profile_prima/*start*
- /tmp/${{ matrix.solver }}_profile_prima/*end*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_bobyqa_small_sq.yml b/.github/workflows/profile_bobyqa_small_sq.yml
new file mode 100644
index 0000000000..31665fdcd1
--- /dev/null
+++ b/.github/workflows/profile_bobyqa_small_sq.yml
@@ -0,0 +1,223 @@
+name: Plot performance profiles for BOBYQA, small, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 14 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [bobyqa]
+ precision: [single, quadruple]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = {'DNIEPER', 'ERRINROSNE', 'PRODPL1', 'TRO6X2'};
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', 'b', options);
+
+
+ % Move the files to prepare for uploading artifacts
+
+ movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
+ /tmp/${{ matrix.solver }}_profile_prima/*.txt
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_cobyla_small.yml b/.github/workflows/profile_cobyla_small.yml
index 4e92d38584..b24b31dfa0 100644
--- a/.github/workflows/profile_cobyla_small.yml
+++ b/.github/workflows/profile_cobyla_small.yml
@@ -19,6 +19,7 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
+
test:
name: Profile PRIMA.
runs-on: ${{ matrix.os }}
@@ -29,72 +30,151 @@ jobs:
matlab: [latest]
dim: [small]
solver: [cobylal, cobylan]
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
with_optim_toolbox: [yes, no]
ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
exclude:
- solver: cobylan
with_optim_toolbox: yes
steps:
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-${{ matrix.with_optim_toolbox }}
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox == 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox == 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
- name: Set up MATLAB without optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox != 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox != 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -106,6 +186,7 @@ jobs:
options = struct();
options.nr = 3;
options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
if strcmp('${{ matrix.solver }}', 'cobylal')
prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
@@ -134,16 +215,46 @@ jobs:
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.with_optim_toolbox }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
path: |
/tmp/cobyla_profile_prima/*summary*.pdf
/tmp/cobyla_profile_prima/*.txt
- /tmp/cobyla_profile_prima/*start*
- /tmp/cobyla_profile_prima/*end*
+ /tmp/cobyla_profile_prima/*prob_start*
+ /tmp/cobyla_profile_prima/*prob_end*
+ /tmp/cobyla_profile_prima/*stuck*
+ /tmp/cobyla_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/cobyla_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_cobyla_small_sq.yml b/.github/workflows/profile_cobyla_small_sq.yml
new file mode 100644
index 0000000000..87147eb21a
--- /dev/null
+++ b/.github/workflows/profile_cobyla_small_sq.yml
@@ -0,0 +1,249 @@
+name: Plot performance profiles for COBYLA, small, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 15 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [cobylal, cobylan]
+ precision: [single, quadruple]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
+
+ steps:
+
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = {'ACOPP14', 'ACOPR14', 'ANTWERP', 'CANTILVR', 'CERI651E', 'DEGENLPA', 'DEGENLPB', 'DNIEPER', 'ERRINROSNE', 'FCCU', 'GBRAIN', 'GOULDQP1', 'GROUPING', 'HS33', 'HS102', 'HS103', 'HS105', 'LOTSCHD', 'LAUNCH', 'LIN', 'LOADBAL', 'MEYER3NE', 'NYSTROM5', 'HALDMADS', 'POLAK2', 'PRODPL0', 'PRODPL1', 'QCNEW', 'RAT42', 'RK23', 'TFI2', 'TRO6X2', 'TRUSPYR1', 'WACHBIEG', 'WATER', 'WATSONNE', 'WAYSEA1NE', 'ZANGWIL3', 'ZECEVIC2', 'ZECEVIC4', 'ZY2', 'DEMBO7', 'MCONCON', 'CERI651C', 'MGH10S', 'HAHN1', 'HS27', 'ELATTAR', 'HS114', 'OET1', 'OET3', 'KSIP'};
+
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+ options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
+
+ if strcmp('${{ matrix.solver }}', 'cobylal')
+ prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'cobylan')
+ prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
+ end
+
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = 'cobyla';
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), '/tmp/cobyla_profile_prima/');
+ movefile(fullfile(cd(), '*.txt'), '/tmp/cobyla_profile_prima/');
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
+ path: |
+ /tmp/cobyla_profile_prima/*summary*.pdf
+ /tmp/cobyla_profile_prima/*.txt
+ /tmp/cobyla_profile_prima/*prob_start*
+ /tmp/cobyla_profile_prima/*prob_end*
+ /tmp/cobyla_profile_prima/*stuck*
+ /tmp/cobyla_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/cobyla_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_compiler_options.yml b/.github/workflows/profile_compiler_options.yml
index 240de4bed2..83502bebdb 100644
--- a/.github/workflows/profile_compiler_options.yml
+++ b/.github/workflows/profile_compiler_options.yml
@@ -3,10 +3,10 @@ name: Plot performance profiles, various compiler options
on:
# Trigger the workflow on push or pull request
#push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 4 * * *'
+ - cron: '0 4 1-31/4 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -22,7 +22,6 @@ jobs:
test:
name: Profile PRIMA.
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
@@ -32,16 +31,7 @@ jobs:
solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
compiler_options: [-O0, -O1, -O2, -O3, -Ofast -fno-stack-arrays, -ffast-math]
competitor: [classical]
- with_optim_toolbox: [yes, no]
- exclude:
- - solver: cobylan
- with_optim_toolbox: yes
- - solver: uobyqa
- with_optim_toolbox: yes
- - solver: newuoa
- with_optim_toolbox: yes
- - solver: bobyqa
- with_optim_toolbox: yes
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
@@ -49,7 +39,7 @@ jobs:
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -63,67 +53,112 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-${{ matrix.with_optim_toolbox }}
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox == 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
- with:
- release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
-
- - name: Set up MATLAB without optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox != 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
- name: Revise setup_compiler_options to print the revised mexopt file
run: |
cd matlab/tests/private || exit 1
- sed -i "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -135,19 +170,22 @@ jobs:
options = struct();
options.compiler_options = '${{ matrix.compiler_options }}';
options.debug = ~contains('${{ matrix.compiler_options }}', 'fast')
- options.nr = 4; % 4 random runs for each problem
+ options.nr = 3; % random runs for each problem
if strcmp('${{ matrix.solver }}', 'cobylal')
- options.nr = 3;
prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
elseif strcmp('${{ matrix.solver }}', 'cobylan')
- options.nr = 3;
+ options.blacklist = {'CERI651B', 'HS99EXP', 'TRO6X2', 'POLAK2', 'GBRAIN', 'HIMMELP6','ORTHREGB', 'PRODPL0', 'DISCS', 'PRODPL1', 'DNIEPER', 'VESUVIA', 'OPTPRLOC', 'NET1', 'CERI651D', 'TRO3X3', 'ERRINROSNE', 'HS85', 'ERRINROSNE'};
prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ options.blacklist = {'HS105'}; % Classical LINCOA encounters a SEGFAULT when compiled with -O1.
+ options.blacklist = [options.blacklist, {'DUAL2', 'SSEBLIN', 'AGG', 'SMBANK', 'GMNCASE3', 'GMNCASE2', 'HYDROELS', 'CVXQP1', 'GMNCASE1', 'SPANHYD', 'OET3', 'DALLASS'}];
prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ options.blacklist = {'CHEBYQAD'};
prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
else
+ options.blacklist={'LUKSAN11LS', 'SPIN2LS', 'ARGLINA', 'ARGLINC', 'ERRINROS', 'QING', 'METHANB8LS', 'SPIN2LS', 'COOLHANSLS', 'SENSORS', 'ARGLINB', 'BA-L1SPLS', 'LUKSAN13LS'};
prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
end
@@ -159,7 +197,7 @@ jobs:
files = dir([solver, '*.summary.*.pdf'])
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['${{ matrix.with_optim_toolbox }}', '_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
movefile(file, newfile);
end
movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
@@ -168,37 +206,51 @@ jobs:
files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['${{ matrix.with_optim_toolbox }}', '_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.compiler_options }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
/tmp/${{ env.SOLNAME }}_profile_prima/*.txt
- /tmp/${{ env.SOLNAME }}_profile_prima/*start*
- /tmp/${{ env.SOLNAME }}_profile_prima/*end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
needs: test
steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_infnan.yml b/.github/workflows/profile_infnan.yml
new file mode 100644
index 0000000000..1bb0a2ecca
--- /dev/null
+++ b/.github/workflows/profile_infnan.yml
@@ -0,0 +1,256 @@
+name: Plot performance profiles, broken infnan
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 4 2-31/4 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
+ compiler_options: [-O0, -O1, -O2, -O3, -Ofast -fno-stack-arrays, -ffast-math]
+ competitor: [norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ steps:
+
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Revise the Fortran code to break infnan
+ run: |
+ mv fortran/tests/testsuite/inf.F90 fortran/common/ && cat fortran/common/inf.F90
+ mv fortran/tests/testsuite/infnan.F90 fortran/common/ && cat fortran/common/infnan.F90
+
+ - name: Revise setup_compiler_options to print the revised mexopt file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.compiler_options = '${{ matrix.compiler_options }}';
+ options.debug = ~contains('${{ matrix.compiler_options }}', 'fast')
+ options.nr = 1; % 1 random runs for each problem
+
+ if strcmp('${{ matrix.solver }}', 'cobylal')
+ prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'cobylan')
+ prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
+ else
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ end
+
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = '${{ env.SOLNAME }}'
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['no', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['no', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.compiler_options }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_integer_kind.yml b/.github/workflows/profile_integer_kind.yml
new file mode 100644
index 0000000000..3cbeac3e40
--- /dev/null
+++ b/.github/workflows/profile_integer_kind.yml
@@ -0,0 +1,267 @@
+name: Plot performance profiles, int16 and int64
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 4 3-31/4 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ ik: [16, 64]
+ dim: [all]
+ solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
+ compiler_options: [-O0, -O1, -O2, -O3, -Ofast -fno-stack-arrays, -ffast-math]
+ competitor: [norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ steps:
+
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Revise the Fortran code to use the intended integer kind. Do NOT use `options.integer_kind`, because it will change the integer kind for the competitor as well.
+ run: |
+ $SEDI "s|#define PRIMA_INTEGER_KIND 0|#define PRIMA_INTEGER_KIND ${{ matrix.ik }}|g" fortran/common/ppf.h
+
+ - name: Revise get_solvers.m to print the revised header file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|\(^.*\)\(setup(solver.*$\)|\1header_file\n\1system(['cat ', header_file]);\n\1\2|" get_solvers.m
+
+ - name: Revise setup_compiler_options.m to print the revised mexopt file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.compiler_options = '${{ matrix.compiler_options }}';
+ options.debug = ~contains('${{ matrix.compiler_options }}', 'fast')
+ options.nr = 3; % random runs for each problem
+
+ if strcmp('${{ matrix.solver }}', 'cobylal')
+ prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'cobylan')
+ options.blacklist = {'CERI651B', 'HS99EXP', 'TRO6X2', 'POLAK2', 'GBRAIN', 'HIMMELP6','ORTHREGB', 'PRODPL0', 'DISCS', 'PRODPL1', 'DNIEPER', 'VESUVIA', 'OPTPRLOC', 'NET1', 'CERI651D', 'TRO3X3', 'ERRINROSNE', 'HS85', 'ERRINROSNE', 'DEMBO7', 'POLAK6', 'OSBORNE2'};
+ prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ options.blacklist = {'HS105'}; % Classical LINCOA encounters a SEGFAULT when compiled with -O1.
+ options.blacklist = [options.blacklist, {'DUAL2', 'SSEBLIN', 'AGG', 'SMBANK', 'GMNCASE3', 'GMNCASE2', 'HYDROELS', 'CVXQP1', 'GMNCASE1', 'SPANHYD', 'OET3', 'DALLASS'}];
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ options.blacklist = {'CHEBYQAD'};
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
+ else
+ options.blacklist={'LUKSAN11LS', 'SPIN2LS', 'ARGLINA', 'ARGLINC', 'ERRINROS', 'QING', 'METHANB8LS', 'SPIN2LS', 'COOLHANSLS', 'SENSORS', 'ARGLINB', 'BA-L1SPLS', 'LUKSAN13LS'};
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ end
+
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = '${{ env.SOLNAME }}'
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.compiler_options }}-${{ matrix.ik }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_intrinsic_linalg.yml b/.github/workflows/profile_intrinsic_linalg.yml
new file mode 100644
index 0000000000..fdc2d3a390
--- /dev/null
+++ b/.github/workflows/profile_intrinsic_linalg.yml
@@ -0,0 +1,288 @@
+name: Plot performance profiles with intrinsic linalg
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 17 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [all]
+ solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa] # prima is too expensive
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ precision: [single, double, quadruple]
+ ctol_indicator: [0, 1, 2]
+
+ exclude:
+ - solver: uobyqa
+ ctol_indicator: 1
+ - solver: uobyqa
+ ctol_indicator: 2
+ - solver: newuoa
+ ctol_indicator: 1
+ - solver: newuoa
+ ctol_indicator: 2
+ - solver: bobyqa
+ ctol_indicator: 1
+ - solver: bobyqa
+ ctol_indicator: 2
+ - solver: uobyqa # See https://github.com/libprima/prima/issues/98
+ competitor: classical
+ precision: single
+
+ steps:
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Revise linalg.f90 to use the intrinsic matmul and dot_product instead of matprod and inprod
+ run: |
+ cd fortran/common && bash ./get_intrinsic_linalg && mv intrinsic_linalg.f90 linalg.f90 && cat linalg.f90
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ blacklist = {};
+ blacklist = [blacklist, {'HYDCAR6LS', 'JENSMP', 'METHANL8LS', 'MEXHAT', 'TOINTQOR'}]; % uobyqa
+ blacklist = [blacklist, {'ARGLINA', 'ARGLINB', 'ARGLINC', 'BA-L1SPLS', 'CHNRSNBM', 'CHNROSNB', 'CHWIRUT1LS', 'ERRINROS', 'LSC1LS', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN16LS', 'METHANB8LS', 'QING', 'SPIN2LS', 'TOINTPSP', 'TRIGON2'}]; % newuoa
+ blacklist = [blacklist, {'CHEBYQAD', 'DECONVU', 'HOLMES'}]; % bobyqa
+ blacklist = [blacklist, {'AGG', 'ARGLALE', 'AVION2', 'CVXQP1', 'DALLASS', 'DUALC1', 'DUAL1', 'DUAL3', 'DUAL4', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'HIMMELBI', 'HYDROELS', 'KSIP', 'QPNBLEND', 'SMBANK', 'SSEBLIN', 'ZECEVIC2'}]; % lincoa
+ blacklist = [blacklist, {'EQC'}]; % Classical LINCOA segfaults when the precision is single
+ blacklist = [blacklist, {'ACOPP14', 'ACOPR14', 'ANTWERP', 'CANTILVR', 'DEGENLPA', 'DEGENLPB', 'DNIEPER', 'ERRINROSNE', 'FCCU', 'GBRAIN', 'GOULDQP1', 'GROUPING', 'HIMMELBK', 'HS33', 'HS102', 'HS103', 'HS105', 'LOTSCHD', 'LAUNCH', 'LIN', 'LOADBAL', 'LSNNODOC', 'MARATOS', 'MEYER3NE', 'NET1', 'NYSTROM5', 'HALDMADS', 'OET2', 'OPTCNTRL', 'OPTPRLOC', 'OSCIPANE', 'PALMER4ANE', 'POLAK2', 'PRODPL0', 'PRODPL1', 'QCNEW', 'QPNBLEND', 'RAT42', 'RK23', 'TFI2', 'TWOBARS', 'TRIGON1NE', 'TRO6X2', 'TRO3X3', 'TRUSPYR1', 'WACHBIEG', 'WATER', 'WATSONNE', 'WAYSEA1NE', 'ZANGWIL3', 'ZECEVIC2', 'ZECEVIC4', 'ZY2', 'BA-L1SP','DISCS', 'CERI651E'}]; % cobyla
+
+ ver;
+
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = blacklist;
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+ options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+
+ try
+ if strcmp('${{ matrix.solver }}', 'cobylal')
+ prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'cobylan')
+ prof('cobyla', '${{ matrix.dim }}', 'n', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
+ else
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ end
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = '${{ env.SOLNAME }}';
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}
+ path: |
+ matlab_crash_dump*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_lincoa_small.yml b/.github/workflows/profile_lincoa_small.yml
index 65cde7a7aa..606588a133 100644
--- a/.github/workflows/profile_lincoa_small.yml
+++ b/.github/workflows/profile_lincoa_small.yml
@@ -19,6 +19,7 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
+
test:
name: Profile PRIMA.
runs-on: ${{ matrix.os }}
@@ -29,69 +30,145 @@ jobs:
matlab: [latest]
dim: [small]
solver: [lincoa]
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
with_optim_toolbox: [yes, no]
ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-${{ matrix.with_optim_toolbox }}
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox == 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox == 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
- name: Set up MATLAB without optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox != 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox != 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -103,6 +180,7 @@ jobs:
options = struct();
options.nr = 3;
options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
@@ -127,16 +205,46 @@ jobs:
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.with_optim_toolbox }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
path: |
/tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
/tmp/${{ matrix.solver }}_profile_prima/*.txt
- /tmp/${{ matrix.solver }}_profile_prima/*start*
- /tmp/${{ matrix.solver }}_profile_prima/*end*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_lincoa_small_sq.yml b/.github/workflows/profile_lincoa_small_sq.yml
new file mode 100644
index 0000000000..00bce53917
--- /dev/null
+++ b/.github/workflows/profile_lincoa_small_sq.yml
@@ -0,0 +1,250 @@
+name: Plot performance profiles for LINCOA, small, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 13 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [lincoa]
+ precision: [single, quadruple]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ ctol_indicator: [0, 1, 2]
+ test_feasibility_problems: [true, false]
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = {'AGG', 'ARGLALE', 'AVION2', 'CVXQP1', 'DALLASS', 'DUAL1', 'DUAL3', 'DUAL4', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'HIMMELBI', 'SMBANK', 'SSEBLIN', 'ZECEVIC2'};
+ options.blacklist = [options.blacklist, {'EQC'}]; % Classical LINCOA segfaults when the precision is single
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+ options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
+ options.test_feasibility_problems = ${{ matrix.test_feasibility_problems }};
+
+ try
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', 'l', options);
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
+
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = 'lincoa';
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ movefile(fullfile(cd(), '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['ctol', '${{ matrix.ctol_indicator }}_', 'yes', '_optool_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}-${{ matrix.test_feasibility_problems }}
+ path: |
+ matlab_crash_dump*
+ /tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
+ /tmp/${{ matrix.solver }}_profile_prima/*.txt
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_newuoa_small.yml b/.github/workflows/profile_newuoa_small.yml
index 774d1d7383..30c0427776 100644
--- a/.github/workflows/profile_newuoa_small.yml
+++ b/.github/workflows/profile_newuoa_small.yml
@@ -30,60 +30,133 @@ jobs:
matlab: [latest]
dim: [small]
solver: [newuoa]
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -101,16 +174,46 @@ jobs:
movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
/tmp/${{ matrix.solver }}_profile_prima/*.txt
- /tmp/${{ matrix.solver }}_profile_prima/*start*
- /tmp/${{ matrix.solver }}_profile_prima/*end*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_newuoa_small_sq.yml b/.github/workflows/profile_newuoa_small_sq.yml
new file mode 100644
index 0000000000..01b2f461e1
--- /dev/null
+++ b/.github/workflows/profile_newuoa_small_sq.yml
@@ -0,0 +1,223 @@
+name: Plot performance profiles for NEWUOA, small, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 12 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [newuoa]
+ precision: [single, quadruple]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = {'ARGLINA', 'ARGLINB', 'ARGLINC', 'BA-L1SPLS', 'CHNRSNBM', 'CHNROSNB', 'CHWIRUT1LS', 'ERRINROS', 'LSC1LS', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN16LS', 'METHANB8LS', 'QING', 'SPIN2LS', 'TOINTPSP', 'TRIGON2'};
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+
+
+ % Move the files to prepare for uploading artifacts
+
+ movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
+ /tmp/${{ matrix.solver }}_profile_prima/*.txt
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_npt.yml b/.github/workflows/profile_npt.yml
new file mode 100644
index 0000000000..4ba70eeddc
--- /dev/null
+++ b/.github/workflows/profile_npt.yml
@@ -0,0 +1,308 @@
+name: Plot performance profiles, various npt
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 4 4-31/4 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small, big, large, all]
+ npt: [n+2, n+6, 2*n, 2*n+2, 3*n, 4*n, n*sqrt(n), 0.25*n*n, 0.5*(n+1)*(n+2)]
+ solver: [newuoa, bobyqa, lincoa]
+ competitor: [classical, default]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ exclude:
+
+ # In the following cases, there is no problem to test due to the blacklist.
+
+ - dim: large
+ npt: 0.5*(n+1)*(n+2)
+ - dim: large
+ npt: 0.25*n*n
+ - dim: large
+ npt: n*sqrt(n)
+ - dim: large
+ npt: 4*n
+ - dim: large
+ npt: 3*n
+
+ - dim: big
+ npt: 0.5*(n+1)*(n+2)
+ solver: newuoa
+ - dim: big
+ npt: 0.25*n*n
+ solver: newuoa
+ - dim: big
+ npt: n*sqrt(n)
+ solver: newuoa
+ - dim: big
+ npt: 4*n
+ solver: newuoa
+ - dim: big
+ npt: 3*n
+ solver: newuoa
+
+ - dim: big
+ npt: 0.5*(n+1)*(n+2)
+ solver: bobyqa
+ - dim: big
+ npt: 0.25*n*n
+ solver: bobyqa
+ - dim: big
+ npt: n*sqrt(n)
+ solver: bobyqa
+ - dim: big
+ npt: 4*n
+ solver: bobyqa
+ - dim: big
+ npt: 3*n
+ solver: bobyqa
+
+ steps:
+
+ - name: Get npt string
+ run: echo "NPT=$(echo '${{ matrix.npt }}' | sed 's|*||g' | sed 's|[[:space:]]||g')" >> $GITHUB_ENV
+
+ - name: Get the solver name
+ run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Revise postprima.m
+ run: |
+ # The following modification is to show x, fx, funx, etc when the error InvalidFX occurs.
+ cd matlab/interfaces/private || exit 42
+ $SEDI "s|\(^\s*if ~(isnan(fx) && isnan(funx))\)|format long; x, fx, funx, abs(fx-funx)/max(1, abs(fx)), bobyqa_prec, cobyla_prec\n\1|" postprima.m
+ cat postprima.m
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.nr = 4; % 4 random runs for each problem
+
+ options.npt = @(n) ${{ matrix.npt }};
+
+ if strcmp('${{ matrix.solver }}', 'lincoa')
+ if ismember('${{ matrix.npt }}', {'n*sqrt(n)', '0.25*n*n', '0.5*(n+1)*(n+2)', '3*n', '4*n'})
+ options.blacklist = {'AGG', 'ANTWERP', 'ARGLALE', 'ARGLBLE', 'AVION2', 'CVXQP1', 'DALLASS', 'DUAL1', 'DUAL3', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'HIMMELBI', 'HYDROELS', 'LEVYMONT', 'QPCBLEND', 'SMBANK', 'SPANHYD', 'SSEBLIN', 'DUAL2', 'QPNBLEND', 'GMNCASE4', 'DUAL4', 'LINSPANH'};
+ end
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ if ismember('${{ matrix.npt }}', {'n*sqrt(n)', '0.25*n*n', '0.5*(n+1)*(n+2)', '3*n', '4*n'})
+ options.blacklist = {'CHEBYQAD', 'HOLMES', 'LEVYMONT', 'BQPGASIM', 'DECONVU', 'DECONVB', '3PK'};
+ end
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', '${{ matrix.competitor }}', options);
+ else % newuoa
+ if ismember('${{ matrix.npt }}', {'n*sqrt(n)', '0.25*n*n', '0.5*(n+1)*(n+2)', '3*n', '4*n'})
+ options.blacklist = {'ARGLINA', 'ARGLINB', 'ARGLINC', 'BA-L1LS', 'BA-L1SPLS', 'CHNROSNB', 'CHNRSNBM', 'ERRINROS', 'FBRAIN3LS', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN15LS', 'LUKSAN16LS', 'QING', 'SENSORS', 'SPIN2LS', 'TOINTPSP', 'TOINTQOR', 'METHANB8LS', 'TOINTGOR', 'HYDCAR6LS', 'METHANL8LS', 'PARKCH'};
+ end
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ end
+
+
+ % Move the files to prepare for uploading artifacts
+
+ solver = '${{ env.SOLNAME }}'
+ cd(fullfile(cd(), 'testdata'));
+ files = dir([solver, '*.summary.*.pdf'])
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
+ for ifile = 1 : length(files)
+ file = fullfile(files(ifile).folder, files(ifile).name)
+ newfile = fullfile(files(ifile).folder, ['yes', '_', files(ifile).name])
+ movefile(file, newfile);
+ end
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ env.NPT }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_prima_small.yml b/.github/workflows/profile_prima_small.yml
index 2d96827ee3..30bc0c8e45 100644
--- a/.github/workflows/profile_prima_small.yml
+++ b/.github/workflows/profile_prima_small.yml
@@ -5,8 +5,8 @@ on:
#push:
#pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- # schedule:
- # - cron: '0 2,6,10,14,18,22 * * *'
+ schedule:
+ - cron: '0 10,22 * * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -19,6 +19,7 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
+
test:
name: Profile PRIMA.
runs-on: ${{ matrix.os }}
@@ -30,70 +31,143 @@ jobs:
dim: [small]
solver: [prima]
competitor: [classical]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
with_optim_toolbox: [yes, no]
ctol_indicator: [0, 1, 2]
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-${{ matrix.with_optim_toolbox }}
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox == 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox == 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
- name: Set up MATLAB without optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' && matrix.with_optim_toolbox != 'yes' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ if: ${{ matrix.with_optim_toolbox != 'yes' }}
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
+ blacklist = {};
+ blacklist = [blacklist, {'LSNNODOC'}]; % Classical lincoa segfaults
+
ver;
root_dir = pwd();
@@ -103,8 +177,16 @@ jobs:
options = struct();
options.nr = 3;
options.ctol_multiple = 10^(2*str2num('${{ matrix.ctol_indicator }}'));
-
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ options.blacklist = blacklist;
+
+ try
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
% Move the files to prepare for uploading artifacts
@@ -127,16 +209,47 @@ jobs:
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.with_optim_toolbox }}-${{ matrix.ctol_indicator }}-${{ matrix.use_system_libgcc }}
path: |
+ matlab_crash_dump*
/tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
/tmp/${{ matrix.solver }}_profile_prima/*.txt
- /tmp/${{ matrix.solver }}_profile_prima/*start*
- /tmp/${{ matrix.solver }}_profile_prima/*end*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_quadruple.yml b/.github/workflows/profile_quadruple.yml
index eb9cac1a91..8b5ff319ce 100644
--- a/.github/workflows/profile_quadruple.yml
+++ b/.github/workflows/profile_quadruple.yml
@@ -3,7 +3,7 @@ name: Plot performance profiles, quadruple
on:
# Trigger the workflow on push or pull request
#push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- cron: '0 12 * * *'
@@ -30,13 +30,14 @@ jobs:
dim: [small]
solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa, prima]
competitor: [quadruple]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
- name: Get the solver name
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -50,56 +51,107 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
-
- - name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -110,6 +162,8 @@ jobs:
cd(fullfile(root_dir, 'matlab/tests'));
options = struct(); options.nr = 2; % 2 random runs for each problem, as quadruple is slow
+ options.blacklist = {'OET7', 'TENBARS1'};
+
if strcmp('${{ matrix.solver }}', 'cobylal')
prof('cobyla', '${{ matrix.dim }}', 'l', '${{ matrix.competitor }}', options);
elseif strcmp('${{ matrix.solver }}', 'cobylan')
@@ -129,16 +183,46 @@ jobs:
movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
movefile(fullfile(cd(), 'testdata', '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
/tmp/${{ env.SOLNAME }}_profile_prima/*.txt
- /tmp/${{ env.SOLNAME }}_profile_prima/*start*
- /tmp/${{ env.SOLNAME }}_profile_prima/*end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_rescue_idz_classical.yml b/.github/workflows/profile_rescue_idz_classical.yml
index fea1ac3377..3b147e54d7 100644
--- a/.github/workflows/profile_rescue_idz_classical.yml
+++ b/.github/workflows/profile_rescue_idz_classical.yml
@@ -17,8 +17,6 @@ on:
# Show the git ref in the workflow name if it is invoked manually.
run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-env:
- BENCHMARK: rescue_idz
jobs:
test:
@@ -31,13 +29,16 @@ jobs:
matlab: [latest]
dim: [small, all]
solver: [newuoa, bobyqa, lincoa]
+ variant: [classical]
+ precision: [single, double, quadruple]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
- name: Get the solver name
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -51,57 +52,117 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-yes # "yes" means that the Optimization_Toolbox is included in the cache
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Make a modified version of PRIMA to test
+ run: |
+ bash benchmark/rescue_idz/make_rescue_idz
+ RESCUE_IDZ_DIR="$(cat benchmark/rescue_idz/RESCUE_IDZ_DIR)"
+ rm -rf matlab fortran setup.m .development
+ shopt -s dotglob # Without this, the `mv` command below will not move hidden files.
+ mv "$RESCUE_IDZ_DIR"/* ./
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -109,34 +170,46 @@ jobs:
% Install MatCUTEst.
cd(fullfile(root_dir, 'matcutest')); install(); which macup
- % Remove unnecessary files to make sure that we are testing the correct version.
- files = {'.development/archiva', 'fortran', '.development/norma', 'matlab'};
- for ifile = 1 : length(files)
- rmdir(fullfile(root_dir, files{ifile}), 's');
- end
- delete(fullfile(root_dir, 'setup.m'));
- movefile(fullfile(root_dir, 'benchmark/${{ env.BENCHMARK }}'), root_dir);
- rmdir(fullfile(root_dir, 'benchmark'), 's');
-
-
- % Conduct the test.
-
- cd(fullfile(root_dir, '${{ env.BENCHMARK }}/matlab/tests'));
+ % Make the test.
+ cd(fullfile(root_dir, 'matlab/tests'));
options = struct();
- options.classical = true;
- options.rhoend = 1.0e-8;
- options.maxfun_dim = 500;
+ options.classical = strcmp('${{ matrix.variant }}', 'classical');
+ options.precision = '${{ matrix.precision }}';
+ options.rhoend = eps;
+ options.maxfun_dim = 1000;
options.strict = 0;
options.nr = 3; % 3 random runs for each problem
- % N.B.: Parallel profiling does not work. The worker will be shut down due to the resource
- % requested by some problem. Worse, we do not know which problem it is due to the parallelism.
- if strcmp('${{ matrix.dim }}', 'small') || strcmp('${{ matrix.solver }}', 'newuoa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'norma', options, 'seq');
- elseif strcmp('${{ matrix.solver }}', 'lincoa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', 'norma', options, 'seq');
- elseif strcmp('${{ matrix.solver }}', 'bobyqa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', 'norma', options, 'seq');
+ options.blacklist = {};
+ switch '${{ matrix.precision }}'
+ case 'single'
+ switch '${{ matrix.solver }}'
+ case 'lincoa'
+ options.blacklist = [options.blacklist, {'EQC', 'GMNCASE3', 'PENTAGON'}]; % Classical LINCOA segfaults
+ end
+ case 'quadruple'
+ switch '${{ matrix.solver }}'
+ case 'newuoa'
+ options.blacklist = [options.blacklist, {'ARGLINA', 'ARGLINB', 'ARGLINC', 'CHNROSNB', 'CHNRSNBM', 'ERRINROS', 'HYDCAR6LS', 'LRIJCNN1', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN15LS', 'METHANB8LS', 'PALMER8C', 'QING', 'SENSORS', 'SPIN2LS', 'TOINTPSP', 'TRIGON1', 'VANDANMSLS', 'VESUVIOULS', 'WATSON'}];
+ case 'bobyqa'
+ options.blacklist = [options.blacklist, {'BQPGABIM', 'BQPGASIM', 'CHEBYQAD', 'DECONVB', 'DECONVU', 'ERRINRSM', 'HOLMES', 'LEVYMONT', 'TOINTGOR'}];
+ case 'lincoa'
+ options.blacklist = [options.blacklist, {'AGG', 'ANTWERP', 'ARGLALE', 'ARGLBLE', 'AVION2', 'CVXQP1', 'DALLASS', 'DUAL1', 'DUAL2', 'DUAL3', 'ERRINRSM', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'HYDROELS', 'LINSPANH', 'METHANB8LS', 'SMBANK', 'DUAL4', 'QPNBLEND', 'SPANHYD', 'SSEBLIN'}];
+ end
+ end
+
+ try
+ if (strcmp('${{ matrix.dim }}', 'small') && ~strcmp('${{ matrix.precision }}', 'quadruple')) || strcmp('${{ matrix.solver }}', 'newuoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'norma', options, 'rev');
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', 'norma', options, 'rev');
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', 'norma', options, 'rev');
+ end
+ catch
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
end
@@ -147,29 +220,60 @@ jobs:
files = dir([solver, '*.summary.*.pdf'])
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['classical_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['${{ matrix.variant }}_', files(ifile).name])
movefile(file, newfile);
end
- movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/']);
- movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/']);
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
- files = [dir(['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/*start*']); dir(['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/*end*'])]
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['classical_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['${{ matrix.variant }}_', files(ifile).name])
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.variant }}-${{ matrix.precision }}-${{ matrix.use_system_libgcc }}
path: |
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*summary*.pdf
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*.txt
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*start*
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*end*
+ matlab_crash_dump*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_rescue_idz_modernized.yml b/.github/workflows/profile_rescue_idz_modernized.yml
index 66f2faad6d..bc330d8265 100644
--- a/.github/workflows/profile_rescue_idz_modernized.yml
+++ b/.github/workflows/profile_rescue_idz_modernized.yml
@@ -17,8 +17,6 @@ on:
# Show the git ref in the workflow name if it is invoked manually.
run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-env:
- BENCHMARK: rescue_idz
jobs:
test:
@@ -31,13 +29,16 @@ jobs:
matlab: [latest]
dim: [small, all]
solver: [newuoa, bobyqa, lincoa]
+ variant: [modernized]
+ precision: [single, double, quadruple]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
- name: Get the solver name
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -51,57 +52,117 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-yes # Yes means that the Optimization_Toolbox is included in the cache
- name: Set up MATLAB with optimization toolbox
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Make a modified version of PRIMA to test
+ run: |
+ bash benchmark/rescue_idz/make_rescue_idz
+ RESCUE_IDZ_DIR="$(cat benchmark/rescue_idz/RESCUE_IDZ_DIR)"
+ rm -rf matlab fortran setup.m .development
+ shopt -s dotglob # Without this, the `mv` command below will not move hidden files.
+ mv "$RESCUE_IDZ_DIR"/* ./
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -109,34 +170,41 @@ jobs:
% Install MatCUTEst.
cd(fullfile(root_dir, 'matcutest')); install(); which macup
- % Remove unnecessary files to make sure that we are testing the correct version.
- files = {'.development/archiva', 'fortran', '.development/norma', 'matlab'};
- for ifile = 1 : length(files)
- rmdir(fullfile(root_dir, files{ifile}), 's');
- end
- delete(fullfile(root_dir, 'setup.m'));
- movefile(fullfile(root_dir, 'benchmark/${{ env.BENCHMARK }}'), root_dir);
- rmdir(fullfile(root_dir, 'benchmark'), 's');
-
-
- % Conduct the test.
-
- cd(fullfile(root_dir, '${{ env.BENCHMARK }}/matlab/tests'));
+ % Make the test.
+ cd(fullfile(root_dir, 'matlab/tests'));
options = struct();
- options.classical = false;
- options.rhoend = 1.0e-8;
- options.maxfun_dim = 500;
+ options.classical = strcmp('${{ matrix.variant }}', 'classical');
+ options.precision = '${{ matrix.precision }}';
+ options.rhoend = eps;
+ options.maxfun_dim = 1000;
options.strict = 0;
options.nr = 3; % 3 random runs for each problem
- % N.B.: Parallel profiling does not work. The worker will be shut down due to the resource
- % requested by some problem. Worse, we do not know which problem it is due to the parallelism.
- if strcmp('${{ matrix.dim }}', 'small') || strcmp('${{ matrix.solver }}', 'newuoa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'norma', options, 'seq');
- elseif strcmp('${{ matrix.solver }}', 'lincoa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', 'norma', options, 'seq');
- elseif strcmp('${{ matrix.solver }}', 'bobyqa')
- prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', 'norma', options, 'seq');
+ options.blacklist = {};
+ switch '${{ matrix.precision }}'
+ case {'double', 'quadruple'}
+ switch '${{ matrix.solver }}'
+ case 'newuoa'
+ options.blacklist = [options.blacklist, {'ARGLINA', 'ARGLINB', 'ARGLINC', 'BA-L1SPLS', 'CHNROSNB', 'CHNRSNBM', 'ERRINROS', 'GAUSS3LS', 'HILBERTB', 'HYDCAR6LS', 'LUKSAN11LS', 'LUKSAN13LS', 'LUKSAN15LS', 'LUKSAN16LS', 'METHANB8LS', 'QING', 'SENSORS', 'SPIN2LS', 'STRATEC', 'TOINTPSP', 'VANDANMSLS'}];
+ case 'bobyqa'
+ options.blacklist = [options.blacklist, {'CHEBYQAD', 'DECONVB', 'DECONVU', 'ERRINROS ', 'HOLMES', 'LEVYMONT', 'TOINTPSP'}];
+ case 'lincoa'
+ options.blacklist = [options.blacklist, {'AGG', 'ARGLALE', 'ARGLBLE', 'CVXQP1', 'DUAL1', 'DUAL2', 'DUAL3', 'DUAL4', 'ERRINROS', 'GMNCASE1', 'GMNCASE2', 'GMNCASE3', 'GMNCASE4', 'HIMMELBI', 'HYDROELS', 'LINSPANH', 'QPCBLEND', 'QPNBLEND', 'SMBANK', 'SPANHYD', 'SSEBLIN'}];
+ end
+ end
+
+ try
+ if (strcmp('${{ matrix.dim }}', 'small') && ~strcmp('${{ matrix.precision }}', 'quadruple')) || strcmp('${{ matrix.solver }}', 'newuoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'norma', options, 'rev');
+ elseif strcmp('${{ matrix.solver }}', 'lincoa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'l', 'norma', options, 'rev');
+ elseif strcmp('${{ matrix.solver }}', 'bobyqa')
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', 'b', 'norma', options, 'rev');
+ end
+ catch
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
end
@@ -147,29 +215,60 @@ jobs:
files = dir([solver, '*.summary.*.pdf'])
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['modernized_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['${{ matrix.variant }}_', files(ifile).name])
movefile(file, newfile);
end
- movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/']);
- movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/']);
+ movefile(fullfile(cd(), '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
+ movefile(fullfile(cd(), '*.txt'), ['/tmp/', solver, '_profile_prima/']);
- files = [dir(['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/*start*']); dir(['/tmp/', solver, '_profile_${{ env.BENCHMARK }}/*end*'])]
+ files = [dir(['/tmp/', solver, '_profile_prima/*start*']); dir(['/tmp/', solver, '_profile_prima/*end*'])]
for ifile = 1 : length(files)
file = fullfile(files(ifile).folder, files(ifile).name)
- newfile = fullfile(files(ifile).folder, ['modernized_', files(ifile).name])
+ newfile = fullfile(files(ifile).folder, ['${{ matrix.variant }}_', files(ifile).name])
movefile(file, newfile);
end
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.variant }}-${{ matrix.precision }}-${{ matrix.use_system_libgcc }}
path: |
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*summary*.pdf
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*.txt
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*start*
- /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}/*end*
+ matlab_crash_dump*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
+ /tmp/${{ env.SOLNAME }}_profile_prima/*.txt
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_${{ env.BENCHMARK }}
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_single.yml b/.github/workflows/profile_single.yml
index fd2581ee57..7a81362e48 100644
--- a/.github/workflows/profile_single.yml
+++ b/.github/workflows/profile_single.yml
@@ -3,7 +3,7 @@ name: Plot performance profiles, single
on:
# Trigger the workflow on push or pull request
#push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- cron: '0 10 * * *'
@@ -30,6 +30,7 @@ jobs:
dim: [small]
solver: [cobylal, cobylan, uobyqa, newuoa, bobyqa, lincoa, prima]
competitor: [single]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
@@ -37,7 +38,7 @@ jobs:
run: echo "SOLNAME=$(echo ${{ matrix.solver }} | cut -c1-6)" >> $GITHUB_ENV
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -51,56 +52,107 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
-
- - name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -130,16 +182,46 @@ jobs:
movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), ['/tmp/', solver, '_profile_prima/']);
movefile(fullfile(cd(), 'testdata', '*.txt'), ['/tmp/', solver, '_profile_prima/']);
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ env.SOLNAME }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ env.SOLNAME }}_profile_prima/*summary*.pdf
/tmp/${{ env.SOLNAME }}_profile_prima/*.txt
- /tmp/${{ env.SOLNAME }}_profile_prima/*start*
- /tmp/${{ env.SOLNAME }}_profile_prima/*end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_start*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*prob_end*
+ /tmp/${{ env.SOLNAME }}_profile_prima/*stuck*
+ /tmp/${{ env.SOLNAME }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ env.SOLNAME }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_uobyqa_small.yml b/.github/workflows/profile_uobyqa_small.yml
index dd5579178e..08725f856d 100644
--- a/.github/workflows/profile_uobyqa_small.yml
+++ b/.github/workflows/profile_uobyqa_small.yml
@@ -19,6 +19,7 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
+
test:
name: Profile PRIMA.
runs-on: ${{ matrix.os }}
@@ -29,66 +30,140 @@ jobs:
matlab: [latest]
dim: [small]
solver: [uobyqa]
- competitor: [classical, archiva]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Install epstopdf and ghostscript
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
root_dir = pwd();
cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
cd(fullfile(root_dir, 'matlab/tests'));
prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}');
@@ -99,16 +174,46 @@ jobs:
movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.use_system_libgcc }}
path: |
/tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
/tmp/${{ matrix.solver }}_profile_prima/*.txt
- /tmp/${{ matrix.solver }}_profile_prima/*start*
- /tmp/${{ matrix.solver }}_profile_prima/*end*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/profile_uobyqa_small_sq.yml b/.github/workflows/profile_uobyqa_small_sq.yml
new file mode 100644
index 0000000000..b9ac5d16c3
--- /dev/null
+++ b/.github/workflows/profile_uobyqa_small_sq.yml
@@ -0,0 +1,225 @@
+name: Plot performance profiles for UOBYQA, small, single and quadruple
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Profile PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [latest]
+ dim: [small]
+ solver: [uobyqa]
+ precision: [single, quadruple]
+ competitor: [classical, archiva, norma]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+ exclude:
+ - precision: single
+ competitor: classical # See https://github.com/libprima/prima/issues/98
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ fetch-depth: 2 # checkout fetches only one commit by default. Set it to two for the `norma` test
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Set norma to the last commit to prepare for profiling
+ if: ${{ matrix.competitor == 'norma' }}
+ run: git checkout HEAD^ && cd .development && bash ./archnorma && cd ../ && git checkout -
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Install epstopdf and ghostscript
+ if: startsWith(matrix.os, 'ubuntu')
+ run: bash .github/scripts/install_epstopdf && bash .github/scripts/install_ghostscript
+
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ options.blacklist = {'TOINTQOR'};
+ options.nr = 1;
+ options.precision = '${{ matrix.precision }}';
+
+ prof('${{ matrix.solver }}', '${{ matrix.dim }}', '${{ matrix.competitor }}', options);
+
+
+ % Move the files to prepare for uploading artifacts
+
+ movefile(fullfile(cd(), 'testdata', '*summary*.pdf'), '/tmp/${{ matrix.solver }}_profile_prima/');
+ movefile(fullfile(cd(), 'testdata', '*.txt'), '/tmp/${{ matrix.solver }}_profile_prima/');
+
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_profile_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.competitor }}-${{ matrix.precision }}-${{ matrix.use_system_libgcc }}
+ path: |
+ /tmp/${{ matrix.solver }}_profile_prima/*summary*.pdf
+ /tmp/${{ matrix.solver }}_profile_prima/*.txt
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_profile_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_profile_prima/*stuck*
+ /tmp/${{ matrix.solver }}_profile_prima/fort.*
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ./matlab/tests/testdata && rm -rf /tmp/${{ matrix.solver }}_profile_prima
+
+
+ merge_artifacts:
+ continue-on-error: true # As of 20240218, this action may fail if there are too many artifacts. We ignore the failure.
+ if: always()
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v7
+ with:
+ name: 00-merged-artifacts
+ pattern: artifact-*
diff --git a/.github/workflows/recursive_test_matlab.yml b/.github/workflows/recursive_test_matlab.yml
new file mode 100644
index 0000000000..349b98ca5d
--- /dev/null
+++ b/.github/workflows/recursive_test_matlab.yml
@@ -0,0 +1,227 @@
+name: Recursive test, MATLAB
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 14 1-31/2 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+ dimension:
+ description: Dimension (Optional)
+ required: false
+ depth:
+ description: Depth of recursion (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}, dimension {2}, recursion depth {3}', inputs.git-ref, inputs.random-seed, inputs.dimension, inputs.depth) || '' }}
+
+jobs:
+ test:
+ name: Recursive test of PRIMA
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+
+ # The matrix is the same as that of stress_test_matlab.yml
+ matrix:
+ os: [ubuntu-latest, macos-26-intel, windows-latest]
+ matlab: [R2023a, latest]
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
+
+ exclude:
+ # MATLAB drops support for macOS on Intel starting from R2026a. We simply exclude the
+ # corresponding tests, as we have done extensive tests previously.
+ - os: macos-26-intel
+ matlab: latest
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows')
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Set up MATLAB with optimization toolbox
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+ cd(fullfile(root_dir, 'matlab/tests'));
+ options = struct();
+ if ~isempty('${{ inputs.random-seed }}')
+ options.seed = str2num('${{ inputs.random-seed }}');
+ end
+ if ~isempty('${{ inputs.dimension }}')
+ options.n = str2num('${{ inputs.dimension }}');
+ end
+ if ~isempty('${{ inputs.depth }}')
+ options.depth = str2num('${{ inputs.depth }}');
+ end
+ options
+
+ % Conduct the test multiple times, in case some errors occur not during the first time but later.
+ try
+ recursive('${{ matrix.solver }}', options);
+ options.compile = false;
+ % Test parallel invocation of the solvers
+ parfor i = 1 : 2
+ recursive('${{ matrix.solver }}', options);
+ end
+ catch exception
+ % Copy the crash dump files to root_dir if exceptions occur.
+ copy_crash_dump_files(root_dir, true)
+ dir(root_dir)
+ rethrow(exception);
+ end
+
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.solver }}
+ path: |
+ matlab_crash_dump*
diff --git a/.github/workflows/recursive_test_matlab_mac.yml b/.github/workflows/recursive_test_matlab_mac.yml
new file mode 100644
index 0000000000..1186db1b0f
--- /dev/null
+++ b/.github/workflows/recursive_test_matlab_mac.yml
@@ -0,0 +1,71 @@
+name: Recursive test, MATLAB, macOS ARM64
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 1-31/3 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+ dimension:
+ description: Dimension (Optional)
+ required: false
+ depth:
+ description: Depth of recursion (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}, dimension {2}, recursion depth {3}', inputs.git-ref, inputs.random-seed, inputs.dimension, inputs.depth) || '' }}
+
+env:
+ MATLAB: /Applications/MATLAB_R2023b.app/bin/matlab
+
+jobs:
+ test:
+ name: Recursive test of PRIMA
+ runs-on: [self-hosted, macOS, ARM64]
+ strategy:
+ fail-fast: false
+ matrix:
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+
+ steps:
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+
+
+ - name: Conduct the test # We do not use matlab-actions/run-command, which is not supported on macOS ARM64 as of 20240119
+ run: ${{ env.MATLAB }} -nojvm -batch "ver; root_dir = pwd(); cd(fullfile(root_dir, 'matlab/tests')); recursive('${{ matrix.solver }}'); copy_crash_dump_files(root_dir, true);"
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.solver }}
+ path: |
+ matlab_crash_dump*
+
+
+
diff --git a/.github/workflows/schedule b/.github/workflows/schedule
index 3f233bb01f..baf1aeefdb 100644
--- a/.github/workflows/schedule
+++ b/.github/workflows/schedule
@@ -1,7 +1,6 @@
equipez/PRIMA:
lint.yml: - cron: '0 17 * * *'
test_nagfor.yml: - cron: '0 19 * * *'
-test_absoft.yml: - cron: '0 20 * * *'
verify_small.yml: - cron: '0 5 * * *', push
verify_big.yml: - cron: '0 21 * * *', push
verify_large.yml: - cron: '0 13 * * *', push
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index a320517de5..1c9fceb34a 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -35,22 +35,7 @@ name: Check Spelling
on:
push:
- branches:
- - "**"
- tags-ignore:
- - "**"
- pull_request_target:
- branches:
- - "**"
- tags-ignore:
- - "**"
- types:
- - 'opened'
- - 'reopened'
- - 'synchronize'
- issue_comment:
- types:
- - 'created'
+ pull_request:
# Trigger the workflow manually
workflow_dispatch:
@@ -86,7 +71,7 @@ jobs:
# Zaikun 20230426: add dictionaries
extra_dictionary_limit: 20
extra_dictionaries:
- cspell:bash/bash-words.txt
+ cspell:bash/src/bash-words.txt
cspell:python/src/common/extra.txt
cspell:python/src/python/python-lib.txt
cspell:python/src/python/python.txt
@@ -94,11 +79,11 @@ jobs:
cspell:software-terms/src/software-terms.txt
cspell:software-terms/src/software-tools.txt
cspell:cpp/src/cpp.txt
- cspell:latex/latex.txt
+ cspell:latex/src/latex.txt
cspell:filetypes/filetypes.txt
- cspell:html/html.txt
+ cspell:html/src/html.txt
cspell:html-symbol-entities/entities.txt
- cspell:css/css.txt
+ cspell:css/src/css.txt
comment-push:
name: Report (Push)
diff --git a/.github/workflows/stress_test_fortran.yml b/.github/workflows/stress_test_fortran.yml
index c558a2e5e8..bd2c516605 100644
--- a/.github/workflows/stress_test_fortran.yml
+++ b/.github/workflows/stress_test_fortran.yml
@@ -3,10 +3,10 @@ name: Stress test on large problems, Fortran
on:
# Trigger the workflow on push or pull request
#push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 18 * * *'
+ - cron: '0 18 2-31/2 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -22,44 +22,89 @@ jobs:
test:
name: Stress test of PRIMA on large problems
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest, macos-latest]
- compiler: [g, i, v, s, f, x, d]
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-26-intel]
+ compiler: [g, s, f, x, d, m, r] # vtest is excluded because nvfortran is buggy and will be replaced by an LLVM-based compiler in the future.
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
testdim: [large]
exclude:
- - os: macos-latest
- compiler: v
- - os: macos-latest
+ - compiler: s # BOBYQA fails stest for unknown reason
+ solver: bobyqa
+
+ # Do not test r (ATfL) on ubuntu-latest, as it is available only on ubuntu arm.
+ - os: ubuntu-latest
+ compiler: r
+
+ # Test only g, v, f, r on ubuntu arm, as other compilers are not available
+ - os: ubuntu-24.04-arm
compiler: s
+ - os: ubuntu-24.04-arm
+ compiler: x
+ - os: ubuntu-24.04-arm
+ compiler: d
+ - os: ubuntu-24.04-arm
+ compiler: m
+
+ # Test only g and f on macOS, as other compilers are not available
- os: macos-latest
- compiler: f
+ compiler: s
- os: macos-latest
compiler: x
- os: macos-latest
compiler: d
- - compiler: s # BOBYQA fails stest for unknown reason
- solver: bobyqa
+ - os: macos-latest
+ compiler: m
+ - os: macos-latest
+ compiler: r
+ - os: macos-26-intel
+ compiler: s
+ - os: macos-26-intel
+ compiler: x
+ - os: macos-26-intel
+ compiler: d
+ - os: macos-26-intel
+ compiler: m
+ - os: macos-26-intel
+ compiler: r
+
+
+
steps:
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
@@ -67,9 +112,22 @@ jobs:
if: ${{ matrix.compiler == 'd' }}
run: bash .github/scripts/install_aocc
+ - name: Install AOMP
+ if: ${{ matrix.compiler == 'm' }}
+ # Retry in case of failure due to transient network issues. If the installation still fails
+ # after all retries, we let it fail silently and continue with the test. This is fine since
+ # there are many tests with other compilers, and since the AOMP compiler is tested in
+ # test_aflang.html. If we try too many times, there may not be enough time left for the test
+ # to run anyway!
+ run: bash .github/scripts/install_aomp 3 1800 || true
+
+ - name: Install Arm Toolchain for Linux
+ if: ${{ matrix.compiler == 'r' }}
+ run: bash .github/scripts/install_atfl
+
- name: Install gfortran
if: ${{ matrix.compiler == 'g' }}
- uses: awvwgk/setup-fortran@main
+ uses: fortran-lang/setup-fortran@main
id: setup-fortran
with:
compiler: gcc
@@ -91,46 +149,38 @@ jobs:
run: bash .github/scripts/install_sunf95
# Install Flang after AOCC, to make sure that flang is this one, while AOCC flang will be
- # known as aflang.
+ # known as aoccflang.
- name: Install Flang
if: ${{ matrix.compiler == 'f' }}
- run: bash .github/scripts/install_flang
-
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/tests
- export TESTDIM=${{ matrix.testdim }}
- RK=$((2**($(date +%-d) % 3 + 2)))
- echo "RK = " $RK
- if [[ $RK==16 && dfrv == *${{ matrix.compiler }}* ]] ; then
- echo "r16 test is skipped for compiler " ${{ matrix.compiler }}
- else
- make ${{ matrix.compiler }}test_i2_r${RK}_d1_tst.${{ matrix.solver }}
- fi
+ run: bash .github/scripts/install_llvm
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ cd "$ROOT_DIR"/fortran/tests
+ export TESTDIM=${{ matrix.testdim }}
+ RK=$((2**($(date +%-d) % 3 + 2)))
+ if [[ $RK==16 && dfrv == *${{ matrix.compiler }}* ]] ; then
+ echo "r16 test is skipped for compiler " ${{ matrix.compiler }}
+ echo "Test r8 instead"
+ RK=8
+ fi
+ echo "RK = " $RK
+ if [[ ${{ matrix.compiler }} == m ]] && ! type amdflang ; then
+ echo "WARNING!!! amdflang does not work after AOMP installation, which probably failed!!!" 2>&1
+ echo "WARNING!!! mtest is skipped because amdflang is not available!!!" 2>& 1
+ fi
+ make ${{ matrix.compiler }}test_i2_r${RK}_d1_tst.${{ matrix.solver }}
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.os }}-${{ matrix.solver }}-${{ matrix.compiler }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/stress_test_matlab.yml b/.github/workflows/stress_test_matlab.yml
index 42250a84c9..29080a60e5 100644
--- a/.github/workflows/stress_test_matlab.yml
+++ b/.github/workflows/stress_test_matlab.yml
@@ -3,10 +3,10 @@ name: Stress test on large problems, MATLAB
on:
# Trigger the workflow on push or pull request
#push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 6 * * *'
+ - cron: '0 6 1-31/2 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -16,117 +16,181 @@ on:
random-seed:
description: Random Seed (Optional)
required: false
+ precision:
+ description: Test Precision (Optional)
+ required: false
+
# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}', inputs.git-ref, inputs.random-seed) || '' }}
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}, precision {2}', inputs.git-ref, inputs.random-seed, inputs.precision) || '' }}
jobs:
test:
name: Stress test of PRIMA on large problems
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
+
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-20.04, macos-11, windows-2019]
- matlab: [latest, R2020b, R2021a, R2022a]
+ os: [ubuntu-latest, macos-26-intel] # We do not test on Windows because run-matlab-command does not support it.
+ matlab: [R2023a, latest]
solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+ use_system_libgcc: [true] # Whether to use the system libgcc or the one shipped with MATLAB.
test: [normal, tough]
- # Exclude some versions of OS and MATLAB. In addition to the latest versions, we intend to
- # test the earliest version of MATLAB on each OS.
exclude:
- - os: ubuntu-latest
- matlab: R2020b
- - os: ubuntu-latest
- matlab: R2021a
- - os: ubuntu-latest
- matlab: R2022a
- - os: ubuntu-20.04
- matlab: R2021a
- - os: ubuntu-20.04
- matlab: R2022a
- - os: ubuntu-20.04
+ # MATLAB drops support for macOS on Intel starting from R2026a. We simply exclude the
+ # corresponding tests, as we have done extensive tests previously.
+ - os: macos-26-intel
matlab: latest
- - os: macos-latest
- matlab: R2020b
- - os: macos-latest
- matlab: R2021a
- - os: macos-latest
- matlab: R2022a
- - os: macos-11
- matlab: R2020b
- - os: macos-11
- matlab: R2021a
- - os: macos-11
- matlab: latest
+ steps:
- - os: windows-latest
- matlab: R2020b
- - os: windows-latest
- matlab: R2021a
- - os: windows-latest
- matlab: R2022a
- - os: windows-2019
- matlab: R2020b
- - os: windows-2019
- matlab: R2022a
- - os: windows-2019
- matlab: latest
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- steps:
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
- - name: Link gfortran for MATLAB on Linux
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
run: |
- GFVER=12
- if [[ "${{ matrix.os }}" = "ubuntu-20.04" ]] ; then
- GFVER=11
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
fi
- if [[ "${{ matrix.matlab }}" = "R2020b" || "${{ matrix.matlab }}" = "R2021a" || "${{ matrix.matlab }}" = "R2021b" ]] ; then
- GFVER=9
- fi
- bash .github/scripts/link_gfortran "$GFVER"
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
- name: Install Intel oneAPI on macOS
if: startsWith(matrix.os, 'macos')
- run: bash .github/scripts/install_oneapi_macos.sh
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
- name: Install Intel oneAPI on Windows
if: startsWith(matrix.os, 'windows')
- run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat"'
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}-yes
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
- name: Set up MATLAB with optimization toolbox
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
- products: Optimization_Toolbox
+ cache: true
+ products: Optimization_Toolbox Parallel_Computing_Toolbox
+
+ - name: Revise the Fortran code to alternate the integer kind.
+ shell: bash
+ run: |
+ IKIND=$(( 2**(4 + $(date +%-d) % 3) ))
+ $SEDI "s|#define PRIMA_INTEGER_KIND 0|#define PRIMA_INTEGER_KIND ${IKIND}|g" fortran/common/ppf.h
+ cat fortran/common/ppf.h
- - name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -134,36 +198,11 @@ jobs:
options = struct();
if ~isempty('${{ inputs.random-seed }}')
options.seed = str2num('${{ inputs.random-seed }}');
- else
- copyfile('private/year_week.m', cd());
- options.seed = year_week('Asia/Shanghai');
end
options.tough = strcmp('${{ matrix.test }}', 'tough');
- daynum = day(datetime('now', 'TimeZone', 'Asia/Shanghai'));
- if mod(daynum, 3) == 0
- options.precision = 'single';
- elseif mod(daynum, 3) == 1
- options.precision = 'double';
+ if ~isempty('${{ inputs.precision }}')
+ options.precision = '${{ inputs.precision }}';
else
- options.precision = 'quadruple';
+ options.precision = 'date';
end
- options
stress('${{ matrix.solver }}', options);
-
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/stress_test_matlab_mac.yml b/.github/workflows/stress_test_matlab_mac.yml
new file mode 100644
index 0000000000..84acfe20bb
--- /dev/null
+++ b/.github/workflows/stress_test_matlab_mac.yml
@@ -0,0 +1,71 @@
+name: Stress test, MATLAB, macOS ARM64
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 3-31/3 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ precision:
+ description: Test Precision (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}, precision {1}', inputs.git-ref, inputs.precision) || '' }}
+
+env:
+ MATLAB: /Applications/MATLAB_R2023b.app/bin/matlab
+
+jobs:
+ test:
+ name: Stress test of PRIMA
+ runs-on: [self-hosted, macOS, ARM64]
+ strategy:
+ fail-fast: false
+ matrix:
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+ test: [normal, tough]
+
+ steps:
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+
+
+ - name: Conduct the test # We do not use matlab-actions/run-command, which is not supported on macOS ARM64 as of 20240119
+ run: |
+ if [[ -n '${{ inputs.precision }}' ]] ; then
+ ${{ env.MATLAB }} -nojvm -batch "ver; root_dir = pwd(); cd(fullfile(root_dir, 'matlab/tests')); options.tough = strcmp('${{ matrix.test }}', 'tough'); options.precision = '${{ inputs.precision }}'; stress('${{ matrix.solver }}', options); copy_crash_dump_files(root_dir, true);"
+ else
+ ${{ env.MATLAB }} -nojvm -batch "ver; root_dir = pwd(); cd(fullfile(root_dir, 'matlab/tests')); options.tough = strcmp('${{ matrix.test }}', 'tough'); options.precision = 'date'; stress('${{ matrix.solver }}', options); copy_crash_dump_files(root_dir, true);"
+ fi
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.solver }}
+ path: |
+ matlab_crash_dump*
+
+
+
diff --git a/.github/workflows/suspended/cmake_mac.yml b/.github/workflows/suspended/cmake_mac.yml
new file mode 100644
index 0000000000..3f5a959cf6
--- /dev/null
+++ b/.github/workflows/suspended/cmake_mac.yml
@@ -0,0 +1,119 @@
+name: CMake build, macOS ARM64
+
+on:
+ # Trigger the workflow on push or pull request
+ push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 18 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ stress-test:
+ description: Stress Test (Optional, true or false)
+ required: false
+ verbose-makefile:
+ description: Verbose Makefile (Optional, true or false)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , {1}, {2}', inputs.git-ref, inputs.stress-test, inputs.verbose-makefile) || '' }}
+
+
+permissions:
+ contents: read
+
+jobs:
+
+ cmake:
+ runs-on: ${{ matrix.os }}
+ continue-on-error: true
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest]
+ cc: [gcc, clang]
+ fc: [gfortran-11, gfortran-12, gfortran-13]
+
+
+ steps:
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ submodules: recursive
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Lint the actions
+ if: runner.os == 'Linux'
+ uses: devops-actions/actionlint@v0.1.10
+ with:
+ shellcheck_opts: '-e SC1090 -e SC2015 -e SC2016 -e SC2028 -e SC2035 -e SC2046 -e SC2086 -e SC2155 -e SC2193'
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Build and test
+ shell: bash
+ run: |
+ export CC=${{ matrix.cc }}
+ CFLAGS="-Wall -Wextra -Wpedantic -Werror"
+
+ export FC=${{ matrix.fc }}
+ export FFLAGS='-Wall -Wextra -Wpedantic -Werror -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'
+
+ $FC --version
+ $CC --version
+ cmake --version
+
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
+
+ cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${CFLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+ ctest --output-on-failure -V -E stress
+
+ - name: Stress test
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
+ shell: bash
+ run: |
+ ctest --output-on-failure -V -R stress
+
+
+ # The following job check whether the tests were successful or cancelled due to timeout.
+ # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
+ check_success_timeout:
+ runs-on: ubuntu-latest
+ if: ${{ !cancelled() }}
+ needs: cmake
+ steps:
+ - name: Clone the GitHub actions scripts
+ uses: actions/checkout@v6.0.2
+ with:
+ repository: equipez/github_actions_scripts
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ path: scripts
+
+ - name: Check whether the tests were successful or cancelled due to timeout
+ run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/suspended/cmake_pi.yml b/.github/workflows/suspended/cmake_pi.yml
new file mode 100644
index 0000000000..9e356df451
--- /dev/null
+++ b/.github/workflows/suspended/cmake_pi.yml
@@ -0,0 +1,107 @@
+name: CMake build on Raspberry Pi
+
+on:
+ push:
+ schedule:
+ - cron: '0 16 * * 6' # 16h Saturday
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ stress-test:
+ description: Stress Test (Optional, true or false)
+ required: false
+ verbose-makefile:
+ description: Verbose Makefile (Optional, true or false)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , {1}, {2}', inputs.git-ref, inputs.stress-test, inputs.verbose-makefile) || '' }}
+
+
+permissions:
+ contents: read
+
+jobs:
+
+ cmake:
+ runs-on: [self-hosted, cmake_pi]
+ strategy:
+ fail-fast: false
+ matrix:
+ toolchain:
+ - {fc: gfortran, fflags: '-Wall -Wextra -Wpedantic -Werror -pedantic -fimplicit-none -fcheck=all -fstack-check -Wno-function-elimination'}
+ # Flang family with -Mchkptr would fail. See https://forums.developer.nvidia.com/t/bug-in-nvfortran-with-mchkptr-for-unallocated-optional-arguments/223220
+ # As of 20240220, flang and armflang with -Mbounds would fail due to the bug at https://github.com/flang-compiler/flang/issues/1238
+ - {fc: nvfortran, fflags: '-C -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mchkstk'}
+ - {fc: flang, fflags: '-pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard'}
+ - {fc: armflang, fflags: '-pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard'}
+
+ steps:
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+ - name: Lint the actions
+ if: runner.os == 'Linux'
+ uses: devops-actions/actionlint@v0.1.10
+ with:
+ shellcheck_opts: '-e SC1090 -e SC2015 -e SC2016 -e SC2028 -e SC2035 -e SC2046 -e SC2086 -e SC2155 -e SC2193'
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Build and test
+ shell: bash
+ run: |
+ export CC=gcc
+ CFLAGS="-Wall -Wextra -Wpedantic -Werror"
+
+ export FC="${{ matrix.toolchain.fc }}"
+ if [[ $FC == "armflang" ]] ; then
+ export FC=$(find /opt/arm/ -name 'armflang' | sort | tail -n 1)
+ fi
+ FFLAGS="${{ matrix.toolchain.fflags }}"
+
+ $FC --version
+ $CC --version
+ cmake --version
+
+ VERBOSE_MAKEFILE=OFF
+ if [[ "${{ github.event.inputs.verbose-makefile }}" == "true" ]] ; then
+ VERBOSE_MAKEFILE=ON
+ fi
+
+ cmake -G Ninja -DCMAKE_VERBOSE_MAKEFILE:BOOL=$VERBOSE_MAKEFILE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=. -LAH -DCMAKE_C_FLAGS="${CFLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}" .
+ cmake --build . --target install
+ cmake --build . --target tests
+
+ # As of 20240316, CMake test fails on cobyla with the AOCC flang and nvfortran.
+ # See https://github.com/libprima/prima/issues/165
+ if [[ $FC == 'gfortran' ]] ; then
+ ctest --output-on-failure -V -E "stress"
+ else
+ ctest --output-on-failure -V -E "stress|cobyla"
+ fi
+
+ - name: Stress test
+ if: ${{ github.event_name == 'schedule' || github.event.inputs.stress-test == 'true' }}
+ shell: bash
+ run: |
+ if [[ $FC == 'gfortran' ]] ; then
+ ctest --output-on-failure -V -R stress
+ else
+ ctest --output-on-failure -V -R stress -E cobyla
+ fi
diff --git a/.github/workflows/suspended/test_DIXMAANA1.yml b/.github/workflows/suspended/test_DIXMAANA1.yml
new file mode 100644
index 0000000000..c99c25b022
--- /dev/null
+++ b/.github/workflows/suspended/test_DIXMAANA1.yml
@@ -0,0 +1,134 @@
+name: Test the DIXMAANA1 problem
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 17 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Test the DIXMAANA1 problem
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+
+ # The matrix is the same as that of compile_mex.yml. We intend to test all supported combinations.
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [R2020b, R2021a, R2021b, R2022a, R2022b, R2023a, R2023b, R2024a, R2024b, R2025a, R2025b, latest]
+ solver: [uobyqa, newuoa, bobyqa, lincoa, cobyla]
+ gfortran: [13, 14, latest]
+
+ steps:
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+
+ - name: Decide gfortran version for MATLAB on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: |
+ GFVER=${{ matrix.gfortran }}
+ # MATLAB R2021b- supports gfortran 9-
+ MATLAB_VERSION_NUMBER=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ if [[ "${MATLAB_VERSION_NUMBER}" != "latest" && "${MATLAB_VERSION_NUMBER}" -le 2021 ]]; then
+ GFVER=9
+ fi
+ echo "GFVER=$GFVER"
+ echo "GFVER=$GFVER" >> $GITHUB_ENV
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: ${{ env.GFVER }}
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.0
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.0.0
+ with:
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, 'matlab/tests'));
+ opt.list = {'DIXMAANA1'};
+ opt.verbose = true;
+ verify('${{ matrix.solver }}', 'seq', opt);
diff --git a/.github/workflows/test_aflang.yml b/.github/workflows/test_aflang.yml
index 4528129587..c2a14e8540 100644
--- a/.github/workflows/test_aflang.yml
+++ b/.github/workflows/test_aflang.yml
@@ -1,4 +1,4 @@
-name: Test Flang in AMD AOCC
+name: Test Flang in AMD AOMP and AOCC
on:
# Trigger the workflow on push or pull request
@@ -6,7 +6,7 @@ on:
pull_request:
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 4 1-31/3 * *'
+ - cron: '0 4 1-31/4 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -21,74 +21,99 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
test:
- name: Run AOCC Flang tests
+ name: Run AOMP and AOCC Flang tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
- ikind: [i2, i8]
- #solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- # With flang in AMD clang version 14.0.6, fortran/cobyla of 54e66dd does not pass dtest.
- # See https://github.com/libprima/prima/issues/41 . To be tested when AMD flang is updated.
- solver: [newuoa, lincoa, bobyqa, uobyqa]
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
fflags: [-O1, -O2, -O3, -g, -fast]
testdim: [small, big]
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- - name: Install AOCC
- run: bash .github/scripts/install_aocc
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- - name: Conduct the test
+ - name: Install AOMP
+ run: |
+ # Retry in case of failure due to transient network issues. If it fails and the date is
+ # not divisible by 3, then we keep going and skip the AOMP test. This is to decrease the
+ # chance of workflow failure due to transient network issues, while still testing AOMP regularly.
+ bash .github/scripts/install_aomp 8 2000 || [[ $(( $(date +%-d) % 3 )) -ne 0 ]]
+
+ - name: Install AOCC
+ run: bash .github/scripts/install_aocc
+
+ - name: Revise getact.f90 to see why assertions fail
run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make dtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make dtest
+ cd fortran/lincoa
+ $SEDI "s|call assert(inprod(psd, psd) <=|write(*,*) '====> psd = ', psd, 'g = ', g, 'psd*g = ', inprod(psd, g), 'psd^2 = ', inprod(psd, psd), 'gg = ', gg\ncall assert(inprod(psd, psd) <=|" getact.f90
+ cat getact.f90
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ export FFLAGS=${{ matrix.fflags }}
+ export TESTDIM=${{ matrix.testdim }}
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+
+ if type amdflang ; then
+ cd "$ROOT_DIR"/fortran/tests && make mtest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make mtest
+ export EXAMPLE_NUM=2 && make clean && make mtest
+ else
+ echo "WARNING!!! amdflang does not work after AOMP installation, which probably failed!!!" 2>&1
+ echo "WARNING!!! mtest is skipped because amdflang is not available!!!" 2>& 1
+ fi
+
+ cd "$ROOT_DIR"/fortran/tests && make dtest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make dtest
+ export EXAMPLE_NUM=2 && make clean && make dtest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_armflang.yml b/.github/workflows/test_armflang.yml
new file mode 100644
index 0000000000..c3e7804f11
--- /dev/null
+++ b/.github/workflows/test_armflang.yml
@@ -0,0 +1,108 @@
+name: Test Arm Fortran compiler (armflang)
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 4 4-31/4 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+
+ test:
+ name: Run armflang tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-24.04-arm]
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
+ fflags: [-O1, -O2, -O3, -g, -fast]
+ testdim: [small, big]
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Install Arm Toolchain for Linux
+ run: bash .github/scripts/install_atfl
+
+ # - name: Revise lincoa/lincob.f90 to see why `call safealloc(ixl, int(count(xl > -BOUNDMAX), IK))` leads to a SIGSEGV
+ # shell: bash
+ # run: |
+ # cd fortran/lincoa || exit 42
+ # $SEDI "s|call safealloc(ixl, int(count(xl > -BOUNDMAX), IK))|write(*,*) '======> xl = ', xl, 'BOUNDMAX = ', BOUNDMAX, 'size(ixl) = ', count(xl > -BOUNDMAX), 'int(size(ixl)) = ', int(count(xl > -BOUNDMAX), IK)\ncall safealloc(ixl, int(count(xl > -BOUNDMAX), IK))|" lincob.f90
+ # $SEDI "s|call safealloc(ixu, int(count(xu < BOUNDMAX), IK))|write(*,*) '======> xu = ', xu, 'BOUNDMAX = ', BOUNDMAX, 'size(ixu) = ', count(xu < BOUNDMAX), 'int(size(ixu)) = ', int(count(xu < BOUNDMAX), IK)\ncall safealloc(ixu, int(count(xu < BOUNDMAX), IK))|" lincob.f90
+ # cat lincob.f90
+
+ - name: Revise bobyqa/trustregion.f90 to see why `xbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1` leads to a SIGFPE
+ shell: bash
+ run: |
+ cd fortran/bobyqa || exit 42
+ $SEDI "s|xbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1|write(*,*) '======> su = ', su, 'xopt = ', xopt, 'gopt = ', gopt\nxbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1|" trustregion.f90
+ cat trustregion.f90
+
+ - name: Conduct the test
+ run: |
+ cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -r && bash ./mlint -r
+ export FFLAGS=${{ matrix.fflags }}
+ export TESTDIM=${{ matrix.testdim }}
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ cd "$ROOT_DIR"/fortran/tests && make rtest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make rtest
+ export EXAMPLE_NUM=2 && make clean && make rtest
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ with:
+ name: ${{ matrix.os }}-${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
+ path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_armflang_kunpeng.yml b/.github/workflows/test_armflang_kunpeng.yml
deleted file mode 100644
index e6637b8dfd..0000000000
--- a/.github/workflows/test_armflang_kunpeng.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-name: Test armflang on Kunpeng
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 5-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run armflang tests
- runs-on: [self-hosted, ARM64, kp]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
- exclude:
- - solver: lincoa # As of armflang 22.1, rtest often get canceled in the following cases.
- fflags: [-O2, -O3]
- testdim: big
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- ARMDIR="$(find /opt/arm -maxdepth 1 -name "arm-linux-compiler*" -type d -print | sort | tail -n 1)"
- if [[ -n "$ARMDIR" ]] ; then
- export PATH=$PATH:"$ARMDIR"/bin
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
- ARMLD="$(find /opt/arm -type f -executable -name ld | xargs dirname)"
- ARMLD_DIR=${ARMLD//$'\n'/:}
- export PATH=$PATH:"$ARMLD_DIR"
- fi
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -r && bash ./mlint -r
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make rtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make rtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_armflang_pi.yml b/.github/workflows/test_armflang_pi.yml
deleted file mode 100644
index be5c3fdd57..0000000000
--- a/.github/workflows/test_armflang_pi.yml
+++ /dev/null
@@ -1,87 +0,0 @@
-name: Test armflang on Raspberry Pi
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 5-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run armflang tests
- runs-on: [self-hosted, ARM64, pi64]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -r && bash ./mlint -r
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make rtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make rtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_flang.yml b/.github/workflows/test_flang.yml
index 1e41a754d2..5a6e6c00e9 100644
--- a/.github/workflows/test_flang.yml
+++ b/.github/workflows/test_flang.yml
@@ -1,4 +1,4 @@
-name: Test Flang
+name: Test LLVM Flang
on:
# Trigger the workflow on push or pull request
@@ -6,86 +6,204 @@ on:
pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 4 2-31/3 * *'
+ - cron: '0 4 2-31/4 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
git-ref:
description: Git Ref (Optional)
required: false
+ maxtr:
+ description: Maximum number of trust-region iterations
+ required: false
# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}, maxtr {1}', inputs.git-ref, inputs.maxtr) || '' }}
jobs:
test:
- name: Run Flang tests
+ name: Run LLVM Flang tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
- matrix:
- os: [ubuntu-latest] # As of 20221217, the flang installed by ../scripts/install_flang may only work with ubuntu-22.04
- ikind: [i2, i8]
+ matrix: # At most 256 combinations are allowed.
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-26-intel, windows-latest, windows-11-arm]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
+ fflags: [-O1, -O2, -g, -fast] # We omit -O3, or there would be too many combinations!
testdim: [small, big]
steps:
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+
submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 260213, checkout with ssh fails on Windows arm runners due to "UNPROTECTED PRIVATE KEY FILE"
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 260213, checkout with ssh fails on Windows arm runners due to "UNPROTECTED PRIVATE KEY FILE"
submodules: recursive
- - name: Install Flang
- run: bash .github/scripts/install_flang
- name: Miscellaneous setup
+ shell: bash
run: bash .github/scripts/misc_setup
+ - name: Install LLVM Flang on non-Windows; treat timeout as SUCCESS (exit 0), which may happen on macos-intel.
+ if: runner.os != 'Windows'
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 180m
+ command: |
+ bash .github/scripts/install_llvm \
+ || { echo "Warning: LLVM Flang installation failed!!" >&2; exit 0; }
+ - name: Install LLVM Flang on Windows
+ if: runner.os == 'Windows'
+ shell: bash
+ run: |
+ bash .github/scripts/install_llvm \
+ || { echo "Warning: LLVM Flang installation failed!!" >&2; exit 0; }
+
+ # Check where is clang_rt.builtins
+ ls -alR "/c/Program Files/LLVM/lib" || echo "LLVM/lib not found!!"
+ ARCH=$(flang -dumpmachine | cut -d- -f1)
+ echo "Flang target architecture: ${ARCH}"
+ echo "Looking for clang_rt.builtins*${ARCH} in /c/Program Files/LLVM/lib"
+ find "/c/Program Files/LLVM/lib" -name "clang_rt.builtins*$(ARCH)*" 2>/dev/null | sort -V | tail -n 1 || echo "clang_rt.builtins*${ARCH} not found!!"
+ echo "Looking for clang_rt.builtins* in /c/Program Files/LLVM/lib (the last one found will be printed)"
+ find "/c/Program Files/LLVM/lib" -name "clang_rt.builtins*" 2>/dev/null | sort -V | tail -n 1 || echo "clang_rt.builtins not found!!"
+ echo "Looking for clang_rt.builtins* in /c/Program Files/LLVM/lib (all found will be printed)"
+ find "/c/Program Files/LLVM/lib" -name "clang_rt.builtins*" 2>/dev/null || echo "clang_rt.builtins not found!!"
+
+ - name: Revise maxtr
+ # This is to see whether "GitHub Actions xx lost communication with the server" was caused
+ # by maxtr = huge(maxtr) - 1_IK. We suspect this because the error occurs with IK = i8
+ # and IK = i4 for all solvers when testdim = small, but not with IK = i2 at all.
+ # Update 20240422: The error does not occur any more after the update. WHY? Does this imply
+ # INFINITE CYCLING in the code?
+ # update 20250816: First, server shutdown is observed on LINCOA/BOBYQA small tests with IK = i4.
+ # Is this the same issue as before? Second, in the current workflow (and many others), the
+ # definition of IK changes daily. If this issue is related to IK, it will not be observed every day.
+ shell: bash
+ if: ${{ github.event.inputs.maxtr != '' }}
+ run: |
+ cd fortran/${{ matrix.solver }}
+ $SEDI 's|maxtr = huge(maxtr) - 1_IK|maxtr = ${{ github.event.inputs.maxtr }}|' *.f90
+ grep 'maxtr = ' *.f90
+
+ - name: Revise linalg.f90 to see why "Y >= 0 unless X contains NaN" fails
+ shell: bash
+ run: |
+ if [[ $(uname) = 'Darwin' ]]; then # macOS uses BSD sed, which does not understand \s or \n
+ brew install gnu-sed
+ SEDI="gsed -i"
+ fi
+ cd fortran/common/
+ $SEDI "s|\(^\s*\)\(call assert(\)\(y >= 0 .or. any(is_nan(x))\)\(.*$\)|\1if (.not. (\3)) then\n\1\1write(*,*) '====> x = ', x, 'y = ', y, 'p = ', p_loc, 'scaling = ', scaling, 'x/scaling = ', x/scaling, 'sumxs2', sum((x / scaling)**2), 'sumx2 = ', sum(x**2), 'sqrt(sumx2) = ', sqrt(sum(x**2))\n\1\1error stop\n\1end if\n\1\2\3\4|" linalg.f90
+ cat linalg.f90
+
+ - name: Revise string.f90
+ shell: bash
+ run: |
+ if [[ $(uname) = 'Darwin' ]]; then # macOS uses BSD sed, which does not understand \s or \n
+ brew install gnu-sed
+ SEDI="gsed -i"
+ fi
+ cd fortran/common/
+ $SEDI "s|\(^.*\)\(x <= -REALMAX \* (1.0 - 10.0\*\*(-ndgt_loc)) .eqv. str2real(s) <= -REALMAX \* (1.0 - 10.0\*\*(-ndgt_loc))\)|if (.not. \2) then\n write(*,*) '====> x = ', x, 's = ', s, 'str2real(s) = ', str2real(s)\nerror stop\nelse\n\1\2|" string.f90
+ $SEDI "s|\(^\s*\& 'IS_NEGINF(X) \.EQV\. IS_NEGINF(STR2REAL(S)).*$\)|\1\nend if|" string.f90
+ $SEDI "s|\(^.*\)\(x >= REALMAX \* (1.0 - 10.0\*\*(-ndgt_loc)) .eqv. str2real(s) >= REALMAX \* (1.0 - 10.0\*\*(-ndgt_loc))\)|if (.not. \2) then\n write(*,*) '====> x = ', x, 's = ', s, 'str2real(s) = ', str2real(s)\nerror stop\nelse\n\1\2|" string.f90
+ $SEDI "s|\(^\s*\& 'IS_POSINF(X) \.EQV\. IS_POSINF(STR2REAL(S)).*$\)|\1\nend if|" string.f90
+ cat string.f90
+
- name: Conduct the test
+ shell: bash
run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
+ ##########################################################################################
+ # Skip the test on windows-latest if flang is not found and LLVM version is less than 22,
+ # as LLVM 21 installed by choco on windows-latest does not include flang. See
+ # https://github.com/llvm/llvm-project/issues/181348
+ if [[ "${{ matrix.os }}" == windows-latest ]] ; then
+ echo "We are on ${{ matrix.os }}. LLVM installed by choco may not provide flang."
+ # Infer the version of LLVM from llvm-ar. We should have used `llvm-config --version`,
+ # but it is not available on windows-latest after LLVM 21.1.8 is installed with choco.
+ LLVM_VERSION=$(llvm-ar --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | cut -d. -f1)
+ echo "LLVM version inferred from llvm-ar: $LLVM_VERSION"
+ FLANG_PATH=$(command -v flang 2>/dev/null || echo "NOT_FOUND")
+ echo "Flang path: $FLANG_PATH"
+ if [[ "$LLVM_VERSION" -lt 22 && "$FLANG_PATH" == "NOT_FOUND" ]] ; then
+ echo "Warning: flang not found!! Skipping the test." >&2
+ exit 0
+ fi
+ fi
+ ##########################################################################################
+
+ # On macos-intel, the installation of LLVM Flang with Homebrew may fail. If so and if the
+ # current day is not divisible by 3, then we skip the test.
+ if ! type flang >/dev/null 2>&1 ; then
+ if [[ ${{ runner.os }} == macOS && ${{ runner.arch }} == X64 && $(( $(date +%-d) % 3 )) -ne 0 ]] ; then
+ echo "Warning: flang not found!! Skipping the test." >&2
+ exit 0
+ else
+ echo "Error: flang not found!! Skipping the test." >&2
+ exit 1
+ fi
+ fi
+
export FFLAGS=${{ matrix.fflags }}
export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make ftest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make ftest
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+
+ # 20260213: We skip the extensive test on Windows due to the following reasons. The
+ # examples will still be tested on Windows.
+ # 0. It is quite difficult to debug on Windows. We choose to develop and debug the code on
+ # Linux and macOS, and only run a quick test on Windows to see whether it works at all.
+ # 1. Windows does not support the symlink of Linux. Makefile will fail when copying source files.
+ # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
+ # Nevertheless, we should still define IK etc., as they will be used when defining the artifact names.
+ if [[ "$RUNNER_OS" != "Windows" ]] ; then
+ cd "$ROOT_DIR"/fortran/tests && make ftest_${IK}.${{ matrix.solver }}
+ fi
+
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make ftest
+ export EXAMPLE_NUM=2 && make clean && make ftest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.os }}-${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
run: rm -rf ${{ env.TEST_DIR }}
-
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_flang_kunpeng.yml b/.github/workflows/test_flang_kunpeng.yml
deleted file mode 100644
index e3a15de4fa..0000000000
--- a/.github/workflows/test_flang_kunpeng.yml
+++ /dev/null
@@ -1,96 +0,0 @@
-name: Test Flang on Kunpeng
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 4-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run Flang tests
- runs-on: [self-hosted, ARM64, kp]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- # For COBYLA, Flang (HUAWEI BiSheng Compiler 2.1.0) raises a false positive SIGSEG in testsuite/prob.f90
- #solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- solver: [newuoa, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- ARMDIR="$(find /opt/arm -maxdepth 1 -name "arm-linux-compiler*" -type d -print | sort | tail -n 1)"
- if [[ -n "$ARMDIR" ]] ; then
- export PATH=$PATH:"$ARMDIR"/bin
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
- ARMLD="$(find /opt/arm -type f -executable -name ld | xargs dirname)"
- ARMLD_DIR=${ARMLD//$'\n'/:}
- export PATH=$PATH:"$ARMLD_DIR" # For some reason, flang also needs this ld. The system ld does not work.
- fi
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -f && bash ./mlint -f
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make ftest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make ftest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_flang_pi.yml b/.github/workflows/test_flang_pi.yml
deleted file mode 100644
index 0d4104340d..0000000000
--- a/.github/workflows/test_flang_pi.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: Test Flang on Raspberry Pi
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 4-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run Flang tests
- runs-on: [self-hosted, ARM64, pi64]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Install Flang
- run: bash .github/scripts/install_flang
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -f && bash ./mlint -f
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make ftest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make ftest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_g95.yml b/.github/workflows/test_g95.yml
index e9393ee40e..247336093e 100644
--- a/.github/workflows/test_g95.yml
+++ b/.github/workflows/test_g95.yml
@@ -21,6 +21,7 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
test:
+
name: Run g95 tests
runs-on: ${{ matrix.os }}
strategy:
@@ -30,30 +31,44 @@ jobs:
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Install g95
run: bash .github/scripts/install_g95
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Conduct the test
run: |
# No need to test in parallel, as 9test compiles without running (identical to 9test_c).
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
cd "$ROOT_DIR"/fortran/tests && make 9test_c.${{ matrix.solver }}
# 20211012: make 9test_newuoa will not work, because, for some unknown reason, 9test_c is
# remade when making 9test even though 9test_c has been made and up to date. Since the
@@ -61,9 +76,10 @@ jobs:
# to problems like un-found modules. Not yet observed for other tests.
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
diff --git a/.github/workflows/test_gfortran.yml b/.github/workflows/test_gfortran.yml
new file mode 100644
index 0000000000..18465790fb
--- /dev/null
+++ b/.github/workflows/test_gfortran.yml
@@ -0,0 +1,231 @@
+name: Test gfortran
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+
+ test:
+ name: Run gfortran tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # As of 20260512, native gcc is not available on ARM64 Windows.
+ os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-26-intel, macos-latest]
+ compiler: [gcc]
+ version: [13, latest] # Too expensive to test all versions
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
+ testdim: [small, big]
+
+ # With FFLAGS=-O3, the following combinations encounter failures of assertion on X == X_UNC/ALT
+ # when testing
+ # bobyqa: gtest_i4_r8_d1_tst, chebquad
+ # lincoa: gtest_i4_r8_d1_tst, chebquad
+ # cobyla: gtest_i4_r8_d1_tst, trigssqs
+ # See
+ # https://github.com/libprima/prima/issues/268
+ # https://github.com/s-prima/prima/actions/runs/22023414613
+ # This should be investigated and fixed when we have a macOS with M* chip at hand.
+ # It is reproducible on zMS.
+ exclude:
+ - os: macos-latest
+ testdim: small
+ version: latest
+ solver: bobyqa
+ - os: macos-latest
+ testdim: small
+ version: latest
+ solver: lincoa
+ - os: macos-latest
+ testdim: small
+ version: latest
+ solver: cobyla
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ shell: bash # Important; otherwise, the following statements do not work on Windows.
+ run: bash .github/scripts/misc_setup
+
+ - name: Set up Fortran
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: ${{ matrix.compiler }}
+ version: ${{ matrix.version }}
+
+ - name: Check gfortran version
+ shell: bash
+ run: |
+ which gcc && gcc --version && which gfortran && gfortran --version
+ gfortran -dumpmachine # See https://github.com/fortran-lang/setup-fortran/issues/211#issuecomment-4313463378
+
+ - name: Revise Makefile.common to exclude r16 tests when the dimension is big
+ if: ${{ matrix.testdim == 'big' }}
+ shell: bash
+ run: |
+ cd fortran/tests/makefiles || exit 42
+ $SEDI "s|\$(TST)_.*r16_d[0,1]_tst||g" Makefile.common && cat Makefile.common
+
+ - name: Revise test_bobyqa.f90, test_lincoa.f90, and test_cobyla.f90 so that we know what is happening if assertion fails
+ shell: bash
+ run: |
+ if [[ $(uname) = 'Darwin' ]]; then # macOS uses BSD sed, which does not understand \s or \n
+ brew install gnu-sed
+ SEDI="gsed -i"
+ fi
+ cd fortran/tests || exit 42
+
+ $SEDI "s|\(\s*\)\(call.*X == X_UNC.*\)|\1if (.not. (all(abs(x - x_unc) <= 0) .and. (abs(f - f_unc) <= 0 .or. (is_neginf(f) .and. is_neginf(f_unc))))) then\n\1write(*,*) 'probname = ', strip(prob%probname), 'n = ', n, 'x0 = ', x0\n\1write(*,*) 'npt = ', npt, 'rhobeg = ', rhobeg, 'rhoend = ', rhoend\n\1write(*,*) 'maxfun = ', maxfun, 'maxhist = ', maxhist, 'ftarget = ', ftarget, 'iprint = ', iprint\n\1write(*,*) 'f, x = ', f, x\n\1write(*,*) 'f_unc, x_unc = ', f_unc, x_unc\n\1write(*,*) 'fhist = ', fhist\n\1write(*,*) 'xhist = ', xhist\n\1write(*,*) 'fhist_unc = ', fhist_unc\n\1write(*,*) 'xhist_unc = ', xhist_unc\n\1error stop\n\1else\n\1write(*,*) '>>> X == X_UNC and F == F_UNC <<< probname = ', strip(prob%probname), ', irand = ', irand\n\1end if\n\1\2|" test_bobyqa.f90
+ cat test_bobyqa.f90
+
+ $SEDI "s|\(\s*\)\(call.*X == X_ALT.*\)|\1if (.not. (all(abs(x - x_alt) <= 0) .and. (abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt))))) then\n\1write(*,*) 'probname = ', strip(prob%probname), 'n = ', n, 'x0 = ', x0\n\1write(*,*) 'npt = ', npt, 'rhobeg = ', rhobeg, 'rhoend = ', rhoend\n\1write(*,*) 'maxfun = ', maxfun, 'maxhist = ', maxhist, 'ftarget = ', ftarget, 'iprint = ', iprint\n\1write(*,*) 'f, x = ', f, x\n\1write(*,*) 'f_alt, x_alt = ', f_alt, x_alt\n\1write(*,*) 'fhist = ', fhist\n\1write(*,*) 'xhist = ', xhist\n\1write(*,*) 'fhist_alt = ', fhist_alt\n\1write(*,*) 'xhist_alt = ', xhist_alt\n\1error stop\n\1else\n\1write(*,*) '>>> X == X_ALT and F == F_ALT <<< probname = ', strip(prob%probname), ', irand = ', irand\n\1end if\n\1\2|" test_lincoa.f90
+ cat test_lincoa.f90
+
+ $SEDI "s|\(\s*\)\(call.*X == X_ALT.*\)|\1if (.not. (all(abs(x - x_alt) <= 0) .and. (abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt))))) then\n\1write(*,*) 'probname = ', strip(prob%probname), 'n = ', n, 'x0 = ', x0\n\1write(*,*) 'rhobeg = ', rhobeg, 'rhoend = ', rhoend\n\1write(*,*) 'maxfun = ', maxfun, 'maxhist = ', maxhist, 'ftarget = ', ftarget, 'iprint = ', iprint\n\1write(*,*) 'f, x = ', f, x\n\1write(*,*) 'f_alt, x_alt = ', f_alt, x_alt\n\1write(*,*) 'fhist = ', fhist\n\1write(*,*) 'xhist = ', xhist\n\1write(*,*) 'fhist_alt = ', fhist_alt\n\1write(*,*) 'xhist_alt = ', xhist_alt\n\1error stop\n\1else\n\1write(*,*) '>>> X == X_ALT and F == F_ALT <<< probname = ', strip(prob%probname), ', irand = ', irand\n\1end if\n\1\2|" test_cobyla.f90
+ cat test_cobyla.f90
+
+ - name: Revise string.f90, so that we know what is happening if assertion fails
+ shell: bash
+ run: |
+ if [[ $(uname) = 'Darwin' ]]; then # macOS uses BSD sed, which does not understand \s or \n
+ brew install gnu-sed
+ SEDI="gsed -i"
+ fi
+ cd fortran/common || exit 42
+ $SEDI "s|\(call assert(abs(x - str2real(s)) <= abs(x) \* 10.0\*\*(-ndgt_loc), 'STR2REAL(S) == X', srname)\)|if (.not. abs(x - str2real(s)) <= abs(x) * 10.0**(-ndgt_loc)) then\nwrite(*,*) '====> x = ', x, 's = ', s, 'sx = ', str2real(s)\nerror stop\nelse\n\1\nend if|" string.f90
+ cat string.f90
+
+ - name: Revise linalg.f90 regarding a postcondition of p_norm, so that we know what is happening if it fails
+ shell: bash
+ run: |
+ if [[ $(uname) = 'Darwin' ]]; then # macOS uses BSD sed, which does not understand \s or \n
+ brew install gnu-sed
+ SEDI="gsed -i"
+ fi
+ cd fortran/common || exit 42
+ $SEDI "s|'Y >= 0 unless X contains NaN'|'Y >= 0 unless X contains NaN'\&\n\&\/\/num2str(y)\/\/num2str(sum(abs(x)))\/\/num2str(int(is_nan(sum(abs(x)))))\/\/num2str(x)|" linalg.f90
+ $SEDI "s|function p_norm(x, p) result(y)|function p_norm(x, p) result(y)\nuse, non_intrinsic :: string_mod, only : num2str|" linalg.f90
+ cat linalg.f90
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0). Only for non-Windows.
+ if: runner.os != 'Windows'
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ # Use $(( )) rather than $(expr ). See https://unix.stackexchange.com/questions/63166/bash-e-exits-when-let-or-expr-evaluates-to-0
+ export TESTDIM=${{ matrix.testdim }}
+ echo "TESTDIM=$TESTDIM" >> "$GITHUB_ENV"
+ FFLAGS=-O$(($(date +%-d) % 5))
+ FFLAGS=${FFLAGS/O0/g}
+ FFLAGS=${FFLAGS/O4/fast}
+ export FFLAGS
+ echo "FFLAGS=$FFLAGS"
+ echo "FFLAGS=$FFLAGS" >> "$GITHUB_ENV"
+
+ cd "$ROOT_DIR"/fortran/tests && make gtest_${IK}_c.${{ matrix.solver }} && make gtest_${IK}.${{ matrix.solver }}
+
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make gtest
+ export EXAMPLE_NUM=2 && make clean && make gtest
+
+ - name: Conduct the test on Windows
+ if: runner.os == 'Windows'
+ shell: bash # Important; otherwise, `<` will not work on Windows.
+ run: |
+ # 20260213: We skip the extensive test on Windows due to the following reasons. The
+ # examples will still be tested on Windows.
+ # 0. It is quite difficult to debug on Windows. We choose to develop and debug the code on
+ # Linux and macOS, and only run a quick test on Windows to see whether it works at all.
+ # 1. Windows does not support the symlink of Linux. Makefile will fail when copying source files.
+ # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
+ # Nevertheless, we should still define IK etc., as they will be used when defining the artifact names.
+
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ # Use $(( )) rather than $(expr ). See https://unix.stackexchange.com/questions/63166/bash-e-exits-when-let-or-expr-evaluates-to-0
+ export TESTDIM=${{ matrix.testdim }}
+ echo "TESTDIM=$TESTDIM" >> "$GITHUB_ENV"
+ FFLAGS=-O$(($(date +%-d) % 5))
+ FFLAGS=${FFLAGS/O0/g}
+ FFLAGS=${FFLAGS/O4/fast}
+ export FFLAGS
+ echo "FFLAGS=$FFLAGS"
+ echo "FFLAGS=$FFLAGS" >> "$GITHUB_ENV"
+
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make gtest
+ export EXAMPLE_NUM=2 && make clean && make gtest
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
+ # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
+ with:
+ name: ${{ matrix.os }}-${{ matrix.solver }}-${{ env.IK }}-${{ matrix.version }}-${{ env.TESTDIM }}-${{ env.FFLAGS }}
+ path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
+
+ - name: Remove the test data
+ if: always() # Always run even if the workflow is canceled manually or due to overtime.
+ shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
+ run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_O12.yml b/.github/workflows/test_gfortran_O12.yml
deleted file mode 100644
index a6b8556ba1..0000000000
--- a/.github/workflows/test_gfortran_O12.yml
+++ /dev/null
@@ -1,111 +0,0 @@
-name: Test gfortran, -O1, -O2
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 3-31/4 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
- compiler: [gcc]
- version: [11, 13] # Too expensive to test all versions
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- testdim: [small]
- exclude:
- - os: windows-latest
- version: 13 # gfortran-13 is not available on Windows as of 20230904
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
-
- - name: Setup Fortran
- id: setup_fortran
- uses: awvwgk/setup-fortran@main
- with:
- compiler: ${{ matrix.compiler }}
- version: ${{ matrix.version }}
-
- - name: Check Fortran compiler
- run: |
- ${{ env.FC }} --version
- ${{ env.CC }} --version
- shell: bash
- env:
- FC: ${{ steps.setup_fortran.outputs.fc }}
- CC: ${{ steps.setup_fortran.outputs.cc }}
-
- - name: Make tools such as grep, make, and git available on Windows
- if: runner.os == 'Windows'
- run: $env:Path += ";C:\Program Files\Git\usr\bin;C:\Program Files\Git\bin;C:\ProgramData\Chocolatey\bin"
-
- - name: Miscellaneous setup
- shell: bash # Important; otherwise, the following statements do not work on Windows.
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash # Important; otherwise, `<` will not work on Windows.
- # Not sure whether the Makefile has bugs, but on Windows the making of gtest always invokes
- # the making of gtest_c twice even if the former is up to date after the first making. It
- # may lead to errors due to parallel making. To avoid this, we make gtest_c first.
- run: |
- # 20221212: We skip the linting and extensive test on Windows due to the following
- # reasons. The example will still be tested on Windows.
- # 1. Windows does not support the symlink of linux, and hence mlint and flint do not work.
- # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
- if [[ "$RUNNER_OS" != "Windows" ]] ; then
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export TESTDIM=${{ matrix.testdim }}
- export FFLAGS='-O1'
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}.${{ matrix.solver }}
- export FFLAGS='-O2'
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}.${{ matrix.solver }}
- fi
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_big.yml b/.github/workflows/test_gfortran_big.yml
deleted file mode 100644
index 60a0ef90c9..0000000000
--- a/.github/workflows/test_gfortran_big.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-name: Test gfortran, big
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 1-31/4 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: ${{ matrix.os }}
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest]
- compiler: [gcc]
- version: [11, 13]
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, O3, -g, -fast]
- testdim: [big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Setup Fortran
- id: setup_fortran
- uses: awvwgk/setup-fortran@main
- with:
- compiler: ${{ matrix.compiler }}
- version: ${{ matrix.version }}
-
- - name: Check Fortran compiler
- run: |
- ${{ env.FC }} --version
- ${{ env.CC }} --version
- shell: bash
- env:
- FC: ${{ steps.setup_fortran.outputs.fc }}
- CC: ${{ steps.setup_fortran.outputs.cc }}
-
- - name: Make tools such as grep, make, and git available on Windows
- if: runner.os == 'Windows'
- run: $env:Path += ";C:\Program Files\Git\usr\bin;C:\Program Files\Git\bin;C:\ProgramData\Chocolatey\bin"
-
- - name: Miscellaneous setup
- shell: bash # Important; otherwise, the following statements do not work on Windows.
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash # Important; otherwise, `<` will not work on Windows.
- # Not sure whether the Makefile has bugs, but on Windows the making of gtest always invokes
- # the making of gtest_c twice even if the former is up to date after the first making. It
- # may lead to errors due to parallel making. To avoid this, we make gtest_c first.
- run: |
- # 20221212: We skip the linting and extensive test on Windows due to the following
- # reasons. The example will still be tested on Windows.
- # 1. Windows does not support the symlink of linux, and hence mlint and flint do not work.
- # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
- if [[ "$RUNNER_OS" != "Windows" ]] ; then
- export TESTDIM=${{ matrix.testdim }}
- export FFLAGS=${{ matrix.fflags }}
- cd "$ROOT_DIR"/fortran/tests
- make gtest_${{ matrix.ikind }}_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}.${{ matrix.solver }}
- fi
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_gfortran_fast.yml b/.github/workflows/test_gfortran_fast.yml
deleted file mode 100644
index 3b13c2b6ff..0000000000
--- a/.github/workflows/test_gfortran_fast.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-name: Test gfortran, -fast
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 4-31/4 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
- compiler: [gcc]
- version: [11, 13] # gfortran 9 is buggy wit logical variables when invoked with -Ofast
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- testdim: [small]
- exclude:
- - os: windows-latest
- version: 13 # gfortran-13 is not available on Windows as of 20230904
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
-
- - name: Setup Fortran
- id: setup_fortran
- uses: awvwgk/setup-fortran@main
- with:
- compiler: ${{ matrix.compiler }}
- version: ${{ matrix.version }}
-
- - name: Check Fortran compiler
- run: |
- ${{ env.FC }} --version
- ${{ env.CC }} --version
- shell: bash
- env:
- FC: ${{ steps.setup_fortran.outputs.fc }}
- CC: ${{ steps.setup_fortran.outputs.cc }}
-
- - name: Make tools such as grep, make, and git available on Windows
- if: runner.os == 'Windows'
- run: $env:Path += ";C:\Program Files\Git\usr\bin;C:\Program Files\Git\bin;C:\ProgramData\Chocolatey\bin"
-
- - name: Miscellaneous setup
- shell: bash # Important; otherwise, the following statements do not work on Windows.
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash # Important; otherwise, `<` will not work on Windows.
- # Not sure whether the Makefile has bugs, but on Windows the making of gtest always invokes
- # the making of gtest_c twice even if the former is up to date after the first making. It
- # may lead to errors due to parallel making. To avoid this, we make gtest_c first.
- run: |
- # 20221212: We skip the linting and extensive test on Windows due to the following
- # reasons. The example will still be tested on Windows.
- # 1. Windows does not support the symlink of linux, and hence mlint and flint do not work.
- # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
- if [[ "$RUNNER_OS" != "Windows" ]] ; then
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export TESTDIM=${{ matrix.testdim }}
- export FFLAGS='-fast'
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}.${{ matrix.solver }}
- fi
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_gO3.yml b/.github/workflows/test_gfortran_gO3.yml
deleted file mode 100644
index fd353fcb99..0000000000
--- a/.github/workflows/test_gfortran_gO3.yml
+++ /dev/null
@@ -1,108 +0,0 @@
-name: Test gfortran, -g, -O3
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 2-31/4 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
- compiler: [gcc]
- version: [11, 13] # Too expensive to test all versions
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- testdim: [small]
- exclude:
- - os: windows-latest
- version: 13 # gfortran-13 is not available on Windows as of 20230904
-
- steps:
-
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- submodules: recursive
- # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
-
- - name: Setup Fortran
- id: setup_fortran
- uses: awvwgk/setup-fortran@main
- with:
- compiler: ${{ matrix.compiler }}
- version: ${{ matrix.version }}
-
- - name: Check Fortran compiler
- run: |
- ${{ env.FC }} --version
- ${{ env.CC }} --version
- shell: bash
- env:
- FC: ${{ steps.setup_fortran.outputs.fc }}
- CC: ${{ steps.setup_fortran.outputs.cc }}
-
- - name: Make tools such as grep, make, and git available on Windows
- if: runner.os == 'Windows'
- run: $env:Path += ";C:\Program Files\Git\usr\bin;C:\Program Files\Git\bin;C:\ProgramData\Chocolatey\bin"
-
- - name: Miscellaneous setup
- shell: bash # Important; otherwise, the following statements do not work on Windows.
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash # Important; otherwise, `<` will not work on Windows.
- # Not sure whether the Makefile has bugs, but on Windows the making of gtest always invokes
- # the making of gtest_c twice even if the former is up to date after the first making. It
- # may lead to errors due to parallel making. To avoid this, we make gtest_c first.
- run: |
- # 20221212: We skip the linting and extensive test on Windows due to the following
- # reasons. The example will still be tested on Windows.
- # 1. Windows does not support the symlink of linux, and hence mlint and flint do not work.
- # 2. Makefile.common does not work on Windows for the moment due to quotation marks.
- if [[ "$RUNNER_OS" != "Windows" ]] ; then
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- cd "$ROOT_DIR"/fortran/tests && export TESTDIM=${{ matrix.testdim }} && make gtest_${{ matrix.ikind }}_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}.${{ matrix.solver }}
- fi
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_kunpeng_big.yml b/.github/workflows/test_gfortran_kunpeng_big.yml
deleted file mode 100644
index 3eaeb71b8b..0000000000
--- a/.github/workflows/test_gfortran_kunpeng_big.yml
+++ /dev/null
@@ -1,94 +0,0 @@
-name: Test gfortran on Kunpeng, big
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 12 2-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM64, kp]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- #cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g # On Kunpeng, perform lintering only for small tests of gfortran
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests
- make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=08
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_gfortran_kunpeng_small.yml b/.github/workflows/test_gfortran_kunpeng_small.yml
deleted file mode 100644
index 81d6551699..0000000000
--- a/.github/workflows/test_gfortran_kunpeng_small.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: Test gfortran on Kunpeng, small
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 12 1-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM64, kp]
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g # On Kunpeng, perform lintering only for small tests of gfortran
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=08
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_pi32_big.yml b/.github/workflows/test_gfortran_pi32_big.yml
deleted file mode 100644
index f86e70480b..0000000000
--- a/.github/workflows/test_gfortran_pi32_big.yml
+++ /dev/null
@@ -1,94 +0,0 @@
-name: Test gfortran on Raspberry Pi32, big
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 2-31/2 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM, pi32]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- #cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests
- make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=18
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_gfortran_pi32_small.yml b/.github/workflows/test_gfortran_pi32_small.yml
deleted file mode 100644
index 5fcf7834b1..0000000000
--- a/.github/workflows/test_gfortran_pi32_small.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: Test gfortran on Raspberry Pi32, small
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 1-31/2 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM, pi32]
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g # On Raspberry Pi, perform lintering only for small tests of gfortran
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=18
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_gfortran_pi64_big.yml b/.github/workflows/test_gfortran_pi64_big.yml
deleted file mode 100644
index e075da2118..0000000000
--- a/.github/workflows/test_gfortran_pi64_big.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-name: Test gfortran on Raspberry Pi64, big
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 2-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM64, pi64]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- #cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests
- make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=18
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_gfortran_pi64_small.yml b/.github/workflows/test_gfortran_pi64_small.yml
deleted file mode 100644
index b55deb2940..0000000000
--- a/.github/workflows/test_gfortran_pi64_small.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: Test gfortran on Raspberry Pi64, small
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 1-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run gfortran tests
- runs-on: [self-hosted, ARM64, pi64]
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- shell: bash
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -g && bash ./mlint -g # On Raspberry Pi, perform lintering only for small tests of gfortran
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r4_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r4_d1_tst.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/tests && make gtest_${{ matrix.ikind }}_r8_d1_tst_c.${{ matrix.solver }} && make gtest_${{ matrix.ikind }}_r8_d1_tst.${{ matrix.solver }}
- export FS=18
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make gtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # Note that `$TEST_DIR` does not work on Windows, where its equivalent is `$env:TEST_DIR`.
- # In the following, we enquire `$TEST_DIR` by using the `env` context, which is platform independent.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- shell: bash # Important; otherwise, `rm -rf` will not work on Windows.
- run: rm -rf ${{ env.TEST_DIR }}
diff --git a/.github/workflows/test_ifort.yml b/.github/workflows/test_ifort.yml
index 4a64a2174e..a9e8f747b1 100644
--- a/.github/workflows/test_ifort.yml
+++ b/.github/workflows/test_ifort.yml
@@ -23,35 +23,52 @@ jobs:
test:
name: Run ifort tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
# Windows does not work. On Windows, the options for ifort/ifx starts with "/" instead of "-".
- os: [ubuntu-latest, macos-latest]
- ikind: [i2, i8]
+ # The latest oneAPI on Linux does not contain ifort anymore.
+ os: [macos-26-intel]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
testdim: [small, big]
- exclude:
- - os: macos-latest
- ikind: i8
steps:
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
+ - name: Miscellaneous setup
+ shell: bash
+ run: bash .github/scripts/misc_setup
+
- name: Install Intel oneAPI on Linux
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_oneapi_linux.sh
@@ -60,42 +77,49 @@ jobs:
if: startsWith(matrix.os, 'macos')
run: bash .github/scripts/install_oneapi_macos.sh
- - name: Miscellaneous setup
+ - name: Revise Makefile.common to exclude r16 tests when the dimension is big
+ if: ${{ matrix.testdim == 'big' }}
shell: bash
- run: bash .github/scripts/misc_setup
+ run: |
+ cd fortran/tests/makefiles || exit 42
+ $SEDI "s|\$(TST)_.*r16_d[0,1]_tst||g" Makefile.common && cat Makefile.common
- - name: Conduct the test
+ - name: Revise cobyla/trustregion.f90 to see what is wrong with ubuntu-latest, i8, cobyla, -O3, small
+ if: ${{ matrix.os == 'ubuntu-latest' && matrix.solver == 'cobyla' && matrix.testdim == 'small' }}
shell: bash
run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make itest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make itest
+ cd fortran/cobyla || exit 42
+ $SEDI 's|\(vmultc = cviol + b\)|\1;write(\*,\*) 253, vmultc|' trustregion.f90
+ $SEDI "s|\(call assert(all(vmultc >= 0), 'VMULTC >= 0', srname)\)|write(\*,\*) 304, vmultc;\1|" trustregion.f90
+ $SEDI 's|\(vmultc(1:nact) = max(ZERO, vmultc(1:nact) - frac \* vmultd(1:nact))\)|\1;write(\*,\*) 367, vmultc|' trustregion.f90
+ $SEDI 's|\(vmultc(\[icon, nact\]) = \[ZERO, frac\]\)|\1;write(\*,\*) 377, vmultc|' trustregion.f90
+ $SEDI 's|\(vmultc = max(ZERO, (ONE - frac) \* vmultc + frac \* vmultd)\)|\1;write(\*,\*) 557, vmultc|' trustregion.f90
+ cat trustregion.f90
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ FFLAGS=-O$(($(date +%-d) % 5)) && FFLAGS=${FFLAGS/O0/g} && FFLAGS=${FFLAGS/O4/fast} && export FFLAGS
+ echo "FFLAGS=$FFLAGS"
+ echo "FFLAGS=$FFLAGS" >> "$GITHUB_ENV"
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ export TESTDIM=${{ matrix.testdim }}
+ cd "$ROOT_DIR"/fortran/tests && make itest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make itest
+ export EXAMPLE_NUM=2 && make clean && make itest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.os }}-${{ matrix.solver }}-${{ env.IK }}-${{ env.FFLAGS }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_ifx.yml b/.github/workflows/test_ifx.yml
index dfe1de9e4c..b53ce87f5f 100644
--- a/.github/workflows/test_ifx.yml
+++ b/.github/workflows/test_ifx.yml
@@ -23,34 +23,53 @@ jobs:
test:
name: Run ifx tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
# Windows does not work. On Windows, the options for ifort/ifx starts with "/" instead of "-".
# As of 202301, ifx is not available on macOS.
- #os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
fflags: [-O1, -O2, -O3, -g, -fast]
testdim: [small, big]
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
steps:
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
+ - name: Miscellaneous setup
+ shell: bash
+ run: bash .github/scripts/misc_setup
+
- name: Install Intel oneAPI on Linux
if: startsWith(matrix.os, 'ubuntu')
run: bash .github/scripts/install_oneapi_linux.sh
@@ -59,42 +78,46 @@ jobs:
if: startsWith(matrix.os, 'macos')
run: bash .github/scripts/install_oneapi_macos.sh
- - name: Miscellaneous setup
- shell: bash
- run: bash .github/scripts/misc_setup
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ export FFLAGS=${{ matrix.fflags }}
+ export TESTDIM=${{ matrix.testdim }}
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ cd "$ROOT_DIR"/fortran/tests
+ if [[ "${{ matrix.solver }}" == "cobyla" || "${{ matrix.testdim }}" == "big" ]]; then
+ $SEDI 's|::[[:space:]]*NRAND_DFT[[:space:]]*=.*$|:: NRAND_DFT = 1|g' testsuite/param.f90 # Number of random tests when the dimension is small
+ RP=r$(( 2**(2 + $(date +%-d) % 2) ))
+ echo "RP=${RP}"
- - name: Conduct the test
- shell: bash
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make xtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make xtest
+ TEST_SOLVERF90="test_cobyla.f90"
+ if [[ "$RP" == "r8" && -f "$TEST_SOLVERF90" ]] ; then
+ echo "Modifying $TEST_SOLVERF90 to skip the recursive tests."
+ sed -i '/Test recursive call/,/^end module test_solver_mod/d' "$TEST_SOLVERF90" || exit 1
+ echo -e "end if\nend subroutine test_solver\nend module test_solver_mod" >> $TEST_SOLVERF90
+ echo "Done."
+ cat "$TEST_SOLVERF90"
+ fi
+
+ make xtest_${IK}_${RP}_d1_tst.${{ matrix.solver }}
+ else
+ make xtest_${IK}.${{ matrix.solver }}
+ fi
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make xtest
+ export EXAMPLE_NUM=2 && make clean && make xtest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_llvm b/.github/workflows/test_llvm
deleted file mode 100644
index 67610ac715..0000000000
--- a/.github/workflows/test_llvm
+++ /dev/null
@@ -1,101 +0,0 @@
-name: Test Flang (llvm)
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 4 2-31/3 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run LLVM Flang tests
- runs-on: ${{ matrix.os }}
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest]
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Install LLVM
- uses: KyleMayes/install-llvm-action@v1.8.3
- with:
- version: "16.0.0"
-
- - name: Check Flang
- run: which flang && flang --version
-
- - name: Symlink libclang.so (Linux)
- if: contains(matrix.os, 'ubuntu')
- run: sudo ln -s libclang-11.so.1 /lib/x86_64-linux-gnu/libclang.so
- working-directory: ${{ env.LLVM_PATH }}/lib
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make ftest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make ftest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_matlab.yml b/.github/workflows/test_matlab.yml
index f2a6fe4289..cbd8f7bca6 100644
--- a/.github/workflows/test_matlab.yml
+++ b/.github/workflows/test_matlab.yml
@@ -2,11 +2,11 @@ name: Test MATLAB
on:
# Trigger the workflow on push or pull request
- push:
+ #push:
pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 1 * * *'
+ - cron: '0 16 * * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -24,135 +24,164 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
+
+ # The matrix is the same as that of compile_mex.yml. We intend to test all supported combinations.
matrix:
- os: [ubuntu-20.04, ubuntu-latest, windows-2019, windows-latest, macos-11, macos-latest]
- matlab: [R2020b, R2021a, R2021b, R2022a, R2022b, R2023a, latest]
+ os: [ubuntu-22.04, ubuntu-latest, macos-15-intel, macos-26-intel, windows-2022, windows-latest]
+ matlab: [R2020b, R2021a, R2021b, R2022a, R2022b, R2023a, R2023b, R2024a, R2024b, R2025a, R2025b, R2026a, latest]
exclude:
- # Below R2022a, MEX is extremely slow on macOS
- - os: macos-11
- matlab: R2020b
- - os: macos-11
- matlab: R2021a
- - os: macos-11
- matlab: R2021b
-
- - os: macos-latest
- matlab: R2020b
- - os: macos-latest
- matlab: R2021a
- - os: macos-latest
- matlab: R2021b
-
- # Only R2021a/b and R2022a are available on Windows 2019.
- - os: windows-2019
- matlab: R2020b
- - os: windows-2019
- matlab: R2022b
- - os: windows-2019
- matlab: R2023a
- - os: windows-2019
- matlab: latest
-
- # Below R2022a, MEX does not work on windows-latest due to the version of MS Visual Studio;
- # R2022a/b should work according to the documentation of MathWorks and GitHub Actions, but
- # they do not as of March 2022
- - os: windows-latest
- matlab: R2020a
- - os: windows-latest
+ # Below R2022a, MEX does not work on windows-2022 due to the version of MS Visual Studio;
+ # R2022a/b should work according to the documentation of MathWorks and GitHub Actions, but
+ # they do not as of July 2025
+ - os: windows-2022
matlab: R2020b
- - os: windows-latest
+ - os: windows-2022
matlab: R2021a
- - os: windows-latest
+ - os: windows-2022
matlab: R2021b
- - os: windows-latest
+ - os: windows-2022
matlab: R2022a
- - os: windows-latest
+ - os: windows-2022
matlab: R2022b
+ # MATLAB drops support for macOS on Intel starting from R2026a.
+ - os: macos-15-intel
+ matlab: R2026a
+ - os: macos-15-intel
+ matlab: latest
+ - os: macos-26-intel
+ matlab: R2026a
+ - os: macos-26-intel
+ matlab: latest
+
steps:
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
submodules: recursive
# ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
submodules: recursive
# ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- # As of 230425, checkout with ssh fails frequently on Windows runners.
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- - name: Link gfortran for MATLAB on Linux
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+
+ - name: Decide gfortran version for MATLAB on Linux
if: startsWith(matrix.os, 'ubuntu')
run: |
- GFVER=${{ env.GFORTRAN_VERSION }}
- if [[ "${{ matrix.os }}" = "ubuntu-20.04" ]] ; then
- GFVER=11
- fi
- if [[ "${{ matrix.matlab }}" = "R2020b" || "${{ matrix.matlab }}" = "R2021a" || "${{ matrix.matlab }}" = "R2021b" ]] ; then
+ GFVER=latest
+ # MATLAB R2021b- supports gfortran 9-
+ MATLAB_VERSION_NUMBER=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ if [[ "${MATLAB_VERSION_NUMBER}" != "latest" && "${MATLAB_VERSION_NUMBER}" -le 2021 ]]; then
GFVER=9
fi
- bash .github/scripts/link_gfortran "$GFVER"
+ echo "GFVER=$GFVER"
+ echo "GFVER=$GFVER" >> $GITHUB_ENV
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: ${{ env.GFVER }}
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
- name: Install Intel oneAPI on macOS
if: startsWith(matrix.os, 'macos')
- run: bash .github/scripts/install_oneapi_macos.sh
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
- name: Install Intel oneAPI on Windows
if: startsWith(matrix.os, 'windows')
- run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat"'
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
- with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
- name: Set up MATLAB
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ if: ${{ !(runner.os == 'macOS' && runner.arch == 'X64') }}
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ cd matlab/tests;
+ test_classical = true;
+ verbose = true;
+ testprima_ex(test_classical, verbose);
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0). Only for macOS on Intel.
+ if: ${{ runner.os == 'macOS' && runner.arch == 'X64' }}
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
- ver;
- root_dir = pwd();
- cd(root_dir);
- options = struct();
- options.debug=true;
- setup(options);
- prima('info')
- testprima(false, 1.0e-10, 100);
- setup
- setup path
- prima('info')
- testprima(false, 1.0e-10, 100);
- setup cobyla
- setup uobyqa
- setup newuoa
- setup bobyqa
- setup lincoa
- setup prima
- setup path
- setup clean
- setup path
- setup uninstall
- setup path
- setup uninstall
- cd(fullfile(root_dir, 'matlab', 'tests')); pdv
- cd(root_dir);
- setup
- prima('info')
- cd(fullfile(root_dir, 'matlab', 'examples')); rosenbrock_example
+ cd matlab/tests;
+ test_classical = true;
+ verbose = true;
+ testprima_ex(test_classical, verbose);
diff --git a/.github/workflows/test_matlab_linux.yml b/.github/workflows/test_matlab_linux.yml
new file mode 100644
index 0000000000..17eb1f4860
--- /dev/null
+++ b/.github/workflows/test_matlab_linux.yml
@@ -0,0 +1,142 @@
+name: Test MATLAB, Linux
+
+on:
+ # Trigger the workflow on push or pull request
+ push:
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Test MATLAB
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ matlab: [R2020b, latest] # The earliest and latest supported versions of MATLAB
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Run `sudo apt update`
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+
+ - name: Decide gfortran version for MATLAB on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: |
+ GFVER=latest
+ # MATLAB R2021b- supports gfortran 9-
+ MATLAB_VERSION_NUMBER=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ if [[ "${MATLAB_VERSION_NUMBER}" != "latest" && "${MATLAB_VERSION_NUMBER}" -le 2021 ]]; then
+ GFVER=9
+ fi
+ echo "GFVER=$GFVER"
+ echo "GFVER=$GFVER" >> $GITHUB_ENV
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: ${{ env.GFVER }}
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows')
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ command: |
+ cd matlab/tests;
+ test_classical = true;
+ verbose = true;
+ testprima_ex(test_classical, verbose);
diff --git a/.github/workflows/test_matlab_mac.yml b/.github/workflows/test_matlab_mac.yml
new file mode 100644
index 0000000000..559085c015
--- /dev/null
+++ b/.github/workflows/test_matlab_mac.yml
@@ -0,0 +1,72 @@
+name: Test MATLAB, macOS ARM64
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 0 * * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+env:
+ MATLAB: /Applications/MATLAB_R2023b.app/bin/matlab
+
+jobs:
+ test:
+ name: Test MATLAB
+ runs-on: [self-hosted, macOS, ARM64]
+ strategy:
+ fail-fast: false
+
+ steps:
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+ # - name: Revise cobylb.f
+ # shell: bash
+ # run: |
+ # cd fortran/classical/cobyla/
+ # $SEDI "s/cstrv = maxval(\[ZERO, -constr\])/cstrv = maxval([\ZERO, -constr\])\n write(*,*) 'x = ', x(1:n)\n write(*,*) 'f = ', f\n write(*,*) 'constr = ', constr(1:m)\n write(*,*) 'cstrv = ', cstrv/" cobylb.f
+ # cat cobylb.f
+
+ - name: Conduct the test # We do not use matlab-actions/run-command, which is not supported on macOS ARM64 as of 20240119
+ # true: test the classical mode.
+ run: ${{ env.MATLAB }} -nojvm -batch "cd matlab/tests; test_classical = true; verbose = true; testprima_ex(test_classical, verbose);"
diff --git a/.github/workflows/test_matlab_mac_intel.yml b/.github/workflows/test_matlab_mac_intel.yml
new file mode 100644
index 0000000000..b26a1d0527
--- /dev/null
+++ b/.github/workflows/test_matlab_mac_intel.yml
@@ -0,0 +1,106 @@
+name: Test MATLAB, macOS Intel
+
+on:
+ # Trigger the workflow on push or pull request
+ push:
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Test MATLAB
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-15-intel, macos-26-intel]
+ matlab: [R2020b, R2025b] # The earliest and latest supported versions of MATLAB; MATLAB drops support for macOS with intel CPU starting from R2026a.
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows')
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ cd matlab/tests;
+ test_classical = true;
+ verbose = true;
+ testprima_ex(test_classical, verbose);
diff --git a/.github/workflows/test_matlab_windows.yml b/.github/workflows/test_matlab_windows.yml
new file mode 100644
index 0000000000..d5b4792519
--- /dev/null
+++ b/.github/workflows/test_matlab_windows.yml
@@ -0,0 +1,140 @@
+name: Test MATLAB, Windows
+
+on:
+ # Trigger the workflow on push or pull request
+ push:
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
+
+
+jobs:
+ test:
+ name: Test MATLAB
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-2022, windows-latest]
+ matlab: [R2023a, R2024a, latest] # The earliest and latest supported versions of MATLAB
+ compiler: [mingw] #,intel
+ # mingw 6.4, 7.5, 8.1 work
+ #mingw: [9.4, 10.3, 11.2, 12.2, 13.2, 14.2, 15.2]
+ mingw: [11.2, 12.1]
+ #mingw: [6.4, 7.5, 8.5, 9.4, 10.3, 11.2.0.07112021, 12.1]
+ exclude:
+ # # As of 20260507 and MATLAB R2026a, MEX does not work on windows-latest with Intel
+ # # compilers due to the version of MS Visual Studio, which is Visual Studio 2026 on
+ # # windows-latest as of 20260507.
+ # - os: windows-latest
+ # compiler: intel
+ # # The earliest version of MATLAB supporting OneAPI is R2023a.
+ # - matlab: R2024a
+ # compiler: intel
+ # The earliest version of MATLAB supporting MinGW is R2024a.
+ - matlab: R2023a
+ compiler: mingw
+
+ steps:
+
+ - name: Set http.postBuffer and core.compression
+ # This is a workaround for random "early EOF" of checkout.
+ # See https://github.com/actions/checkout/issues/748, https://github.com/actions/checkout/issues/1379
+ if: startsWith(matrix.os, 'windows')
+ run: git config --global http.postBuffer 1048576000 && git config --global core.compression 0
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ submodules: recursive
+ # ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ # As of 231227, checkout with ssh fails frequently on Windows runners.
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Revise fmxapi.F90
+ shell: bash
+ run: |
+ cd matlab/mex_gateways/
+ $SEDI "s|\(.*maybe due to overflow.*$\)|\1\nwrite(*,*) 'x = ', x; write(*,*) 'x_dp = ', x_dp|" fmxapi.F90
+ cat fmxapi.F90
+
+ - name: Revise postprima.m
+ shell: bash
+ run: |
+ cd matlab/interfaces/private/
+ $SEDI "s/max(\[0, chist\]) > 0)/max(\[0, chist\]) > 0)\nprobinfo.raw_data\noutput\nchist/" postprima.m
+ cat postprima.m
+
+ - name: Decide the version of oneAPI to use on macOS and Windows
+ shell: bash
+ if: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') }}
+ run: |
+ # Initialize ONEAPI_VERSION to "latest", causing the installer script to install the latest oneAPI.
+ ONEAPI_VERSION="latest"
+ # On Windows. the latest oneAPI supported by MATLAB R2023a/b and R2024a/b is 2023 and 2024,
+ # respectively. Thus we set ONEAPI_VERSION to the following. If matrix.matlab is "latest",
+ # then ONEAPI_VERSION will be "latest".
+ if [[ "${{ matrix.os }}" == windows* ]]; then
+ ONEAPI_VERSION=$(echo "${{ matrix.matlab }}" | sed -e 's/R\([0-9]*\)\([ab]\)/\1/')
+ fi
+ echo "ONEAPI_VERSION=$ONEAPI_VERSION" >> $GITHUB_ENV
+ echo "ONEAPI_VERSION:" $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on macOS
+ if: startsWith(matrix.os, 'macos')
+ run: bash .github/scripts/install_oneapi_macos.sh $ONEAPI_VERSION
+
+ - name: Install Intel oneAPI on Windows
+ if: startsWith(matrix.os, 'windows') && matrix.compiler == 'intel'
+ run: cmd.exe "/K" '".github\scripts\install_oneapi_windows.bat %ONEAPI_VERSION%"'
+
+ - name: Install MinGW on Windows
+ shell: bash
+ if: startsWith(matrix.os, 'windows') && matrix.compiler == 'mingw'
+ #run: choco install mingw -y --no-progress && choco upgrade mingw -y --no-progress
+ run: choco install mingw --version=${{ matrix.mingw }} -y --no-progress
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: matlab-actions/run-command@v3.1.1
+ with:
+ startup-options: -nodesktop
+ command: |
+ if strcmpi('${{ matrix.compiler }}', 'mingw')
+ if exist('C:\ProgramData\mingw64\mingw64', 'dir')
+ setenv('MW_MINGW64_LOC', 'C:\ProgramData\mingw64\mingw64');
+ elseif exist('C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64', 'dir')
+ setenv('MW_MINGW64_LOC', 'C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64')
+ else
+ error('MinGW is not found. Please check the installation path of MinGW and set MW_MINGW64_LOC environment variable accordingly.');
+ end
+ mex('-setup', '-v', 'fortran');
+ end
+ root_dir = pwd();
+ cd(fullfile(root_dir, 'matlab/tests'));
+ test_classical = true;
+ verbose = true;
+ testprima_ex(test_classical, verbose);
diff --git a/.github/workflows/test_nagfor.yml b/.github/workflows/test_nagfor.yml
index 9a984189d8..20b27ef7b3 100644
--- a/.github/workflows/test_nagfor.yml
+++ b/.github/workflows/test_nagfor.yml
@@ -22,29 +22,29 @@ jobs:
test:
name: Run nagfor tests
runs-on: [self-hosted, nagfor]
- continue-on-error: true
strategy:
fail-fast: false
matrix:
- ikind: [i2]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
testdim: [small, big]
steps:
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
@@ -53,37 +53,26 @@ jobs:
export PATH=$PATH:"~/local/bin"
source ~/local/bin/nag_licensing || echo "\n\nNAG licensing failed.\n\n"
# Use $(( )) rather than $(expr ). See https://unix.stackexchange.com/questions/63166/bash-e-exits-when-let-or-expr-evaluates-to-0
- FFLAGS=-O$(($(date +%-d) % 5))
- FFLAGS=${FFLAGS/O0/g}
- FFLAGS=${FFLAGS/O4/fast}
- export FFLAGS
+ FFLAGS=-O$(($(date +%-d) % 5)) && FFLAGS=${FFLAGS/O0/g} && FFLAGS=${FFLAGS/O4/fast} && export FFLAGS
+ echo "FFLAGS=$FFLAGS"
+ echo "FFLAGS=$FFLAGS" >> "$GITHUB_ENV"
export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make ntest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make ntest
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ cd "$ROOT_DIR"/fortran/tests && make ntest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make ntest
+ export EXAMPLE_NUM=2 && make clean && make ntest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}-${{ env.IK }}-${{ matrix.testdim }}-${{ env.FFLAGS }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_absoft.yml b/.github/workflows/test_nagfor_mac.yml
similarity index 61%
rename from .github/workflows/test_absoft.yml
rename to .github/workflows/test_nagfor_mac.yml
index 0bef3d99ef..d49416693e 100644
--- a/.github/workflows/test_absoft.yml
+++ b/.github/workflows/test_nagfor_mac.yml
@@ -1,4 +1,4 @@
-name: Test Absoft
+name: Test nagfor, macOS ARM64
on:
# Trigger the workflow on push or pull request
@@ -6,7 +6,7 @@ on:
#pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 20 * * *'
+ - cron: '0 8 * * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -20,53 +20,58 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
test:
- name: Run Absoft tests
- runs-on: [self-hosted, absoft]
+ name: Run nagfor tests
+ runs-on: [self-hosted, macOS, ARM64]
strategy:
fail-fast: false
matrix:
- ikind: [i2]
- solver: [newuoa, lincoa, bobyqa, uobyqa] # COBYLA cannot pass the test due to internal subroutines used as arguments
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
+ testdim: [small, big]
steps:
-
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Conduct the test
run: |
- LOCAL="$HOME/local"
- AFDIR="$(find "$LOCAL" -maxdepth 2 -name "absoft[0-9]*" -type d -print | sort | tail -n 1)"
- source "$AFDIR"/bin/absvars.sh
- export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH
+ export PATH=$PATH:"~/local/bin"
+ source ~/local/bin/nag_licensing || echo "\n\nNAG licensing failed.\n\n"
# Use $(( )) rather than $(expr ). See https://unix.stackexchange.com/questions/63166/bash-e-exits-when-let-or-expr-evaluates-to-0
- FFLAGS=-O$(($(date +%-d) % 4))
- FFLAGS=${FFLAGS/O0/g}
- export FFLAGS
- cd "$ROOT_DIR"/fortran/tests && make atest_${{ matrix.ikind }}.${{ matrix.solver }}
- # af95 cannot handle allocatable characters.
- # cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make atest
+ FFLAGS=-O$(($(date +%-d) % 5)) && FFLAGS=${FFLAGS/O0/g} && FFLAGS=${FFLAGS/O4/fast} && export FFLAGS
+ echo "FFLAGS=$FFLAGS"
+ echo "FFLAGS=$FFLAGS" >> "$GITHUB_ENV"
+ export TESTDIM=${{ matrix.testdim }}
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ cd "$ROOT_DIR"/fortran/tests && make ntest_${IK}.${{ matrix.solver }}
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make ntest
+ export EXAMPLE_NUM=2 && make clean && make ntest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}-${{ env.IK }}-${{ matrix.testdim }}-${{ env.FFLAGS }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
+
diff --git a/.github/workflows/test_nvfortran.yml b/.github/workflows/test_nvfortran.yml
index 10ee578af3..7ab7681d1d 100644
--- a/.github/workflows/test_nvfortran.yml
+++ b/.github/workflows/test_nvfortran.yml
@@ -6,7 +6,7 @@ on:
pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- - cron: '0 4 3-31/3 * *'
+ - cron: '0 4 3-31/4 * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -21,70 +21,94 @@ run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}
jobs:
test:
+
name: Run nvfortran tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest]
- ikind: [i2, i8]
+ os: [ubuntu-latest, ubuntu-24.04-arm]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
+ #fflags: [-O1, -O2, -O3, -g, -fast]
+ #testdim: [small, big]
steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- - name: Install nvfortran
- run: bash .github/scripts/install_nvfortran
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make vtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make vtest
+ - name: Install nvfortran
+ run: bash .github/scripts/install_nvfortran
+
+ # - name: Revise bobyqa/trustregion.f90 to see why `xbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1` leads to a SIGFPE
+ # shell: bash
+ # run: |
+ # cd fortran/bobyqa || exit 42
+ # $SEDI "s|xbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1|write(*,*) '========> su = ', su, 'xopt = ', xopt, 'gopt = ', gopt\nxbdi(trueloc(xopt >= su .and. gopt <= 0)) = 1|" trustregion.f90
+ # cat trustregion.f90
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+# command: |
+# ########################################################################################
+# # Zaikun 20260406: Starting from today, for nvfortran we will only run the examples,
+# # not the extensive tests. nvfortran was quite useful as a reference for debugging the
+# # early versions of PRIMA, but in the past years our work concerning nvfortran has been
+# # mostly debugging the compiler itself (especially concerning empty arrays). In addition,
+# # nvfortran will likely be replaced with a LLVM-based compiler in the future. See
+# # https://forums.developer.nvidia.com/t/bug-of-nvfortran-25-7-with-empty-arrays/342404/2
+# # https://forums.developer.nvidia.com/t/bug-of-nvfortran-24-3-0-fort1-terminated-by-signal-11/289026/6
+# # export FFLAGS=${{ matrix.fflags }}
+# # export TESTDIM=${{ matrix.testdim }}
+# # IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+# # echo "IK=${IK}"
+# # echo "IK=${IK}" >> "$GITHUB_ENV"
+# # cd "$ROOT_DIR"/fortran/tests && make vtest_${IK}.${{ matrix.solver }}
+# ########################################################################################
+ command: |
+ cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ export EXAMPLE_NUM=1 && make clean && make vtest
+ export EXAMPLE_NUM=2 && make clean && make vtest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ #name: ${{ matrix.os }}-${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
+ name: ${{ matrix.os }}-${{ matrix.solver }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_nvfortran_kunpeng.yml b/.github/workflows/test_nvfortran_kunpeng.yml
deleted file mode 100644
index ae1229417e..0000000000
--- a/.github/workflows/test_nvfortran_kunpeng.yml
+++ /dev/null
@@ -1,86 +0,0 @@
-name: Test nvfortran on Kunpeng
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 3-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run nvfortran tests
- runs-on: [self-hosted, ARM64, kp]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -v && bash ./mlint -v
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make vtest_${{ matrix.ikind }}.${{ matrix.solver }}
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make vtest
-
- # - name: Store artifacts
- # uses: actions/upload-artifact@v3.1.2
- # if: always() # Always run even if the workflow is canceled manually or due to overtime.
- # with:
- # path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_nvfortran_pi.yml b/.github/workflows/test_nvfortran_pi.yml
deleted file mode 100644
index b25bbc6f7b..0000000000
--- a/.github/workflows/test_nvfortran_pi.yml
+++ /dev/null
@@ -1,94 +0,0 @@
-name: Test nvfortran on Raspberry Pi
-
-on:
- # Trigger the workflow on push or pull request
- #push:
- #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
- # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
- schedule:
- - cron: '0 16 3-31/5 * *'
- # Trigger the workflow manually
- workflow_dispatch:
- inputs:
- git-ref:
- description: Git Ref (Optional)
- required: false
-
-# Show the git ref in the workflow name if it is invoked manually.
-run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0}', inputs.git-ref) || '' }}
-
-
-jobs:
-
- test:
- name: Run nvfortran tests
- runs-on: [self-hosted, ARM64, pi64]
- continue-on-error: true
- strategy:
- fail-fast: false
- matrix:
- ikind: [i2, i8]
- solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
- fflags: [-O1, -O2, -O3, -g, -fast]
- testdim: [small, big]
-
- steps:
- - name: Clone Repository (Latest)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref == ''
- with:
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
- - name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
- if: github.event.inputs.git-ref != ''
- with:
- ref: ${{ github.event.inputs.git-ref }}
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- submodules: recursive
-
- - name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
-
- - name: Conduct the test
- run: |
- # Without the following lines, flint does not invoke vtest. No idea why.
- NVDIR="/opt/nvidia/hpc_sdk/Linux_aarch64/"
- if [[ -d "$NVDIR" ]] ; then NVBIN=$(find "$NVDIR" -wholename "*compilers/bin" -type d -print | sort | tail -n 1) ; fi
- if [[ -n "$NVBIN" ]] ; then export PATH=$PATH:"$NVBIN" ; fi
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint -v && bash ./mlint -v
- # As of nvfortran 23.1, date_and_time() and random_number are not supported on Raspberry Pi
- # 4B. Thus we have to disable the extensive tests, trying only the simple example.
- #cd "$ROOT_DIR"/fortran/tests && export FFLAGS=${{ matrix.fflags }} && export TESTDIM=${{ matrix.testdim }} && make vtest_${{ matrix.ikind }}.${{ matrix.solver }}
- printf "\n\n********************************************************************************"
- printf "\nExtensive tests are skipped due to the unavailability of date_and_time() and random_number."
- printf "\nSee the comments in the yml file.\nCheck whether they are available now."
- printf "\n********************************************************************************\n\n"
- cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make vtest
-
- - name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- with:
- path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
-
- - name: Remove the test data
- if: always() # Always run even if the workflow is canceled manually or due to overtime.
- run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/test_pyprima.yml b/.github/workflows/test_pyprima.yml
new file mode 100644
index 0000000000..9062eea2a0
--- /dev/null
+++ b/.github/workflows/test_pyprima.yml
@@ -0,0 +1,56 @@
+name: Test PyPRIMA
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 16 4-31/4 * *'
+ # Trigger the workflow manually
+ workflow_dispatch:
+
+
+jobs:
+
+ test:
+ name: Run PyPRIMA tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6.0.2
+
+
+ - name: Setup Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: "3.12"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r pyprima/tests/requirements.txt
+
+ - name: Conduct the test
+ shell: bash
+ run: |
+ cd pyprima
+ # Need editable mode for coverage to work correctly
+ python -m pip install --editable .
+ pytest --cov=src --cov-report=html tests/
+
+
+ - name: Store artifacts
+ uses: actions/upload-artifact@v7
+ id: artifact-coverage
+ with:
+ name: coverage-html-${{ matrix.os }}
+ path: pyprima/htmlcov/*
+
+ - name: Output artifact URL
+ run: echo 'Artifact URL is https://github.com/nbelakovski/prima/actions/runs/${{ github.run_id }}/artifacts/${{ steps.artifact-coverage.outputs.artifact-id }}'
diff --git a/.github/workflows/test_sunf95.yml b/.github/workflows/test_sunf95.yml
index 05b87feeb5..3479d1aa51 100644
--- a/.github/workflows/test_sunf95.yml
+++ b/.github/workflows/test_sunf95.yml
@@ -24,12 +24,10 @@ jobs:
test:
name: Run sunf95 tests
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
- ikind: [i2, i8]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
# As of 20230213, -fast fails often; seems due to stack overflow. How to force sunf95 to use
# heap only?
@@ -37,58 +35,72 @@ jobs:
testdim: [small, big]
steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
- ref: ${{ github.event.inputs.git-ref }}
+ ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- - name: Install Oracle sunf95
- run: bash .github/scripts/install_sunf95
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- - name: Conduct the test
+ - name: Install Oracle sunf95
+ run: bash .github/scripts/install_sunf95
+
+ - name: Revise Makefile.common to exclude r16 tests when the dimension is big
+ if: ${{ matrix.testdim == 'big' }}
+ shell: bash
run: |
- cd "$ROOT_DIR"/fortran/${{ matrix.solver }} && bash ./flint --all && bash ./mlint --all
- export FFLAGS=${{ matrix.fflags }}
- export TESTDIM=${{ matrix.testdim }}
- cd "$ROOT_DIR"/fortran/tests && make stest_${{ matrix.ikind }}.${{ matrix.solver }}
- # sunf95 cannot handle matrix indexing with TRUELOC or allocatable characters.
- # cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }} && make stest
+ cd fortran/tests/makefiles || exit 42
+ $SEDI "s|\$(TST)_.*r16_d[0,1]_tst||g" Makefile.common && cat Makefile.common
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-bash-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ export FFLAGS=${{ matrix.fflags }}
+ export TESTDIM=${{ matrix.testdim }}
+ IK=i$(( 2**(1 + $(date +%-d) % 3) ))
+ echo "IK=${IK}"
+ echo "IK=${IK}" >> "$GITHUB_ENV"
+ cd "$ROOT_DIR"/fortran/tests && make stest_${IK}.${{ matrix.solver }}
+ # sunf95 cannot handle matrix indexing with TRUELOC or allocatable characters.
+ # cd "$ROOT_DIR"/fortran/examples/${{ matrix.solver }}
+ # export EXAMPLE_NUM=1 && make clean && make stest
+ # export EXAMPLE_NUM=2 && make clean && make stest
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: ${{ matrix.solver }}-${{ env.IK }}-${{ matrix.fflags }}-${{ matrix.testdim }}
path: ${{ env.TEST_DIR }}/prima/fortran/tests/test.${{ matrix.solver }}/log/*.log
- name: Remove the test data
if: always() # Always run even if the workflow is canceled manually or due to overtime.
run: rm -rf ${{ env.TEST_DIR }}
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout_big_test ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/verify_archiva_antiqua.yml b/.github/workflows/verify_archiva_antiqua.yml
new file mode 100644
index 0000000000..0a570d44d0
--- /dev/null
+++ b/.github/workflows/verify_archiva_antiqua.yml
@@ -0,0 +1,125 @@
+name: Verification, archiva antiqua
+# antiqua: the archiva versions with GOTO
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 5 8-14 */3 *' # The 8-14th day of every 3rd month at 5:00 (UTC)
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}', inputs.git-ref, inputs.random-seed) || '' }}
+
+
+jobs:
+ test:
+ name: Verify PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ # Do NOT use MATLAB R2025a/b due to its bug.
+ # See https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+ matlab: [R2024b]
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
+ dim: [small]
+ version: [220208, 220227, 220513, 220531, 220601, 220616, 220819]
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, '.development/archiva/${{ matrix.version }}/matlab/tests'));
+ tz = 'Asia/Shanghai';
+ dt = datetime('now', 'TimeZone', tz);
+ DayMonth = day(dt);
+ options = struct();
+ options.verbose = true;
+ if ~isempty('${{ inputs.random-seed }}')
+ options.seed = str2num('${{ inputs.random-seed }}');
+ else
+ options.seed = 100*mod(year(dt), 100) + week(dt);
+ end
+ options.nr = 1; % Since the code will not change along the time, big nr is not needed.
+ options.no_classical = true; % Starting from 20230212, we do not verify the classical version for the archiva.
+ options
+ verify('${{ matrix.solver }}', '${{ matrix.dim }}', 'seq', options); % Zaikun 20230328: Conduct the verification sequentially to avoid the debugging pain.
+ delete([upper('${{ matrix.solver }}'), '_output.txt']);
+ delete('fort.6');
+ cd(root_dir); setup clean; setup uninstall;
diff --git a/.github/workflows/verify_archiva_media.yml b/.github/workflows/verify_archiva_media.yml
new file mode 100644
index 0000000000..01cf6c7061
--- /dev/null
+++ b/.github/workflows/verify_archiva_media.yml
@@ -0,0 +1,125 @@
+name: Verification, archiva media
+# media: the archiva versions without GOTO, except for the two latest
+
+on:
+ # Trigger the workflow on push or pull request
+ #push:
+ #pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
+ # Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
+ schedule:
+ - cron: '0 5 1-7 * *' # The first 7 days of each month at 5:00 (UTC)
+ # Trigger the workflow manually
+ workflow_dispatch:
+ inputs:
+ git-ref:
+ description: Git Ref (Optional)
+ required: false
+ random-seed:
+ description: Random Seed (Optional)
+ required: false
+
+# Show the git ref in the workflow name if it is invoked manually.
+run-name: ${{ github.event_name == 'workflow_dispatch' && format('Manual run {0} , seed {1}', inputs.git-ref, inputs.random-seed) || '' }}
+
+
+jobs:
+ test:
+ name: Verify PRIMA.
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ # Do NOT use MATLAB R2025a/b due to its bug.
+ # See https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+ matlab: [R2024b]
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
+ dim: [small, big]
+ version: [220926, 221105, 221128, 230108, 230305, 230430, 230509]
+
+ steps:
+
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
+ - name: Clone Repository (Latest)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref == ''
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+ - name: Clone Repository (Custom Ref)
+ uses: actions/checkout@v6.0.2
+ if: github.event.inputs.git-ref != ''
+ with:
+ ref: ${{ github.event.inputs.git-ref }}
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ submodules: recursive
+
+
+ - name: Miscellaneous setup
+ run: bash .github/scripts/misc_setup
+
+ - name: Clone MatCUTEst
+ uses: actions/checkout@v6.0.2
+ with:
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
+ repository: matcutest/matcutest_compiled
+ path: matcutest
+
+ - name: Set up gfortran on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ uses: fortran-lang/setup-fortran@main
+ with:
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
+
+ - name: Set up MATLAB
+ uses: matlab-actions/setup-matlab@v3.0.1
+ with:
+ release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Conduct the test
+ uses: equipez/run-matlab-command@v2
+ with:
+ timelimit: 320m
+ command: |
+ ver;
+ root_dir = pwd();
+
+ cd(fullfile(root_dir, 'matcutest')); install(); which macup
+
+ cd(fullfile(root_dir, '.development/archiva/${{ matrix.version }}/matlab/tests'));
+ tz = 'Asia/Shanghai';
+ dt = datetime('now', 'TimeZone', tz);
+ DayMonth = day(dt);
+ options = struct();
+ options.verbose = true;
+ if ~isempty('${{ inputs.random-seed }}')
+ options.seed = str2num('${{ inputs.random-seed }}');
+ else
+ options.seed = 100*mod(year(dt), 100) + week(dt);
+ end
+ options.nr = 1; % Since the code will not change along the time, big nr is not needed.
+ options.no_classical = true; % Starting from 20230212, we do not verify the classical version for the archiva.
+ options
+ verify('${{ matrix.solver }}', '${{ matrix.dim }}', 'seq', options); % Zaikun 20230328: Conduct the verification sequentially to avoid the debugging pain.
+ delete([upper('${{ matrix.solver }}'), '_output.txt']);
+ delete('fort.6');
+ cd(root_dir); setup clean; setup uninstall;
diff --git a/.github/workflows/verify_archiva.yml b/.github/workflows/verify_archiva_nova.yml
similarity index 63%
rename from .github/workflows/verify_archiva.yml
rename to .github/workflows/verify_archiva_nova.yml
index b06ac6f7c3..342f21420c 100644
--- a/.github/workflows/verify_archiva.yml
+++ b/.github/workflows/verify_archiva_nova.yml
@@ -1,4 +1,5 @@
-name: Verification, archiva
+name: Verification, archiva nova
+# nova: the two latest archiva versions
on:
# Trigger the workflow on push or pull request
@@ -6,8 +7,7 @@ on:
#pull_request: # DANGEROUS! MUST be disabled for self-hosted runners!
# Trigger the workflow by cron. The default time zone of GitHub Actions is UTC.
schedule:
- # - cron: '0 1,9,17 * * *'
- - cron: '0 22 * * *'
+ - cron: '0 6 * * *'
# Trigger the workflow manually
workflow_dispatch:
inputs:
@@ -26,15 +26,16 @@ jobs:
test:
name: Verify PRIMA.
runs-on: ${{ matrix.os }}
- continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
- matlab: [latest]
+ # Do NOT use MATLAB R2025a/b due to its bug.
+ # See https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+ matlab: [R2024b]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
dim: [small, big, large]
- version: [220208, 220227, 220513, 220531,220601,220616,220819,220926,221105,221128,230108,230305,230430,230509,230614,230804]
+ version: [230614, 230804]
exclude:
- solver: lincoa
dim: large # very few problems but takes too much time to run
@@ -44,7 +45,7 @@ jobs:
steps:
- name: Run `sudo apt update`
- run: sudo apt update # Otherwise, free-disk-space or other actions relying on `apt` may fail
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
- name: Free disk space
uses: jlumbroso/free-disk-space@main
@@ -58,52 +59,51 @@ jobs:
swap-storage: false # Important, or the runner may be shut down due to memory starvation.
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
-
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
+ uses: fortran-lang/setup-fortran@main
with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
+ compiler: gcc
+ version: latest
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -119,29 +119,12 @@ jobs:
if ~isempty('${{ inputs.random-seed }}')
options.seed = str2num('${{ inputs.random-seed }}');
else
- options.seed = 10000*mod(year(dt), 100) + 100*week(dt) + hour(dt);
+ options.seed = 100*mod(year(dt), 100) + week(dt);
end
options.nr = 1; % Since the code will not change along the time, big nr is not needed.
options.no_classical = true; % Starting from 20230212, we do not verify the classical version for the archiva.
options
- verify('${{ matrix.solver }}', '${{ matrix.dim }}', 'seq', options); % Zaikun 20230328: Conduct the verification in sequential to avoid the debugging pain.
+ verify('${{ matrix.solver }}', '${{ matrix.dim }}', 'seq', options); % Zaikun 20230328: Conduct the verification sequentially to avoid the debugging pain.
delete([upper('${{ matrix.solver }}'), '_output.txt']);
delete('fort.6');
cd(root_dir); setup clean; setup uninstall;
-
- # The following job check whether the tests were successful or cancelled due to timeout.
- # N.B.: Remember to specify `continue-on-error: true` for the job of the tests.
- check_success_timeout:
- runs-on: ubuntu-latest
- if: ${{ !cancelled() }}
- needs: test
- steps:
- - name: Clone the GitHub actions scripts
- uses: actions/checkout@v4
- with:
- repository: equipez/github_actions_scripts
- ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
- path: scripts
-
- - name: Check whether the tests were successful or cancelled due to timeout
- run: bash scripts/check_success_timeout ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} ${{ github.run_id }}
diff --git a/.github/workflows/verify_big.yml b/.github/workflows/verify_big.yml
index 6a888ec883..8d2f29b0c1 100644
--- a/.github/workflows/verify_big.yml
+++ b/.github/workflows/verify_big.yml
@@ -32,56 +32,134 @@ jobs:
matlab: [latest]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
dim: [big]
+ use_system_libgcc: [true, false] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
-
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
+ uses: fortran-lang/setup-fortran@main
with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
+ compiler: gcc
+ version: latest
+
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
- - name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise get_solvers.m to print the revised header file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|\(^.*\)\(setup(solver.*$\)|\1header_file\n\1system(['cat ', header_file]);\n\1\2|" get_solvers.m
+
+ - name: Revise setup_compiler_options.m to print the revised mexopt file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -97,16 +175,31 @@ jobs:
copyfile('private/year_week.m', cd());
options.seed = year_week('Asia/Shanghai');
end
- if strcmp('${{ matrix.solver }}', 'bobyqa') || strcmp('${{ matrix.solver }}', 'lincoa'), options.nr = 4; end;
options
verify('${{ matrix.solver }}', '${{ matrix.dim }}', options);
cd(root_dir); setup path; setup clean; setup uninstall % Test that `setup` works properly.
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_verify_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.matlab }}-${{ matrix.use_system_libgcc }}-${{ matrix.os }}
path: |
- /tmp/${{ matrix.solver }}_verify_prima/*start*
- /tmp/${{ matrix.solver }}_verify_prima/*end*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_verify_prima/*stuck*
+ /tmp/${{ matrix.solver }}_verify_prima/fort.*
diff --git a/.github/workflows/verify_large.yml b/.github/workflows/verify_large.yml
index 6854d4c2e8..ed3c85ba47 100644
--- a/.github/workflows/verify_large.yml
+++ b/.github/workflows/verify_large.yml
@@ -30,58 +30,136 @@ jobs:
matrix:
os: [ubuntu-latest]
matlab: [latest]
- solver: [newuoa, cobyla, bobyqa, uobyqa] # With the blacklist, there is no large problem for lincoa
+ solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
dim: [large]
+ use_system_libgcc: [true, false] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
run: bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
-
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
+ uses: fortran-lang/setup-fortran@main
with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
+ compiler: gcc
+ version: latest
+
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
- - name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise get_solvers.m to print the revised header file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|\(^.*\)\(setup(solver.*$\)|\1header_file\n\1system(['cat ', header_file]);\n\1\2|" get_solvers.m
+
+ - name: Revise setup_compiler_options.m to print the revised mexopt file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
+
+ - name: Conduct the test; treat timeout as SUCCESS (exit 0)
+ uses: equipez/run-matlab-command@v2
with:
+ timelimit: 320m
command: |
ver;
root_dir = pwd();
@@ -91,8 +169,6 @@ jobs:
cd(fullfile(root_dir, 'matlab/tests'));
options = struct();
options.verbose = true;
- options.nr = 5;
- if strcmp('${{ matrix.solver }}', 'lincoa'), options.nr = 1; end;
options.no_classical = true; % We do not test the classical mode on "large" problems.
if ~isempty('${{ inputs.random-seed }}')
options.seed = str2num('${{ inputs.random-seed }}');
@@ -104,11 +180,27 @@ jobs:
verify('${{ matrix.solver }}', '${{ matrix.dim }}', options);
cd(root_dir); setup path; setup clean; setup uninstall % Test that `setup` works properly.
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_verify_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.matlab }}-${{ matrix.use_system_libgcc }}-${{ matrix.os }}
path: |
- /tmp/${{ matrix.solver }}_verify_prima/*start*
- /tmp/${{ matrix.solver }}_verify_prima/*end*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_verify_prima/*stuck*
+ /tmp/${{ matrix.solver }}_verify_prima/fort.*
diff --git a/.github/workflows/verify_small.yml b/.github/workflows/verify_small.yml
index f321063390..f254bf3922 100644
--- a/.github/workflows/verify_small.yml
+++ b/.github/workflows/verify_small.yml
@@ -32,55 +32,132 @@ jobs:
matlab: [latest]
solver: [newuoa, cobyla, lincoa, bobyqa, uobyqa]
dim: [small]
+ use_system_libgcc: [true, false] # Whether to use the system libgcc or the one shipped with MATLAB.
steps:
+ - name: Run `sudo apt update`
+ run: sudo apt update || true # Otherwise, free-disk-space or other actions relying on `apt` may fail
+
+ - name: Free disk space
+ uses: jlumbroso/free-disk-space@main
+ if: startsWith(matrix.os, 'ubuntu')
+ with:
+ # all of these default to true, but feel free to set to "false" if necessary for your workflow
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: false # Important, or the runner may be shut down due to memory starvation.
+
- name: Clone Repository (Latest)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref == ''
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
- name: Clone Repository (Custom Ref)
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
if: github.event.inputs.git-ref != ''
with:
ref: ${{ github.event.inputs.git-ref }}
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
submodules: recursive
+
- name: Miscellaneous setup
- run: bash .github/scripts/misc_setup
+ run: export SWAP_SIZE=20 && bash .github/scripts/misc_setup
- name: Clone MatCUTEst
- uses: actions/checkout@v4
+ uses: actions/checkout@v6.0.2
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_ACT }} # This forces checkout to use SSH, not HTTPS
repository: matcutest/matcutest_compiled
path: matcutest
- - name: Link gfortran for MATLAB on Linux
+ - name: Set up gfortran on Linux
if: startsWith(matrix.os, 'ubuntu')
- run: bash .github/scripts/link_gfortran ${{ env.GFORTRAN_VERSION }}
-
- - name: Check MATLAB
- id: check_matlab
- run: if type 'matlab' &> /dev/null ; then echo "::set-output name=has_matlab::true" ; fi
-
- - name: Cache MATLAB # N.B.: Clear the cache when the `latest` version of MATLAB changes in March and September
- uses: actions/cache@v3.3.2
+ uses: fortran-lang/setup-fortran@main
with:
- path: ${{ runner.tool_cache }}/MATLAB
- key: ${{ matrix.os }}-${{ matrix.matlab }}
+ compiler: gcc
+ version: latest
+
+ - name: Check gfortran version on Linux
+ if: startsWith(matrix.os, 'ubuntu')
+ run: which gcc && gcc --version && which gfortran && gfortran --version
- name: Set up MATLAB
- if: ${{ steps.check_matlab.outputs.has_matlab != 'true' }}
- uses: matlab-actions/setup-matlab@v2-beta
+ uses: matlab-actions/setup-matlab@v3.0.1
with:
release: ${{ matrix.matlab }}
+ cache: true
+ products: Parallel_Computing_Toolbox
+
+ - name: Link system libgcc to MATLAB if needed
+ if: startsWith(matrix.os, 'ubuntu')
+ shell: bash
+ run: |
+ matlab_root=$(realpath $(dirname $(which matlab))/../)
+ echo "MATLAB root is ${matlab_root}"
+
+ matlab_libgcc="${matlab_root}/sys/os/glnxa64/libgcc_s.so.1"
+ echo "MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ system_libgcc="/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"
+ echo "System libgcc is:"
+ ls -alh ${system_libgcc} || exit 42
+ echo "gcc strings in system libgcc are:"
+ strings ${system_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in system libgcc is:"
+ readelf -V ${system_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ if [[ "${{ matrix.use_system_libgcc }}" == "true" ]] ; then
+ ln -sf ${system_libgcc} ${matlab_libgcc} || exit 42
+ echo "Linked ${system_libgcc} to ${matlab_libgcc}."
+ else
+ echo "Keep ${matlab_libgcc} untouched."
+ fi
+ echo "After the operation, MATLAB libgcc is:"
+ ls -alh ${matlab_libgcc} || exit 42
+ echo "gcc strings in MATLAB libgcc are:"
+ strings ${matlab_libgcc} | grep -iE "^GCC_"
+ echo "latest gcc version string in MATLAB libgcc is:"
+ readelf -V ${matlab_libgcc} | grep -oiE 'GCC_([0-9]+)(.[0-9]+){0,2}' | sort -V | tail -1
+
+ - name: Revise get_solvers.m to print the revised header file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|\(^.*\)\(setup(solver.*$\)|\1header_file\n\1system(['cat ', header_file]);\n\1\2|" get_solvers.m
+
+ - name: Revise setup_compiler_options.m to print the revised mexopt file
+ run: |
+ cd matlab/tests/private || exit 1
+ $SEDI "s|return|for ifile = 1 : length(config_files), ifile, cfile = fullfile(config_dir, config_files{ifile}) , system(['cat ', cfile]); end|" set_compiler_options.m
+
+ - name: Revise getMexLibgcc and compiled.m to print necessary information
+ shell: bash
+ run: |
+ cd matlab/setup_tools || exit 42
+ $SEDI 's|\(.*lddOut.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Path.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*String.*\);|\1|' getMexLibgcc.m
+ $SEDI 's|\(.*Version.*\);|\1|' getMexLibgcc.m
+ cat getMexLibgcc.m
+ $SEDI 's|\(common_mex_options\s*=\s*.*$\)$|\1\ncommon_mex_options{:}|' compile.m
+ $SEDI 's|\(.*\s*=\s*getMexLibgcc().gccVersion.*\)|\1\ngetMexLibgcc() |' compile.m
+ $SEDI 's|\(.*_version\s*=\s*.*\);|\1 |' compile.m
+ $SEDI 's|\(compiler_options\s*=\s*.*\);|\1 |' compile.m
+ $SEDI "s|\(if\s*~support_internal_procedures\)|verLessThan('matlab', '25.1'), verLessThan('matlab', '25.2'), compiler_manufacturer, compiler_options, support_internal_procedures\n\1|" compile.m
+ cat compile.m
- name: Conduct the test
- uses: matlab-actions/run-command@v1.2.1
+ uses: matlab-actions/run-command@v3.1.1
with:
command: |
ver;
@@ -101,11 +178,27 @@ jobs:
verify('${{ matrix.solver }}', '${{ matrix.dim }}', options);
cd(root_dir); setup path; setup clean; setup uninstall % Test that `setup` works properly.
+ - name: List problems that started but did not end
+ # The solver got stuck when solving these problems. Investigate what happened.
+ if: always()
+ shell: bash
+ run: |
+ solver=${{ matrix.solver }}
+ cd /tmp/${solver}_verify_prima/
+ ls -R1 *${solver}*_start > ${solver}_prob_start
+ ls -R1 *${solver}*_end > ${solver}_prob_end
+ diff ${solver}_prob_start ${solver}_prob_end > ${solver}_stuck || :
+ printf "\n\n>>>>>>>>>>>>>>>>\nProblems that started but did not end:\n\n"
+ cat ${solver}_stuck
+ printf "\n<<<<<<<<<<<<<<<<\n\n"
- name: Store artifacts
- uses: actions/upload-artifact@v3.1.3
+ uses: actions/upload-artifact@v7
if: always() # Always run even if the workflow is canceled manually or due to overtime.
with:
+ name: artifact-${{ matrix.solver }}-${{ matrix.dim }}-${{ matrix.matlab }}-${{ matrix.use_system_libgcc }}-${{ matrix.os }}
path: |
- /tmp/${{ matrix.solver }}_verify_prima/*start*
- /tmp/${{ matrix.solver }}_verify_prima/*end*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_start*
+ /tmp/${{ matrix.solver }}_verify_prima/*prob_end*
+ /tmp/${{ matrix.solver }}_verify_prima/*stuck*
+ /tmp/${{ matrix.solver }}_verify_prima/fort.*
diff --git a/.gitignore b/.gitignore
index ccc044fb7c..5c50bacf64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ install/
build/
include/
!c/include/
+!python/pybind11/include/
lib/
mod/
@@ -108,6 +109,7 @@ parts/
sdist/
var/
wheels/
+wheelhouse
share/python-wheels/
*.egg-info/
.installed.cfg
@@ -227,3 +229,12 @@ cython_debug/
# Mac files
.DS_Store
+
+# Version file
+_version.txt
+
+# MathWorks files
+fintrf.h
+
+# Mac OS X debug symbols
+*.dSYM/
diff --git a/.gitmodules b/.gitmodules
index f9c1cf5a82..9e31e79af6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,9 @@
[submodule ".github/scripts"]
path = .github/scripts
- url = git@github.com:equipez/github_actions_scripts.git
-[submodule "prima_development"]
- path = prima_development
- url = git@github.com:libprima/prima_development.git
+ url = https://github.com/equipez/github_actions_scripts
[submodule ".development"]
path = .development
- url = git@github.com:libprima/prima_development.git
+ url = https://github.com/libprima/prima_development
+[submodule "python/pybind11"]
+ path = python/pybind11
+ url = https://github.com/pybind/pybind11
diff --git a/.mygit/config b/.mygit/config
index d835597baa..5f484930f9 100644
--- a/.mygit/config
+++ b/.mygit/config
@@ -11,13 +11,15 @@
pushurl = git@github.com:libprima/prima.git
pushurl = git@github.com:libsprima/prima.git
pushurl = git@github.com:primapack/prima.git
+ pushurl = git@github.com:fortlab/prima.git
pushurl = git@github.com:primalib/prima.git
+ pushurl = git@github.com:opt4ai/prima.git
+ pushurl = git@github.com:opt2ai/prima.git
pushurl = git@github.com:zequipe/prima.git
pushurl = git@github.com:equipez/prima.git
pushurl = git@github.com:s-prima/prima.git
pushurl = git@github.com:zaikunzhang/prima.git
pushurl = git@github.com:sprimalib/prima.git
- pushurl = git@gitlab.com:libprima/prima.git
pushurl = git@gitee.com:libprima/prima.git
gh-resolved = base
[branch "main"]
@@ -27,3 +29,7 @@
url = git@github.com:libprima/prima_development.git
[submodule ".github/scripts"]
url = git@github.com:equipez/github_actions_scripts.git
+[submodule "python/pybind11"]
+ url = https://github.com/pybind/pybind11
+[pull]
+ rebase = false
diff --git a/.mygit/hooks/post-commit b/.mygit/hooks/post-commit
index ffdbe9ddff..8c24319759 100755
--- a/.mygit/hooks/post-commit
+++ b/.mygit/hooks/post-commit
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# Auto push after commit
###################################################################################################
diff --git a/.mygit/modules/.development/config b/.mygit/modules/.development/config
index 3e71487439..e62b383648 100644
--- a/.mygit/modules/.development/config
+++ b/.mygit/modules/.development/config
@@ -8,7 +8,6 @@
url = git@github.com:libprima/prima_development.git
fetch = +refs/heads/*:refs/remotes/origin/*
pushurl = git@github.com:libprima/prima_development.git
- pushurl = git@gitlab.com:libprima/prima_development.git
pushurl = git@gitee.com:libprima/prima_development.git
[branch "main"]
remote = origin
diff --git a/.mygit/modules/.github/scripts/hooks/post-commit b/.mygit/modules/.github/scripts/hooks/post-commit
index ffdbe9ddff..8c24319759 100755
--- a/.mygit/modules/.github/scripts/hooks/post-commit
+++ b/.mygit/modules/.github/scripts/hooks/post-commit
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# Auto push after commit
###################################################################################################
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb86ddd886..87d008e3bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,27 +9,109 @@ option (BUILD_SHARED_LIBS "shared/static" ON)
include (GNUInstallDirs)
# the compiler enables an executable stack because of nested functions and this is fine
+# Zaikun 20260206: Is this still fine and needed? See commit 5e5e8716e709c4e534ccc1a916a15b7f89676ed3
+# executable stacks are not allowed by glibc 2.4+; see
+# https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines,
+# https://github.com/JuliaLang/julia/issues/57250, https://forums.gentoo.org/viewtopic-p-8866168.html
+# We should probably do the opposite: use `-Wl,-z,noexecstack` to pass the `-z noexecstack` option
+# to the linker and tells the linker to set the "stack is not executable" attribute in the output file.
+# Zaikun 20260226: See https://github.com/libprima/prima/issues/275#issuecomment-3963552883
if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU" AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)
include (CheckLinkerFlag)
check_linker_flag (Fortran "-Wl,--no-warn-execstack" HAVE_WARN_EXECSTACK)
endif ()
-# allocate arrays on the heap instead of the stack, slower but can avoid memory errors on large problems
+# Set additional Fortran compiler flags
+# 0. See https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html for compiler IDs.
+# 1. We require the compilers to allocate arrays on the heap instead of the stack, which is
+# slower (does not matter for DFO applications) but can avoid memory errors on large problems.
+# The compiler flags for heap arrays are as follows:
+# - AMD AOCC Flang: -fno-stack-arrays
+# - AMD AOMP Flang: -fno-stack-arrays -mmlir -fdynamic-heap-array (-fno-stack-arrays insufficient as of AMOP 22.0)
+# - Arm Fortran Compiler: -fno-stack-arrays -mmlir -fdynamic-heap-array (-fno-stack-arrays insufficient as of ATfL 21.1)
+# - LLVM Flang: -fno-stack-arrays -mmlir -fdynamic-heap-array (-fno-stack-arrays insufficient as of LLVM 21.1.8)
+# - GNU gfortran: -fno-stack-arrays
+# - Intel ifx: -heap-arrays
+# - Intel ifort: -heap-arrays
+# - NVIDIA nvfortran: -Mnostack_arrays
+# - NAG Fortran Compiler: Not needed. According to the NAG support, "the behaviour of nagfor is for
+# small fixed-size arrays to go on the stack, and for variable-size arrays and fixed-size arrays to
+# go on the heap".
+# 2. We require the compilers to compile the solvers so that they can be called recursively.
+# See https://fortran-lang.discourse.group/t/frecursive-assume-recursion-and-recursion-thread-safety
+# 3. Zaikun 20260206: Is the ID for ARM Flang correct? What about the AMD AOMP Flang? See
+# https://github.com/arm/arm-toolchain/issues/717
+# https://github.com/ROCm/aomp/issues/1931
option (PRIMA_HEAP_ARRAYS "allocate arrays on heap" ON)
if (PRIMA_HEAP_ARRAYS)
- if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU")
- set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fno-stack-arrays")
- elseif (CMAKE_Fortran_COMPILER_ID MATCHES "Intel")
+ if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU") # gfortran
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fno-stack-arrays -frecursive")
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "Intel|IntelLLVM") # Intel compilers
if (WIN32)
- set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /heap-arrays")
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /heap-arrays /assume:recursion")
else ()
- set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -heap-arrays")
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -heap-arrays -assume recursion")
endif ()
- elseif (CMAKE_Fortran_COMPILER_ID MATCHES "NVHPC")
- set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Mnostack_arrays")
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "NAG") # nagfor
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -recursive") # What about stack/heap?
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "LLVMFlang") # LLVM Flang
+ # See https://github.com/llvm/llvm-project/issues/88344
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fno-stack-arrays -mmlir -fdynamic-heap-array")
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "ARMClang") # ARM Flang (Zaikun 20260206: is the ID correct?)
+ # N.B.: `-mmlir -fdynamic-heap-array` is needed; see
+ # https://github.com/libprima/prima/commit/6e0963c210129273964aa6ea822fc52bd4736974
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fno-stack-arrays -mmlir -fdynamic-heap-array")
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "NVHPC") # nvfortran
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Mnostack_arrays -Mrecursive")
+ elseif (CMAKE_Fortran_COMPILER_ID MATCHES "Flang") # Classic Flang and AOCC Flang
+ set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fno-stack-arrays -Mrecursive")
endif ()
endif ()
+# Set additional linker flags
+# Zaikun 20240217: Fix https://github.com/libprima/prima/issues/158, which is due to the new linker
+# implemented in Xcode 15 on macOS. It happens only if the Fortran compiler is ifort.
+# An alternative is `add_link_options("-ld_classic")`, which forces Xcode to use the old linker.
+# Will CMake adapt itself to the new linker later? Will a newer version of CMake make the fix unnecessary?
+# See
+# https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Linking
+# https://stackoverflow.com/questions/77525544/apple-linker-warning-ld-warning-undefined-error-is-deprecated
+# https://medium.com/@hackingwithcode/cmake-and-xcode-15-solving-the-undefined-error-puzzle-3c847e6d1008
+# Zaikun 20240501: "undefined,dynamic_lookup" seems included by default since CMake 3.28.1. We keep
+# the following code in case it is not included for some versions.
+if((APPLE) AND (CMAKE_Fortran_COMPILER_ID MATCHES "Intel"))
+ add_link_options("-Wl,-undefined,dynamic_lookup")
+endif()
+# Zaikun 20240501: Fix "ld: Assertion failed: (resultIndex < sectData.atoms.size())", which happens
+# when building Python wheels on macOS 13 and 14. This is a bug of Xcode 15.0 on macOS and fixed in
+# Xcode 15.1 beta according to https://github.com/Homebrew/homebrew-core/issues/145991.
+# See also
+# https://forums.developer.apple.com/forums/thread/737707
+# https://github.com/hansec/OpenFUSIONToolkit/pull/29
+if(APPLE)
+ # Get the Xcode version
+ execute_process(
+ COMMAND xcodebuild -version
+ OUTPUT_VARIABLE XCODE_OUTPUT
+ )
+ string(REGEX MATCH "Xcode ([0-9]+\\.[0-9]+(\\.[0-9]+)?)" XCODE_VERSION "${XCODE_OUTPUT}")
+ # Set linker flags for Xcode 15.0 or 15.0.x
+ if(XCODE_VERSION MATCHES "Xcode 15\\.0(\\.[0-9]+)?")
+ add_link_options("-Wl,-ld_classic")
+ endif()
+endif()
+
+# For running tests with gdb. $_exitcode != 0 means the program ran without exiting
+# normally, and in this case we want to show a stack trace
+file(WRITE ${CMAKE_BINARY_DIR}/cmdfile.gdb "init-if-undefined $_exitcode = 0
+run
+set language c
+if $_exitcode != 0
+ bt full
+end
+quit $_exitcode
+")
+
option(PRIMA_ENABLE_EXAMPLES "build examples by default" OFF)
add_custom_target (examples)
enable_testing ()
@@ -47,8 +129,60 @@ if (PRIMA_ENABLE_C)
set(primac_target "primac")
endif ()
-file (READ VERSION.txt PRIMA_VERSION)
-
+# Get the version number
+find_package(Git)
+set(IS_REPO FALSE)
+if(GIT_EXECUTABLE)
+ # --always means git describe will output the commit hash if no tags are found
+ # This is usually the case for forked repos since they do not clone tags by default.
+ execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ OUTPUT_VARIABLE PRIMA_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ RESULT_VARIABLE GIT_RESULT ERROR_QUIET)
+ if (GIT_RESULT EQUAL 0)
+ set(IS_REPO TRUE)
+ endif()
+endif()
+if(NOT GIT_EXECUTABLE OR NOT IS_REPO)
+ # If git is not available, or this isn't a repo, that may indicate we are building
+ # on macports which downloads the bundle from github (which uses git archive) and
+ # so the version number should be in .git-archival.txt.
+ # Alternatively it might mean that we're building the Python bindings, in which case
+ # the version is output in _version.txt. I know, it's complicated. I don't make the rules.
+ if(EXISTS _version.txt)
+ file(STRINGS _version.txt PRIMA_VERSION)
+ else()
+ file(STRINGS .git-archival.txt PRIMA_VERSION)
+ if(PRIMA_VERSION MATCHES "describe")
+ message(WARNING "No git detected and .git-archival.txt does not contain a version number")
+ set(PRIMA_VERSION "unknown")
+ endif()
+ endif()
+
+endif()
+# Remove the leading v from PRIMA_VERSION, if it contains one.
+string(REGEX REPLACE "^v" "" PRIMA_VERSION ${PRIMA_VERSION})
+message(STATUS "Setting PRIMA version to ${PRIMA_VERSION}")
+
+option (PRIMA_ENABLE_PYTHON "Python binding" OFF)
+if (PRIMA_ENABLE_PYTHON)
+ if(NOT PRIMA_ENABLE_C)
+ message(FATAL_ERROR "Building Python bindings requires C bindings. Please turn on PRIMA_ENABLE_C")
+ endif()
+ if(BUILD_SHARED_LIBS)
+ # This will include libprimaf, libprimafc, and libprimac into the compiled Python binding, removing the need
+ # to properly set the rpath or find those libraries at runtime.
+ # Even if we did make it successfully build with shared libraries, delocate/auditwheel will copy them into the
+ # bindings anyway
+ message(FATAL_ERROR "Building Python bindings requires static libraries. Please disable BUILD_SHARED_LIBS")
+ endif()
+ if(NOT CMAKE_Fortran_COMPILER_ID MATCHES "GNU")
+ message(WARNING "Compiling Python bindings with compilers other than GNU has not been tested and no support is planned at this time")
+ endif()
+ enable_language(CXX)
+ add_subdirectory(python)
+endif ()
install(
TARGETS primaf ${primac_target}
diff --git a/LICENCE.txt b/LICENCE.txt
index 7bbbb9097e..deb166c916 100644
--- a/LICENCE.txt
+++ b/LICENCE.txt
@@ -1,6 +1,6 @@
BSD 3-Clause License
-Copyright (c) 2020--2023, Zaikun ZHANG ( https://www.zhangzk.net )
+Copyright (c) 2020--2026, Zaikun ZHANG ( https://www.zhangzk.net )
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000000..a9775c734a
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,20 @@
+# New C API
+
+The solver options and problem definition options moved to a new structs
+that must be initialized before use and the results moved also:
+```
+prima_problem problem;
+prima_init_problem(&problem, n);
+problem.x0 = x0;
+prima_options options;
+prima_init_options(&options);
+options.iprint = PRIMA_MSG_EXIT;
+options.rhoend= 1e-3;
+options.maxfun = 200*n;
+prima_result result;
+const int rc = prima_bobyqa(&fun, &problem, &options, &result);
+// print results, or copy them, or use them in computations
+prima_free_result(&result);
+```
+Result struct should be freed after use with its dedicated free function. Make sure to
+either use the results or copy them into your own structure before freeing.
diff --git a/README.md b/README.md
index 0d4d215557..f598fd9b24 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@
- [Current status](#current-status)
- [Modern Fortran](#modern-fortran)
- [C](#c)
- - [MATLAB](#matlab)
- [Python](#python)
+ - [MATLAB](#matlab)
- [Julia](#julia)
- [Other languages](#other-languages)
- [Bug fixes](#bug-fixes)
@@ -22,8 +22,8 @@
- [Mirrors](#mirrors)
- [Gitee](https://gitee.com/libprima/prima)
- [GitHub](https://github.com/libprima/prima)
- - [GitLab](https://gitlab.com/libprima/prima)
- [Star history](#star-history)
+- [Users](#users)
### What
@@ -33,16 +33,13 @@ It provides the reference implementation for Powell's renowned derivative-free o
The "P" in the name stands for [**P**owell](https://www.zhangzk.net/powell.html),
and "RIMA" is an acronym for "**R**eference **I**mplementation with **M**odernization and **A**melioration".
-PRIMA is part of a research project funded by the
-[Hong Kong Research Grants Council](https://www.ugc.edu.hk/eng/rgc) and
-the [Department of Applied Mathematics](https://www.polyu.edu.hk/ama) (AMA) at the
-[Hong Kong Polytechnic University](https://www.polyu.edu.hk) (PolyU).
-The current version is ready to be used [in Fortran](#modern-fortran), [in C](#c), and [in MATLAB](https://github.com/libprima/prima/blob/main/README_mat.md).
-If you want to use the above-mentioned methods in Python, see the [website](https://www.pdfo.net)
-and [repository](https://github.com/pdfo/pdfo) of [PDFO](https://www.pdfo.net) instead.
+The current version is ready to be used [in Fortran](#modern-fortran), [in C](#c),
+[in Python](https://github.com/libprima/prima#python),
+[in MATLAB](https://github.com/libprima/prima/blob/main/README_mat.md),
+and [in Julia](https://juliahub.com/ui/Packages/General/PRIMA).
PRIMA was initiated by [Zaikun Zhang](https://www.zhangzk.net) in July 2020, based on
-the [PDFO](https://www.pdfo.net) package by [Tom M. Ragonneau](https://tomragonneau.com/) and Zaikun Zhang.
+the [PDFO](https://www.pdfo.net) package.
See [Zaikun Zhang's talk](https://raw.githubusercontent.com/ztalks/20230825-iciam23/main/20230825-iciam.pdf)
on PRIMA at [The 10th International Congress on Industrial and Applied Mathematics](https://iciam2023.org/) for more information.
@@ -60,7 +57,7 @@ However, Professor Powell's implementation was done in [Fortran 77](./fortran/or
The code is nontrivial to understand or maintain, let alone extend.
For many practitioners, this has become an obstacle to exploiting these solvers in their
applications. Even worse, it has hindered researchers from exploring the wealth left by Professor
-Powell to us. By all means, it is
+Powell. By all means, it is
[necessary to make the solvers available in languages other than Fortran](https://permalink.lanl.gov/object/tr?what=info:lanl-repo/lareport/LA-UR-23-23992)
promptly, first wrapping Powell's code, which is the objective of [PDFO](https://www.pdfo.net),
and then providing native and modernized implementations, which is the mission of PRIMA.
@@ -74,9 +71,9 @@ Few people remember [who translated *Elements*](https://en.wikipedia.org/wiki/Eu
but it is a job that must be done.
PRIMA aims to provide the reference implementation of Powell's methods in modern languages,
-including [**modern** Fortran](https://fortran-lang.org) (F2008 or newer), MATLAB, Python, C/C++,
+including [**modern** Fortran](https://fortran-lang.org) (F2008 or newer), C/C++, Python, MATLAB,
Julia, and R. It will be a **faithful** implementation, in the sense that the code will be
-mathematically equivalent to Powell’s, **except for** the
+mathematically equivalent to Powell’s (verified by [differential testing](https://en.wikipedia.org/wiki/Differential_testing)), **except for** the
[bug fixes](#bug-fixes) and [improvements](#improvements) made intentionally.
The focus is to implement these methods in a **structured** and **modularized** way so that they
@@ -110,108 +107,35 @@ reference implementation, extensive tests are conducted after each and every tin
using the [CUTEst](https://github.com/ralna/CUTEst) problems via [MatCUTEst](https://github.com/matcutest/matcutest).
The tests do not only verify the faithfulness of the implementation but also check that **the solvers
behave properly even if they are invoked with improper inputs or [encounter failures of function
-evaluations](https://github.com/libprima/prima/blob/main/matlab/tests/private/tough.m)**.
+evaluations](https://github.com/libprima/prima/blob/main/matlab/tests/private/tough.m)**
+(essentially [fuzz testing](https://en.wikipedia.org/wiki/Fuzzing)).
[**Stress tests**](#stress-tests) are also conducted
periodically to verify that the solvers work correctly without running into errors when applied to
**excessively large problems**.
-The tests are **automated** by
-[GitHub Actions](https://docs.github.com/en/actions). As of August 2023, more than
-45,000 "workflows" have been successfully run by GitHub Actions. Normally, each workflow consists of \~ 5
-([sometimes more than 200](https://github.com/primalib/prima/actions/runs/5763631681))
+[The tests](./tests.md) are **automated** by
+[GitHub Actions](https://docs.github.com/en/actions).
+As of August 2023, more than
+45,000 "workflows" have been successfully run by GitHub Actions. Normally, each workflow consists of
+\~ 5 ([sometimes more than 200](https://github.com/primalib/prima/actions/runs/5763631681))
**randomized** tests,
each test taking from tens of minutes to several hours (the maximum
is 6 hours, after which the test will be canceled automatically). In other words,
PRIMA has been verified by more than 200,000 hours (or **more than 20 years**) of randomized tests.
**Code must be battle-tested before becoming software.**
-Since each GitHub Team account can only run at most 60 GitHub Actions workflows concurrently, I have
-to distribute this large amount of tests to multiple Team accounts as follows.
-
-- [Tests](https://github.com/libprima/prima/actions) at [libprima/prima](https://github.com/libprima/prima)
-
- - [](https://github.com/libprima/prima/actions/workflows/cmake.yml)
- - [](https://github.com/libprima/prima/actions/workflows/cmake_nagfor.yml)
- - [](https://github.com/libprima/prima/actions/workflows/test_absoft.yml)
- - [](https://github.com/libprima/prima/actions/workflows/test_nagfor.yml)
- - [](https://github.com/libprima/prima/actions/workflows/lint_nagfor.yml)
-
-- [Tests](https://github.com/libsprima/prima/actions) at [libsprima/prima](https://github.com/libsprima/prima)
- - [](https://github.com/libsprima/prima/actions/workflows/verify_small.yml)
- - [](https://github.com/libsprima/prima/actions/workflows/verify_big.yml)
- - [](https://github.com/libsprima/prima/actions/workflows/verify_large.yml)
- - [](https://github.com/libsprima/prima/actions/workflows/profile_lincoa_small.yml)
-
-- [Tests](https://github.com/primapack/prima/actions) at [primapack/prima](https://github.com/primapack/prima)
-
- - [](https://github.com/primapack/prima/actions/workflows/profile_cobyla_small.yml)
- - [](https://github.com/primapack/prima/actions/workflows/profile_uobyqa_small.yml)
- - [](https://github.com/primapack/prima/actions/workflows/profile_newuoa_small.yml)
- - [](https://github.com/primapack/prima/actions/workflows/profile_bobyqa_small.yml)
- - [](https://github.com/primapack/prima/actions/workflows/profile_prima_small.yml)
-
-- [Tests](https://github.com/primalib/prima/actions) at [primalib/prima](https://github.com/primalib/prima)
-
- - [](https://github.com/primalib/prima/actions/workflows/test_matlab.yml)
- - [](https://github.com/primalib/prima/actions/workflows/verify_archiva.yml)
- - [](https://github.com/primalib/prima/actions/workflows/profile_all.yml)
- - [](https://github.com/primalib/prima/actions/workflows/profile_single.yml)
- - [](https://github.com/primalib/prima/actions/workflows/profile_quadruple.yml)
- - [](https://github.com/primalib/prima/actions/workflows/profile_compiler_options.yml)
-
-- [Tests](https://github.com/sprimalib/prima/actions) at [sprimalib/prima](https://github.com/sprimalib/prima)
-
- - [](https://github.com/sprimalib/prima/actions/workflows/stress_test_fortran.yml)
- - [](https://github.com/sprimalib/prima/actions/workflows/stress_test_matlab.yml)
-
-- [Tests](https://github.com/zequipe/prima/actions) at [zequipe/prima](https://github.com/zequipe/prima)
- - [](https://github.com/zequipe/prima/actions/workflows/test_flang.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/test_aflang.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/test_nvfortran.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/test_sunf95.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/test_g95.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/profile_rescue_idz_classical.yml)
- - [](https://github.com/zequipe/prima/actions/workflows/profile_rescue_idz_modernized.yml)
-
-- [Tests](https://github.com/equipez/prima/actions) at [equipez/prima](https://github.com/equipez/prima)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_kunpeng_small.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_kunpeng_big.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_flang_kunpeng.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_nvfortran_kunpeng.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_armflang_kunpeng.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_pi32_small.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_pi32_big.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_pi64_small.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_gfortran_pi64_big.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_flang_pi.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_armflang_pi.yml)
- - [](https://github.com/equipez/prima/actions/workflows/test_nvfortran_pi.yml)
-
-- [Tests](https://github.com/s-prima/prima/actions) at [s-prima/prima](https://github.com/s-prima/prima)
-
- - [](https://github.com/s-prima/prima/actions/workflows/lint_hosted.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_gfortran_O12.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_gfortran_gO3.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_gfortran_fast.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_gfortran_big.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_ifort.yml)
- - [](https://github.com/s-prima/prima/actions/workflows/test_ifx.yml)
-
-- [Tests](https://github.com/equipez/gitpersonal/actions) at [equipez/gitpersonal](https://github.com/equipez/gitpersonal)
- - [all the tests](https://github.com/equipez/gitpersonal/actions) are disabled
-
### Current status
#### Modern Fortran
-After almost **three** years of intensive coding, **the [modern Fortran version](./fortran) of
-PRIMA has been finished by December 2022.**
+After almost **three** years of intensive coding, the [modern Fortran version](./fortran) of
+PRIMA was finished by December 2022.
It can be compiled using CMake as follows.
```bash
git clone --depth 1 https://github.com/libprima/prima.git
cd prima
-cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install -DBUILD_SHARED_LIBS=OFF
+cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build --target install
```
This should create the `primaf` library for Fortran usage, located in the `install/lib/` directory
@@ -226,7 +150,7 @@ Below is an illustration with COBYLA.
cd fortran/examples/cobyla
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install -DPRIMA_DIR=$PWD/../../../install/lib/cmake/prima/
cmake --build build --target install
-./install/bin/cobyla_example
+LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/../../../install/lib ./install/bin/cobyla_example_1
```
#### C
@@ -241,9 +165,14 @@ Below is an illustration with COBYLA.
cd c/examples/cobyla
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install -DPRIMA_DIR=$PWD/../../../install/lib/cmake/prima/
cmake --build build --target install
-./install/bin/cobyla_example
+LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/../../../install/lib ./install/bin/cobyla_example
```
+#### Python
+
+- An [interface](./python) is provided for [using the **modern** Fortran implementation in Python](./python/examples/rosenbrock.py).
+- SciPy 1.16.0 replaces the [buggy](#bug-fixes) and unmaintained Fortran 77 version of [COBYLA underlying `scipy.optimize.minimize`](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla) with the PRIMA version, which is a **faithful** Python translation of the **[modern Fortran implementation](./fortran/cobyla)**.
+
#### MATLAB
- An [interface](./matlab/interfaces/prima.m) is provided for [using the **modern** Fortran implementation in MATLAB](./README_mat.md).
@@ -252,14 +181,12 @@ cmake --build build --target install
**modern** Fortran code (with the help of Mr. Galann Pennec). The other four solvers will be
implemented in MATLAB similarly.
-#### Python
-
-- The inclusion of PRIMA into SciPy is [under discussion](https://github.com/scipy/scipy/issues/18118). It will replace the [buggy](#bug-fixes) and unmaintained Fortran 77 version of [COBYLA underlying `scipy.optimize.minimize`](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla), and make the other four solvers available to all SciPy users.
-- My ultimate objective is to have a native Python implementation of PRIMA **independent of Fortran**, similar to what we have done with NEWUOA in MATLAB as [mentioned above](#newuoa_mat).
-
#### Julia
-- [Binaries for Julia](https://github.com/JuliaPackaging/Yggdrasil/pull/7332) have been built.
-- [A Julia interface](https://github.com/libprima/prima/pull/62) will be available soon.
+
+- A [Julia interface](https://juliahub.com/ui/Packages/General/PRIMA) is provided
+by [`PRIMA.jl`](https://github.com/libprima/prima.jl).
+It is registered in the General Registry of Julia as
+[`PRIMA`](https://github.com/JuliaRegistries/General/tree/master/P/PRIMA).
#### Other languages
@@ -267,6 +194,9 @@ cmake --build build --target install
- Given the **modern** Fortran version, **native implementations** in other languages
become **much easier**, because we now have a structured and modularized implementation as a reference.
My team will implement the methods in other languages in this way.
+For instance, see the [MATLAB version of NEWUOA](https://github.com/libprima/prima/blob/main/matlab/interfaces/%2Bnewuoa_mat)
+and the [Python version of COBYLA](https://github.com/libprima/prima/tree/main/pyprima/src/pyprima/cobyla)
+([included in SciPy](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla) since 1.16.0).
This is the main motivation for developing the **modern** Fortran version first —
to provide a modernized reference implementation for the development in other languages.
@@ -300,8 +230,12 @@ of Powell's solver. Inevitably, they suffer from the bugs in the Fortran 77 code
- [BOBYQA gets stuck in infinite loop. #7](https://github.com/cureos/csnumerics/issues/7)
+ - [Cobyla turns into infinite loop and never finishes #8](https://github.com/cureos/csnumerics/issues/8)
+
- [Algorithm turns into infinite loop and never finishes #3](https://github.com/xypron/jcobyla/issues/3)
+ - [The Fortran 77 version of UOBYQA encounters infinite cyclings very often if PRIMA_REAL_PRECISION is 32](https://github.com/libprima/prima/issues/98)
+
- The Fortran 77 solvers may **crash** with [segmentation faults](https://en.wikipedia.org/wiki/Segmentation_fault)
due to uninitialized variables that are used as indices.
@@ -332,11 +266,18 @@ large constraint violation even though the starting point is feasible.
### Improvements
-Due to the improvements introduced into the new implementation, PRIMA outperforms Powell's
-original code in terms of the **number of function evaluations**, which is the standard performance
-indicator in derivative-free optimization.
+Thanks to the improvements introduced into the new implementation, PRIMA
+generally produces better solutions with fewer function evaluations compared with Powell's Fortran 77 implementation.
+This makes PRIMA preferable **if function evaluations are expensive**,
+which is typically the case for [derivative-free optimization problems](https://github.com/orgs/libprima/discussions/145).
+However, if function evaluations are not the dominant cost in your application (e.g., a function
+evaluation takes only milliseconds), the Fortran 77
+solvers are likely to be faster, as they are more efficient in terms of memory usage and flops
+thanks to the careful and ingenious (but unmaintained and unmaintainable) implementation by Powell.
+
Below are the [performance profiles](https://arxiv.org/pdf/cs/0102001.pdf)
-of the PRIMA solvers compared with Powell's implementation, the convergence tolerance being $\tau = 10^{-6}$.
+of the PRIMA solvers compared with Powell's implementation in terms of the **number of function evaluations**,
+the convergence tolerance being $\tau = 10^{-6}$.
Roughly speaking, performance profiles plot the percentage of test problems solved against the budget,
which is measured relative to the cost of the most efficient solver in the comparison.
A **higher** curve indicates a **better** solver.
@@ -373,7 +314,7 @@ and [SQP method](https://en.wikipedia.org/wiki/Sequential_quadratic_programming)
Each of them is a pillar of modern numerical optimization. He also made significant contributions
to [approximation theory and methods](https://www.cambridge.org/highereducation/books/approximation-theory-and-methods/66FD8CD6F18FE1ED499A8CA9A05F2A5A#overview).
-Among numerous honors, Powell was one of the first two recipients of the
+Among numerous honors, Powell was one of the two recipients of the first
[Dantzig Prize](https://en.wikipedia.org/wiki/Dantzig_Prize)
from the Mathematical Programming Society (MOS) and Society for Industrial and Applied Mathematics (SIAM).
This is considered the highest award in optimization.
@@ -382,7 +323,7 @@ This is considered the highest award in optimization.
### A "fun" fact
In the past years, while working on PRIMA, I have spotted a dozen of [bugs in reputable Fortran compilers](https://github.com/zequipe/test_compiler)
-and two [bugs in MATLAB](https://github.com/zequipe/test_matlab). Each of them represents days of **bitter** debugging, which finally led to the conclusion
+and three [bugs in MATLAB](https://github.com/zequipe/test_matlab). Each of them represents days of **bitter** debugging, which finally led to the conclusion
that it was not a problem in my code but a flaw in the Fortran compilers or in MATLAB. From a very unusual angle, this reflects how intensive
the coding has been.
@@ -397,29 +338,53 @@ I did this for three years and I do not want anyone else to do it again.
PRIMA is dedicated to the memory of the late [Professor Powell](https://www.zhangzk.net/powell.html) with gratitude for his inspiration and
for the wealth he left to us.
-I am grateful to [Professor Ya-xiang Yuan](http://lsec.cc.ac.cn/~yyx) for his everlasting encouragement and support.
+I am profoundly grateful to [Professor Ya-xiang Yuan](http://lsec.cc.ac.cn/~yyx) for his everlasting encouragement and support.
+
+During the years working on PRIMA, due to the gap in my publication record, I needed a lot of
+support from the optimization community and beyond.
+**Thank you for help, known or unknown to me, explicit or implicit, without which I would not have survived.**
The development of PRIMA would have been a mission impossible without the groundwork laid by the [PDFO](https://www.pdfo.net)
-package of [Tom M. Ragonneau](https://tomragonneau.com/) and Zaikun Zhang. PDFO is Chapter 3 of
-Ragonneau's [thesis](https://tomragonneau.com/documents/thesis.pdf) co-supervised by Zaikun Zhang and Professor [Xiaojun Chen](https://www.polyu.edu.hk/ama/staff/xjchen/ChenXJ.htm), with financial support from
-the [Hong Kong Ph.D. Fellowship Scheme](https://cerg1.ugc.edu.hk/hkpfs/index.html) (ref. PF18-24698).
+package of [Tom M. Ragonneau](https://ragonneau.github.io) and Zaikun Zhang
+(even though PRIMA is a completely different project from PDFO and Tom did not have time to work on PRIMA).
+PDFO is Chapter 3 of Ragonneau's [thesis](https://theses.lib.polyu.edu.hk/handle/200/12294) co-supervised by Zaikun Zhang
+and Professor [Xiaojun Chen](https://www.polyu.edu.hk/ama/staff/xjchen/ChenXJ.htm),
+with financial support from the [Hong Kong Ph.D. Fellowship Scheme](https://cerg1.ugc.edu.hk/hkpfs/index.html) (ref. PF18-24698).
+
+
PRIMA is a long-term project, which would not have been sustainable without the continued funds from the
-[Hong Kong Research Grants Council](https://www.ugc.edu.hk/eng/rgc/) (ref. PolyU 253012/17P, PolyU 153054/20P,
-PolyU 153066/21P, and PolyU 153086/23P) and [The Hong Kong Polytechnic University](https://www.polyu.edu.hk/) (PolyU),
-in particular the [Department of Applied Mathematics](https://www.polyu.edu.hk/ama) (AMA).
+[National Natural Science Foundation of China](https://www.nsfc.gov.cn/english/site_1/index.html) (NSFC),
+[Hong Kong Research Grants Council](https://www.ugc.edu.hk/eng/rgc) (RGC;
+ref. PolyU 253012/17P, PolyU 153054/20P, PolyU 153066/21P, and PolyU 153086/23P)
+[Sun Yat-sen University](https://en.wikipedia.org/wiki/Sun_Yat-sen_University)
+(particularly the [School of Mathematics](https://math.sysu.edu.cn/page/25)), and
+[The Hong Kong Polytechnic University](https://www.polyu.edu.hk) (particularly the
+[Department of Applied Mathematics](https://www.polyu.edu.hk/ama)).
+Last but not least, I am deeply grateful to the [contributors](https://github.com/libprima/prima/graphs/contributors)
+from the open-source community.
### Citing PRIMA
-If you use PRIMA, please cite it as follows. Note that PRIMA contains [bug fixes](#bug-fixes)
-and [improvements](#improvements) that do not exist in Powell's original implementation of the solvers.
+PRIMA has taken me significant energy and time. I will be delighted if it is useful to you. All I need is a citation / acknowledgment,
+**which is crucial for the sustainability of the project, as software development is not well recognized in academia despite
+[its importance](https://xkcd.com/2347/) and the significant efforts it requires**.
+
+Note that PRIMA contains [bug fixes](#bug-fixes) and [improvements](#improvements) that do not exist in Powell's Fortran 77
+implementation of the solvers. Results produced by PRIMA are surely different from Powell's original solvers,
+even though the algorithms are essentially the same. Therefore,
+**it is important to point out that you are using PRIMA rather than the original solvers if you want your results to be reproducible**.
+It is wrong to pretend that PRIMA is just Powell's original solvers.
+
+If you use PRIMA, please cite it as follows. The citation will be pointed to my paper on PRIMA when I finish it.
-[1] Z. Zhang, PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration, available at http://www.libprima.net, [DOI: 10.5281/zenodo.8052654](https://doi.org/10.5281/zenodo.8052654), 2023
+[1] Z. Zhang, PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration,
+available at https://www.libprima.net, [DOI: 10.5281/zenodo.8052654](https://doi.org/10.5281/zenodo.8052654), 2023
```bibtex
@misc{Zhang_2023,
- title = {PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration},
+ title = {{PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration}},
author = {Zhang, Z.},
howpublished = {available at http://www.libprima.net, DOI: 10.5281/zenodo.8052654},
year = {2023}
@@ -429,8 +394,8 @@ and [improvements](#improvements) that do not exist in Powell's original impleme
In addition, Powell’s methods can be cited as follows.
[2] M. J. D. Powell, A direct search optimization method that models the
-objective and constraint functions by linear interpolation, In Advances
-in *Optimization and Numerical Analysis*, *eds.* S. Gomez and J. P. Hennart,
+objective and constraint functions by linear interpolation,
+In *Advances in Optimization and Numerical Analysis*, *eds.* S. Gomez and J. P. Hennart,
pages 51--67, Springer Verlag, Dordrecht, Netherlands, 1994
[3] M. J. D. Powell, UOBYQA: unconstrained optimization by quadratic
@@ -446,8 +411,8 @@ Department of Applied Mathematics and Theoretical Physics, Cambridge
University, Cambridge, UK, 2009
[6] T. M. Ragonneau and Z. Zhang,
-[PDFO: a cross-platform package for Powell's derivative-free optimization solvers](https://arxiv.org/pdf/2302.13246.pdf),
-arXiv:2302.13246, 2023
+[PDFO: a cross-platform package for Powell's derivative-free optimization solvers](https://link.springer.com/article/10.1007/s12532-024-00257-9),
+*Math. Program. Comput.*, 16:535--559, 2024
**Remarks**
@@ -456,8 +421,8 @@ arXiv:2302.13246, 2023
linear inequality constraints without using derivatives of the objective
function. Powell did not publish a paper to introduce the algorithm.
-- [The paper [6]](https://arxiv.org/pdf/2302.13246.pdf) introduces [the PDFO package](https://www.pdfo.net)
-rather than PRIMA. Nevertheless, it provides a good introduction to Powell's methods.
+- [The paper [6]](https://link.springer.com/article/10.1007/s12532-024-00257-9) introduces [the PDFO package](https://www.pdfo.net)
+rather than PRIMA. Nevertheless, it provides probably the most accessible introduction to Powell's methods.
### Charityware
@@ -483,11 +448,22 @@ Zaikun Zhang](https://www.zhangzk.net).
- GitHub: [https://github.com/libprima/prima](https://github.com/libprima/prima)
-- GitLab: [https://gitlab.com/libprima/prima](https://gitlab.com/libprima/prima)
-
### Star history
+[stardev](https://stardev.io/) ranking: [28 among 37,983](https://stardev.io/top/repos/fortran?developer=libprima&repo=prima) Fortran repos as of April 2025.
+
+
Thank you for your support.
+
+### Users
+
+Some users of PRIMA are as follows. If you want your project to be listed
+here, please [open a GitHub issue](https://github.com/libprima/prima/issues).
+
+- [SciPy](https://docs.scipy.org/doc/scipy/release/1.16.0-notes.html#highlights-of-this-release)
+- [NVIDIA CUDA-QX](https://nvidia.github.io/cudaqx/api/solvers/cpp_api.html#_CPPv4N5cudaq5optim6cobylaE)
+- [Qoro Quantum](https://github.com/QoroQuantum/divi/blob/main/divi/extern/scipy/pyprima/LICENCE.txt)
+- [AWS Center for Quantum Computing](https://github.com/aws-cqc/DeviceLayout.jl/blob/main/docs/src/examples/singletransmon.md)
diff --git a/README_mat.md b/README_mat.md
index 985ad06c48..896aef99c7 100644
--- a/README_mat.md
+++ b/README_mat.md
@@ -2,7 +2,7 @@ This is the README file for using PRIMA under MATLAB.
## Prerequisites
-PRIMA supports MATLAB R2018a and later releases. To use PRIMA, you need first
+PRIMA supports MATLAB R2020b and later releases. To use PRIMA, you need first
set up the [MEX](https://www.mathworks.com/help/matlab/ref/mex.html) of your MATLAB.
**The setup of MEX is a pure MATLAB usage problem and it has nothing to do with PRIMA.**
diff --git a/VERSION.txt b/VERSION.txt
deleted file mode 100644
index eb49d7c7fd..0000000000
--- a/VERSION.txt
+++ /dev/null
@@ -1 +0,0 @@
-0.7
diff --git a/benchmark/rescue_idz/README.txt b/benchmark/rescue_idz/230406/README.txt
similarity index 100%
rename from benchmark/rescue_idz/README.txt
rename to benchmark/rescue_idz/230406/README.txt
diff --git a/benchmark/rescue_idz/archiva/230305/fortran/.keep b/benchmark/rescue_idz/230406/archiva/230305/fortran/.keep
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/fortran/.keep
rename to benchmark/rescue_idz/230406/archiva/230305/fortran/.keep
diff --git a/benchmark/rescue_idz/archiva/230305/matlab/.gitignore b/benchmark/rescue_idz/230406/archiva/230305/matlab/.gitignore
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/matlab/.gitignore
rename to benchmark/rescue_idz/230406/archiva/230305/matlab/.gitignore
diff --git a/benchmark/rescue_idz/archiva/230305/matlab/.keep b/benchmark/rescue_idz/230406/archiva/230305/matlab/.keep
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/matlab/.keep
rename to benchmark/rescue_idz/230406/archiva/230305/matlab/.keep
diff --git a/benchmark/rescue_idz/archiva/230305/norma/fortran/.keep b/benchmark/rescue_idz/230406/archiva/230305/norma/fortran/.keep
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/norma/fortran/.keep
rename to benchmark/rescue_idz/230406/archiva/230305/norma/fortran/.keep
diff --git a/benchmark/rescue_idz/archiva/230305/norma/matlab/.keep b/benchmark/rescue_idz/230406/archiva/230305/norma/matlab/.keep
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/norma/matlab/.keep
rename to benchmark/rescue_idz/230406/archiva/230305/norma/matlab/.keep
diff --git a/benchmark/rescue_idz/archiva/230305/norma/setup.m b/benchmark/rescue_idz/230406/archiva/230305/norma/setup.m
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/norma/setup.m
rename to benchmark/rescue_idz/230406/archiva/230305/norma/setup.m
diff --git a/benchmark/rescue_idz/archiva/230305/setup.m b/benchmark/rescue_idz/230406/archiva/230305/setup.m
similarity index 100%
rename from benchmark/rescue_idz/archiva/230305/setup.m
rename to benchmark/rescue_idz/230406/archiva/230305/setup.m
diff --git a/benchmark/rescue_idz/fortran/README.txt b/benchmark/rescue_idz/230406/fortran/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/README.txt
rename to benchmark/rescue_idz/230406/fortran/README.txt
diff --git a/benchmark/rescue_idz/fortran/bobyqa/.fortls b/benchmark/rescue_idz/230406/fortran/bobyqa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/.fortls
rename to benchmark/rescue_idz/230406/fortran/bobyqa/.fortls
diff --git a/benchmark/rescue_idz/fortran/bobyqa/README.txt b/benchmark/rescue_idz/230406/fortran/bobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/README.txt
rename to benchmark/rescue_idz/230406/fortran/bobyqa/README.txt
diff --git a/benchmark/rescue_idz/fortran/bobyqa/bobyqa.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/bobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/bobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/bobyqb.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/bobyqb.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/bobyqb.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/bobyqb.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/bobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/bobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/bobyqa/geometry.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/geometry.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/geometry.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/geometry.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/initialize.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/initialize.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/initialize.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/rescue.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/rescue.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/rescue.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/rescue.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/trustregion.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/trustregion.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/trustregion.f90
diff --git a/benchmark/rescue_idz/fortran/bobyqa/update.f90 b/benchmark/rescue_idz/230406/fortran/bobyqa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/bobyqa/update.f90
rename to benchmark/rescue_idz/230406/fortran/bobyqa/update.f90
diff --git a/benchmark/rescue_idz/fortran/classical/README.txt b/benchmark/rescue_idz/230406/fortran/classical/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/README.txt
rename to benchmark/rescue_idz/230406/fortran/classical/README.txt
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/altmov.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/altmov.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/altmov.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/altmov.f
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/bobyqa.f90 b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/bobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/bobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/bobyqb.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/bobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/bobyqb.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/bobyqb.f
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/prelim.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/prelim.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/prelim.f
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/rescue.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/rescue.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/rescue.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/rescue.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/trsbox.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/trsbox.f
similarity index 99%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/trsbox.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/trsbox.f
index a461510378..09b354ea51 100644
--- a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/trsbox.f
+++ b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/trsbox.f
@@ -105,7 +105,7 @@ SUBROUTINE TRSBOX (N,NPT,XPT,XOPT,GOPT,HQ,PQ,SL,SU,DELTA,
C
C Multiply the search direction by the second derivative matrix of Q and
C calculate some scalars for the choice of steplength. Then set BLEN to
-C the length of the the step to the trust region boundary and STPLEN to
+C the length of the step to the trust region boundary and STPLEN to
C the steplength, ignoring the simple bounds.
C
GOTO 210
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/update.f b/benchmark/rescue_idz/230406/fortran/classical/bobyqa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/update.f
rename to benchmark/rescue_idz/230406/fortran/classical/bobyqa/update.f
diff --git a/benchmark/rescue_idz/fortran/classical/cobyla/cobyla.f90 b/benchmark/rescue_idz/230406/fortran/classical/cobyla/cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/cobyla/cobyla.f90
rename to benchmark/rescue_idz/230406/fortran/classical/cobyla/cobyla.f90
diff --git a/benchmark/rescue_idz/fortran/classical/cobyla/cobylb.f b/benchmark/rescue_idz/230406/fortran/classical/cobyla/cobylb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/cobyla/cobylb.f
rename to benchmark/rescue_idz/230406/fortran/classical/cobyla/cobylb.f
diff --git a/benchmark/rescue_idz/fortran/classical/cobyla/ffiles.txt b/benchmark/rescue_idz/230406/fortran/classical/cobyla/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/cobyla/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/classical/cobyla/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/classical/cobyla/trstlp.f b/benchmark/rescue_idz/230406/fortran/classical/cobyla/trstlp.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/cobyla/trstlp.f
rename to benchmark/rescue_idz/230406/fortran/classical/cobyla/trstlp.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/classical/lincoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/getact.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/getact.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/getact.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/getact.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/lincoa.f90 b/benchmark/rescue_idz/230406/fortran/classical/lincoa/lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/lincoa.f90
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/lincoa.f90
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/lincob.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/lincob.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/lincob.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/lincob.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/prelim.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/prelim.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/prelim.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/qmstep.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/qmstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/qmstep.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/qmstep.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/trstep.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/trstep.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/trstep.f
diff --git a/benchmark/rescue_idz/fortran/classical/lincoa/update.f b/benchmark/rescue_idz/230406/fortran/classical/lincoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/lincoa/update.f
rename to benchmark/rescue_idz/230406/fortran/classical/lincoa/update.f
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/bigden.f b/benchmark/rescue_idz/230406/fortran/classical/newuoa/bigden.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/bigden.f
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/bigden.f
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/biglag.f b/benchmark/rescue_idz/230406/fortran/classical/newuoa/biglag.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/biglag.f
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/biglag.f
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/classical/newuoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/newuoa.f90 b/benchmark/rescue_idz/230406/fortran/classical/newuoa/newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/newuoa.f90
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/newuoa.f90
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/newuob.f b/benchmark/rescue_idz/230406/fortran/classical/newuoa/newuob.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/newuob.f
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/newuob.f
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/trsapp.f b/benchmark/rescue_idz/230406/fortran/classical/newuoa/trsapp.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/trsapp.f
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/trsapp.f
diff --git a/benchmark/rescue_idz/fortran/classical/newuoa/update.f b/benchmark/rescue_idz/230406/fortran/classical/newuoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/newuoa/update.f
rename to benchmark/rescue_idz/230406/fortran/classical/newuoa/update.f
diff --git a/benchmark/rescue_idz/fortran/classical/uobyqa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/classical/uobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/uobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/classical/uobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/classical/uobyqa/lagmax.f b/benchmark/rescue_idz/230406/fortran/classical/uobyqa/lagmax.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/uobyqa/lagmax.f
rename to benchmark/rescue_idz/230406/fortran/classical/uobyqa/lagmax.f
diff --git a/benchmark/rescue_idz/fortran/classical/uobyqa/trstep.f b/benchmark/rescue_idz/230406/fortran/classical/uobyqa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/uobyqa/trstep.f
rename to benchmark/rescue_idz/230406/fortran/classical/uobyqa/trstep.f
diff --git a/benchmark/rescue_idz/fortran/classical/uobyqa/uobyqa.f90 b/benchmark/rescue_idz/230406/fortran/classical/uobyqa/uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/uobyqa/uobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/classical/uobyqa/uobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/classical/uobyqa/uobyqb.f b/benchmark/rescue_idz/230406/fortran/classical/uobyqa/uobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/uobyqa/uobyqb.f
rename to benchmark/rescue_idz/230406/fortran/classical/uobyqa/uobyqb.f
diff --git a/benchmark/rescue_idz/fortran/cobyla/.fortls b/benchmark/rescue_idz/230406/fortran/cobyla/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/.fortls
rename to benchmark/rescue_idz/230406/fortran/cobyla/.fortls
diff --git a/benchmark/rescue_idz/fortran/cobyla/README.txt b/benchmark/rescue_idz/230406/fortran/cobyla/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/README.txt
rename to benchmark/rescue_idz/230406/fortran/cobyla/README.txt
diff --git a/benchmark/rescue_idz/fortran/cobyla/cobyla.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/cobyla.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/cobyla.f90
diff --git a/benchmark/rescue_idz/fortran/cobyla/cobylb.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/cobylb.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/cobylb.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/cobylb.f90
diff --git a/benchmark/rescue_idz/fortran/cobyla/ffiles.txt b/benchmark/rescue_idz/230406/fortran/cobyla/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/cobyla/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/cobyla/geometry.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/geometry.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/geometry.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/geometry.f90
diff --git a/benchmark/rescue_idz/fortran/cobyla/initialize.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/initialize.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/initialize.f90
diff --git a/benchmark/rescue_idz/fortran/cobyla/trustregion.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/trustregion.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/trustregion.f90
diff --git a/benchmark/rescue_idz/fortran/cobyla/update.f90 b/benchmark/rescue_idz/230406/fortran/cobyla/update.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/cobyla/update.f90
rename to benchmark/rescue_idz/230406/fortran/cobyla/update.f90
diff --git a/benchmark/rescue_idz/fortran/common/.fortls b/benchmark/rescue_idz/230406/fortran/common/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/.fortls
rename to benchmark/rescue_idz/230406/fortran/common/.fortls
diff --git a/benchmark/rescue_idz/fortran/common/README.txt b/benchmark/rescue_idz/230406/fortran/common/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/README.txt
rename to benchmark/rescue_idz/230406/fortran/common/README.txt
diff --git a/benchmark/rescue_idz/fortran/common/checkexit.f90 b/benchmark/rescue_idz/230406/fortran/common/checkexit.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/checkexit.f90
rename to benchmark/rescue_idz/230406/fortran/common/checkexit.f90
diff --git a/benchmark/rescue_idz/fortran/common/consts.F90 b/benchmark/rescue_idz/230406/fortran/common/consts.F90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/consts.F90
rename to benchmark/rescue_idz/230406/fortran/common/consts.F90
diff --git a/benchmark/rescue_idz/fortran/common/debug.F90 b/benchmark/rescue_idz/230406/fortran/common/debug.F90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/debug.F90
rename to benchmark/rescue_idz/230406/fortran/common/debug.F90
diff --git a/benchmark/rescue_idz/fortran/common/evaluate.f90 b/benchmark/rescue_idz/230406/fortran/common/evaluate.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/evaluate.f90
rename to benchmark/rescue_idz/230406/fortran/common/evaluate.f90
diff --git a/benchmark/rescue_idz/fortran/common/ffiles.txt b/benchmark/rescue_idz/230406/fortran/common/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/common/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/common/flint b/benchmark/rescue_idz/230406/fortran/common/flint
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/flint
rename to benchmark/rescue_idz/230406/fortran/common/flint
diff --git a/benchmark/rescue_idz/fortran/common/history.f90 b/benchmark/rescue_idz/230406/fortran/common/history.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/history.f90
rename to benchmark/rescue_idz/230406/fortran/common/history.f90
diff --git a/benchmark/rescue_idz/fortran/common/inf.F90 b/benchmark/rescue_idz/230406/fortran/common/inf.F90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/inf.F90
rename to benchmark/rescue_idz/230406/fortran/common/inf.F90
diff --git a/benchmark/rescue_idz/fortran/common/infnan.F90 b/benchmark/rescue_idz/230406/fortran/common/infnan.F90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/infnan.F90
rename to benchmark/rescue_idz/230406/fortran/common/infnan.F90
diff --git a/benchmark/rescue_idz/fortran/common/infos.f90 b/benchmark/rescue_idz/230406/fortran/common/infos.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/infos.f90
rename to benchmark/rescue_idz/230406/fortran/common/infos.f90
diff --git a/benchmark/rescue_idz/fortran/common/linalg.f90 b/benchmark/rescue_idz/230406/fortran/common/linalg.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/linalg.f90
rename to benchmark/rescue_idz/230406/fortran/common/linalg.f90
diff --git a/benchmark/rescue_idz/fortran/common/memory.F90 b/benchmark/rescue_idz/230406/fortran/common/memory.F90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/memory.F90
rename to benchmark/rescue_idz/230406/fortran/common/memory.F90
diff --git a/benchmark/rescue_idz/fortran/common/mlint b/benchmark/rescue_idz/230406/fortran/common/mlint
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/mlint
rename to benchmark/rescue_idz/230406/fortran/common/mlint
diff --git a/benchmark/rescue_idz/fortran/common/old/comp b/benchmark/rescue_idz/230406/fortran/common/old/comp
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/old/comp
rename to benchmark/rescue_idz/230406/fortran/common/old/comp
diff --git a/benchmark/rescue_idz/fortran/common/old/ieee_4dev.f90 b/benchmark/rescue_idz/230406/fortran/common/old/ieee_4dev.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/old/ieee_4dev.f90
rename to benchmark/rescue_idz/230406/fortran/common/old/ieee_4dev.f90
diff --git a/benchmark/rescue_idz/fortran/common/output.f90 b/benchmark/rescue_idz/230406/fortran/common/output.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/output.f90
rename to benchmark/rescue_idz/230406/fortran/common/output.f90
diff --git a/benchmark/rescue_idz/fortran/common/pintrf.f90 b/benchmark/rescue_idz/230406/fortran/common/pintrf.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/pintrf.f90
rename to benchmark/rescue_idz/230406/fortran/common/pintrf.f90
diff --git a/benchmark/rescue_idz/fortran/common/powalg.f90 b/benchmark/rescue_idz/230406/fortran/common/powalg.f90
similarity index 66%
rename from benchmark/rescue_idz/fortran/common/powalg.f90
rename to benchmark/rescue_idz/230406/fortran/common/powalg.f90
index c941094fec..64b6d21822 100644
--- a/benchmark/rescue_idz/fortran/common/powalg.f90
+++ b/benchmark/rescue_idz/230406/fortran/common/powalg.f90
@@ -8,7 +8,7 @@ module powalg_mod
! - QUADRATIC: procedures concerning quadratic polynomials represented by [GQ, PQ, HQ] so that
! Q(Y) = + 0.5*,
! HESSIAN consists of an explicit part HQ and an implicit part PQ in Powell's way:
-! HESSIAN = HQ + sum_K=1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
+! HESSIAN = HQ+sum_K = 1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
! - LAGINT: procedures concerning quadratic LAGrange INTerpolation.
!
! Zaikun (20230321): In a test on 20230321 on problems of at most 200 variables, it affects (not
@@ -21,7 +21,7 @@ module powalg_mod
!
! Started: July 2020
!
-! Last Modified: Wednesday, April 12, 2023 PM11:41:22
+! Last Modified: Mon 04 Aug 2025 07:04:11 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -29,17 +29,17 @@ module powalg_mod
!--------------------------------------------------------------------------------------------------!
! QR:
-public :: qradd, qrexc
+public:: qradd, qrexc
!--------------------------------------------------------------------------------------------------!
! QUADRATIC:
-public :: quadinc, errquad
-public :: hess_mul
+public:: quadinc, errquad
+public:: hess_mul
!--------------------------------------------------------------------------------------------------!
! LAGINT (quadratic LAGrange INTerpolation):
-public :: omega_col, omega_mul, omega_inprod
-public :: updateh, errh
-public :: calvlag, calbeta, calden
-public :: setij
+public:: omega_col, omega_mul, omega_inprod
+public:: updateh, errh
+public:: calvlag, calbeta, calden
+public:: setij
!--------------------------------------------------------------------------------------------------!
interface qradd
@@ -76,39 +76,39 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
! 2. The subroutine changes only Q(:, NSAVE+1:M) (NSAVE is the original value of N)
! and R(:, N) (N takes the updated value).
! 3. Indeed, when C is in range(A), Powell wrote in comments that "set IOUT to the index of the
-! constraint (here, column of A --- Zaikun) to be deleted, but branch if no suitable index can be
+! constraint (here, column of A--- Zaikun) to be deleted, but branch if no suitable index can be
! found". The idea is to replace a column of A by C so that the new matrix still has full rank
! (such a column must exist unless C = 0). But his code essentially sets IOUT = N always. Maybe he
! found this worked well enough in practice. Meanwhile, Powell's code includes a snippet that can
! never be reached, which was probably intended to deal with the case with IOUT =/= N.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, planerot, hypotenuse, isorth, isminor, trueloc
+use, non_intrinsic:: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : matprod, inprod, norm, planerot, hypotenuse, isorth, isminor, trueloc
implicit none
! Inputs
-real(RP), intent(in) :: c(:) ! C(M)
+real(RP), intent(in):: c(:) ! C(M)
! In-outputs
-integer(IK), intent(inout) :: n
-real(RP), intent(inout) :: Q(:, :) ! Q(M, M)
-real(RP), intent(inout) :: Rdiag(:) ! MIN(M, N+1) <= SIZE(Rdiag) <= M
+integer(IK), intent(inout):: n
+real(RP), intent(inout):: Q(:, :) ! Q(M, M)
+real(RP), intent(inout):: Rdiag(:) ! MIN(M, N+1) <= SIZE(Rdiag) <= M
! Local variables
-character(len=*), parameter :: srname = 'QRADD_RDIAG'
-integer(IK) :: k
-integer(IK) :: m
-integer(IK) :: nsave
-real(RP) :: cq(size(Q, 2))
-real(RP) :: cqa(size(Q, 2))
-real(RP) :: G(2, 2)
+character(len=*), parameter:: srname = 'QRADD_RDIAG'
+integer(IK):: k
+integer(IK):: m
+integer(IK):: nsave
+real(RP):: cq(size(Q, 2))
+real(RP):: cqa(size(Q, 2))
+real(RP):: G(2, 2)
!------------------------------------------------------------!
-real(RP) :: Qsave(size(Q, 1), n) ! Debugging only
-real(RP) :: Rdsave(n) ! Debugging only
-real(RP) :: tol ! Debugging only
+real(RP):: Qsave(size(Q, 1), n) ! Debugging only
+real(RP):: Rdsave(n) ! Debugging only
+real(RP):: tol ! Debugging only
!------------------------------------------------------------!
! Sizes
@@ -118,9 +118,9 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
if (DEBUGGING) then
call assert(n >= 0 .and. n <= m, '0 <= N <= M', srname) ! N = 0 is possible.
call assert(size(c) == m, 'SIZE(C) == M', srname)
- call assert(size(Rdiag) >= min(m, n + 1_IK) .and. size(Rdiag) <= m, 'MIN(M, N+1) <= SIZE(Rdiag) <= M', srname)
+ call assert(size(Rdiag) >= min(m, n+1_IK) .and. size(Rdiag) <= m, 'MIN(M, N+1) <= SIZE(Rdiag) <= M', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) == m, 'SIZE(Q) == [M, M]', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E12_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E12_RP*EPS*real(m+1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
Qsave = Q(:, 1:n) ! For debugging only
Rdsave = Rdiag(1:n) ! For debugging only
@@ -141,23 +141,23 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
! Update Q so that the columns of Q(:, N+2:M) are orthogonal to C. This is done by applying a 2D
! Givens rotation to Q(:, [K, K+1]) from the right to zero C'*Q(:, K+1) out for K = N+1, ..., M-1.
! Nothing will be done if N >= M-1.
-do k = m - 1_IK, n + 1_IK, -1
- if (abs(cq(k + 1)) > 0) then
+do k = m-1_IK, n+1_IK, -1
+ if (abs(cq(k+1)) > 0) then
! Powell wrote CQ(K+1) /= 0 instead of ABS(CQ(K+1)) > 0. The two differ if CQ(K+1) is NaN.
! If we apply the rotation below when CQ(K+1) = 0, then CQ(K) will get updated to |CQ(K)|.
- G = planerot(cq([k, k + 1_IK]))
- Q(:, [k, k + 1_IK]) = matprod(Q(:, [k, k + 1_IK]), transpose(G))
- cq(k) = hypotenuse(cq(k), cq(k + 1))
- !cq(k) = sqrt(cq(k)**2 + cq(k + 1)**2)
+ G = planerot(cq([k, k+1_IK]))
+ Q(:, [k, k+1_IK]) = matprod(Q(:, [k, k+1_IK]), transpose(G))
+ cq(k) = hypotenuse(cq(k), cq(k+1))
+ !cq(k) = sqrt(cq(k)**2+cq(k+1)**2)
end if
end do
! Augment N by 1 if C is not in range(A).
-! The two IFs cannot be merged as Fortran may evaluate CQ(N+1) even if N>=M, leading to a SEGFAULT.
+! The two IFs cannot be merged as Fortran may evaluate CQ(N+1) even if N >= M, leading to a SEGFAULT.
if (n < m) then
! Powell's condition for the following IF: CQ(N+1) /= 0.
- if (abs(cq(n + 1)) > EPS**2 .and. .not. isminor(cq(n + 1), cqa(n + 1))) then
- n = n + 1_IK
+ if (abs(cq(n+1)) > EPS**2 .and. .not. isminor(cq(n+1), cqa(n+1))) then
+ n = n+1_IK
end if
end if
@@ -172,19 +172,19 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
! Postconditions
if (DEBUGGING) then
- call assert(n >= nsave .and. n <= min(nsave + 1_IK, m), 'NSAV <= N <= MIN(NSAV + 1, M)', srname)
+ call assert(n >= nsave .and. n <= min(nsave+1_IK, m), 'NSAV <= N <= MIN(NSAV+1, M)', srname)
call assert(size(Rdiag) >= n .and. size(Rdiag) <= m, 'N <= SIZE(Rdiag) <= M', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) == m, 'SIZE(Q) == [M, M]', srname)
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
call assert(all(abs(Q(:, 1:nsave) - Qsave(:, 1:nsave)) <= 0), 'Q(:, 1:NSAVE) is unchanged', srname)
- call assert(all(abs(Rdiag(1:n - 1) - Rdsave(1:n - 1)) <= 0), 'Rdiag(1:N-1) is unchanged', srname)
+ call assert(all(abs(Rdiag(1:n-1) - Rdsave(1:n-1)) <= 0), 'Rdiag(1:N-1) is unchanged', srname)
if (n < m .and. is_finite(norm(c))) then
- call assert(norm(matprod(c, Q(:, n + 1:m))) <= max(tol, tol * norm(c)), 'C^T*Q(:, N+1:M) == 0', srname)
+ call assert(norm(matprod(c, Q(:, n+1:m))) <= max(tol, tol*norm(c)), 'C^T*Q(:, N+1:M) == 0', srname)
end if
if (n >= 1) then ! N = 0 is possible.
- call assert(abs(inprod(c, Q(:, n)) - Rdiag(n)) <= max(tol, tol * inprod(abs(c), abs(Q(:, n)))) &
+ call assert(abs(inprod(c, Q(:, n)) - Rdiag(n)) <= max(tol, tol*inprod(abs(c), abs(Q(:, n)))) &
& .or. .not. is_finite(Rdiag(n)), 'C^T*Q(:, N) == Rdiag(N)', srname)
end if
end if
@@ -197,47 +197,47 @@ subroutine qradd_Rfull(c, Q, R, n) ! Used in LINCOA
! is appended to this matrix A as the LAST column.
! N.B.:
! 0. Different from QRADD_RDIAG, it seems that QRADD_RFULL does not try to maintain that A is of
-! full column rank after the update; QRADD_RFULL always append C to A, and always increase N by 1,
+! full column rank after the update; QRADD_RFULL always append C to A, and always increase N by 1,
! but QRADD_RDIAG does so only if C is not in the column space of A.
! 1. At entry, Q is a MxM orthonormal matrix, and R is a MxL upper triangular matrix with N < L <= M.
! 2. The subroutine changes only Q(:, N+1:M) and R(:, N+1) with N taking the original value.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod, planerot, isorth, istriu
+use, non_intrinsic:: consts_mod, only : RP, IK, EPS, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod, planerot, isorth, istriu
implicit none
! Inputs
-real(RP), intent(in) :: c(:) ! C(M)
+real(RP), intent(in):: c(:) ! C(M)
! In-outputs
-integer(IK), intent(inout) :: n
-real(RP), intent(inout) :: Q(:, :) ! Q(M, M)
-real(RP), intent(inout) :: R(:, :) ! R(M, :), N+1 <= SIZE(R, 2) <= M
+integer(IK), intent(inout):: n
+real(RP), intent(inout):: Q(:, :) ! Q(M, M)
+real(RP), intent(inout):: R(:, :) ! R(M, :), N+1 <= SIZE(R, 2) <= M
! Local variables
-character(len=*), parameter :: srname = 'QRADD_RFULL'
-integer(IK) :: k
-integer(IK) :: m
-real(RP) :: cq(size(Q, 2))
-real(RP) :: G(2, 2)
+character(len=*), parameter:: srname = 'QRADD_RFULL'
+integer(IK):: k
+integer(IK):: m
+real(RP):: cq(size(Q, 2))
+real(RP):: G(2, 2)
!------------------------------------------------------------!
-real(RP) :: Anew(size(Q, 1), n + 1) ! Debugging only
-real(RP) :: Qsave(size(Q, 1), n) ! Debugging only
-real(RP) :: Rsave(size(R, 1), n) ! Debugging only
-real(RP) :: tol ! Debugging only
+real(RP):: Anew(size(Q, 1), n+1) ! Debugging only
+real(RP):: Qsave(size(Q, 1), n) ! Debugging only
+real(RP):: Rsave(size(R, 1), n) ! Debugging only
+real(RP):: tol ! Debugging only
!------------------------------------------------------------!
! Sizes
m = int(size(Q, 1), kind(m))
if (DEBUGGING) then
- call assert(n >= 0 .and. n <= m - 1, '0 <= N <= M - 1', srname)
+ call assert(n >= 0 .and. n <= m-1, '0 <= N <= M-1', srname)
call assert(size(c) == m, 'SIZE(C) == M', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) == m, 'SIZE(Q) = [M, M]', srname)
call assert(size(Q, 2) == size(R, 1), 'SIZE(Q, 2) == SIZE(R, 1)', srname)
- call assert(size(R, 2) >= n + 1 .and. size(R, 2) <= m, 'N+1 <= SIZE(R, 2) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ call assert(size(R, 2) >= n+1 .and. size(R, 2) <= m, 'N+1 <= SIZE(R, 2) <= M', srname)
+ tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP*EPS*real(m+1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(istriu(R), 'R is upper triangular', srname)
Anew = reshape([matprod(Q, R(:, 1:n)), c], shape(Anew))
@@ -250,23 +250,23 @@ subroutine qradd_Rfull(c, Q, R, n) ! Used in LINCOA
! Update Q so that the columns of Q(:, N+2:M) are orthogonal to C. This is done by applying a 2D
! Givens rotation to Q(:, [K, K+1]) from the right to zero C'*Q(:, K+1) out for K = N+1, ..., M-1.
! Nothing will be done if N >= M-1.
-do k = m - 1_IK, n + 1_IK, -1
- if (abs(cq(k + 1)) > 0) then ! Powell: IF (ABS(CQ(K + 1)) > 1.0D-20 * ABS(CQ(K))) THEN
- G = planerot(cq([k, k + 1_IK])) ! G = [c, -s; s, c]. It improves the performance of LINCOA
- Q(:, [k, k + 1_IK]) = matprod(Q(:, [k, k + 1_IK]), transpose(G))
- cq(k) = sqrt(cq(k)**2 + cq(k + 1)**2)
+do k = m-1_IK, n+1_IK, -1
+ if (abs(cq(k+1)) > 0) then ! Powell: IF (ABS(CQ(K+1)) > 1.0D-20*ABS(CQ(K))) THEN
+ G = planerot(cq([k, k+1_IK])) ! G = [c, -s; s, c]. It improves the performance of LINCOA
+ Q(:, [k, k+1_IK]) = matprod(Q(:, [k, k+1_IK]), transpose(G))
+ cq(k) = sqrt(cq(k)**2+cq(k+1)**2)
end if
end do
-R(1:n, n + 1) = matprod(c, Q(:, 1:n))
+R(1:n, n+1) = matprod(c, Q(:, 1:n))
! Maintain the positiveness of the diagonal entries of R.
-if (cq(n + 1) < 0) then
- Q(:, n + 1) = -Q(:, n + 1)
+if (cq(n+1) < 0) then
+ Q(:, n+1) = -Q(:, n+1)
end if
-R(n + 1, n + 1) = abs(cq(n + 1))
+R(n+1, n+1) = abs(cq(n+1))
-n = n + 1_IK
+n = n+1_IK
if (DEBUGGING) then
call assert(n >= 1 .and. n <= m, '1 <= N <= M', srname)
@@ -276,14 +276,14 @@ subroutine qradd_Rfull(c, Q, R, n) ! Used in LINCOA
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(istriu(R), 'R is upper triangular', srname)
- ! !call assert(.not. any(abs(Q(:, 1:n - 1) - Qsave(:, 1:n - 1)) > 0), 'Q(:, 1:N-1) is unchanged', srname)
- ! !call assert(.not. any(abs(R(:, 1:n - 1) - Rsave(:, 1:n - 1)) > 0), 'R(:, 1:N-1) is unchanged', srname)
+ ! !call assert(.not. any(abs(Q(:, 1:n-1) - Qsave(:, 1:n-1)) > 0), 'Q(:, 1:N-1) is unchanged', srname)
+ ! !call assert(.not. any(abs(R(:, 1:n-1) - Rsave(:, 1:n-1)) > 0), 'R(:, 1:N-1) is unchanged', srname)
! If we can ensure that Q and R do not contain NaN or Inf, use the following lines instead of the last two.
- call assert(all(abs(Q(:, 1:n - 1) - Qsave(:, 1:n - 1)) <= 0), 'Q(:, 1:N-1) is unchanged', srname)
- call assert(all(abs(R(:, 1:n - 1) - Rsave(:, 1:n - 1)) <= 0), 'R(:, 1:N-1) is unchanged', srname)
+ call assert(all(abs(Q(:, 1:n-1) - Qsave(:, 1:n-1)) <= 0), 'Q(:, 1:N-1) is unchanged', srname)
+ call assert(all(abs(R(:, 1:n-1) - Rsave(:, 1:n-1)) <= 0), 'R(:, 1:N-1) is unchanged', srname)
! The following test may fail.
- call assert(all(abs(Anew - matprod(Q, R(:, 1:n))) <= max(tol, tol * maxval(abs(Anew)))), 'Anew = Q*R', srname)
+ call assert(all(abs(Anew-matprod(Q, R(:, 1:n))) <= max(tol, tol*maxval(abs(Anew)))), 'Anew = Q*R', srname)
end if
end subroutine qradd_Rfull
@@ -293,38 +293,38 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
! This subroutine updates the QR factorization for an MxN matrix A = Q*R so that the updated Q and
! R form a QR factorization of [A_1, ..., A_{I-1}, A_{I+1}, ..., A_N, A_I], which is the matrix
! obtained by rearranging columns [I, I+1, ..., N] of A to [I+1, ..., N, I]. Here, A is ASSUMED TO
-! BE OF FULL COLUMN RANK, Q is a matrix whose columns are orthogonal, and R, which is not present,
+! BE OF FULL COLUMN RANK, Q is a matrix whose columns are orthogonal, and R, which is not present,
! is an upper triangular matrix whose diagonal entries are nonzero. Q and R need not to be square.
! N.B.:
! 0. Instead of R, this subroutine updates RDIAG, which is diag(R), the size being N.
! 1. With L = SIZE(Q, 2) = SIZE(R, 1), we have M >= L >= N. Most often, L = M or N.
! 2. The subroutine changes only Q(:, I:N) and RDIAG(I:N).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, planerot, isorth, istriu, diag
+use, non_intrinsic:: consts_mod, only : RP, IK, EPS, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod, inprod, norm, planerot, isorth, istriu, diag
implicit none
! Inputs
-real(RP), intent(in) :: A(:, :) ! A(M, N)
+real(RP), intent(in):: A(:, :) ! A(M, N)
! In-outputs
-real(RP), intent(inout) :: Q(:, :) ! Q(M, :), N <= SIZE(Q, 2) <= M
-real(RP), intent(inout) :: Rdiag(:) ! Rdiag(N)
-integer(IK), intent(in) :: i
+real(RP), intent(inout):: Q(:, :) ! Q(M, :), N <= SIZE(Q, 2) <= M
+real(RP), intent(inout):: Rdiag(:) ! Rdiag(N)
+integer(IK), intent(in):: i
! Local variables
-character(len=*), parameter :: srname = 'QREXC_RDIAG'
-integer(IK) :: k
-integer(IK) :: m
-integer(IK) :: n
-real(RP) :: G(2, 2)
+character(len=*), parameter:: srname = 'QREXC_RDIAG'
+integer(IK):: k
+integer(IK):: m
+integer(IK):: n
+real(RP):: G(2, 2)
!------------------------------------------------------------!
-real(RP) :: Anew(size(A, 1), size(A, 2)) ! Debugging only
-real(RP) :: Qsave(size(Q, 1), size(Q, 2)) ! Debugging only
-real(RP) :: QtAnew(size(Q, 2), size(A, 2)) ! Debugging only
-real(RP) :: Rdsave(i) ! Debugging only
-real(RP) :: tol ! Debugging only
+real(RP):: Anew(size(A, 1), size(A, 2)) ! Debugging only
+real(RP):: Qsave(size(Q, 1), size(Q, 2)) ! Debugging only
+real(RP):: QtAnew(size(Q, 2), size(A, 2)) ! Debugging only
+real(RP):: Rdsave(i) ! Debugging only
+real(RP):: tol ! Debugging only
!------------------------------------------------------------!
! Sizes
@@ -338,10 +338,10 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
call assert(size(Rdiag) == n, 'SIZE(Rdiag) == N', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) >= n .and. size(Q, 2) <= m, &
& 'SIZE(Q, 1) == M, N <= SIZE(Q, 2) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP*EPS*real(m+1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
Qsave = Q ! For debugging only.
- Rdsave = Rdiag(1:i) ! For debugging only.
+ Rdsave = Rdiag(1:i) ! For debugging only.
end if
!====================!
@@ -354,7 +354,7 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
end if
! Let R be the upper triangular matrix in the QR factorization, namely R = Q^T*A.
-! For each K, find the Givens rotation G with G*R([K, K+1], :) = [HYPT, 0], and update Q(:, [K,K+1])
+! For each K, find the Givens rotation G with G*R([K, K+1], :) = [HYPT, 0], and update Q(:, [K, K+1])
! to Q(:, [K, K+1])*G^T. Then R = Q^T*A is an upper triangular matrix as long as A(:, [K, K+1]) is
! updated to A(:, [K+1, K]). Indeed, this new upper triangular matrix can be obtained by first
! updating R([K, K+1], :) to G*R([K, K+1], :) and then exchanging its columns K and K+1; at the same
@@ -364,14 +364,14 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
! Powell's code, however, is slightly different: before everything, he first exchanged columns K and
! K+1 of Q (as well as rows K and K+1 of R). This makes sure that the entires of the update RDIAG
! are all positive if it is the case for the original RDIAG.
-do k = i, n - 1_IK
- !hypt = hypotenuse(Rdiag(k + 1), inprod(Q(:, k), A(:, k + 1)))
- !hypt = sqrt(Rdiag(k + 1)**2 + inprod(Q(:, k), A(:, k + 1))**2)
- G = planerot([Rdiag(k + 1), inprod(Q(:, k), A(:, k + 1))])
- Q(:, [k, k + 1_IK]) = matprod(Q(:, [k + 1_IK, k]), transpose(G))
+do k = i, n-1_IK
+ !hypt = hypotenuse(Rdiag(k+1), inprod(Q(:, k), A(:, k+1)))
+ !hypt = sqrt(Rdiag(k+1)**2+inprod(Q(:, k), A(:, k+1))**2)
+ G = planerot([Rdiag(k+1), inprod(Q(:, k), A(:, k+1))])
+ Q(:, [k, k+1_IK]) = matprod(Q(:, [k+1_IK, k]), transpose(G))
! Powell's code updates RDIAG in the following way.
- ! !RDIAG([K, K + 1_IK]) = [HYPT, (RDIAG(K + 1) / HYPT) * RDIAG(K)]
+ ! !RDIAG([K, K+1_IK]) = [HYPT, (RDIAG(K+1) / HYPT) * RDIAG(K)]
! Note that RDIAG(N) inherits all rounding in RDIAG(I:N-1) and Q(:, I:N-1) and hence contain
! significant errors. Thus we may modify Powell's code to set only RDIAG(K) = HYPT here and then
! calculate RDIAG(N) by an inner product after the loop. Nevertheless, we simply calculate RDIAG
@@ -379,7 +379,7 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
end do
! Calculate RDIAG(I:N) from scratch.
-Rdiag(i:n - 1) = [(inprod(Q(:, k), A(:, k + 1)), k=i, n - 1_IK)]
+Rdiag(i:n-1) = [(inprod(Q(:, k), A(:, k+1)), k = i, n-1_IK)]
!!MATLAB: Rdiag(i:n-1) = sum(Q(:, i:n-1) .* A(:, i+1:n), 1); % Row vector
Rdiag(n) = inprod(Q(:, n), A(:, i)) ! Calculate RDIAG(N) from scratch. See the comments above.
@@ -395,16 +395,16 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
Qsave(:, i:n) = Q(:, i:n)
- call assert(all(abs(Q - Qsave) <= 0), 'Q is unchanged except Q(:, I:N)', srname)
- call assert(all(abs(Rdiag(1:i - 1) - Rdsave(1:i - 1)) <= 0), 'Rdiag(1:I-1) is unchanged', srname)
+ call assert(all(abs(Q-Qsave) <= 0), 'Q is unchanged except Q(:, I:N)', srname)
+ call assert(all(abs(Rdiag(1:i-1) - Rdsave(1:i-1)) <= 0), 'Rdiag(1:I-1) is unchanged', srname)
- Anew = reshape([A(:, 1:i - 1), A(:, i + 1:n), A(:, i)], shape(Anew))
+ Anew = reshape([A(:, 1:i-1), A(:, i+1:n), A(:, i)], shape(Anew))
QtAnew = matprod(transpose(Q), Anew)
call assert(istriu(QtAnew, tol), 'Q^T*Anew is upper triangular', srname)
! The following test may fail if RDIAG is not calculated from scratch.
- call assert(norm(diag(QtAnew) - Rdiag) <= max(tol, tol * norm([(inprod(abs(Q(:, k)), &
- & abs(Anew(:, k))), k=1, n)])), 'Rdiag == diag(Q^T*Anew)', srname)
- !!MATLAB: norm(diag(QtAnew) - Rdiag) <= max(tol, tol * norm(sum(abs(Q(:, 1:n)) .* abs(Anew), 1)))
+ call assert(norm(diag(QtAnew) - Rdiag) <= max(tol, tol*norm([(inprod(abs(Q(:, k)), &
+ & abs(Anew(:, k))), k = 1, n)])), 'Rdiag == diag(Q^T*Anew)', srname)
+ !!MATLAB: norm(diag(QtAnew) - Rdiag) <= max(tol, tol*norm(sum(abs(Q(:, 1:n)) .* abs(Anew), 1)))
end if
end subroutine qrexc_Rdiag
@@ -420,30 +420,30 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
! 1. With L = SIZE(Q, 2) = SIZE(R, 1), we have M >= L >= N. Most often, L = M or N.
! 2. The subroutine changes only Q(:, I:N) and R(:, I:N).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod, planerot, isorth, istriu
+use, non_intrinsic:: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod, planerot, isorth, istriu
implicit none
! Inputs
-integer(IK), intent(in) :: i
+integer(IK), intent(in):: i
! In-outputs
-real(RP), intent(inout) :: Q(:, :) ! Q(M, :), SIZE(Q, 2) <= M
-real(RP), intent(inout) :: R(:, :) ! R(:, N), SIZE(R, 1) >= N
+real(RP), intent(inout):: Q(:, :) ! Q(M, :), SIZE(Q, 2) <= M
+real(RP), intent(inout):: R(:, :) ! R(:, N), SIZE(R, 1) >= N
! Local variables
-character(len=*), parameter :: srname = 'QREXC_RFULL'
-integer(IK) :: k
-integer(IK) :: m
-integer(IK) :: n
-real(RP) :: G(2, 2)
-real(RP) :: hypt
+character(len=*), parameter:: srname = 'QREXC_RFULL'
+integer(IK):: k
+integer(IK):: m
+integer(IK):: n
+real(RP):: G(2, 2)
+real(RP):: hypt
!------------------------------------------------------------!
-real(RP) :: Anew(size(Q, 1), size(R, 2)) ! Debugging only
-real(RP) :: Qsave(size(Q, 1), size(Q, 2)) ! Debugging only
-real(RP) :: Rsave(size(R, 1), i) ! Debugging only
-real(RP) :: tol ! Debugging only
+real(RP):: Anew(size(Q, 1), size(R, 2)) ! Debugging only
+real(RP):: Qsave(size(Q, 1), size(Q, 2)) ! Debugging only
+real(RP):: Rsave(size(R, 1), i) ! Debugging only
+real(RP):: tol ! Debugging only
!------------------------------------------------------------!
! Sizes
@@ -457,11 +457,11 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
call assert(size(Q, 2) == size(R, 1), 'SIZE(Q, 2) == SIZE(R, 1)', srname)
call assert(size(Q, 2) >= n .and. size(Q, 2) <= m, 'N <= SIZE(Q, 2) <= M', srname)
call assert(size(R, 1) >= n .and. size(R, 1) <= m, 'N <= SIZE(R, 1) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP*EPS*real(m+1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(istriu(R), 'R is upper triangular', srname)
Anew = matprod(Q, R)
- Anew = reshape([Anew(:, 1:i - 1), Anew(:, i + 1:n), Anew(:, i)], shape(Anew))
+ Anew = reshape([Anew(:, 1:i-1), Anew(:, i+1:n), Anew(:, i)], shape(Anew))
Qsave = Q ! For debugging only.
Rsave = R(:, 1:i) ! For debugging only.
end if
@@ -484,40 +484,40 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
! Powell's code, however, is slightly different: before everything, he first exchanged columns K and
! K+1 of Q as well as rows K and K+1 of R. This makes sure that the diagonal entries of the updated
! R are all positive if it is the case for the original R.
-do k = i, n - 1_IK
- G = planerot(R([k + 1_IK, k], k + 1)) ! G = [c, -s; s, c]. It improves the performance of LINCOA
- hypt = sqrt(R(k, k + 1)**2 + R(k + 1, k + 1)**2) ! HYPT must be calculated before R is updated
- ! !HYPT = G(1, 1) * R(K + 1, K + 1) + G(1, 2) * R(K, K + 1) ! Does not perform well on 20220312
- ! !HYPT = HYPOTENUSE(R(K + 1, K + 1), R(K, K + 1)) ! Does not perform well on 20220312
+do k = i, n-1_IK
+ G = planerot(R([k+1_IK, k], k+1)) ! G = [c, -s; s, c]. It improves the performance of LINCOA
+ hypt = sqrt(R(k, k+1)**2+R(k+1, k+1)**2) ! HYPT must be calculated before R is updated
+ ! !HYPT = G(1, 1) * R(K+1, K+1) + G(1, 2) * R(K, K+1) ! Does not perform well on 20220312
+ ! !HYPT = HYPOTENUSE(R(K+1, K+1), R(K, K+1)) ! Does not perform well on 20220312
! Update Q(:, [K, K+1]).
- Q(:, [k, k + 1_IK]) = matprod(Q(:, [k + 1_IK, k]), transpose(G))
+ Q(:, [k, k+1_IK]) = matprod(Q(:, [k+1_IK, k]), transpose(G))
! Update R([K, K+1], :).
- R([k, k + 1_IK], k:n) = matprod(G, R([k + 1_IK, k], k:n))
- R(1:k + 1, [k, k + 1_IK]) = R(1:k + 1, [k + 1_IK, k])
+ R([k, k+1_IK], k:n) = matprod(G, R([k+1_IK, k], k:n))
+ R(1:k+1, [k, k+1_IK]) = R(1:k+1, [k+1_IK, k])
! N.B.: The above two lines implement the following while noting that R is upper triangular.
- ! !R([K, K + 1_IK], :) = MATPROD(G, R([K + 1_IK, K], :)) ! No need for R([K, K+1], 1:K-1) = 0
- ! !R(:, [K, K + 1_IK]) = R(:, [K + 1_IK, K]) ! No need for R(K+2:, [K, K+1]) = 0
+ ! !R([K, K+1_IK], :) = MATPROD(G, R([K+1_IK, K], :)) ! No need for R([K, K+1], 1:K-1) = 0
+ ! !R(:, [K, K+1_IK]) = R(:, [K+1_IK, K]) ! No need for R(K+2:, [K, K+1]) = 0
! Revise R([K, K+1], K). Changes nothing in theory but seems good for the practical performance.
- R([k, k + 1_IK], k) = [hypt, ZERO]
+ R([k, k+1_IK], k) = [hypt, ZERO]
!----------------------------------------------------------------------------------------------!
! The following code performs the update without exchanging columns K and K+1 of Q or rows K and
! K+1 of R beforehand. If the diagonal entries of the original R are positive, then all the
! updated ones become negative.
!
- ! !G = planerot(R([k, k + 1_IK], k + 1))
- ! !hypt = sqrt(R(k, k + 1)**2 + R(k + 1, k + 1)**2)
- ! !!HYPT = G(1, 1) * R(K, K + 1) + G(1, 2) * R(K+1, K + 1) ! Does not perform well on 20220312
- ! !!HYPT = HYPOTENUSE(R(K, K + 1), R(K + 1, K + 1)) ! Does not perform well on 20220312
+ ! !G = planerot(R([k, k+1_IK], k+1))
+ ! !hypt = sqrt(R(k, k+1)**2+R(k+1, k+1)**2)
+ ! !!HYPT = G(1, 1) * R(K, K+1) + G(1, 2) * R(K+1, K+1) ! Does not perform well on 20220312
+ ! !!HYPT = HYPOTENUSE(R(K, K+1), R(K+1, K+1)) ! Does not perform well on 20220312
!
- ! !Q(:, [k, k + 1_IK]) = matprod(Q(:, [k, k + 1_IK]), transpose(G))
+ ! !Q(:, [k, k+1_IK]) = matprod(Q(:, [k, k+1_IK]), transpose(G))
! !
- ! !R([k, k + 1_IK], k:n) = matprod(G, R([k, k + 1_IK], k:n))
- ! !R(1:k + 1, [k, k + 1_IK]) = R(1:k + 1, [k + 1_IK, k])
- ! !R([k, k + 1_IK], k) = [hypt, ZERO]
+ ! !R([k, k+1_IK], k:n) = matprod(G, R([k, k+1_IK], k:n))
+ ! !R(1:k+1, [k, k+1_IK]) = R(1:k+1, [k+1_IK, k])
+ ! !R([k, k+1_IK], k) = [hypt, ZERO]
!----------------------------------------------------------------------------------------------!
end do
@@ -534,14 +534,14 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
call assert(istriu(R), 'R is upper triangular', srname)
Qsave(:, i:n) = Q(:, i:n)
- ! !call assert(.not. any(abs(Q - Qsave) > 0), 'Q is unchanged except Q(:, I:N)', srname)
- ! !call assert(.not. any(abs(R(:, 1:i - 1) - Rsave(:, 1:i - 1)) > 0), 'R(:, 1:I-1) is unchanged', srname)
+ ! !call assert(.not. any(abs(Q-Qsave) > 0), 'Q is unchanged except Q(:, I:N)', srname)
+ ! !call assert(.not. any(abs(R(:, 1:i-1) - Rsave(:, 1:i-1)) > 0), 'R(:, 1:I-1) is unchanged', srname)
! If we can ensure that Q and R do not contain NaN or Inf, use the following lines instead of the last two.
- call assert(all(abs(Q - Qsave) <= 0), 'Q is unchanged except Q(:, I:N)', srname)
- call assert(all(abs(R(:, 1:i - 1) - Rsave(:, 1:i - 1)) <= 0), 'R(:, 1:I-1) is unchanged', srname)
+ call assert(all(abs(Q-Qsave) <= 0), 'Q is unchanged except Q(:, I:N)', srname)
+ call assert(all(abs(R(:, 1:i-1) - Rsave(:, 1:i-1)) <= 0), 'R(:, 1:I-1) is unchanged', srname)
! The following test may fail.
- call assert(all(abs(Anew - matprod(Q, R)) <= max(tol, tol * maxval(abs(Anew)))), 'Anew = Q*R', srname)
+ call assert(all(abs(Anew-matprod(Q, R)) <= max(tol, tol*maxval(abs(Anew)))), 'Anew = Q*R', srname)
end if
end subroutine qrexc_Rfull
@@ -552,37 +552,37 @@ end subroutine qrexc_Rfull
!! It is because we use GOPT instead of GQ when representing the quadratic model. Here, GOPT is the
!! gradient at the current "best" point, but GQ is the gradient at the base point XBASE.
!!--------------------------------------------------------------------------------------------------!
-!! This function evaluates QINC = Q(X + D) - Q(X) with Q being the quadratic function defined
+!! This function evaluates QINC = Q(X+D) - Q(X) with Q being the quadratic function defined
!! via [GQ, HQ, PQ] by
!! Q(Y) = + 0.5*,
!! where HESSIAN consists of an explicit part HQ and an implicit part PQ in Powell's way:
-!! HESSIAN = HQ + sum_K=1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
+!! HESSIAN = HQ+sum_K = 1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
!! N.B.: QUADINC_DX(D, ZEROS(SIZE(D)), XPT, GQ, PQ, HQ) = QUADINC_D0(D, XPT, GQ, PQ, HQ)
!!--------------------------------------------------------------------------------------------------!
-!use, non_intrinsic :: consts_mod, only : RP, IK, HALF, DEBUGGING
-!use, non_intrinsic :: debug_mod, only : assert
-!use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-!use, non_intrinsic :: linalg_mod, only : matprod, inprod, issymmetric
+!use, non_intrinsic:: consts_mod, only : RP, IK, HALF, DEBUGGING
+!use, non_intrinsic:: debug_mod, only : assert
+!use, non_intrinsic:: infnan_mod, only : is_nan, is_finite
+!use, non_intrinsic:: linalg_mod, only : matprod, inprod, issymmetric
!implicit none
!! Inputs
-!real(RP), intent(in) :: d(:) ! D(N)
-!real(RP), intent(in) :: x(:) ! X(N)
-!real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-!real(RP), intent(in) :: gq(:) ! GQ(N)
-!real(RP), intent(in) :: pq(:) ! PQ(NPT)
-!real(RP), intent(in), optional :: hq(:, :) ! HQ(N, N)
+!real(RP), intent(in):: d(:) ! D(N)
+!real(RP), intent(in):: x(:) ! X(N)
+!real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+!real(RP), intent(in):: gq(:) ! GQ(N)
+!real(RP), intent(in):: pq(:) ! PQ(NPT)
+!real(RP), intent(in), optional:: hq(:, :) ! HQ(N, N)
!! Output
-!real(RP) :: qinc
+!real(RP):: qinc
!! Local variable
-!character(len=*), parameter :: srname = 'QUADINC_DX'
-!integer(IK) :: n
-!integer(IK) :: npt
-!real(RP) :: dxpt(size(pq))
-!real(RP) :: s(size(x))
-!real(RP) :: sxpt(size(pq))
+!character(len=*), parameter:: srname = 'QUADINC_DX'
+!integer(IK):: n
+!integer(IK):: npt
+!real(RP):: dxpt(size(pq))
+!real(RP):: s(size(x))
+!real(RP):: sxpt(size(pq))
!! Sizes
!n = int(size(xpt, 1), kind(n))
@@ -607,47 +607,47 @@ end subroutine qrexc_Rfull
!!--------------------------------------------------------------------------------------------------!
!! The following is Powell's scheme in NEWUOA.
-!! !s = x + d
+!! !s = x+d
!! !qinc = ZERO
!! !do j = 1, n
!! ! ! First-order term
-!! ! qinc = qinc + d(j) * gq(j)
+!! ! qinc = qinc+d(j) * gq(j)
!! ! ! Explicit second-order term
!! ! if (present(hq)) then
!! ! do i = 1, j
!! ! t = d(i) * s(j) + d(j) * x(i)
!! ! if (i == j) then
-!! ! t = HALF * t
+!! ! t = HALF*t
!! ! end if
-!! ! qinc = qinc + t * hq(i, j)
+!! ! qinc = qinc+t * hq(i, j)
!! ! end do
!! ! end if
!! !end do
!! !! Implicit second-order term
!! !dxpt = matprod(d, xpt)
-!! !w = dxpt * (HALF * dxpt + matprod(x, xpt))
+!! !w = dxpt * (HALF*dxpt+matprod(x, xpt))
!! !do i = 1, npt
-!! ! qinc = qinc + pq(i) * w(i)
+!! ! qinc = qinc+pq(i) * w(i)
!! !end do
!!--------------------------------------------------------------------------------------------------!
!!--------------------------------------------------------------------------------------------------!
!! The following is a loop-free implementation, which should be applied in MATLAB/Python/R/Julia.
-!! N.B.: INPROD(DXPT, PQ * SXPT) = INPROD(D, HESS_MUL(S, XPT, PQ))
+!! N.B.: INPROD(DXPT, PQ*SXPT) = INPROD(D, HESS_MUL(S, XPT, PQ))
!!--------------------------------------------------------------------------------------------------!
-!s = HALF * d + x
+!s = HALF*d + x
!sxpt = matprod(s, xpt)
!dxpt = matprod(d, xpt)
!if (present(hq)) then
-! qinc = inprod(d, gq + matprod(hq, s)) + inprod(dxpt, pq * sxpt)
+! qinc = inprod(d, gq+matprod(hq, s)) + inprod(dxpt, pq*sxpt)
!else
-! qinc = inprod(d, gq) + inprod(dxpt, pq * sxpt)
+! qinc = inprod(d, gq) + inprod(dxpt, pq*sxpt)
!end if
!!!MATLAB:
!!!if nargin >= 6
-!!! qinc = d'*(gq + hq*s) + 0.5*dxpt'*(pq*sxpt);
+!!! qinc = d'*(gq+hq*s) + 0.5*dxpt'*(pq*sxpt);
!!!else
-!!! qinc = d'*gq + 0.5*dxpt'*(pq*sxpt);
+!!! qinc = d'*gq+0.5*dxpt'*(pq*sxpt);
!!!end
!!--------------------------------------------------------------------------------------------------!
@@ -664,30 +664,30 @@ function quadinc_d0(d, xpt, gq, pq, hq) result(qinc)
! via [GQ, HQ, PQ] by
! Q(Y) = + 0.5*,
! where HESSIAN consists of an explicit part HQ and an implicit part PQ in Powell's way:
-! HESSIAN = HQ + sum_K=1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
+! HESSIAN = HQ+sum_K = 1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T) .
! N.B.: QUADINC_D0(D, XPT, GQ, PQ, HQ) = QUADINC_DX(D, ZEROS(SIZE(D)), XPT, GQ, PQ, HQ)
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, HALF, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : matprod, inprod, issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, HALF, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : matprod, inprod, issymmetric
implicit none
! Inputs
-real(RP), intent(in) :: d(:) ! D(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: gq(:) ! GQ(N)
-real(RP), intent(in) :: pq(:) ! PQ(NPT)
-real(RP), intent(in), optional :: hq(:, :) ! HQ(N, N)
+real(RP), intent(in):: d(:) ! D(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: gq(:) ! GQ(N)
+real(RP), intent(in):: pq(:) ! PQ(NPT)
+real(RP), intent(in), optional:: hq(:, :) ! HQ(N, N)
! Output
-real(RP) :: qinc
+real(RP):: qinc
! Local variable
-character(len=*), parameter :: srname = 'QUADINC_D0'
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: dxpt(size(pq))
+character(len=*), parameter:: srname = 'QUADINC_D0'
+integer(IK):: n
+integer(IK):: npt
+real(RP):: dxpt(size(pq))
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -714,14 +714,14 @@ function quadinc_d0(d, xpt, gq, pq, hq) result(qinc)
! !! First-order term and explicit second-order term
! !qinc = ZERO
! !do j = 1, n
-! ! qinc = qinc + d(j) * gq(j)
+! ! qinc = qinc+d(j) * gq(j)
! ! do i = 1, j
! ! t = d(i) * d(j)
! ! if (i == j) then
-! ! t = HALF * t
+! ! t = HALF*t
! ! end if
! ! if (present(hq)) then
-! ! qinc = qinc + t * hq(i, j)
+! ! qinc = qinc+t * hq(i, j)
! ! end if
! ! end do
! !end do
@@ -729,25 +729,25 @@ function quadinc_d0(d, xpt, gq, pq, hq) result(qinc)
! !! Implicit second-order term
! !dxpt = matprod(d, xpt)
! !do i = 1, npt
-! ! qinc = qinc + HALF * pq(i) * dxpt(i) * dxpt(i) ! In BOBYQA, it is QINC - HALF * PQ(I) * DXPT(I)**2.
+! ! qinc = qinc+HALF*pq(i) * dxpt(i) * dxpt(i) ! In BOBYQA, it is QINC-HALF*PQ(I) * DXPT(I)**2.
! !end do
!--------------------------------------------------------------------------------------------------!
!--------------------------------------------------------------------------------------------------!
! The following is a loop-free implementation, which should be applied in MATLAB/Python/R/Julia.
-! N.B.: INPROD(DXPT, PQ * DXPT) = INPROD(D, HESS_MUL(D, XPT, PQ))
+! N.B.: INPROD(DXPT, PQ*DXPT) = INPROD(D, HESS_MUL(D, XPT, PQ))
!--------------------------------------------------------------------------------------------------!
dxpt = matprod(d, xpt)
if (present(hq)) then
- qinc = inprod(d, gq + HALF * matprod(hq, d)) + HALF * inprod(dxpt, pq * dxpt)
+ qinc = inprod(d, gq+HALF*matprod(hq, d)) + HALF*inprod(dxpt, pq*dxpt)
else
- qinc = inprod(d, gq) + HALF * inprod(dxpt, pq * dxpt)
+ qinc = inprod(d, gq) + HALF*inprod(dxpt, pq*dxpt)
end if
!!MATLAB:
!!if nargin >= 5
-!! qinc = d'*(gq + 0.5*hq*d) + 0.5*dxpt'*(pq*dxpt);
+!! qinc = d'*(gq+0.5*hq*d) + 0.5*dxpt'*(pq*dxpt);
!!else
-!! qinc = d'*gq + 0.5*dxpt'*(pq*dxpt);
+!! qinc = d'*gq+0.5*dxpt'*(pq*dxpt);
!!end
!--------------------------------------------------------------------------------------------------!
@@ -765,23 +765,23 @@ function quadinc_ghv(ghv, d, x) result(qinc)
! where GQ is GHV(1:N), and HESSIAN is the symmetric matrix whose upper triangular part is stored in
! GHV(N+1:N*(N+3)/2) column by column.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, HALF, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : inprod
+use, non_intrinsic:: consts_mod, only : RP, IK, HALF, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : inprod
implicit none
! Inputs
-real(RP), intent(in) :: ghv(:)
-real(RP), intent(in) :: d(:)
-real(RP), intent(in) :: x(:)
+real(RP), intent(in):: ghv(:)
+real(RP), intent(in):: d(:)
+real(RP), intent(in):: x(:)
! Outputs
-real(RP) :: qinc
+real(RP):: qinc
! Local variables
-character(len=*), parameter :: srname = 'QUADINC_GHV'
-integer(IK) :: ih
-integer(IK) :: n
-integer(IK) :: j
-real(RP) :: s(size(x))
-real(RP) :: w(size(ghv))
+character(len=*), parameter:: srname = 'QUADINC_GHV'
+integer(IK):: ih
+integer(IK):: n
+integer(IK):: j
+real(RP):: s(size(x))
+real(RP):: w(size(ghv))
! Sizes
n = int(size(x), kind(n))
@@ -789,20 +789,20 @@ function quadinc_ghv(ghv, d, x) result(qinc)
! Preconditions
if (DEBUGGING) then
call assert(size(d) == n, 'SIZE(D) = N', srname)
- call assert(size(ghv) == n * (n + 3) / 2, 'SIZE(GHV) = N*(N+3)/2', srname)
+ call assert(size(ghv) == n * (n+3) / 2, 'SIZE(GHV) = N*(N+3)/2', srname)
end if
!====================!
! Calculation starts !
!====================!
-s = x + d
+s = x+d
w(1:n) = d
do j = 1, n
- ih = n + (j - 1_IK) * j / 2_IK
- w(ih + 1:ih + j) = d(1:j) * s(j) + d(j) * x(1:j)
- w(ih + j) = HALF * w(ih + j)
+ ih = n + (j-1_IK) * j/2_IK
+ w(ih+1:ih+j) = d(1:j) * s(j) + d(j) * x(1:j)
+ w(ih+j) = HALF*w(ih+j)
end do
qinc = inprod(ghv, w)
@@ -818,36 +818,36 @@ function errquad(fval, xpt, gq, pq, hq, kref) result(err)
!--------------------------------------------------------------------------------------------------!
! This function calculates the maximal relative error of Q in interpolating FVAL on XPT.
! Here, Q is the quadratic function defined via [GQ, HQ, PQ] by
-! Q(Y) = + 0.5* if KREF is absent,
+! Q(Y) = + 0.5* if KREF is absent,
! Q(Y) = + 0.5* with XREF = XPT(:, KREF), if KREF is present.
! Here, HESSIAN consists of an explicit part HQ and an implicit part PQ in Powell's way:
-! HESSIAN = HQ + sum_K=1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T).
+! HESSIAN = HQ+sum_K = 1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T).
! N.B.: If KREF is absent, then GQ = nabla Q(0); otherwise, GQ = nabla Q(XREF).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, REALMAX, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite, is_nan, is_posinf
-use, non_intrinsic :: linalg_mod, only : issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, ONE, REALMAX, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite, is_nan, is_posinf
+use, non_intrinsic:: linalg_mod, only : issymmetric
implicit none
! Inputs
-real(RP), intent(in) :: fval(:) ! FVAL(NPT)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: gq(:) ! GQ(N)
-real(RP), intent(in) :: pq(:) ! PQ(NPT)
-real(RP), intent(in) :: hq(:, :) ! HQ(N, N)
-integer(IK), intent(in), optional :: kref
+real(RP), intent(in):: fval(:) ! FVAL(NPT)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: gq(:) ! GQ(N)
+real(RP), intent(in):: pq(:) ! PQ(NPT)
+real(RP), intent(in):: hq(:, :) ! HQ(N, N)
+integer(IK), intent(in), optional:: kref
! Outputs
-real(RP) :: err
+real(RP):: err
! Local variables
-character(len=*), parameter :: srname = 'ERRQUAD'
-integer(IK) :: k
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: fmq(size(xpt, 2))
-real(RP) :: qval(size(xpt, 2))
+character(len=*), parameter:: srname = 'ERRQUAD'
+integer(IK):: k
+integer(IK):: n
+integer(IK):: npt
+real(RP):: fmq(size(xpt, 2))
+real(RP):: qval(size(xpt, 2))
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -872,21 +872,21 @@ function errquad(fval, xpt, gq, pq, hq, kref) result(err)
!====================!
if (present(kref)) then
- qval = [(quadinc(xpt(:, k) - xpt(:, kref), xpt, gq, pq, hq), k=1, npt)]
+ qval = [(quadinc(xpt(:, k) - xpt(:, kref), xpt, gq, pq, hq), k = 1, npt)]
else
- qval = [(quadinc(xpt(:, k), xpt, gq, pq, hq), k=1, npt)]
+ qval = [(quadinc(xpt(:, k), xpt, gq, pq, hq), k = 1, npt)]
end if
!!MATLAB:
!!if nargin >= 5
-!! qval = cellfun(@(x) quadinc(x, xpt, gq, pq, hq), num2cell(xpt - xpt(:, kref), 1)); % Row vector
-!! % xpt - xpt(:, kref): Implicit expansion
+!! qval = cellfun(@(x) quadinc(x, xpt, gq, pq, hq), num2cell(xpt-xpt(:, kref), 1)); % Row vector
+!! % xpt-xpt(:, kref): Implicit expansion
!!else
!! qval = cellfun(@(x) quadinc(x, xpt, gq, pq, hq), num2cell(xpt, 1)); % Row vector
!!end
if (.not. all(is_finite(qval))) then
err = REALMAX
else
- fmq = fval - qval
+ fmq = fval-qval
err = (maxval(fmq) - minval(fmq)) / maxval([ONE, abs(fval)])
end if
@@ -900,28 +900,28 @@ end function errquad
function hess_mul(x, xpt, pq, hq) result(y)
!--------------------------------------------------------------------------------------------------!
! This function calculates HESSIAN*X, with HESSIAN consisting of an explicit part HQ (0 if absent)
-! and an implicit part PQ in Powell's way: HESSIAN = HQ + sum_K=1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T).
+! and an implicit part PQ in Powell's way: HESSIAN = HQ+sum_K = 1^NPT PQ(K)*(XPT(:, K)*XPT(:, K)^T).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : matprod, issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : matprod, issymmetric
implicit none
! Inputs
-real(RP), intent(in) :: x(:) ! X(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: pq(:) ! PQ(NPT)
-real(RP), intent(in), optional :: hq(:, :) ! HQ(N, N)
+real(RP), intent(in):: x(:) ! X(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: pq(:) ! PQ(NPT)
+real(RP), intent(in), optional:: hq(:, :) ! HQ(N, N)
! Outputs
-real(RP) :: y(size(x))
+real(RP):: y(size(x))
! Local variables
-character(len=*), parameter :: srname = 'HESS_MUL'
-integer(IK) :: j
-integer(IK) :: n
-integer(IK) :: npt
+character(len=*), parameter:: srname = 'HESS_MUL'
+integer(IK):: j
+integer(IK):: n
+integer(IK):: npt
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -943,12 +943,12 @@ function hess_mul(x, xpt, pq, hq) result(y)
!====================!
!--------------------------------------------------------------------------------!
-!----------! y = matprod(hq, x) + matprod(xpt, pq * matprod(x, xpt)) !-----------!
+!----------! y = matprod(hq, x) + matprod(xpt, pq*matprod(x, xpt)) ! -----------!
!--------------------------------------------------------------------------------!
-y = matprod(xpt, pq * matprod(x, xpt))
+y = matprod(xpt, pq*matprod(x, xpt))
if (present(hq)) then
do j = 1, n
- y = y + hq(:, j) * x(j)
+ y = y+hq(:, j) * x(j)
end do
end if
@@ -961,26 +961,26 @@ end function hess_mul
function omega_col(idz, zmat, k) result(y)
!--------------------------------------------------------------------------------------------------!
-! This function calculates Y = column K of OMEGA. As Powell did in NEWUOA, BOBYQA, and LINCOA,
-! OMEGA = sum_{i=1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
+! This function calculates Y = column K of OMEGA. As Powell did in NEWUOA, BOBYQA, and LINCOA,
+! OMEGA = sum_{i = 1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
! OMEGA is the leading NPT-by-NPT block of the matrix H in (3.12) of the NEWUOA paper.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod
+use, non_intrinsic:: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod
implicit none
! Inputs
-integer(IK), intent(in) :: idz
-integer(IK), intent(in) :: k
-real(RP), intent(in) :: zmat(:, :)
+integer(IK), intent(in):: idz
+integer(IK), intent(in):: k
+real(RP), intent(in):: zmat(:, :)
! Outputs
-real(RP) :: y(size(zmat, 1))
+real(RP):: y(size(zmat, 1))
! Local variables
-character(len=*), parameter :: srname = 'OMEGA_COL'
-real(RP) :: zk(size(zmat, 2))
+character(len=*), parameter:: srname = 'OMEGA_COL'
+real(RP):: zk(size(zmat, 2))
! Preconditions
if (DEBUGGING) then
@@ -993,7 +993,7 @@ function omega_col(idz, zmat, k) result(y)
!====================!
zk = zmat(k, :)
-zk(1:idz - 1) = -zk(1:idz - 1)
+zk(1:idz-1) = -zk(1:idz-1)
y = matprod(zmat, zk)
!====================!
@@ -1005,26 +1005,26 @@ end function omega_col
function omega_mul(idz, zmat, x) result(y)
!--------------------------------------------------------------------------------------------------!
-! This function calculates Y = OMEGA*X. As Powell did in NEWUOA, BOBYQA, and LINCOA,
-! OMEGA = sum_{i=1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
+! This function calculates Y = OMEGA*X. As Powell did in NEWUOA, BOBYQA, and LINCOA,
+! OMEGA = sum_{i = 1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
! OMEGA is the leading NPT-by-NPT block of the matrix H in (3.12) of the NEWUOA paper.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod
+use, non_intrinsic:: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod
implicit none
! Inputs
-integer(IK), intent(in) :: idz
-real(RP), intent(in) :: zmat(:, :)
-real(RP), intent(in) :: x(:)
+integer(IK), intent(in):: idz
+real(RP), intent(in):: zmat(:, :)
+real(RP), intent(in):: x(:)
! Outputs
-real(RP) :: y(size(zmat, 1))
+real(RP):: y(size(zmat, 1))
! Local variables
-character(len=*), parameter :: srname = 'OMEGA_MUL'
-real(RP) :: xz(size(zmat, 2))
+character(len=*), parameter:: srname = 'OMEGA_MUL'
+real(RP):: xz(size(zmat, 2))
! Preconditions
if (DEBUGGING) then
@@ -1037,7 +1037,7 @@ function omega_mul(idz, zmat, x) result(y)
!====================!
xz = matprod(x, zmat)
-xz(1:idz - 1) = -xz(1:idz - 1)
+xz(1:idz-1) = -xz(1:idz-1)
y = matprod(zmat, xz)
!====================!
@@ -1049,28 +1049,28 @@ end function omega_mul
function omega_inprod(idz, zmat, x, y) result(p)
!--------------------------------------------------------------------------------------------------!
-! This function calculates P = X^T*OMEGA*Y. As Powell did in NEWUOA, BOBYQA, and LINCOA,
-! OMEGA = sum_{i=1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
+! This function calculates P = X^T*OMEGA*Y. As Powell did in NEWUOA, BOBYQA, and LINCOA,
+! OMEGA = sum_{i = 1}^{K} S_i*ZMAT(:, i)*ZMAT(:, i)^T if S_i = -1 when i < IDZ and S_i = 1 if i >= IDZ
! OMEGA is the leading NPT-by-NPT block of the matrix H in (3.12) of the NEWUOA paper.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod, inprod
+use, non_intrinsic:: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod, inprod
implicit none
! Inputs
-integer(IK), intent(in) :: idz
-real(RP), intent(in) :: zmat(:, :)
-real(RP), intent(in) :: x(:)
-real(RP), intent(in) :: y(:)
+integer(IK), intent(in):: idz
+real(RP), intent(in):: zmat(:, :)
+real(RP), intent(in):: x(:)
+real(RP), intent(in):: y(:)
! Outputs
-real(RP) :: p
+real(RP):: p
! Local variables
-character(len=*), parameter :: srname = 'OMEGA_INPROD'
-real(RP) :: xz(size(zmat, 2))
-real(RP) :: yz(size(zmat, 2))
+character(len=*), parameter:: srname = 'OMEGA_INPROD'
+real(RP):: xz(size(zmat, 2))
+real(RP):: yz(size(zmat, 2))
! Preconditions
if (DEBUGGING) then
@@ -1084,7 +1084,7 @@ function omega_inprod(idz, zmat, x, y) result(p)
!====================!
xz = matprod(x, zmat)
-xz(1:idz - 1) = -xz(1:idz - 1)
+xz(1:idz-1) = -xz(1:idz-1)
yz = matprod(y, zmat)
p = inprod(xz, yz)
@@ -1103,34 +1103,34 @@ function errh(idz, bmat, zmat, xpt) result(err)
! W = [A, ONES(NPT, 1), XPT^T; ONES(1, NPT), ZERO, ZEROS(1, N); XPT, ZEROS(N, 1), ZEROS(N, N)]
! H = [Omega, r, BMAT(:, 1:NPT)^T; r^T, t(1), s^T, BMAT(:, 1:NPT), s, BMAT(:, NPT+1:NPT+N)]
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, HALF, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : matprod, eye, issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, ONE, HALF, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : matprod, eye, issymmetric
implicit none
! Inputs
-integer(IK), intent(in) :: idz
-real(RP), intent(in) :: bmat(:, :)
-real(RP), intent(in) :: zmat(:, :)
-real(RP), intent(in) :: xpt(:, :)
+integer(IK), intent(in):: idz
+real(RP), intent(in):: bmat(:, :)
+real(RP), intent(in):: zmat(:, :)
+real(RP), intent(in):: xpt(:, :)
! Outputs
-real(RP) :: err
+real(RP):: err
! Local variables
-character(len=*), parameter :: srname = 'ERRH'
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: A(size(xpt, 2), size(xpt, 2))
-real(RP) :: e(3, 3)
-real(RP) :: maxabs
-real(RP) :: Omega(size(xpt, 2), size(xpt, 2))
-real(RP) :: U(size(xpt, 2), size(xpt, 2))
-real(RP) :: V(size(xpt, 1), size(xpt, 2))
-real(RP) :: r(size(xpt, 2))
-real(RP) :: s(size(xpt, 1))
-real(RP) :: t(size(xpt, 2))
+character(len=*), parameter:: srname = 'ERRH'
+integer(IK):: n
+integer(IK):: npt
+real(RP):: A(size(xpt, 2), size(xpt, 2))
+real(RP):: e(3, 3)
+real(RP):: maxabs
+real(RP):: Omega(size(xpt, 2), size(xpt, 2))
+real(RP):: U(size(xpt, 2), size(xpt, 2))
+real(RP):: V(size(xpt, 1), size(xpt, 2))
+real(RP):: r(size(xpt, 2))
+real(RP):: s(size(xpt, 1))
+real(RP):: t(size(xpt, 2))
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -1139,11 +1139,11 @@ function errh(idz, bmat, zmat, xpt) result(err)
! Preconditions
if (DEBUGGING) then
call assert(n >= 1, 'N >= 1', srname)
- call assert(npt >= n + 2, 'NPT >= N + 2', srname)
- call assert(idz >= 1 .and. idz <= npt - n, '1 <= IDZ <= NPT-N', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
+ call assert(npt >= n+2, 'NPT >= N+2', srname)
+ call assert(idz >= 1 .and. idz <= npt-n, '1 <= IDZ <= NPT-N', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, &
& 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
end if
@@ -1152,25 +1152,25 @@ function errh(idz, bmat, zmat, xpt) result(err)
! Calculation starts !
!====================!
-A = HALF * matprod(transpose(xpt), xpt)**2
-Omega = -matprod(zmat(:, 1:idz - 1), transpose(zmat(:, 1:idz - 1))) + &
- & matprod(zmat(:, idz:npt - n - 1), transpose(zmat(:, idz:npt - n - 1)))
+A = HALF*matprod(transpose(xpt), xpt)**2
+Omega = -matprod(zmat(:, 1:idz-1), transpose(zmat(:, 1:idz-1))) + &
+ & matprod(zmat(:, idz:npt-n - 1), transpose(zmat(:, idz:npt-n - 1)))
maxabs = maxval([ONE, maxval(abs(A)), maxval(abs(Omega)), maxval(abs(bmat))])
U = eye(npt) - matprod(A, Omega) - matprod(transpose(xpt), bmat(:, 1:npt))
-V = -matprod(bmat(:, 1:npt), A) - matprod(bmat(:, npt + 1:npt + n), xpt)
-r = sum(U, dim=1) / real(npt, RP)
-s = sum(V, dim=2) / real(npt, RP)
+V = -matprod(bmat(:, 1:npt), A) - matprod(bmat(:, npt+1:npt+n), xpt)
+r = sum(U, dim = 1) / real(npt, RP)
+s = sum(V, dim = 2) / real(npt, RP)
t = -matprod(A, r) - matprod(s, xpt)
-e(1, 1) = maxval(maxval(U, dim=1) - minval(U, dim=1))
+e(1, 1) = maxval(maxval(U, dim = 1) - minval(U, dim = 1))
e(1, 2) = maxval(t) - minval(t)
-e(1, 3) = maxval(maxval(V, dim=2) - minval(V, dim=2))
-e(2, 1) = maxval(abs(sum(Omega, dim=1)))
+e(1, 3) = maxval(maxval(V, dim = 2) - minval(V, dim = 2))
+e(2, 1) = maxval(abs(sum(Omega, dim = 1)))
e(2, 2) = abs(sum(r) - ONE)
-e(2, 3) = maxval(abs(sum(bmat(:, 1:npt), dim=2)))
+e(2, 3) = maxval(abs(sum(bmat(:, 1:npt), dim = 2)))
e(3, 1) = maxval(abs(matprod(xpt, Omega)))
e(3, 2) = maxval(abs(matprod(xpt, r)))
e(3, 3) = maxval(abs(matprod(xpt, transpose(bmat(:, 1:npt))) - eye(n)))
-err = maxval(e) / (maxabs * real(n + npt, RP))
+err = maxval(e) / (maxabs*real(n+npt, RP))
!====================!
! Calculation ends !
@@ -1183,93 +1183,93 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
!--------------------------------------------------------------------------------------------------!
! This subroutine updates arrays [BMAT, ZMAT, IDZ], in order to replace the interpolation point
! XPT(:, KNEW) by XNEW = XPT(:, KREF) + D, where KREF usually equals KOPT in practice. See Section 4
-! of the NEWUOA paper. [BMAT, ZMAT, IDZ] describes the matrix H in the NEWUOA paper (eq. 3.12),
+! of the NEWUOA paper. [BMAT, ZMAT, IDZ] describes the matrix H in the NEWUOA paper (eq. 3.12),
! which is the inverse of the coefficient matrix of the KKT system for the least-Frobenius norm
! interpolation problem: ZMAT holds a factorization of the leading NPT*NPT submatrix OMEGA of H, the
-! factorization being OMEGA = ZMAT*Diag(S)*ZMAT^T with S(1:IDZ-1)= -1 and S(IDZ : NPT-N-1) = +1;
-! BMAT holds the last N ROWs of H except for the (NPT+1)th column. Note that the (NPT + 1)th row and
-! (NPT + 1)th column of H are not stored as they are unnecessary for the calculation. The matrix
+! factorization being OMEGA = ZMAT*Diag(S)*ZMAT^T with S(1:IDZ-1)= -1 and S(IDZ : NPT-N-1) = +1;
+! BMAT holds the last N ROWs of H except for the (NPT+1)th column. Note that the (NPT+1)th row and
+! (NPT+1)th column of H are not stored as they are unnecessary for the calculation. The matrix
! H is also formulated in (2.7) of the BOBYQA paper. Thanks to the RESCUE method (see Section 5 of
! the BOBYQA paper), BOBYQA does not have IDZ (equivalent to IDZ = 1).
!
! N.B.:
! 1. What is H? As mentioned above, it is the inverse of the coefficient matrix of the KKT system
! for the lest-Frobenius norm interpolation problem. Moreover, we should note that the K-th column
-! of H contain the coefficients of the K-th Lagrange function LFUNC_K for this interpolation problem,
+! of H contain the coefficients of the K-th Lagrange function LFUNC_K for this interpolation problem,
! where 1 <= K <= NPT. More specifically, the first NPT entries of H(:, K) provide the parameters
-! for the Hessian of LFUNC_K so that nabla^2 LFUNC_K = sum_{I=1}^NPT H(I, K) XPT(:, I)*XPT(:, I)^T;
+! for the Hessian of LFUNC_K so that nabla^2 LFUNC_K = sum_{I = 1}^NPT H(I, K) XPT(:, I)*XPT(:, I)^T;
! the last N entries of H(:, K) constitute precisely the gradient of LFUNC_K at the base point XBASE.
! Recalling that H is represented by OMEGA and BMAT in the block form elaborated above, we can see
! OMEGA(:, K) contains the leading NPT entries of H(:, K), while BMAT(:, K) contains the last N.
! 2. Powell's code normally invokes this subroutine with KREF set to KOPT, which is the index of the
! current best interpolation point (also the current center of the trust region). In theory, the
! update should however be independent of KREF. The most natural version (not necessarily the best
-! one in practice) of UPDATEH should work based on [KNEW, XNEW - XPT(:,KNEW)] rather than
+! one in practice) of UPDATEH should work based on [KNEW, XNEW-XPT(:,KNEW)] rather than
! [KNEW, KREF, D]. UPDATEH needs KREF only for calculating VLAG and BETA, where XPT(:, KREF) is used
! as a reference point that can be any column of XPT in precise arithmetic. Using XPT(:, KNEW) as
! the reference point, VLAG and BETA can be calculated by
-! !VLAG = CALVLAG(KNEW, BMAT, XNEW - XPT(:, KNEW), XPT, ZMAT, IDZ)
-! !BETA = CALBETA(KNEW, BMAT, XNEW - XPT(:, KNEW), XPT, ZMAT, IDZ)
+! !VLAG = CALVLAG(KNEW, BMAT, XNEW-XPT(:, KNEW), XPT, ZMAT, IDZ)
+! !BETA = CALBETA(KNEW, BMAT, XNEW-XPT(:, KNEW), XPT, ZMAT, IDZ)
! Theoretically (but not numerically), they should return the same VLAG and BETA as the calls below.
! However, as observed on 20220412, such an implementation can lead to significant errors in H!
!--------------------------------------------------------------------------------------------------!
! List of local arrays (including function-output arrays; likely to be stored on the stack):
-! REAL(RP) :: GROT(2, 2), V1(N), V2(N), VLAG(NPT+N), HCOL(NPT+N)
+! REAL(RP):: GROT(2, 2), V1(N), V2(N), VLAG(NPT+N), HCOL(NPT+N)
! Size of local arrays: REAL(RP)*(4+4*N+2*NPT)
!--------------------------------------------------------------------------------------------------!
! Generic modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, ZERO, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
-use, non_intrinsic :: linalg_mod, only : matprod, planerot, symmetrize, issymmetric, outprod!, r2update
+use, non_intrinsic:: consts_mod, only : RP, IK, ONE, ZERO, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
+use, non_intrinsic:: linalg_mod, only : matprod, planerot, symmetrize, issymmetric, outprod!, r2update
implicit none
! Inputs
-integer(IK), intent(in) :: knew
-integer(IK), intent(in) :: kref
-real(RP), intent(in) :: d(:) ! D(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
+integer(IK), intent(in):: knew
+integer(IK), intent(in):: kref
+real(RP), intent(in):: d(:) ! D(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
! In-outputs
-integer(IK), intent(inout) :: idz
-real(RP), intent(inout) :: bmat(:, :) ! BMAT(N, NPT + N)
-real(RP), intent(inout) :: zmat(:, :) ! ZMAT(NPT, NPT - N - 1)
+integer(IK), intent(inout):: idz
+real(RP), intent(inout):: bmat(:, :) ! BMAT(N, NPT+N)
+real(RP), intent(inout):: zmat(:, :) ! ZMAT(NPT, NPT-N - 1)
! Outputs
-integer(IK), intent(out), optional :: info
+integer(IK), intent(out), optional:: info
! Local variables
-character(len=*), parameter :: srname = 'UPDATEH'
-integer(IK) :: j
-integer(IK) :: ja
-integer(IK) :: jb
-integer(IK) :: jl
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: alpha
-real(RP) :: beta
-real(RP) :: denom
-real(RP) :: grot(2, 2)
-real(RP) :: hcol(size(bmat, 2))
-real(RP) :: scala
-real(RP) :: scalb
-real(RP) :: sqrtdn
-real(RP) :: tau
-real(RP) :: temp
-real(RP) :: tempa
-real(RP) :: tempb
-real(RP) :: v1(size(bmat, 1))
-real(RP) :: v2(size(bmat, 1))
-real(RP) :: vlag(size(bmat, 2))
-real(RP) :: ztest
+character(len=*), parameter:: srname = 'UPDATEH'
+integer(IK):: j
+integer(IK):: ja
+integer(IK):: jb
+integer(IK):: jl
+integer(IK):: n
+integer(IK):: npt
+real(RP):: alpha
+real(RP):: beta
+real(RP):: denom
+real(RP):: grot(2, 2)
+real(RP):: hcol(size(bmat, 2))
+real(RP):: scala
+real(RP):: scalb
+real(RP):: sqrtdn
+real(RP):: tau
+real(RP):: temp
+real(RP):: tempa
+real(RP):: tempb
+real(RP):: v1(size(bmat, 1))
+real(RP):: v2(size(bmat, 1))
+real(RP):: vlag(size(bmat, 2))
+real(RP):: ztest
! Debugging variables
-!real(RP) :: beta_test
-!real(RP) :: tol
-!real(RP), allocatable :: vlag_test(:)
-!real(RP), allocatable :: xpt_test(:, :)
+!real(RP):: beta_test
+!real(RP):: tol
+!real(RP), allocatable:: vlag_test(:)
+!real(RP), allocatable:: xpt_test(:, :)
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -1277,31 +1277,31 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
+ call assert(n >= 1 .and. npt >= n+2, 'N >= 1, NPT >= N+2', srname)
call assert(knew >= 0 .and. knew <= npt, '0 <= KNEW <= NPT', srname)
call assert(kref >= 1 .and. kref <= npt, '1 <= KREF <= NPT', srname)
call assert(idz >= 1 .and. idz <= size(zmat, 2) + 1, '1 <= IDZ <= SIZE(ZMAT, 2) + 1', srname)
call assert(size(d) == n .and. all(is_finite(d)), 'SIZE(D) == N, D is finite', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
- & 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, &
+ & 'SIZE(ZMAT) == [NPT, NPT-N - 1]', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
! Theoretically, CALVLAG and CALBETA should be independent of the reference point XPT(:, KREF).
! So we test the following. By the implementation of CALVLAG and CALBETA, we are indeed testing
- ! H*[w(X_KREF) - w(X_KNEW)] = e_KREF - e_KNEW. Thus H = W^{-1} is also tested to some extend.
+ ! H*[w(X_KREF) - w(X_KNEW)] = e_KREF-e_KNEW. Thus H = W^{-1} is also tested to some extend.
! However, this is expensive to check.
!if (knew >= 1) then
! tol = 1.0E-2_RP ! W and H are quite ill-conditioned, so we do not test a high precision.
- ! call safealloc(vlag_test, npt + n)
+ ! call safealloc(vlag_test, npt+n)
! vlag_test = calvlag(knew, bmat, d + (xpt(:, kref) - xpt(:, knew)), xpt, zmat, idz)
- ! call wassert(all(abs(vlag_test - calvlag(kref, bmat, d, xpt, zmat, idz)) <= &
- ! & tol * maxval([ONE, abs(vlag_test)])) .or. RP == kind(0.0), 'VLAG_TEST == VLAG', srname)
+ ! call wassert(all(abs(vlag_test-calvlag(kref, bmat, d, xpt, zmat, idz)) <= &
+ ! & tol*maxval([ONE, abs(vlag_test)])) .or. RP == kind(0.0), 'VLAG_TEST == VLAG', srname)
! deallocate (vlag_test)
! beta_test = calbeta(knew, bmat, d + (xpt(:, kref) - xpt(:, knew)), xpt, zmat, idz)
- ! call wassert(abs(beta_test - calbeta(kref, bmat, d, xpt, zmat, idz)) <= &
- ! & tol * max(ONE, abs(beta_test)) .or. RP == kind(0.0), 'BETA_TEST == BETA', srname)
+ ! call wassert(abs(beta_test-calbeta(kref, bmat, d, xpt, zmat, idz)) <= &
+ ! & tol*max(ONE, abs(beta_test)) .or. RP == kind(0.0), 'BETA_TEST == BETA', srname)
!end if
! The following is too expensive to check.
@@ -1309,7 +1309,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! & 'H = W^{-1} in (3.12) of the NEWUOA paper', srname)
end if
-idz = 1 ! FOR TRESTING !!!!!!
+idz = 1 ! FOR TESTING !!!!!!
!====================!
! Calculation starts !
!====================!
@@ -1326,12 +1326,12 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! Set the first NPT components of HCOL to the leading elements of the KNEW-th column of H. Powell's
! code does this after ZMAT is rotated blow, which saves flops but also introduces rounding errors.
hcol(1:npt) = omega_col(idz, zmat, knew)
-hcol(npt + 1:npt + n) = bmat(:, knew)
+hcol(npt+1:npt+n) = bmat(:, knew)
! Calculate VLAG and BETA according to D.
-! VLAG contains the components of the vector H*w of the updating formula (4.11) in the NEWUOA paper,
+! VLAG contains the components of the vector H*w of the updating formula (4.11) in the NEWUOA paper,
! and BETA holds the value of the parameter that has this name.
-! N.B.: Powell's original comments mention that VLAG is "the vector THETA*WCHECK + e_b of the
+! N.B.: Powell's original comments mention that VLAG is "the vector THETA*WCHECK+e_b of the
! updating formula (6.11)", which does not match the published version of the NEWUOA paper.
vlag = calvlag(kref, bmat, d, xpt, zmat, idz)
beta = calbeta(kref, bmat, d, xpt, zmat, idz)
@@ -1339,7 +1339,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! Calculate the parameters of the updating formula (4.18)--(4.20) in the NEWUOA paper.
alpha = hcol(knew)
tau = vlag(knew)
-denom = alpha * beta + tau**2
+denom = alpha*beta+tau**2
! After the following line, VLAG = H*w - e_KNEW in the NEWUOA paper (where t = KNEW).
vlag(knew) = vlag(knew) - ONE
@@ -1355,33 +1355,33 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
end if
! Update the matrix BMAT. It implements the last N rows of (4.11) in the NEWUOA paper.
-v1 = (alpha * vlag(npt + 1:npt + n) - tau * hcol(npt + 1:npt + n)) / denom
-v2 = (-beta * hcol(npt + 1:npt + n) - tau * vlag(npt + 1:npt + n)) / denom
-bmat = bmat + outprod(v1, vlag) + outprod(v2, hcol) !call r2update(bmat, ONE, v1, vlag, ONE, v2, hcol)
+v1 = (alpha*vlag(npt+1:npt+n) - tau*hcol(npt+1:npt+n)) / denom
+v2 = (-beta*hcol(npt+1:npt+n) - tau*vlag(npt+1:npt+n)) / denom
+bmat = bmat+outprod(v1, vlag) + outprod(v2, hcol) ! call r2update(bmat, ONE, v1, vlag, ONE, v2, hcol)
! Numerically, the update above does not guarantee BMAT(:, NPT+1 : NPT+N) to be symmetric.
-call symmetrize(bmat(:, npt + 1:npt + n))
+call symmetrize(bmat(:, npt+1:npt+n))
-! Apply Givens rotations to put zeros in the KNEW-th row of ZMAT and set JL. After this,
+! Apply Givens rotations to put zeros in the KNEW-th row of ZMAT and set JL. After this,
! ZMAT(KNEW, :) contains at most two nonzero entries ZMAT(KNEW, 1) and ZMAT(KNEW, JL), one
-! corresponding to all the columns of ZMAT that has a coefficient -1 in the factorization of
-! OMEGA (if any), and the other corresponding to all the columns with +1. In specific,
-! 1. If IDZ = 1 (all coefficients are +1 for the columns of ZMAT in the factorization of OMEGA ) or
-! NPT - N (all the coefficients are -1), then JL = 1, and ZMAT(KNEW, 1) is L2-norm of ZMAT(KNEW, :);
-! 2. If 2 <= IDZ <= NPT - N -1, then JL = IDZ, and ZMAT(KNEW, 1) is L2-norm of ZMAT(KNEW, 1 : IDZ-1),
+! corresponding to all the columns of ZMAT that has a coefficient-1 in the factorization of
+! OMEGA (if any), and the other corresponding to all the columns with+1. In specific,
+! 1. If IDZ = 1 (all coefficients are+1 for the columns of ZMAT in the factorization of OMEGA ) or
+! NPT-N (all the coefficients are-1), then JL = 1, and ZMAT(KNEW, 1) is L2-norm of ZMAT(KNEW, :);
+! 2. If 2 <= IDZ <= NPT-N -1, then JL = IDZ, and ZMAT(KNEW, 1) is L2-norm of ZMAT(KNEW, 1 : IDZ-1),
! while ZMAT(KNEW, JL) is L2 norm of ZMAT(KNEW, IDZ : NPT-N-1).
! See (4.15)--(4.17) of the NEWUOA paper and the elaboration around them.
-ztest = 1.0E-20_RP * maxval(abs(zmat)) ! Taken from BOBYQA. It is implicitly zero in NEWUOA/LINCOA.
+ztest = 1.0E-20_RP*maxval(abs(zmat)) ! Taken from BOBYQA. It is implicitly zero in NEWUOA/LINCOA.
jl = 1 ! In the loop below, if 2 <= J < IDZ, then JL = 1; if IDZ < J <= NPT-N-1, then JL = IDZ.
-do j = 2, npt - n - 1_IK
+do j = 2, npt-n - 1_IK
if (j == idz) then
jl = idz ! Do nothing but changing JL from 1 to IDZ. It occurs at most once along the loop.
cycle
end if
! Powell's condition in NEWUOA/LINCOA for the IF ... THEN below: IF (ZMAT(KNEW, J) /= 0) THEN
- ! A possible alternative: IF (ABS(ZMAT(KNEW, J)) > 1.0E-20_RP * ABS(ZMAT(KNEW, JL))) THEN
+ ! A possible alternative: IF (ABS(ZMAT(KNEW, J)) > 1.0E-20_RP*ABS(ZMAT(KNEW, JL))) THEN
if (abs(zmat(knew, j)) > ztest) then
- ! Multiply a Givens rotation to ZMAT from the right so that ZMAT(KNEW, [JL,J]) becomes [*,0].
+ ! Multiply a Givens rotation to ZMAT from the right so that ZMAT(KNEW, [JL, J]) becomes [*,0].
grot = planerot(zmat(knew, [jl, j])) !!MATLAB: grot = planerot(zmat(knew, [jl, j])')
zmat(:, [jl, j]) = matprod(zmat(:, [jl, j]), transpose(grot))
end if
@@ -1394,13 +1394,13 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! Complete the updating of ZMAT when there is only 1 nonzero in ZMAT(KNEW, :) after the rotation.
! This is the normal case, as IDZ = 1 in precise arithmetic; it also covers the rare case that
! IDZ = NPT-N, meaning that OMEGA = -ZMAT*ZMAT^T. See (4.18) of the NEWUOA paper for details.
- ! Note that (4.18) updates Z_{NPT-N-1}, but the code here updates ZMAT(:, 1). Correspondingly,
+ ! Note that (4.18) updates Z_{NPT-N-1}, but the code here updates ZMAT(:, 1). Correspondingly,
! we implicitly update S_1 to SIGN(DENOM)*S_1 according to (4.18). If IDZ = NPT-N before the
! update, then IDZ is reduced by 1, and we need to switch ZMAT(:, 1) and ZMAT(:, IDZ) to maintain
! that S_J = -1 iff 1 <= J < IDZ, which is done after the END IF together with another case.
!----------------------------------------------------------------------------------------------!
- ! Up to now, TEMPA = ZMAT(KNEW, 1) if IDZ = 1 and TEMPA = -ZMAT(KNEW, 1) if IDZ >= 2. However,
+ ! Up to now, TEMPA = ZMAT(KNEW, 1) if IDZ = 1 and TEMPA = -ZMAT(KNEW, 1) if IDZ >= 2. However,
! according to (4.18) of the NEWUOA paper, TEMPB should always be ZMAT(KNEW, 1)/SQRTDN
! regardless of IDZ. Therefore, the following definition of TEMPB is inconsistent with (4.18).
! This is probably a BUG. See also Lemma 4 and (5.13) of Powell's paper "On updating the inverse
@@ -1411,17 +1411,17 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! !tempa = tau/sqrtdn
!--------------------------------------------!
! Here is the corrected version (only TEMPB is changed).
- tempa = tau / sqrtdn
+ tempa = tau/sqrtdn
tempb = zmat(knew, 1) / sqrtdn
!----------------------------------------------------------------------------------------------!
! The following line updates ZMAT(:, 1) according to (4.18) of the NEWUOA paper.
- zmat(:, 1) = tempa * zmat(:, 1) - tempb * vlag(1:npt)
+ zmat(:, 1) = tempa*zmat(:, 1) - tempb*vlag(1:npt)
!----------------------------------------------------------------------------------------------!
! Zaikun 20220411: The update of IDZ is decoupled from the update of ZMAT, located after END IF.
!----------------------------------------------------------------------------------------------!
- ! The following six lines from Powell's NEWUOA code are obviously problematic --- SQRTDN is
+ ! The following six lines from Powell's NEWUOA code are obviously problematic--- SQRTDN is
! always nonnegative. According to (4.18) of the NEWUOA paper, "SQRTDN < 0" and "SQRTDN >= 0"
! below should be both revised to "DENOM < 0". See also the corresponding part of the LINCOA
! code. Note that the NEWUOA paper uses SIGMA to denote DENOM. Check also Lemma 4 and (5.13) of
@@ -1445,8 +1445,8 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
else
! Complete the updating of ZMAT in the alternative case: ZMAT(KNEW, :) has 2 nonzeros. See (4.19)
! and (4.20) of the NEWUOA paper.
- ! First, set JA and JB so that ZMAT(: [JA, JB]) corresponds to [Z_1, Z_2] in (4.19) when BETA>=0,
- ! and corresponds to [Z2, Z1] in (4.20) when BETA<0. In this way, the update of ZMAT(:, [JA, JB])
+ ! First, set JA and JB so that ZMAT(: [JA, JB]) corresponds to [Z_1, Z_2] in (4.19) when BETA >= 0,
+ ! and corresponds to [Z2, Z1] in (4.20) when BETA < 0. In this way, the update of ZMAT(:, [JA, JB])
! follows the same scheme regardless of BETA. Indeed, since S_1 = 1 and S_2 = -1 in (4.19)-(4.20)
! as elaborated above the equations, ZMAT(:, [1, JL]) always correspond to [Z_2, Z_1].
if (beta >= 0) then ! ZMAT(:, [JA, JB]) corresponds to [Z_1, Z_2] in (4.19)
@@ -1458,13 +1458,13 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
end if
! Now update ZMAT(:, [ja, jb]) according to (4.19)--(4.20) of the NEWUOA paper.
temp = zmat(knew, jb) / denom
- tempa = temp * beta
- tempb = temp * tau
+ tempa = temp*beta
+ tempb = temp*tau
temp = zmat(knew, ja)
- scala = ONE / sqrt(abs(beta) * temp**2 + tau**2) ! 1/SQRT(ZETA) in (4.19)-(4.20) of NEWUOA paper
- scalb = scala * sqrtdn
- zmat(:, ja) = scala * (tau * zmat(:, ja) - temp * vlag(1:npt))
- zmat(:, jb) = scalb * (zmat(:, jb) - tempa * hcol(1:npt) - tempb * vlag(1:npt))
+ scala = ONE/sqrt(abs(beta) * temp**2 + tau**2) ! 1/SQRT(ZETA) in (4.19)-(4.20) of NEWUOA paper
+ scalb = scala*sqrtdn
+ zmat(:, ja) = scala * (tau*zmat(:, ja) - temp*vlag(1:npt))
+ zmat(:, jb) = scalb * (zmat(:, jb) - tempa*hcol(1:npt) - tempb*vlag(1:npt))
!----------------------------------------------------------------------------------------------!
! Zaikun 20220411: The update of IDZ is decoupled from the update of ZMAT, located after END IF.
@@ -1473,7 +1473,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! See (4.19)--(4.20) of the NEWUOA paper.
! !if (denom < 0) then
! ! if (beta < 0) then
- ! ! idz = idz + 1_IK
+ ! ! idz = idz+1_IK
! ! else
! ! reduce_idz = .true.
! ! end if
@@ -1486,7 +1486,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
!--------------------------------------------------------------------------------------------------!
! IDZ is reduced in the following case. Then exchange ZMAT(:, 1) and ZMAT(:, IDZ).
! !if (reduce_idz) then
-! ! idz = idz - 1_IK
+! ! idz = idz-1_IK
! ! if (idz > 1) then
! ! zmat(:, [1_IK, idz]) = zmat(:, [idz, 1_IK])
! ! end if
@@ -1495,12 +1495,12 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! According to (4.18) and (4.19)--(4.20) of the NEWUOA paper, the coefficients {S_J} need update iff
! DENOM < 0, in which case one of the S_J will flip the sign when multiplied by SIGN(DENOM), leading
-! to an increase of IDZ (if S_J flipped from 1 to -1) or a decrease (if S_J flipped from -1 to 1).
+! to an increase of IDZ (if S_J flipped from 1 to-1) or a decrease (if S_J flipped from-1 to 1).
if (denom < 0) then
- if (idz == 1 .or. (idz < npt - n .and. beta < 0)) then ! (4.18), (4.20) of the NEWUOA paper
- idz = idz + 1_IK
- elseif (idz == npt - n .or. (idz > 1 .and. beta >= 0)) then ! (4.18), (4.19) of the NEWUOA paper
- idz = idz - 1_IK
+ if (idz == 1 .or. (idz < npt-n .and. beta < 0)) then ! (4.18), (4.20) of the NEWUOA paper
+ idz = idz+1_IK
+ elseif (idz == npt-n .or. (idz > 1 .and. beta >= 0)) then ! (4.18), (4.19) of the NEWUOA paper
+ idz = idz-1_IK
! Exchange ZMAT(:, 1) and ZMAT(:. IDZ) if IDZ > 1. Why? No matter whether the update is
! given by (4.18) (IDZ = NPT-N) or (4.19) (1 < IDZ < NPT-N and BETA >= 0), we have S_1 = +1
! and S_{IDZ} = -1 at this moment (unless IDZ = 1). Thus we need to exchange ZMAT(:, 1) with
@@ -1520,9 +1520,9 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! Postconditions
if (DEBUGGING) then
call assert(idz >= 1 .and. idz <= size(zmat, 2) + 1, '1 <= IDZ <= SIZE(ZMAT, 2) + 1', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
! The following is too expensive to check.
!call safealloc(xpt_test, n, npt)
!xpt_test = xpt
@@ -1532,14 +1532,14 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
!deallocate (xpt_test)
end if
-idz = 1 ! FOR TRESTING !!!!!!
+idz = 1 ! FOR TESTING !!!!!!
end subroutine updateh
!--------------------------------------------------------------------------------------------------!
! CALVLAG, CALBETA, and CALDEN are subroutine that calculate VLAG, BETA, and DEN for a given step D.
! VLAG(K), BETA, and DEN(K) are critical for the updating procedure of H when the interpolation set
-! replaces XPT(:, K) with D. The updating formula of H is detailed in (4.11) of the NEWUOA paper,
+! replaces XPT(:, K) with D. The updating formula of H is detailed in (4.11) of the NEWUOA paper,
! where the point being replaced is XPT(:, t). See (4.12) for the definition of BETA; VLAG is indeed
! H*w without the (NPT+1)the entry; DEN(t) is SIGMA in (4.12). (4.25)--(4.26) formulate the actual
! calculating scheme of VLAG and BETA.
@@ -1549,45 +1549,45 @@ end subroutine updateh
! repeating some calculation) because Fortran functions can only have one output.
!
! Explanation on the matrix H in (3.12) and w(X) in (6.3) of the NEWUOA paper and WCHECK in the code:
-! 0. As defined in (6.3) of the paper w(X)(K) = 0.5*[(X-X_0)^T*(X_K-X_0)]^2 for K = 1, ..., NPT,
-! w(X)(NPT+1) = 1, and w(X)(NPT+2:NPT+N+1) = X - X_0. As in (3.12) of the paper, w(X_K) is the K-th
+! 0. As defined in (6.3) of the paper w(X)(K) = 0.5*[(X-X_0)^T*(X_K-X_0)]^2 for K = 1, ..., NPT,
+! w(X)(NPT+1) = 1, and w(X)(NPT+2:NPT+N+1) = X-X_0. As in (3.12) of the paper, w(X_K) is the K-th
! column in the coefficient matrix W of the KKT system for the interpolation problem
! Minimize ||nabla^2 Q||_F s.t. Q(X_K) = Y_K, K = 1, ..., NPT.
! This is why w(X) is ubiquitous in the code.
-! 1. By (3.9) of the paper, the solution to the above interpolation problem is Q(X) = Y^T*H*w(X),
+! 1. By (3.9) of the paper, the solution to the above interpolation problem is Q(X) = Y^T*H*w(X),
! with H = W^{-1}, and Y being the vector [Y_1; ...; Y_NPT; 0; ...; 0] with N trailing zeros. In
! particular, the K-th Lagrange function of this interpolation problem is e_K^T*H*w(X), namely the
! K-th entry of the vector H*w(X). This is why H*w(X) appears as the vector VLAG in the code.
! As a consequence, SUM(VLAG(1:NPT)) = 1 in theory.
! 2. As above, H can provide us interpolants and the Lagrange functions. Thus the code maintains H.
-! Indeed, the K-th column of H contain the coefficients of the K-th Lagrange function LFUNC_K,
+! Indeed, the K-th column of H contain the coefficients of the K-th Lagrange function LFUNC_K,
! where 1 <= K <= NPT. More specifically, the first NPT entries of H(:, K) provide the parameters
-! for the Hessian of LFUNC_K so that nabla^2 LFUNC_K = sum_{I=1}^NPT H(I, K) XPT(:, I)*XPT(:, I)^T;
+! for the Hessian of LFUNC_K so that nabla^2 LFUNC_K = sum_{I = 1}^NPT H(I, K) XPT(:, I)*XPT(:, I)^T;
! the last N entries of H(:, K) constitute precisely the gradient of LFUNC_K at the base point X_0.
! Recalling that H (except for the (NPT+1)the row and column) is represented by OMEGA and BMAT in
! the block form [OMEGA, BMAT(:, 1:NPT)'; BMAT(:, 1:NPT), BMAT(:, NPT+1:NPT+N)], we can see
! OMEGA(:, K) contains the leading NPT entries of H(:, K), while BMAT(:, K) contains the last N.
-! Hence, if X corresponds to XOPT + D, then for K /= KOPT, the K-th entry of VLAG = H*w(X) equals
-! LFUNC_K(X_0 + XOPT + D) - LFUNC_K(X_0 + XOPT) = QUADINC_DX(D, X, XPT, BMAT(:, K), OMEGA(:, K)),
-! because LFUNC_K(X_0 + XOPT) = 0; for K = KOPT, it equals
-! LFUNC_K(X_0 + XOPT + D)-LFUNC_K(X_0 + XOPT)+1 = QUADINC_DX(D, X, XPT, BMAT(:, K), OMEGA(:, K)) + 1,
-! as LFUNC_K(X_0 + XOPT) = 1 in this case.
+! Hence, if X corresponds to XOPT+D, then for K /= KOPT, the K-th entry of VLAG = H*w(X) equals
+! LFUNC_K(X_0+XOPT+D) - LFUNC_K(X_0+XOPT) = QUADINC_DX(D, X, XPT, BMAT(:, K), OMEGA(:, K)),
+! because LFUNC_K(X_0+XOPT) = 0; for K = KOPT, it equals
+! LFUNC_K(X_0+XOPT+D)-LFUNC_K(X_0+XOPT)+1 = QUADINC_DX(D, X, XPT, BMAT(:, K), OMEGA(:, K)) + 1,
+! as LFUNC_K(X_0+XOPT) = 1 in this case.
! 3. Since the matrix H is W^{-1} as defined in (3.12) of the paper, we have H*w(X_K) = e_K for
! any K in {1, ..., NPT}.
! 4. When the interpolation set is updated by replacing X_K with X, W is correspondingly updated by
! changing the K-th column from w(X_K) to w(X). This is why the update of H = W^{-1} must involve
! H*[w(X) - w(X_K)] = H*w(X) - e_K.
! 5. As explained above, the vector H*w(X) is essential to the algorithm. The quantity w(X)^T*H*w(X)
-! is also needed in the update of H (particularly by BETA). However, they can be tricky to calculate,
+! is also needed in the update of H (particularly by BETA). However, they can be tricky to calculate,
! because much cancellation can happen when X_0 is far away from the interpolation set, as explained
! in (7.8)--(7.10) of the paper and the discussions around. To overcome the difficulty, we take
! an integer KREF in {1, ..., NPT}, use XPT(:, KREF) as a reference point, and note that
! H*w(X) = H*[w(X) - w(X_KREF)] + H*w(X_KREF) = H*[w(X) - w(X_KREF)] + e_KREF, and
-! w(x)^T*H*w(X) = [w(X) - w(X_KREF)]^T*H*[w(X) - w(X_KREF)] + 2*w(X)(KREF) - w(X_KREF)(KREF),
+! w(x)^T*H*w(X) = [w(X) - w(X_KREF)]^T*H*[w(X) - w(X_KREF)] + 2*w(X)(KREF) - w(X_KREF)(KREF),
! The dependence of w(X)-w(X_KREF) on X_0 is weaker, which reduces (but does not resolve) the
! difficulty. In theory, these formulas are invariant with respect to KREF. In the code, this means
-! CALVLAG(KREF, BMAT, X - XPT(:, KREF), XPT, ZMAT, IDZ) and
-! CALBETA(KREF, BMAT, X - XPT(:, KREF), XPT, ZMAT, IDZ)
+! CALVLAG(KREF, BMAT, X-XPT(:, KREF), XPT, ZMAT, IDZ) and
+! CALBETA(KREF, BMAT, X-XPT(:, KREF), XPT, ZMAT, IDZ)
! are invariant with respect to KREF. Powell's code normally uses KREF = KOPT.
! 6. Since the (NPT+1)-th entry of w(X) - w(X_KREF) is 0, the above formulas do not require the
! (NPT+1)-th column of H, which is not stored in the code.
@@ -1606,37 +1606,37 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
! point as well as the center of the trust region. See (4.25) of the NEWUOA paper.
!--------------------------------------------------------------------------------------------------!
! List of local arrays (including function-output arrays; likely to be stored on the stack):
-! REAL(RP) :: VLAG(NPT+N), WCHECK(NPT), XREF(N)
+! REAL(RP):: VLAG(NPT+N), WCHECK(NPT), XREF(N)
! Size of local arrays: REAL(RP)*(2*NPT+2*N)
!--------------------------------------------------------------------------------------------------!
! Generic modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, HALF, EPS, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert, wassert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : matprod, issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, ONE, HALF, EPS, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert, wassert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : matprod, issymmetric
implicit none
! Inputs
-integer(IK), intent(in) :: kref
-real(RP), intent(in) :: bmat(:, :) ! BMAT(N, NPT + N)
-real(RP), intent(in) :: d(:) ! D(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: zmat(:, :) ! ZMAT(NPT, NPT - N - 1)
-integer(IK), intent(in), optional :: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
+integer(IK), intent(in):: kref
+real(RP), intent(in):: bmat(:, :) ! BMAT(N, NPT+N)
+real(RP), intent(in):: d(:) ! D(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: zmat(:, :) ! ZMAT(NPT, NPT-N - 1)
+integer(IK), intent(in), optional:: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
! Outputs
-real(RP) :: vlag(size(xpt, 1) + size(xpt, 2)) ! VLAG(NPT + N)
+real(RP):: vlag(size(xpt, 1) + size(xpt, 2)) ! VLAG(NPT+N)
! Local variables
-character(len=*), parameter :: srname = 'CALVLAG'
-integer(IK) :: idz_loc
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: tol ! For debugging only
-real(RP) :: wcheck(size(zmat, 1))
-real(RP) :: xref(size(xpt, 1))
+character(len=*), parameter:: srname = 'CALVLAG'
+integer(IK):: idz_loc
+integer(IK):: n
+integer(IK):: npt
+real(RP):: tol ! For debugging only
+real(RP):: wcheck(size(zmat, 1))
+real(RP):: xref(size(xpt, 1))
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -1650,13 +1650,13 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
+ call assert(n >= 1 .and. npt >= n+2, 'N >= 1, NPT >= N+2', srname)
call assert(idz_loc >= 1 .and. idz_loc <= size(zmat, 2) + 1, '1 <= ID <= SIZE(ZMAT, 2) + 1', srname)
call assert(kref >= 1 .and. kref <= npt, '1 <= KREF <= NPT', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
- & 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, &
+ & 'SIZE(ZMAT) == [NPT, NPT-N - 1]', srname)
call assert(size(d) == n .and. all(is_finite(d)), 'SIZE(D) == N, D is finite', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
end if
@@ -1669,13 +1669,13 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
! Set WCHECK to the first NPT entries of (w-v) for w and v in (4.10) and (4.24) of the NEWUOA paper.
wcheck = matprod(d, xpt)
-wcheck = wcheck * (HALF * wcheck + matprod(xref, xpt))
+wcheck = wcheck * (HALF*wcheck+matprod(xref, xpt))
! The following two lines set VLAG to H*(w-v).
vlag(1:npt) = omega_mul(idz_loc, zmat, wcheck) + matprod(d, bmat(:, 1:npt))
-vlag(npt + 1:npt + n) = matprod(bmat, [wcheck, d])
+vlag(npt+1:npt+n) = matprod(bmat, [wcheck, d])
! The following line is equivalent to the above one, but handles WCHECK and D separately.
-! !vlag(npt + 1:npt + n) = matprod(bmat(:, 1:npt), wcheck) + matprod(bmat(:, npt + 1:npt + n), d)
+! !vlag(npt+1:npt+n) = matprod(bmat(:, 1:npt), wcheck) + matprod(bmat(:, npt+1:npt+n), d)
! The following line sets VLAG(KREF) to the correct value.
vlag(kref) = vlag(kref) + ONE
@@ -1686,8 +1686,8 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
! Postconditions
if (DEBUGGING) then
- call assert(size(vlag) == npt + n, 'SIZE(VLAG) == NPT + N', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E10_RP * EPS * real(npt + n, RP)))
+ call assert(size(vlag) == npt+n, 'SIZE(VLAG) == NPT+N', srname)
+ tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E10_RP*EPS*real(npt+n, RP)))
call wassert(abs(sum(vlag(1:npt)) - ONE) / real(npt, RP) <= tol .or. RP == kind(0.0), &
& 'SUM(VLAG(1:NPT)) == 1', srname)
end if
@@ -1702,43 +1702,43 @@ function calbeta(kref, bmat, d, xpt, zmat, idz) result(beta)
! point as well as the center of the trust region. See (4.12) and (4.26) of the NEWUOA paper.
!--------------------------------------------------------------------------------------------------!
! List of local arrays (including function-output arrays; likely to be stored on the stack):
-! REAL(RP) :: BW(N), BD(N), WCHECK(NPT), XREF(N)
+! REAL(RP):: BW(N), BD(N), WCHECK(NPT), XREF(N)
! Size of local arrays: REAL(RP)*(3*NPT+4*N)
!--------------------------------------------------------------------------------------------------!
! Generic modules
-use, non_intrinsic :: consts_mod, only : RP, IK, HALF, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : inprod, matprod, issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, HALF, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : inprod, matprod, issymmetric
implicit none
! Inputs
-integer(IK), intent(in) :: kref
-real(RP), intent(in) :: bmat(:, :) ! BMAT(N, NPT + N)
-real(RP), intent(in) :: d(:) ! D(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: zmat(:, :) ! ZMAT(NPT, NPT - N - 1)
-integer(IK), intent(in), optional :: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
+integer(IK), intent(in):: kref
+real(RP), intent(in):: bmat(:, :) ! BMAT(N, NPT+N)
+real(RP), intent(in):: d(:) ! D(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: zmat(:, :) ! ZMAT(NPT, NPT-N - 1)
+integer(IK), intent(in), optional:: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
! Outputs
-real(RP) :: beta
+real(RP):: beta
! Local variables
-character(len=*), parameter :: srname = 'CALBETA'
-integer(IK) :: idz_loc
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: dsq
-real(RP) :: dvlag
-real(RP) :: dxref
-real(RP) :: vlag(size(xpt, 1) + size(xpt, 2))
-real(RP) :: wcheck(size(zmat, 1))
-real(RP) :: wmv(size(xpt, 1) + size(xpt, 2))
-real(RP) :: wvlag
-real(RP) :: xref(size(xpt, 1))
-real(RP) :: xrefsq
+character(len=*), parameter:: srname = 'CALBETA'
+integer(IK):: idz_loc
+integer(IK):: n
+integer(IK):: npt
+real(RP):: dsq
+real(RP):: dvlag
+real(RP):: dxref
+real(RP):: vlag(size(xpt, 1) + size(xpt, 2))
+real(RP):: wcheck(size(zmat, 1))
+real(RP):: wmv(size(xpt, 1) + size(xpt, 2))
+real(RP):: wvlag
+real(RP):: xref(size(xpt, 1))
+real(RP):: xrefsq
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -1752,13 +1752,13 @@ function calbeta(kref, bmat, d, xpt, zmat, idz) result(beta)
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
+ call assert(n >= 1 .and. npt >= n+2, 'N >= 1, NPT >= N+2', srname)
call assert(idz_loc >= 1 .and. idz_loc <= size(zmat, 2) + 1, '1 <= IDZ <= SIZE(ZMAT, 2) + 1', srname)
call assert(kref >= 1 .and. kref <= npt, '1 <= KREF <= NPT', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
- & 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, &
+ & 'SIZE(ZMAT) == [NPT, NPT-N - 1]', srname)
call assert(size(d) == n .and. all(is_finite(d)), 'SIZE(D) == N, D is finite', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
end if
@@ -1775,40 +1775,40 @@ function calbeta(kref, bmat, d, xpt, zmat, idz) result(beta)
! Set WCHECK to the first NPT entries of (w-v) for w and v in (4.10) and (4.24) of the NEWUOA paper.
wcheck = matprod(d, xpt)
-wcheck = wcheck * (HALF * wcheck + matprod(xref, xpt))
+wcheck = wcheck * (HALF*wcheck+matprod(xref, xpt))
! WMV is the vector (w-v) for w and v in (4.10) and (4.24) of the NEWUOA paper.
wmv = [wcheck, d]
! The following two lines set VLAG to H*(w-v).
vlag(1:npt) = omega_mul(idz_loc, zmat, wcheck) + matprod(d, bmat(:, 1:npt))
-vlag(npt + 1:npt + n) = matprod(bmat, wmv)
+vlag(npt+1:npt+n) = matprod(bmat, wmv)
! The following line is equivalent to the above one, but handles WCHECK and D separately.
-! !VLAG(NPT + 1:NPT + N) = MATPROD(BMAT(:, 1:NPT), WCHECK) + MATPROD(BMAT(:, NPT + 1:NPT + N), D)
+! !VLAG(NPT+1:NPT+N) = MATPROD(BMAT(:, 1:NPT), WCHECK) + MATPROD(BMAT(:, NPT+1:NPT+N), D)
-! Set BETA = HALF*||XREF + D||^4 - (W-V)'*H*(W-V) - [XREF'*(X+XREF)]^2 + HALF*||XREF||^4. See
+! Set BETA = HALF*||XREF+D||^4 - (W-V)'*H*(W-V) - [XREF'*(X+XREF)]^2+HALF*||XREF||^4. See
! equations (4.10), (4.12), (4.24), and (4.26) of the NEWUOA paper.
dxref = inprod(d, xref)
dsq = inprod(d, d)
xrefsq = inprod(xref, xref)
-dvlag = inprod(d, vlag(npt + 1:npt + n))
+dvlag = inprod(d, vlag(npt+1:npt+n))
wvlag = inprod(wcheck, vlag(1:npt))
-beta = dxref**2 + dsq * (xrefsq + dxref + dxref + HALF * dsq) - dvlag - wvlag
+beta = dxref**2 + dsq * (xrefsq+dxref+dxref+HALF*dsq) - dvlag-wvlag
!---------------------------------------------------------------------------------------------------!
! The last line is equivalent to either of the following lines, but performs better numerically.
-! !BETA = DXREF**2 + DSQ * (XREFSQ + DXREF + DXREF + HALF * DSQ) - INPROD(VLAG, WMV) ! not good
-! !BETA = DXREF**2 + DSQ * (XREFSQ + DXREF + DXREF + HALF * DSQ) - WVLAG - DVLAG ! bad
+! !BETA = DXREF**2 + DSQ * (XREFSQ+DXREF+DXREF+HALF*DSQ) - INPROD(VLAG, WMV) ! not good
+! !BETA = DXREF**2 + DSQ * (XREFSQ+DXREF+DXREF+HALF*DSQ) - WVLAG-DVLAG ! bad
!---------------------------------------------------------------------------------------------------!
! N.B.:
! 1. Mathematically, the following two quantities are equal:
-! DXREF**2 + DSQ * (XREFSQ + DXREF + DXREF + HALF * DSQ) ,
-! HALF * (INPROD(X, X)**2 + INPROD(XREF, XREF)**2) - INPROD(X, XREF)**2 with X = XREF + D.
-! However, the first (by Powell) is a better numerical scheme. According to the first formulation,
+! DXREF**2 + DSQ * (XREFSQ+DXREF+DXREF+HALF*DSQ),
+! HALF * (INPROD(X, X)**2+INPROD(XREF, XREF)**2) - INPROD(X, XREF)**2 with X = XREF+D.
+! However, the first (by Powell) is a better numerical scheme. According to the first formulation,
! this quantity is in the order of ||D||^2*||XREF||^2 if ||XREF|| >> ||D||, which is normally the case.
! However, each term in the second formulation has an order of ||XREF||^4. Thus much cancellation
! will occur in the second formulation. In addition, the first formulation contracts the rounding
-! error in (XREFSQ + DXREF + DXREF + HALF * DSQ) by a factor of ||D||^2, which is typically small.
-! 2. We can evaluate INPROD(VLAG, WMV) as INPROD(VLAG(1:NPT), WCHECK) + INPROD(VLAG(NPT+1:NPT+N),D)
+! error in (XREFSQ+DXREF+DXREF+HALF*DSQ) by a factor of ||D||^2, which is typically small.
+! 2. We can evaluate INPROD(VLAG, WMV) as INPROD(VLAG(1:NPT), WCHECK) + INPROD(VLAG(NPT+1:NPT+N), D)
! if it is desirable to handle WCHECK and D separately due to their significantly different magnitudes.
! The following line sets VLAG(KREF) to the correct value if we intend to output VLAG.
@@ -1832,32 +1832,32 @@ function calden(kref, bmat, d, xpt, zmat, idz) result(den)
!--------------------------------------------------------------------------------------------------!
! Generic modules
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : issymmetric
+use, non_intrinsic:: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: infnan_mod, only : is_finite
+use, non_intrinsic:: linalg_mod, only : issymmetric
implicit none
! Inputs
-integer(IK), intent(in) :: kref
-real(RP), intent(in) :: bmat(:, :) ! BMAT(N, NPT + N)
-real(RP), intent(in) :: d(:) ! D(N)
-real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
-real(RP), intent(in) :: zmat(:, :) ! ZMAT(NPT, NPT - N - 1)
-integer(IK), intent(in), optional :: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
+integer(IK), intent(in):: kref
+real(RP), intent(in):: bmat(:, :) ! BMAT(N, NPT+N)
+real(RP), intent(in):: d(:) ! D(N)
+real(RP), intent(in):: xpt(:, :) ! XPT(N, NPT)
+real(RP), intent(in):: zmat(:, :) ! ZMAT(NPT, NPT-N - 1)
+integer(IK), intent(in), optional:: idz ! Absent in BOBYQA, being equivalent to IDZ = 1
! Outputs
-real(RP) :: den(size(xpt, 2))
+real(RP):: den(size(xpt, 2))
! Local variables
-character(len=*), parameter :: srname = 'CALDEN'
-integer(IK) :: idz_loc
-integer(IK) :: n
-integer(IK) :: npt
-real(RP) :: beta
-real(RP) :: hdiag(size(xpt, 2))
-real(RP) :: vlag(size(xpt, 1) + size(xpt, 2))
+character(len=*), parameter:: srname = 'CALDEN'
+integer(IK):: idz_loc
+integer(IK):: n
+integer(IK):: npt
+real(RP):: beta
+real(RP):: hdiag(size(xpt, 2))
+real(RP):: vlag(size(xpt, 1) + size(xpt, 2))
! Sizes
n = int(size(xpt, 1), kind(n))
@@ -1871,13 +1871,13 @@ function calden(kref, bmat, d, xpt, zmat, idz) result(den)
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
+ call assert(n >= 1 .and. npt >= n+2, 'N >= 1, NPT >= N+2', srname)
call assert(idz_loc >= 1 .and. idz_loc <= size(zmat, 2) + 1, '1 <= IDZ <= SIZE(ZMAT, 2) + 1', srname)
call assert(kref >= 1 .and. kref <= npt, '1 <= KREF <= NPT', srname)
- call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
- call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
- call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
- & 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
+ call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt+n, 'SIZE(BMAT)==[N, NPT+N]', srname)
+ call assert(issymmetric(bmat(:, npt+1:npt+n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
+ call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt-n - 1, &
+ & 'SIZE(ZMAT) == [NPT, NPT-N - 1]', srname)
call assert(size(d) == n .and. all(is_finite(d)), 'SIZE(D) == N, D is finite', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
end if
@@ -1886,10 +1886,10 @@ function calden(kref, bmat, d, xpt, zmat, idz) result(den)
! Calculation starts !
!====================!
-hdiag = -sum(zmat(:, 1:idz_loc - 1)**2, dim=2) + sum(zmat(:, idz_loc:size(zmat, 2))**2, dim=2)
+hdiag = -sum(zmat(:, 1:idz_loc-1)**2, dim = 2) + sum(zmat(:, idz_loc:size(zmat, 2))**2, dim = 2)
vlag = calvlag(kref, bmat, d, xpt, zmat, idz_loc)
beta = calbeta(kref, bmat, d, xpt, zmat, idz_loc)
-den = hdiag * beta + vlag(1:npt)**2
+den = hdiag*beta+vlag(1:npt)**2
!====================!
! Calculation ends !
@@ -1907,29 +1907,29 @@ function calvlag_qint(pl, d, xref, kref) result(vlag)
! The coefficients of LFUNC_K are provided by PL(:, K) so that LFUNC_K(Y) = + ,
! where G is PL(1:N, K), and HESSIAN is the symmetric matrix whose upper triangular part is stored
! in PL(N+1:N*(N+3)/2, K) column by column. Note the following:
-! 1. For K /= KREF, LFUNC_K(XREF + D) = LFUNC_K(XREF + D) - LFUNC_K(XREF) as LFUNC_K(XREF) = 0.
-! 2. When K = KREF, LFUNC_K(XREF + D) = LFUNC_K(XREF + D) - LFUNC_K(XREF) + 1 as LFUNC_K(XREF) = 1.
+! 1. For K /= KREF, LFUNC_K(XREF+D) = LFUNC_K(XREF+D) - LFUNC_K(XREF) as LFUNC_K(XREF) = 0.
+! 2. When K = KREF, LFUNC_K(XREF+D) = LFUNC_K(XREF+D) - LFUNC_K(XREF) + 1 as LFUNC_K(XREF) = 1.
! Therefore, the function first calculates VLAG(K) = QUADINC_GHV(PL(:, K), D, XREF) for each K, and
! then increase VLAG(KREF) by 1.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, HALF, ONE, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : matprod
+use, non_intrinsic:: consts_mod, only : RP, IK, HALF, ONE, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : matprod
implicit none
! Inputs
-real(RP), intent(in) :: pl(:, :)
-real(RP), intent(in) :: d(:)
-real(RP), intent(in) :: xref(:)
-integer(IK), intent(in) :: kref
+real(RP), intent(in):: pl(:, :)
+real(RP), intent(in):: d(:)
+real(RP), intent(in):: xref(:)
+integer(IK), intent(in):: kref
! Outputs
-real(RP) :: vlag(size(pl, 2))
+real(RP):: vlag(size(pl, 2))
! Local variables
-character(len=*), parameter :: srname = 'CALVLAG_QINT'
-integer(IK) :: ih
-integer(IK) :: n
-integer(IK) :: j
-real(RP) :: s(size(xref))
-real(RP) :: w(size(pl, 1))
+character(len=*), parameter:: srname = 'CALVLAG_QINT'
+integer(IK):: ih
+integer(IK):: n
+integer(IK):: j
+real(RP):: s(size(xref))
+real(RP):: w(size(pl, 1))
! Sizes
n = int(size(xref), kind(n))
@@ -1937,7 +1937,7 @@ function calvlag_qint(pl, d, xref, kref) result(vlag)
! Preconditions
if (DEBUGGING) then
call assert(size(d) == n, 'SIZE(D) = N', srname)
- call assert(size(pl, 2) == (n + 1) * (n + 2) / 2 .and. size(pl, 1) == size(pl, 2) - 1, &
+ call assert(size(pl, 2) == (n+1) * (n+2) / 2 .and. size(pl, 1) == size(pl, 2) - 1, &
& 'SIZE(PL) = [N*(N+3)/2, (N+1)*(N+2)/2]', srname)
end if
@@ -1945,13 +1945,13 @@ function calvlag_qint(pl, d, xref, kref) result(vlag)
! Calculation starts !
!====================!
-s = xref + d
+s = xref+d
w(1:n) = d
do j = 1, n
- ih = n + (j - 1_IK) * j / 2_IK
- w(ih + 1:ih + j) = d(1:j) * s(j) + d(j) * xref(1:j)
- w(ih + j) = HALF * w(ih + j)
+ ih = n + (j-1_IK) * j/2_IK
+ w(ih+1:ih+j) = d(1:j) * s(j) + d(j) * xref(1:j)
+ w(ih+j) = HALF*w(ih+j)
end do
vlag = matprod(w, pl) ! VLAG(K) = QUADINC_GHV(PL(:, K), D, XREF)
@@ -1966,54 +1966,54 @@ end function calvlag_qint
function setij(n, npt, sorting_direction) result(ij)
!--------------------------------------------------------------------------------------------------!
-! Set IJ to a 2-by-(NPT-2*N-1) integer array so that IJ(:, K) = [P(K + 2*N + 1), Q(K + 2*N + 1)],
+! Set IJ to a 2-by-(NPT-2*N-1) integer array so that IJ(:, K) = [P(K+2*N+1), Q(K+2*N+1)],
! with P and Q defined in (2.4) of the BOBYQA paper as well as Section 3 of the NEWUOA paper.
! If NPT <= 2*N + 1, then IJ is empty. Assume that NPT >= 2*N + 2. Then SIZE(IJ) = [2, NPT-2*N-1].
! IJ contains integers between 1 and N. When NPT = (N+1)*(N+2)/2, the columns of IJ correspond to
! a permutation of {{I, J} : 1 <= I /= J <= N}; when NPT < (N+1)*(N+2)/2, they correspond to the
-! first NPT - 2*N - 1 elements of such a permutation. The permutation is enumerated first in the
-! ascending order of |I - J| and then in the ascending order of MAX{I, J}. We do not distinguish
-! between {I, J} and {J, I}, which represent the same set. If we want to ensure IJ(1, :) > IJ(2, :),
+! first NPT-2*N-1 elements of such a permutation. The permutation is enumerated first in the
+! ascending order of |I-J| and then in the ascending order of MAX{I, J}. We do not distinguish
+! between {I, J} and {J, I}, which represent the same set. If we want to ensure IJ(1, :) > IJ(2, :),
! then we can specify SORTING_DIRECTION = 'descend'.
!
! This function is used in the initialization of NEWUOA, BOBYQA, and LINCOA.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: linalg_mod, only : sort
-use, non_intrinsic :: string_mod, only : lower
+use, non_intrinsic:: consts_mod, only : IK, DEBUGGING
+use, non_intrinsic:: debug_mod, only : assert
+use, non_intrinsic:: linalg_mod, only : sort
+use, non_intrinsic:: string_mod, only : lower
implicit none
! Inputs
-integer(IK), intent(in) :: n
-integer(IK), intent(in) :: npt
-character(len=*), intent(in), optional :: sorting_direction
+integer(IK), intent(in):: n
+integer(IK), intent(in):: npt
+character(len=*), intent(in), optional:: sorting_direction
! Outputs
-integer(IK) :: ij(2, max(0_IK, npt - 2_IK * n - 1_IK))
+integer(IK):: ij(2, max(0_IK, npt-2_IK*n - 1_IK))
! Local variables
-character(len=*), parameter :: srname = 'SETIJ'
-integer(IK) :: k
-integer(IK) :: ell(max(0_IK, npt - 2_IK * n - 1_IK))
+character(len=*), parameter:: srname = 'SETIJ'
+integer(IK):: k
+integer(IK):: ell(max(0_IK, npt-2_IK*n - 1_IK))
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
+ call assert(n >= 1 .and. npt >= n+2, 'N >= 1, NPT >= N+2', srname)
end if
!====================!
! Calculation starts !
!====================!
-ell = int([(k, k=n, npt - n - 2_IK)] / n, IK) ! The ell below (2.4) of the BOBYQA paper.
-ij(1, :) = [(k, k=n, npt - n - 2_IK)] - n * ell + 1_IK
-ij(2, :) = modulo(ij(1, :) + ell - 1_IK, n) + 1_IK ! MODULO(K-1, N) + 1 = K-N for K in [N+1, 2N]
+ell = int([(k, k = n, npt-n - 2_IK)] / n, IK) ! The ell below (2.4) of the BOBYQA paper.
+ij(1, :) = [(k, k = n, npt-n - 2_IK)] - n*ell+1_IK
+ij(2, :) = modulo(ij(1, :) + ell-1_IK, n) + 1_IK ! MODULO(K-1, N) + 1 = K-N for K in [N+1, 2N]
if (present(sorting_direction)) then
ij = sort(ij, 1, sorting_direction) ! SORTING_DIRECTION is 'DESCEND' of 'ASCEND'
end if
!!MATLAB: (N.B.: Fortran MODULO == MATLAB `mod`, Fortran MOD == MATLAB `rem`)
-!!ell = floor((n : npt-n-2) / n);
-!!ij(1, :) = (n : npt-n-2) - n*ell + 1;
-!!ij(2, :) = mod(ij(1, :) + ell - 1, n) + 1; % mod(k-1,n) + 1 = k-n for k in [n+1,2n]
+!!ell = floor((n : npt-n-2) / n);
+!!ij(1, :) = (n : npt-n-2) - n*ell+1;
+!!ij(2, :) = mod(ij(1, :) + ell-1, n) + 1; % mod(k-1, n) + 1 = k-n for k in [n+1, 2n]
!!if nargin >= 3
!! ij = sort(ij, 2, sorting_direction) % `sorting_direction` is 'descend' of 'ascend'
!!end
@@ -2024,8 +2024,8 @@ function setij(n, npt, sorting_direction) result(ij)
! Postconditions
if (DEBUGGING) then
- call assert(size(ij, 1) == 2 .and. size(ij, 2) == max(0_IK, npt - 2_IK * n - 1_IK), &
- & 'SIZE(IJ) == [2, NPT - 2*N - 1]', srname)
+ call assert(size(ij, 1) == 2 .and. size(ij, 2) == max(0_IK, npt-2_IK*n - 1_IK), &
+ & 'SIZE(IJ) == [2, NPT-2*N-1]', srname)
call assert(all(ij >= 1 .and. ij <= n), '1 <= IJ <= N', srname)
if (present(sorting_direction)) then
if (lower(sorting_direction) == 'descend') then
diff --git a/benchmark/rescue_idz/fortran/common/ppf.h b/benchmark/rescue_idz/230406/fortran/common/ppf.h
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/ppf.h
rename to benchmark/rescue_idz/230406/fortran/common/ppf.h
diff --git a/benchmark/rescue_idz/fortran/common/preproc.f90 b/benchmark/rescue_idz/230406/fortran/common/preproc.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/preproc.f90
rename to benchmark/rescue_idz/230406/fortran/common/preproc.f90
diff --git a/benchmark/rescue_idz/fortran/common/rand.f90 b/benchmark/rescue_idz/230406/fortran/common/rand.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/rand.f90
rename to benchmark/rescue_idz/230406/fortran/common/rand.f90
diff --git a/benchmark/rescue_idz/fortran/common/ratio.f90 b/benchmark/rescue_idz/230406/fortran/common/ratio.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/ratio.f90
rename to benchmark/rescue_idz/230406/fortran/common/ratio.f90
diff --git a/benchmark/rescue_idz/fortran/common/redrho.f90 b/benchmark/rescue_idz/230406/fortran/common/redrho.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/redrho.f90
rename to benchmark/rescue_idz/230406/fortran/common/redrho.f90
diff --git a/benchmark/rescue_idz/fortran/common/selectx.f90 b/benchmark/rescue_idz/230406/fortran/common/selectx.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/selectx.f90
rename to benchmark/rescue_idz/230406/fortran/common/selectx.f90
diff --git a/benchmark/rescue_idz/fortran/common/shiftbase.f90 b/benchmark/rescue_idz/230406/fortran/common/shiftbase.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/shiftbase.f90
rename to benchmark/rescue_idz/230406/fortran/common/shiftbase.f90
diff --git a/benchmark/rescue_idz/fortran/common/string.f90 b/benchmark/rescue_idz/230406/fortran/common/string.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/string.f90
rename to benchmark/rescue_idz/230406/fortran/common/string.f90
diff --git a/benchmark/rescue_idz/fortran/common/univar.f90 b/benchmark/rescue_idz/230406/fortran/common/univar.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/univar.f90
rename to benchmark/rescue_idz/230406/fortran/common/univar.f90
diff --git a/benchmark/rescue_idz/fortran/common/xinbd.f90 b/benchmark/rescue_idz/230406/fortran/common/xinbd.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/common/xinbd.f90
rename to benchmark/rescue_idz/230406/fortran/common/xinbd.f90
diff --git a/benchmark/rescue_idz/fortran/examples/.fortls b/benchmark/rescue_idz/230406/fortran/examples/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/.fortls
rename to benchmark/rescue_idz/230406/fortran/examples/.fortls
diff --git a/benchmark/rescue_idz/fortran/examples/README.txt b/benchmark/rescue_idz/230406/fortran/examples/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/README.txt
rename to benchmark/rescue_idz/230406/fortran/examples/README.txt
diff --git a/benchmark/rescue_idz/fortran/examples/bobyqa/Makefile b/benchmark/rescue_idz/230406/fortran/examples/bobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/bobyqa/Makefile
rename to benchmark/rescue_idz/230406/fortran/examples/bobyqa/Makefile
diff --git a/benchmark/rescue_idz/fortran/examples/bobyqa/bobyqa_example.f90 b/benchmark/rescue_idz/230406/fortran/examples/bobyqa/bobyqa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/bobyqa/bobyqa_example.f90
rename to benchmark/rescue_idz/230406/fortran/examples/bobyqa/bobyqa_example.f90
diff --git a/benchmark/rescue_idz/fortran/examples/cobyla/Makefile b/benchmark/rescue_idz/230406/fortran/examples/cobyla/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/cobyla/Makefile
rename to benchmark/rescue_idz/230406/fortran/examples/cobyla/Makefile
diff --git a/benchmark/rescue_idz/fortran/examples/cobyla/cobyla_example.f90 b/benchmark/rescue_idz/230406/fortran/examples/cobyla/cobyla_example.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/cobyla/cobyla_example.f90
rename to benchmark/rescue_idz/230406/fortran/examples/cobyla/cobyla_example.f90
diff --git a/benchmark/rescue_idz/fortran/examples/lincoa/Makefile b/benchmark/rescue_idz/230406/fortran/examples/lincoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/lincoa/Makefile
rename to benchmark/rescue_idz/230406/fortran/examples/lincoa/Makefile
diff --git a/benchmark/rescue_idz/fortran/examples/lincoa/lincoa_example.f90 b/benchmark/rescue_idz/230406/fortran/examples/lincoa/lincoa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/lincoa/lincoa_example.f90
rename to benchmark/rescue_idz/230406/fortran/examples/lincoa/lincoa_example.f90
diff --git a/benchmark/rescue_idz/fortran/examples/newuoa/Makefile b/benchmark/rescue_idz/230406/fortran/examples/newuoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/newuoa/Makefile
rename to benchmark/rescue_idz/230406/fortran/examples/newuoa/Makefile
diff --git a/benchmark/rescue_idz/fortran/examples/newuoa/newuoa_example.f90 b/benchmark/rescue_idz/230406/fortran/examples/newuoa/newuoa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/newuoa/newuoa_example.f90
rename to benchmark/rescue_idz/230406/fortran/examples/newuoa/newuoa_example.f90
diff --git a/benchmark/rescue_idz/fortran/examples/uobyqa/Makefile b/benchmark/rescue_idz/230406/fortran/examples/uobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/uobyqa/Makefile
rename to benchmark/rescue_idz/230406/fortran/examples/uobyqa/Makefile
diff --git a/benchmark/rescue_idz/fortran/examples/uobyqa/uobyqa_example.f90 b/benchmark/rescue_idz/230406/fortran/examples/uobyqa/uobyqa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/examples/uobyqa/uobyqa_example.f90
rename to benchmark/rescue_idz/230406/fortran/examples/uobyqa/uobyqa_example.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/.fortls b/benchmark/rescue_idz/230406/fortran/lincoa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/.fortls
rename to benchmark/rescue_idz/230406/fortran/lincoa/.fortls
diff --git a/benchmark/rescue_idz/fortran/lincoa/README.txt b/benchmark/rescue_idz/230406/fortran/lincoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/README.txt
rename to benchmark/rescue_idz/230406/fortran/lincoa/README.txt
diff --git a/benchmark/rescue_idz/fortran/lincoa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/lincoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/lincoa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/lincoa/geometry.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/fortran/lincoa/geometry.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/geometry.f90
index 35e36e6a59..3b54e6a072 100644
--- a/benchmark/rescue_idz/fortran/lincoa/geometry.f90
+++ b/benchmark/rescue_idz/230406/fortran/lincoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: February 2022
!
-! Last Modified: Tuesday, March 21, 2023 PM10:57:53
+! Last Modified: Tuesday, October 03, 2023 AM10:50:00
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -97,7 +97,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/fortran/lincoa/getact.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/getact.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/getact.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/getact.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/initialize.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/initialize.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/initialize.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/lincoa.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/lincoa.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/lincoa.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/lincob.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/lincob.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/lincob.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/lincob.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/trustregion.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/trustregion.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/trustregion.f90
diff --git a/benchmark/rescue_idz/fortran/lincoa/update.f90 b/benchmark/rescue_idz/230406/fortran/lincoa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/lincoa/update.f90
rename to benchmark/rescue_idz/230406/fortran/lincoa/update.f90
diff --git a/benchmark/rescue_idz/fortran/newuoa/.fortls b/benchmark/rescue_idz/230406/fortran/newuoa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/.fortls
rename to benchmark/rescue_idz/230406/fortran/newuoa/.fortls
diff --git a/benchmark/rescue_idz/fortran/newuoa/README.txt b/benchmark/rescue_idz/230406/fortran/newuoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/README.txt
rename to benchmark/rescue_idz/230406/fortran/newuoa/README.txt
diff --git a/benchmark/rescue_idz/fortran/newuoa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/newuoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/newuoa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/newuoa/geometry.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/fortran/newuoa/geometry.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/geometry.f90
index 7711d451fb..d8ba3f12e4 100644
--- a/benchmark/rescue_idz/fortran/newuoa/geometry.f90
+++ b/benchmark/rescue_idz/230406/fortran/newuoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: July 2020
!
-! Last Modified: Friday, March 31, 2023 AM09:47:10
+! Last Modified: Tuesday, October 03, 2023 AM10:50:18
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -104,7 +104,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/fortran/newuoa/initialize.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/initialize.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/initialize.f90
diff --git a/benchmark/rescue_idz/fortran/newuoa/newuoa.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/newuoa.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/newuoa.f90
diff --git a/benchmark/rescue_idz/fortran/newuoa/newuob.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/newuob.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/newuob.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/newuob.f90
diff --git a/benchmark/rescue_idz/fortran/newuoa/trustregion.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/trustregion.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/trustregion.f90
diff --git a/benchmark/rescue_idz/fortran/newuoa/update.f90 b/benchmark/rescue_idz/230406/fortran/newuoa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/newuoa/update.f90
rename to benchmark/rescue_idz/230406/fortran/newuoa/update.f90
diff --git a/benchmark/rescue_idz/fortran/original/README.txt b/benchmark/rescue_idz/230406/fortran/original/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/LICENCE.txt b/benchmark/rescue_idz/230406/fortran/original/bobyqa/LICENCE.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/LICENCE.txt
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/LICENCE.txt
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/Makefile b/benchmark/rescue_idz/230406/fortran/original/bobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/Makefile
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/Makefile
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/README.txt b/benchmark/rescue_idz/230406/fortran/original/bobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/altmov.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/altmov.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/altmov.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/altmov.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/bobyqa.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/bobyqa.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/bobyqa.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/bobyqa.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/bobyqb.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/bobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/bobyqb.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/bobyqb.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/calfun.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/calfun.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/calfun.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/email.txt b/benchmark/rescue_idz/230406/fortran/original/bobyqa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/email.txt
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/email.txt
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/main.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/main.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/main.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/main.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/output.txt b/benchmark/rescue_idz/230406/fortran/original/bobyqa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/output.txt
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/output.txt
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/prelim.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/prelim.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/prelim.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/rescue.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/rescue.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/rescue.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/rescue.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/trsbox.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/trsbox.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/trsbox.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/trsbox.f
diff --git a/benchmark/rescue_idz/fortran/original/bobyqa/update.f b/benchmark/rescue_idz/230406/fortran/original/bobyqa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/bobyqa/update.f
rename to benchmark/rescue_idz/230406/fortran/original/bobyqa/update.f
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/LICENCE.txt b/benchmark/rescue_idz/230406/fortran/original/cobyla/LICENCE.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/LICENCE.txt
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/LICENCE.txt
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/Makefile b/benchmark/rescue_idz/230406/fortran/original/cobyla/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/Makefile
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/Makefile
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/README.txt b/benchmark/rescue_idz/230406/fortran/original/cobyla/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/calcfc.f b/benchmark/rescue_idz/230406/fortran/original/cobyla/calcfc.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/calcfc.f
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/calcfc.f
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/cobyla.f b/benchmark/rescue_idz/230406/fortran/original/cobyla/cobyla.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/cobyla.f
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/cobyla.f
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/cobylb.f b/benchmark/rescue_idz/230406/fortran/original/cobyla/cobylb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/cobylb.f
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/cobylb.f
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/email.txt b/benchmark/rescue_idz/230406/fortran/original/cobyla/email.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/email.txt
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/email.txt
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/main.f b/benchmark/rescue_idz/230406/fortran/original/cobyla/main.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/main.f
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/main.f
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/output.txt b/benchmark/rescue_idz/230406/fortran/original/cobyla/output.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/output.txt
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/output.txt
diff --git a/benchmark/rescue_idz/fortran/original/cobyla/trstlp.f b/benchmark/rescue_idz/230406/fortran/original/cobyla/trstlp.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/cobyla/trstlp.f
rename to benchmark/rescue_idz/230406/fortran/original/cobyla/trstlp.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/LICENCE.txt b/benchmark/rescue_idz/230406/fortran/original/lincoa/LICENCE.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/LICENCE.txt
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/LICENCE.txt
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/Makefile b/benchmark/rescue_idz/230406/fortran/original/lincoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/Makefile
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/Makefile
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/README.txt b/benchmark/rescue_idz/230406/fortran/original/lincoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/calfun.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/calfun.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/calfun.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/email.txt b/benchmark/rescue_idz/230406/fortran/original/lincoa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/email.txt
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/email.txt
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/getact.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/getact.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/getact.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/getact.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/lincoa.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/lincoa.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/lincoa.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/lincoa.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/lincob.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/lincob.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/lincob.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/lincob.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/main.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/main.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/main.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/main.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/output.txt b/benchmark/rescue_idz/230406/fortran/original/lincoa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/output.txt
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/output.txt
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/prelim.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/prelim.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/prelim.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/qmstep.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/qmstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/qmstep.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/qmstep.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/trstep.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/trstep.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/trstep.f
diff --git a/benchmark/rescue_idz/fortran/original/lincoa/update.f b/benchmark/rescue_idz/230406/fortran/original/lincoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/lincoa/update.f
rename to benchmark/rescue_idz/230406/fortran/original/lincoa/update.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/LICENCE.txt b/benchmark/rescue_idz/230406/fortran/original/newuoa/LICENCE.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/LICENCE.txt
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/LICENCE.txt
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/Makefile b/benchmark/rescue_idz/230406/fortran/original/newuoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/Makefile
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/Makefile
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/README.txt b/benchmark/rescue_idz/230406/fortran/original/newuoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/bigden.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/bigden.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/bigden.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/bigden.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/biglag.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/biglag.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/biglag.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/biglag.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/calfun.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/calfun.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/calfun.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/email.txt b/benchmark/rescue_idz/230406/fortran/original/newuoa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/email.txt
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/email.txt
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/main.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/main.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/main.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/main.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/newuoa.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/newuoa.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/newuoa.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/newuoa.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/newuob.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/newuob.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/newuob.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/newuob.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/output.txt b/benchmark/rescue_idz/230406/fortran/original/newuoa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/output.txt
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/output.txt
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/trsapp.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/trsapp.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/trsapp.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/trsapp.f
diff --git a/benchmark/rescue_idz/fortran/original/newuoa/update.f b/benchmark/rescue_idz/230406/fortran/original/newuoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/newuoa/update.f
rename to benchmark/rescue_idz/230406/fortran/original/newuoa/update.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/LICENCE.txt b/benchmark/rescue_idz/230406/fortran/original/uobyqa/LICENCE.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/LICENCE.txt
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/LICENCE.txt
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/Makefile b/benchmark/rescue_idz/230406/fortran/original/uobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/Makefile
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/Makefile
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/README.txt b/benchmark/rescue_idz/230406/fortran/original/uobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/README.txt
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/README.txt
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/calfun.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/calfun.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/calfun.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/email.txt b/benchmark/rescue_idz/230406/fortran/original/uobyqa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/email.txt
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/email.txt
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/lagmax.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/lagmax.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/lagmax.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/lagmax.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/main.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/main.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/main.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/main.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/output.txt b/benchmark/rescue_idz/230406/fortran/original/uobyqa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/output.txt
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/output.txt
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/trstep.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/trstep.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/trstep.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/uobyqa.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/uobyqa.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/uobyqa.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/uobyqa.f
diff --git a/benchmark/rescue_idz/fortran/original/uobyqa/uobyqb.f b/benchmark/rescue_idz/230406/fortran/original/uobyqa/uobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/original/uobyqa/uobyqb.f
rename to benchmark/rescue_idz/230406/fortran/original/uobyqa/uobyqb.f
diff --git a/benchmark/rescue_idz/fortran/prima_f90 b/benchmark/rescue_idz/230406/fortran/prima_f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/prima_f90
rename to benchmark/rescue_idz/230406/fortran/prima_f90
diff --git a/benchmark/rescue_idz/fortran/tests/.fortls b/benchmark/rescue_idz/230406/fortran/tests/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/.fortls
rename to benchmark/rescue_idz/230406/fortran/tests/.fortls
diff --git a/benchmark/rescue_idz/fortran/tests/Makefile b/benchmark/rescue_idz/230406/fortran/tests/Makefile
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/Makefile
rename to benchmark/rescue_idz/230406/fortran/tests/Makefile
diff --git a/benchmark/rescue_idz/fortran/tests/README.txt b/benchmark/rescue_idz/230406/fortran/tests/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/README.txt
rename to benchmark/rescue_idz/230406/fortran/tests/README.txt
diff --git a/benchmark/rescue_idz/fortran/tests/a9src b/benchmark/rescue_idz/230406/fortran/tests/a9src
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/a9src
rename to benchmark/rescue_idz/230406/fortran/tests/a9src
diff --git a/benchmark/rescue_idz/fortran/tests/checktest b/benchmark/rescue_idz/230406/fortran/tests/checktest
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/checktest
rename to benchmark/rescue_idz/230406/fortran/tests/checktest
diff --git a/benchmark/rescue_idz/fortran/tests/fvdsrc b/benchmark/rescue_idz/230406/fortran/tests/fvdsrc
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/fvdsrc
rename to benchmark/rescue_idz/230406/fortran/tests/fvdsrc
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.bobyqa b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.bobyqa
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.bobyqa
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.bobyqa
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.cobyla b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.cobyla
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.cobyla
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.cobyla
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.common b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.common
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.common
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.common
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.lincoa b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.lincoa
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.lincoa
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.lincoa
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.newuoa b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.newuoa
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.newuoa
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.newuoa
diff --git a/benchmark/rescue_idz/fortran/tests/makefiles/Makefile.uobyqa b/benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.uobyqa
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/makefiles/Makefile.uobyqa
rename to benchmark/rescue_idz/230406/fortran/tests/makefiles/Makefile.uobyqa
diff --git a/benchmark/rescue_idz/fortran/tests/old/Makefile.dev b/benchmark/rescue_idz/230406/fortran/tests/old/Makefile.dev
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/old/Makefile.dev
rename to benchmark/rescue_idz/230406/fortran/tests/old/Makefile.dev
diff --git a/benchmark/rescue_idz/fortran/tests/old/flsrc b/benchmark/rescue_idz/230406/fortran/tests/old/flsrc
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/old/flsrc
rename to benchmark/rescue_idz/230406/fortran/tests/old/flsrc
diff --git a/benchmark/rescue_idz/fortran/tests/old/nagsrc b/benchmark/rescue_idz/230406/fortran/tests/old/nagsrc
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/old/nagsrc
rename to benchmark/rescue_idz/230406/fortran/tests/old/nagsrc
diff --git a/benchmark/rescue_idz/fortran/tests/sunsrc b/benchmark/rescue_idz/230406/fortran/tests/sunsrc
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/sunsrc
rename to benchmark/rescue_idz/230406/fortran/tests/sunsrc
diff --git a/benchmark/rescue_idz/fortran/tests/test.f90 b/benchmark/rescue_idz/230406/fortran/tests/test.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test.f90
diff --git a/benchmark/rescue_idz/fortran/tests/test_bobyqa.f90 b/benchmark/rescue_idz/230406/fortran/tests/test_bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test_bobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test_bobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/tests/test_cobyla.f90 b/benchmark/rescue_idz/230406/fortran/tests/test_cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test_cobyla.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test_cobyla.f90
diff --git a/benchmark/rescue_idz/fortran/tests/test_lincoa.f90 b/benchmark/rescue_idz/230406/fortran/tests/test_lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test_lincoa.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test_lincoa.f90
diff --git a/benchmark/rescue_idz/fortran/tests/test_newuoa.f90 b/benchmark/rescue_idz/230406/fortran/tests/test_newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test_newuoa.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test_newuoa.f90
diff --git a/benchmark/rescue_idz/fortran/tests/test_uobyqa.f90 b/benchmark/rescue_idz/230406/fortran/tests/test_uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/test_uobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/tests/test_uobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/.fortls b/benchmark/rescue_idz/230406/fortran/tests/testsuite/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/.fortls
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/.fortls
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/bigprob.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/bigprob.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/bigprob.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/bigprob.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/chebyquad.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/chebyquad.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/chebyquad.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/chebyquad.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/chrosen.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/chrosen.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/chrosen.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/chrosen.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/circle.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/circle.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/circle.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/circle.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/datetime.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/datetime.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/datetime.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/datetime.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/ellipsoid.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/ellipsoid.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/ellipsoid.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/ellipsoid.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/ffiles.txt b/benchmark/rescue_idz/230406/fortran/tests/testsuite/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/fletcheq1.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/fletcheq1.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/fletcheq1.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/fletcheq1.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/fletcheq2.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/fletcheq2.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/fletcheq2.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/fletcheq2.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/freeform.m b/benchmark/rescue_idz/230406/fortran/tests/testsuite/freeform.m
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/freeform.m
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/freeform.m
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/hexagon.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/hexagon.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/hexagon.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/hexagon.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/hs100.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/hs100.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/hs100.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/hs100.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/noise.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/noise.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/noise.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/noise.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/param.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/param.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/param.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/param.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/prob.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/prob.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/prob.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/prob.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/ptinsq.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/ptinsq.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/ptinsq.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/ptinsq.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/rsnszk.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/rsnszk.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/rsnszk.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/rsnszk.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/tetrahedron.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/tetrahedron.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/tetrahedron.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/tetrahedron.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/trigsabs.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/trigsabs.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/trigsabs.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/trigsabs.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/trigssqs.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/trigssqs.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/trigssqs.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/trigssqs.f90
diff --git a/benchmark/rescue_idz/fortran/tests/testsuite/vardim.f90 b/benchmark/rescue_idz/230406/fortran/tests/testsuite/vardim.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/tests/testsuite/vardim.f90
rename to benchmark/rescue_idz/230406/fortran/tests/testsuite/vardim.f90
diff --git a/benchmark/rescue_idz/fortran/uobyqa/.fortls b/benchmark/rescue_idz/230406/fortran/uobyqa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/.fortls
rename to benchmark/rescue_idz/230406/fortran/uobyqa/.fortls
diff --git a/benchmark/rescue_idz/fortran/uobyqa/README.txt b/benchmark/rescue_idz/230406/fortran/uobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/README.txt
rename to benchmark/rescue_idz/230406/fortran/uobyqa/README.txt
diff --git a/benchmark/rescue_idz/fortran/uobyqa/ffiles.txt b/benchmark/rescue_idz/230406/fortran/uobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/fortran/uobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/fortran/uobyqa/geometry.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/fortran/uobyqa/geometry.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/geometry.f90
index b2e4b7deee..8d59a0ba8b 100644
--- a/benchmark/rescue_idz/fortran/uobyqa/geometry.f90
+++ b/benchmark/rescue_idz/230406/fortran/uobyqa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: February 2022
!
-! Last Modified: Tuesday, March 21, 2023 PM11:01:31
+! Last Modified: Tuesday, October 03, 2023 AM10:50:08
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -92,7 +92,7 @@ function setdrop_tr(kopt, ximproved, d, pl, rho, xpt) result(knew)
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/fortran/uobyqa/initialize.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/initialize.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/initialize.f90
diff --git a/benchmark/rescue_idz/fortran/uobyqa/trustregion.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/trustregion.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/trustregion.f90
diff --git a/benchmark/rescue_idz/fortran/uobyqa/uobyqa.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/uobyqa.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/uobyqa.f90
diff --git a/benchmark/rescue_idz/fortran/uobyqa/uobyqb.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/uobyqb.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/uobyqb.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/uobyqb.f90
diff --git a/benchmark/rescue_idz/fortran/uobyqa/update.f90 b/benchmark/rescue_idz/230406/fortran/uobyqa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/fortran/uobyqa/update.f90
rename to benchmark/rescue_idz/230406/fortran/uobyqa/update.f90
diff --git a/benchmark/rescue_idz/matlab/CHANGES b/benchmark/rescue_idz/230406/matlab/CHANGES
similarity index 100%
rename from benchmark/rescue_idz/matlab/CHANGES
rename to benchmark/rescue_idz/230406/matlab/CHANGES
diff --git a/benchmark/rescue_idz/matlab/examples/README.txt b/benchmark/rescue_idz/230406/matlab/examples/README.txt
similarity index 100%
rename from benchmark/rescue_idz/matlab/examples/README.txt
rename to benchmark/rescue_idz/230406/matlab/examples/README.txt
diff --git a/benchmark/rescue_idz/matlab/examples/rosenbrock_example.m b/benchmark/rescue_idz/230406/matlab/examples/rosenbrock_example.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/examples/rosenbrock_example.m
rename to benchmark/rescue_idz/230406/matlab/examples/rosenbrock_example.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/newuoa_mat.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/newuoa_mat.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/newuoa_mat.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/newuoa_mat.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/calquad.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/calquad.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/calquad.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/calquad.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/consts.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/consts.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/consts.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/consts.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/fmsg.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/fmsg.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/fmsg.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/fmsg.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/geostep.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/geostep.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/geostep.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/geostep.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/infos.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/infos.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/infos.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/infos.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/inith.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/inith.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/inith.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/inith.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/initq.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/initq.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/initq.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/initq.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/initxf.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/initxf.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/initxf.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/initxf.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/newuob.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/newuob.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/newuob.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/newuob.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/preproc.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/preproc.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/preproc.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/preproc.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/retmsg.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/retmsg.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/retmsg.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/retmsg.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/rhomsg.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/rhomsg.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/rhomsg.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/rhomsg.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/shiftbase.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/shiftbase.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/shiftbase.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/shiftbase.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/trrad.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/trrad.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/trrad.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/trrad.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/trsapp.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/trsapp.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/trsapp.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/trsapp.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/tryqalt.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/tryqalt.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/tryqalt.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/tryqalt.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/updateh.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/updateh.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/updateh.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/updateh.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/updateq.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/updateq.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/updateq.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/updateq.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/verisize.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/verisize.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/verisize.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/verisize.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/verisym.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/verisym.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/verisym.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/verisym.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/vlagbeta.m b/benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/vlagbeta.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/+newuoa_mat/private/vlagbeta.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/+newuoa_mat/private/vlagbeta.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/README.txt b/benchmark/rescue_idz/230406/matlab/interfaces/README.txt
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/README.txt
rename to benchmark/rescue_idz/230406/matlab/interfaces/README.txt
diff --git a/benchmark/rescue_idz/matlab/interfaces/bobyqa.m b/benchmark/rescue_idz/230406/matlab/interfaces/bobyqa.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/bobyqa.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/bobyqa.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/cobyla.m b/benchmark/rescue_idz/230406/matlab/interfaces/cobyla.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/cobyla.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/cobyla.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/lincoa.m b/benchmark/rescue_idz/230406/matlab/interfaces/lincoa.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/lincoa.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/lincoa.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/newuoa.m b/benchmark/rescue_idz/230406/matlab/interfaces/newuoa.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/newuoa.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/newuoa.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/prima.m b/benchmark/rescue_idz/230406/matlab/interfaces/prima.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/prima.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/prima.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/all_solvers.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/all_solvers.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/all_solvers.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/all_solvers.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/dbgstr.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/dbgstr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/dbgstr.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/dbgstr.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/evalcon.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/evalcon.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/evalcon.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/evalcon.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/evalobj.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/evalobj.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/evalobj.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/evalobj.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/get_cstrv.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/get_cstrv.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/get_cstrv.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/get_cstrv.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/get_mexname.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/get_mexname.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/get_mexname.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/get_mexname.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/getmax.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/getmax.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/getmax.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/getmax.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/ischarstr.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/ischarstr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/ischarstr.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/ischarstr.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isintegerscalar.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isintegerscalar.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isintegerscalar.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isintegerscalar.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/islogicalscalar.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/islogicalscalar.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/islogicalscalar.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/islogicalscalar.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isrealcolumn.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isrealcolumn.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isrealcolumn.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isrealcolumn.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isrealmatrix.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isrealmatrix.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isrealmatrix.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isrealmatrix.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isrealrow.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isrealrow.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isrealrow.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isrealrow.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isrealscalar.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isrealscalar.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isrealscalar.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isrealscalar.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/isrealvector.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/isrealvector.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/isrealvector.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/isrealvector.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/maxint.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/maxint.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/maxint.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/maxint.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/package_info.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/package_info.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/package_info.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/package_info.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/postprima.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/postprima.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/postprima.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/postprima.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/preprima.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/preprima.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/preprima.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/preprima.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/project.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/project.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/project.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/project.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/private/rmempty.m b/benchmark/rescue_idz/230406/matlab/interfaces/private/rmempty.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/private/rmempty.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/private/rmempty.m
diff --git a/benchmark/rescue_idz/matlab/interfaces/uobyqa.m b/benchmark/rescue_idz/230406/matlab/interfaces/uobyqa.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/interfaces/uobyqa.m
rename to benchmark/rescue_idz/230406/matlab/interfaces/uobyqa.m
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/.fortls b/benchmark/rescue_idz/230406/matlab/mex_gateways/.fortls
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/.fortls
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/.fortls
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/README.txt b/benchmark/rescue_idz/230406/matlab/mex_gateways/README.txt
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/README.txt
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/README.txt
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/bobyqa_mex.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/bobyqa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/bobyqa_mex.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/bobyqa_mex.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/cbfun.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/cbfun.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/cbfun.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/cbfun.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/cobyla_mex.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/cobyla_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/cobyla_mex.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/cobyla_mex.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/debug.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/debug.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/debug.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/debug.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/fmxapi.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/fmxapi.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/fmxapi.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/fmxapi.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/lincoa_mex.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/lincoa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/lincoa_mex.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/lincoa_mex.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/newuoa_mex.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/newuoa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/newuoa_mex.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/newuoa_mex.F90
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/.fortls b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/.fortls
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/.fortls
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/.fortls
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/Makefile b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/Makefile
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/Makefile
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/Makefile
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.cobyla b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.common b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.common
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.common
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.common
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.lincoa b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.newuoa b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/old/Makefile.dev b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/tests/s9src b/benchmark/rescue_idz/230406/matlab/mex_gateways/tests/s9src
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/tests/s9src
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/tests/s9src
diff --git a/benchmark/rescue_idz/matlab/mex_gateways/uobyqa_mex.F90 b/benchmark/rescue_idz/230406/matlab/mex_gateways/uobyqa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/matlab/mex_gateways/uobyqa_mex.F90
rename to benchmark/rescue_idz/230406/matlab/mex_gateways/uobyqa_mex.F90
diff --git a/benchmark/rescue_idz/matlab/notes/How_to_call_MATLAB_functions_under_a_directory b/benchmark/rescue_idz/230406/matlab/notes/How_to_call_MATLAB_functions_under_a_directory
similarity index 100%
rename from benchmark/rescue_idz/matlab/notes/How_to_call_MATLAB_functions_under_a_directory
rename to benchmark/rescue_idz/230406/matlab/notes/How_to_call_MATLAB_functions_under_a_directory
diff --git a/benchmark/rescue_idz/matlab/setup_tools/README.txt b/benchmark/rescue_idz/230406/matlab/setup_tools/README.txt
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/README.txt
rename to benchmark/rescue_idz/230406/matlab/setup_tools/README.txt
diff --git a/benchmark/rescue_idz/matlab/setup_tools/add_save_path.m b/benchmark/rescue_idz/230406/matlab/setup_tools/add_save_path.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/add_save_path.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/add_save_path.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/all_precisions_possible.m b/benchmark/rescue_idz/230406/matlab/setup_tools/all_precisions_possible.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/all_precisions_possible.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/all_precisions_possible.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/all_solvers.m b/benchmark/rescue_idz/230406/matlab/setup_tools/all_solvers.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/all_solvers.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/all_solvers.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/all_variants_possible.m b/benchmark/rescue_idz/230406/matlab/setup_tools/all_variants_possible.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/all_variants_possible.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/all_variants_possible.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/clean_mex.m b/benchmark/rescue_idz/230406/matlab/setup_tools/clean_mex.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/clean_mex.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/clean_mex.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/compile.m b/benchmark/rescue_idz/230406/matlab/setup_tools/compile.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/compile.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/compile.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/copy_shared_tools.m b/benchmark/rescue_idz/230406/matlab/setup_tools/copy_shared_tools.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/copy_shared_tools.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/copy_shared_tools.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/copyfiles.m b/benchmark/rescue_idz/230406/matlab/setup_tools/copyfiles.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/copyfiles.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/copyfiles.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/create_all_precisions.m b/benchmark/rescue_idz/230406/matlab/setup_tools/create_all_precisions.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/create_all_precisions.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/create_all_precisions.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/create_all_variants.m b/benchmark/rescue_idz/230406/matlab/setup_tools/create_all_variants.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/create_all_variants.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/create_all_variants.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/dbgstr.m b/benchmark/rescue_idz/230406/matlab/setup_tools/dbgstr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/dbgstr.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/dbgstr.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/del_str_ln.m b/benchmark/rescue_idz/230406/matlab/setup_tools/del_str_ln.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/del_str_ln.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/del_str_ln.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/files_with_wildcard.m b/benchmark/rescue_idz/230406/matlab/setup_tools/files_with_wildcard.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/files_with_wildcard.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/files_with_wildcard.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/freeform.m b/benchmark/rescue_idz/230406/matlab/setup_tools/freeform.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/freeform.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/freeform.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/get_mexname.m b/benchmark/rescue_idz/230406/matlab/setup_tools/get_mexname.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/get_mexname.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/get_mexname.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/getfintrf.m b/benchmark/rescue_idz/230406/matlab/setup_tools/getfintrf.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/getfintrf.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/getfintrf.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/interform.m b/benchmark/rescue_idz/230406/matlab/setup_tools/interform.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/interform.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/interform.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/isavailable.m b/benchmark/rescue_idz/230406/matlab/setup_tools/isavailable.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/isavailable.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/isavailable.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/ischarstr.m b/benchmark/rescue_idz/230406/matlab/setup_tools/ischarstr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/ischarstr.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/ischarstr.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/islogicalscalar.m b/benchmark/rescue_idz/230406/matlab/setup_tools/islogicalscalar.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/islogicalscalar.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/islogicalscalar.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/isrealscalar.m b/benchmark/rescue_idz/230406/matlab/setup_tools/isrealscalar.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/isrealscalar.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/isrealscalar.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/parse_input.m b/benchmark/rescue_idz/230406/matlab/setup_tools/parse_input.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/parse_input.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/parse_input.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/rep_str.m b/benchmark/rescue_idz/230406/matlab/setup_tools/rep_str.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/rep_str.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/rep_str.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/try_mex_setup.m b/benchmark/rescue_idz/230406/matlab/setup_tools/try_mex_setup.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/try_mex_setup.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/try_mex_setup.m
diff --git a/benchmark/rescue_idz/matlab/setup_tools/uninstall_prima.m b/benchmark/rescue_idz/230406/matlab/setup_tools/uninstall_prima.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/setup_tools/uninstall_prima.m
rename to benchmark/rescue_idz/230406/matlab/setup_tools/uninstall_prima.m
diff --git a/benchmark/rescue_idz/matlab/tests/README.txt b/benchmark/rescue_idz/230406/matlab/tests/README.txt
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/README.txt
rename to benchmark/rescue_idz/230406/matlab/tests/README.txt
diff --git a/benchmark/rescue_idz/matlab/tests/pdv.m b/benchmark/rescue_idz/230406/matlab/tests/pdv.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/pdv.m
rename to benchmark/rescue_idz/230406/matlab/tests/pdv.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/compdf b/benchmark/rescue_idz/230406/matlab/tests/private/compdf
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/compdf
rename to benchmark/rescue_idz/230406/matlab/tests/private/compdf
diff --git a/benchmark/rescue_idz/matlab/tests/private/dataprof.m b/benchmark/rescue_idz/230406/matlab/tests/private/dataprof.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/dataprof.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/dataprof.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/get_perms.m b/benchmark/rescue_idz/230406/matlab/tests/private/get_perms.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/get_perms.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/get_perms.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/get_solvers.m b/benchmark/rescue_idz/230406/matlab/tests/private/get_solvers.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/get_solvers.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/get_solvers.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/homedir.m b/benchmark/rescue_idz/230406/matlab/tests/private/homedir.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/homedir.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/homedir.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/ischstr.m b/benchmark/rescue_idz/230406/matlab/tests/private/ischstr.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/ischstr.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/ischstr.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/isequiv.m b/benchmark/rescue_idz/230406/matlab/tests/private/isequiv.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/isequiv.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/isequiv.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/isintnum.m b/benchmark/rescue_idz/230406/matlab/tests/private/isintnum.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/isintnum.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/isintnum.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/isnumvec.m b/benchmark/rescue_idz/230406/matlab/tests/private/isnumvec.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/isnumvec.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/isnumvec.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/locate_matcutest.m b/benchmark/rescue_idz/230406/matlab/tests/private/locate_matcutest.m
similarity index 77%
rename from benchmark/rescue_idz/matlab/tests/private/locate_matcutest.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/locate_matcutest.m
index 33f7d2ecdd..a7765149c0 100644
--- a/benchmark/rescue_idz/matlab/tests/private/locate_matcutest.m
+++ b/benchmark/rescue_idz/230406/matlab/tests/private/locate_matcutest.m
@@ -1,7 +1,7 @@
function cmpaths = locate_matcutest(directory)
%This function finds where MatCUTEst (https://github.com/matcutest/matcutest) is installed, adds the
% paths needed for using MatCUTEst, and returns these paths in a cell array.
-% We search at most 3 levels below the given directory, whose default value is the home directory.
+% We search at most 10 levels below the given directory, whose default value is the home directory.
% N.B.: As of 202301, MatCUTEst supports only Linux.
if nargin < 1
@@ -9,7 +9,7 @@
end
% In the following line, the "*/" before "matcutest" cannot be removed.
-[~, cmtools] = system(['find ', directory, ' -maxdepth 6 -wholename "*/matcutest/mtools/src" -type d -print -quit']);
+[~, cmtools] = system(['find ', directory, ' -maxdepth 13 -wholename "*/matcutest/mtools/src" -type d -print -quit']);
if isempty(cmtools)
error('locate_matcutest:MatCUTEstNotFound', 'MatCUTEst is not found under %s.', directory);
diff --git a/benchmark/rescue_idz/matlab/tests/private/parse_input.m b/benchmark/rescue_idz/230406/matlab/tests/private/parse_input.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/parse_input.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/parse_input.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/perfdata.m b/benchmark/rescue_idz/230406/matlab/tests/private/perfdata.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/perfdata.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/perfdata.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/perfprof.m b/benchmark/rescue_idz/230406/matlab/tests/private/perfprof.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/perfprof.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/perfprof.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/permprob.m b/benchmark/rescue_idz/230406/matlab/tests/private/permprob.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/permprob.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/permprob.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/prepare_test_dir.m b/benchmark/rescue_idz/230406/matlab/tests/private/prepare_test_dir.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/prepare_test_dir.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/prepare_test_dir.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/restore_compiler_options.m b/benchmark/rescue_idz/230406/matlab/tests/private/restore_compiler_options.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/restore_compiler_options.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/restore_compiler_options.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/set_compiler_options.m b/benchmark/rescue_idz/230406/matlab/tests/private/set_compiler_options.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/set_compiler_options.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/set_compiler_options.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/setpath.m b/benchmark/rescue_idz/230406/matlab/tests/private/setpath.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/setpath.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/setpath.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/showpath.m b/benchmark/rescue_idz/230406/matlab/tests/private/showpath.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/showpath.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/showpath.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/testcu.m b/benchmark/rescue_idz/230406/matlab/tests/private/testcu.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/testcu.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/testcu.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/testperm.m b/benchmark/rescue_idz/230406/matlab/tests/private/testperm.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/testperm.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/testperm.m
diff --git a/benchmark/rescue_idz/matlab/tests/private/warnoff.m b/benchmark/rescue_idz/230406/matlab/tests/private/warnoff.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/private/warnoff.m
rename to benchmark/rescue_idz/230406/matlab/tests/private/warnoff.m
diff --git a/benchmark/rescue_idz/matlab/tests/prof.m b/benchmark/rescue_idz/230406/matlab/tests/prof.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/prof.m
rename to benchmark/rescue_idz/230406/matlab/tests/prof.m
diff --git a/benchmark/rescue_idz/matlab/tests/profile.m b/benchmark/rescue_idz/230406/matlab/tests/profile.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/profile.m
rename to benchmark/rescue_idz/230406/matlab/tests/profile.m
diff --git a/benchmark/rescue_idz/matlab/tests/testprima.m b/benchmark/rescue_idz/230406/matlab/tests/testprima.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/testprima.m
rename to benchmark/rescue_idz/230406/matlab/tests/testprima.m
diff --git a/benchmark/rescue_idz/matlab/tests/timing.m b/benchmark/rescue_idz/230406/matlab/tests/timing.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/timing.m
rename to benchmark/rescue_idz/230406/matlab/tests/timing.m
diff --git a/benchmark/rescue_idz/matlab/tests/verify.m b/benchmark/rescue_idz/230406/matlab/tests/verify.m
similarity index 100%
rename from benchmark/rescue_idz/matlab/tests/verify.m
rename to benchmark/rescue_idz/230406/matlab/tests/verify.m
diff --git a/benchmark/rescue_idz/norma/fortran/README.txt b/benchmark/rescue_idz/230406/norma/fortran/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/.fortls b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/bobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/bobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/bobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/bobyqb.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/bobyqb.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/bobyqb.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/bobyqb.f90
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/geometry.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/geometry.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/geometry.f90
index e2bf286518..5a9861a93c 100644
--- a/benchmark/rescue_idz/norma/fortran/bobyqa/geometry.f90
+++ b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: February 2022
!
-! Last Modified: Tuesday, February 14, 2023 AM12:53:25
+! Last Modified: Tuesday, October 03, 2023 AM10:49:47
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -103,7 +103,7 @@ function setdrop_tr(kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result(knew
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/initialize.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/initialize.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/initialize.f90
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/rescue.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/rescue.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/rescue.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/rescue.f90
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/trustregion.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/trustregion.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/trustregion.f90
diff --git a/benchmark/rescue_idz/norma/fortran/bobyqa/update.f90 b/benchmark/rescue_idz/230406/norma/fortran/bobyqa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/bobyqa/update.f90
rename to benchmark/rescue_idz/230406/norma/fortran/bobyqa/update.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/README.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/altmov.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/altmov.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/altmov.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/altmov.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/bobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/bobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/bobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/bobyqb.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/bobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/bobyqb.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/bobyqb.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/prelim.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/prelim.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/prelim.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/rescue.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/rescue.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/rescue.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/rescue.f
diff --git a/benchmark/rescue_idz/fortran/classical/bobyqa/trsbox.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/trsbox.f
similarity index 100%
rename from benchmark/rescue_idz/fortran/classical/bobyqa/trsbox.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/trsbox.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/bobyqa/update.f b/benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/bobyqa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/bobyqa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/cobyla/cobyla.f90 b/benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/cobyla/cobyla.f90
rename to benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/cobyla.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/cobyla/cobylb.f b/benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/cobylb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/cobyla/cobylb.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/cobylb.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/cobyla/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/cobyla/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/cobyla/trstlp.f b/benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/trstlp.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/cobyla/trstlp.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/cobyla/trstlp.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/getact.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/getact.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/getact.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/getact.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/lincoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/lincoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/lincoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/lincob.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/lincob.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/lincob.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/lincob.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/prelim.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/prelim.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/prelim.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/qmstep.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/qmstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/qmstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/qmstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/trstep.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/trstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/trstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/lincoa/update.f b/benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/lincoa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/lincoa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/bigden.f b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/bigden.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/bigden.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/bigden.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/biglag.f b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/biglag.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/biglag.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/biglag.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/newuoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/newuoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/newuoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/newuob.f b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/newuob.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/newuob.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/newuob.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/trsapp.f b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/trsapp.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/trsapp.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/trsapp.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/newuoa/update.f b/benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/newuoa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/newuoa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/uobyqa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/uobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/classical/uobyqa/lagmax.f b/benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/lagmax.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/uobyqa/lagmax.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/lagmax.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/uobyqa/trstep.f b/benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/uobyqa/trstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/trstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/classical/uobyqa/uobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/uobyqa/uobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/uobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/classical/uobyqa/uobyqb.f b/benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/uobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/classical/uobyqa/uobyqb.f
rename to benchmark/rescue_idz/230406/norma/fortran/classical/uobyqa/uobyqb.f
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/.fortls b/benchmark/rescue_idz/230406/norma/fortran/cobyla/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/README.txt b/benchmark/rescue_idz/230406/norma/fortran/cobyla/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/cobyla.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/cobyla.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/cobyla.f90
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/cobylb.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/cobylb.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/cobylb.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/cobylb.f90
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/cobyla/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/geometry.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/geometry.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/geometry.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/geometry.f90
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/initialize.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/initialize.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/initialize.f90
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/trustregion.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/trustregion.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/trustregion.f90
diff --git a/benchmark/rescue_idz/norma/fortran/cobyla/update.f90 b/benchmark/rescue_idz/230406/norma/fortran/cobyla/update.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/cobyla/update.f90
rename to benchmark/rescue_idz/230406/norma/fortran/cobyla/update.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/.fortls b/benchmark/rescue_idz/230406/norma/fortran/common/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/common/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/common/README.txt b/benchmark/rescue_idz/230406/norma/fortran/common/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/common/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/common/checkexit.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/checkexit.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/checkexit.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/checkexit.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/consts.F90 b/benchmark/rescue_idz/230406/norma/fortran/common/consts.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/consts.F90
rename to benchmark/rescue_idz/230406/norma/fortran/common/consts.F90
diff --git a/benchmark/rescue_idz/norma/fortran/common/debug.F90 b/benchmark/rescue_idz/230406/norma/fortran/common/debug.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/debug.F90
rename to benchmark/rescue_idz/230406/norma/fortran/common/debug.F90
diff --git a/benchmark/rescue_idz/norma/fortran/common/evaluate.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/evaluate.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/evaluate.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/evaluate.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/common/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/common/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/common/flint b/benchmark/rescue_idz/230406/norma/fortran/common/flint
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/flint
rename to benchmark/rescue_idz/230406/norma/fortran/common/flint
diff --git a/benchmark/rescue_idz/norma/fortran/common/history.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/history.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/history.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/history.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/inf.F90 b/benchmark/rescue_idz/230406/norma/fortran/common/inf.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/inf.F90
rename to benchmark/rescue_idz/230406/norma/fortran/common/inf.F90
diff --git a/benchmark/rescue_idz/norma/fortran/common/infnan.F90 b/benchmark/rescue_idz/230406/norma/fortran/common/infnan.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/infnan.F90
rename to benchmark/rescue_idz/230406/norma/fortran/common/infnan.F90
diff --git a/benchmark/rescue_idz/norma/fortran/common/infos.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/infos.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/infos.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/infos.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/linalg.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/linalg.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/linalg.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/linalg.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/memory.F90 b/benchmark/rescue_idz/230406/norma/fortran/common/memory.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/memory.F90
rename to benchmark/rescue_idz/230406/norma/fortran/common/memory.F90
diff --git a/benchmark/rescue_idz/norma/fortran/common/mlint b/benchmark/rescue_idz/230406/norma/fortran/common/mlint
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/mlint
rename to benchmark/rescue_idz/230406/norma/fortran/common/mlint
diff --git a/benchmark/rescue_idz/norma/fortran/common/old/comp b/benchmark/rescue_idz/230406/norma/fortran/common/old/comp
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/old/comp
rename to benchmark/rescue_idz/230406/norma/fortran/common/old/comp
diff --git a/benchmark/rescue_idz/norma/fortran/common/old/ieee_4dev.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/old/ieee_4dev.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/old/ieee_4dev.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/old/ieee_4dev.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/output.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/output.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/output.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/output.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/pintrf.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/pintrf.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/pintrf.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/pintrf.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/powalg.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/powalg.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/powalg.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/powalg.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/ppf.h b/benchmark/rescue_idz/230406/norma/fortran/common/ppf.h
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/ppf.h
rename to benchmark/rescue_idz/230406/norma/fortran/common/ppf.h
diff --git a/benchmark/rescue_idz/norma/fortran/common/preproc.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/preproc.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/preproc.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/preproc.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/rand.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/rand.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/rand.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/rand.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/ratio.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/ratio.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/ratio.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/ratio.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/redrho.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/redrho.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/redrho.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/redrho.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/selectx.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/selectx.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/selectx.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/selectx.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/shiftbase.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/shiftbase.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/shiftbase.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/shiftbase.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/string.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/string.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/string.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/string.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/univar.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/univar.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/univar.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/univar.f90
diff --git a/benchmark/rescue_idz/norma/fortran/common/xinbd.f90 b/benchmark/rescue_idz/230406/norma/fortran/common/xinbd.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/common/xinbd.f90
rename to benchmark/rescue_idz/230406/norma/fortran/common/xinbd.f90
diff --git a/benchmark/rescue_idz/norma/fortran/examples/.fortls b/benchmark/rescue_idz/230406/norma/fortran/examples/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/examples/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/examples/README.txt b/benchmark/rescue_idz/230406/norma/fortran/examples/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/examples/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/examples/bobyqa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/examples/bobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/bobyqa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/examples/bobyqa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/examples/bobyqa/bobyqa_example.f90 b/benchmark/rescue_idz/230406/norma/fortran/examples/bobyqa/bobyqa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/bobyqa/bobyqa_example.f90
rename to benchmark/rescue_idz/230406/norma/fortran/examples/bobyqa/bobyqa_example.f90
diff --git a/benchmark/rescue_idz/norma/fortran/examples/cobyla/Makefile b/benchmark/rescue_idz/230406/norma/fortran/examples/cobyla/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/cobyla/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/examples/cobyla/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/examples/cobyla/cobyla_example.f90 b/benchmark/rescue_idz/230406/norma/fortran/examples/cobyla/cobyla_example.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/cobyla/cobyla_example.f90
rename to benchmark/rescue_idz/230406/norma/fortran/examples/cobyla/cobyla_example.f90
diff --git a/benchmark/rescue_idz/norma/fortran/examples/lincoa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/examples/lincoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/lincoa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/examples/lincoa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/examples/lincoa/lincoa_example.f90 b/benchmark/rescue_idz/230406/norma/fortran/examples/lincoa/lincoa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/lincoa/lincoa_example.f90
rename to benchmark/rescue_idz/230406/norma/fortran/examples/lincoa/lincoa_example.f90
diff --git a/benchmark/rescue_idz/norma/fortran/examples/newuoa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/examples/newuoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/newuoa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/examples/newuoa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/examples/newuoa/newuoa_example.f90 b/benchmark/rescue_idz/230406/norma/fortran/examples/newuoa/newuoa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/newuoa/newuoa_example.f90
rename to benchmark/rescue_idz/230406/norma/fortran/examples/newuoa/newuoa_example.f90
diff --git a/benchmark/rescue_idz/norma/fortran/examples/uobyqa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/examples/uobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/uobyqa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/examples/uobyqa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/examples/uobyqa/uobyqa_example.f90 b/benchmark/rescue_idz/230406/norma/fortran/examples/uobyqa/uobyqa_example.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/examples/uobyqa/uobyqa_example.f90
rename to benchmark/rescue_idz/230406/norma/fortran/examples/uobyqa/uobyqa_example.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/.fortls b/benchmark/rescue_idz/230406/norma/fortran/lincoa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/lincoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/lincoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/geometry.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/norma/fortran/lincoa/geometry.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/geometry.f90
index 93eb9effe6..23e024f9e1 100644
--- a/benchmark/rescue_idz/norma/fortran/lincoa/geometry.f90
+++ b/benchmark/rescue_idz/230406/norma/fortran/lincoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: February 2022
!
-! Last Modified: Monday, February 27, 2023 PM03:19:13
+! Last Modified: Tuesday, October 03, 2023 AM10:49:14
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -97,7 +97,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/getact.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/getact.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/getact.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/getact.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/initialize.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/initialize.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/initialize.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/lincoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/lincoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/lincoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/lincob.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/lincob.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/lincob.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/lincob.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/trustregion.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/trustregion.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/trustregion.f90
diff --git a/benchmark/rescue_idz/norma/fortran/lincoa/update.f90 b/benchmark/rescue_idz/230406/norma/fortran/lincoa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/lincoa/update.f90
rename to benchmark/rescue_idz/230406/norma/fortran/lincoa/update.f90
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/.fortls b/benchmark/rescue_idz/230406/norma/fortran/newuoa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/newuoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/newuoa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/geometry.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/norma/fortran/newuoa/geometry.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/geometry.f90
index cf90c0029e..6b1e03d587 100644
--- a/benchmark/rescue_idz/norma/fortran/newuoa/geometry.f90
+++ b/benchmark/rescue_idz/230406/norma/fortran/newuoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: July 2020
!
-! Last Modified: Sunday, March 05, 2023 PM10:04:46
+! Last Modified: Tuesday, October 03, 2023 AM10:49:39
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -104,7 +104,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/initialize.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/initialize.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/initialize.f90
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/newuoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/newuoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/newuoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/newuob.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/newuob.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/newuob.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/newuob.f90
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/trustregion.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/trustregion.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/trustregion.f90
diff --git a/benchmark/rescue_idz/norma/fortran/newuoa/update.f90 b/benchmark/rescue_idz/230406/norma/fortran/newuoa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/newuoa/update.f90
rename to benchmark/rescue_idz/230406/norma/fortran/newuoa/update.f90
diff --git a/benchmark/rescue_idz/norma/fortran/original/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/altmov.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/altmov.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/altmov.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/altmov.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/bobyqa.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/bobyqa.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/bobyqa.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/bobyqa.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/bobyqb.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/bobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/bobyqb.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/bobyqb.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/calfun.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/calfun.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/calfun.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/email.txt b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/email.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/email.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/main.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/main.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/main.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/main.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/output.txt b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/output.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/output.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/prelim.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/prelim.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/prelim.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/rescue.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/rescue.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/rescue.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/rescue.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/trsbox.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/trsbox.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/trsbox.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/trsbox.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/bobyqa/update.f b/benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/bobyqa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/bobyqa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/Makefile b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/calcfc.f b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/calcfc.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/calcfc.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/calcfc.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/cobyla.f b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/cobyla.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/cobyla.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/cobyla.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/cobylb.f b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/cobylb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/cobylb.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/cobylb.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/email.txt b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/email.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/email.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/email.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/main.f b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/main.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/main.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/main.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/output.txt b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/output.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/output.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/output.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/cobyla/trstlp.f b/benchmark/rescue_idz/230406/norma/fortran/original/cobyla/trstlp.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/cobyla/trstlp.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/cobyla/trstlp.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/calfun.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/calfun.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/calfun.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/email.txt b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/email.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/email.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/getact.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/getact.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/getact.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/getact.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/lincoa.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/lincoa.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/lincoa.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/lincoa.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/lincob.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/lincob.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/lincob.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/lincob.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/main.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/main.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/main.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/main.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/output.txt b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/output.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/output.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/prelim.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/prelim.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/prelim.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/prelim.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/qmstep.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/qmstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/qmstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/qmstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/trstep.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/trstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/trstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/lincoa/update.f b/benchmark/rescue_idz/230406/norma/fortran/original/lincoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/lincoa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/lincoa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/bigden.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/bigden.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/bigden.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/bigden.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/biglag.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/biglag.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/biglag.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/biglag.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/calfun.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/calfun.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/calfun.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/email.txt b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/email.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/email.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/main.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/main.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/main.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/main.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/newuoa.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/newuoa.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/newuoa.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/newuoa.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/newuob.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/newuob.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/newuob.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/newuob.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/output.txt b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/output.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/output.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/trsapp.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/trsapp.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/trsapp.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/trsapp.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/newuoa/update.f b/benchmark/rescue_idz/230406/norma/fortran/original/newuoa/update.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/newuoa/update.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/newuoa/update.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/Makefile b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/calfun.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/calfun.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/calfun.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/calfun.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/email.txt b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/email.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/email.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/email.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/lagmax.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/lagmax.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/lagmax.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/lagmax.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/main.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/main.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/main.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/main.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/output.txt b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/output.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/output.txt
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/output.txt
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/trstep.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/trstep.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/trstep.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/trstep.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/uobyqa.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/uobyqa.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/uobyqa.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/uobyqa.f
diff --git a/benchmark/rescue_idz/norma/fortran/original/uobyqa/uobyqb.f b/benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/uobyqb.f
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/original/uobyqa/uobyqb.f
rename to benchmark/rescue_idz/230406/norma/fortran/original/uobyqa/uobyqb.f
diff --git a/benchmark/rescue_idz/norma/fortran/prima_f90 b/benchmark/rescue_idz/230406/norma/fortran/prima_f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/prima_f90
rename to benchmark/rescue_idz/230406/norma/fortran/prima_f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/.fortls b/benchmark/rescue_idz/230406/norma/fortran/tests/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/tests/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/tests/Makefile b/benchmark/rescue_idz/230406/norma/fortran/tests/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/Makefile
rename to benchmark/rescue_idz/230406/norma/fortran/tests/Makefile
diff --git a/benchmark/rescue_idz/norma/fortran/tests/README.txt b/benchmark/rescue_idz/230406/norma/fortran/tests/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/tests/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/tests/a9src b/benchmark/rescue_idz/230406/norma/fortran/tests/a9src
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/a9src
rename to benchmark/rescue_idz/230406/norma/fortran/tests/a9src
diff --git a/benchmark/rescue_idz/norma/fortran/tests/checktest b/benchmark/rescue_idz/230406/norma/fortran/tests/checktest
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/checktest
rename to benchmark/rescue_idz/230406/norma/fortran/tests/checktest
diff --git a/benchmark/rescue_idz/norma/fortran/tests/fvdsrc b/benchmark/rescue_idz/230406/norma/fortran/tests/fvdsrc
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/fvdsrc
rename to benchmark/rescue_idz/230406/norma/fortran/tests/fvdsrc
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.bobyqa b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.bobyqa
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.bobyqa
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.bobyqa
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.cobyla b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.cobyla
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.cobyla
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.cobyla
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.common b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.common
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.common
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.common
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.lincoa b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.lincoa
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.lincoa
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.lincoa
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.newuoa b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.newuoa
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.newuoa
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.newuoa
diff --git a/benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.uobyqa b/benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.uobyqa
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/makefiles/Makefile.uobyqa
rename to benchmark/rescue_idz/230406/norma/fortran/tests/makefiles/Makefile.uobyqa
diff --git a/benchmark/rescue_idz/norma/fortran/tests/nagsrc b/benchmark/rescue_idz/230406/norma/fortran/tests/nagsrc
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/nagsrc
rename to benchmark/rescue_idz/230406/norma/fortran/tests/nagsrc
diff --git a/benchmark/rescue_idz/norma/fortran/tests/old/Makefile.dev b/benchmark/rescue_idz/230406/norma/fortran/tests/old/Makefile.dev
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/old/Makefile.dev
rename to benchmark/rescue_idz/230406/norma/fortran/tests/old/Makefile.dev
diff --git a/benchmark/rescue_idz/norma/fortran/tests/old/flsrc b/benchmark/rescue_idz/230406/norma/fortran/tests/old/flsrc
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/old/flsrc
rename to benchmark/rescue_idz/230406/norma/fortran/tests/old/flsrc
diff --git a/benchmark/rescue_idz/norma/fortran/tests/sunsrc b/benchmark/rescue_idz/230406/norma/fortran/tests/sunsrc
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/sunsrc
rename to benchmark/rescue_idz/230406/norma/fortran/tests/sunsrc
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test_bobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test_bobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test_bobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test_bobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test_cobyla.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test_cobyla.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test_cobyla.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test_cobyla.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test_lincoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test_lincoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test_lincoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test_lincoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test_newuoa.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test_newuoa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test_newuoa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test_newuoa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/test_uobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/test_uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/test_uobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/test_uobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/.fortls b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/bigprob.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/bigprob.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/bigprob.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/bigprob.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/chebyquad.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/chebyquad.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/chebyquad.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/chebyquad.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/chrosen.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/chrosen.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/chrosen.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/chrosen.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/circle.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/circle.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/circle.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/circle.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/datetime.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/datetime.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/datetime.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/datetime.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/ellipsoid.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ellipsoid.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/ellipsoid.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ellipsoid.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/fletcheq1.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/fletcheq1.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/fletcheq1.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/fletcheq1.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/fletcheq2.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/fletcheq2.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/fletcheq2.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/fletcheq2.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/freeform.m b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/freeform.m
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/freeform.m
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/freeform.m
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/hexagon.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/hexagon.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/hexagon.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/hexagon.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/hs100.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/hs100.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/hs100.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/hs100.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/noise.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/noise.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/noise.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/noise.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/param.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/param.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/param.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/param.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/prob.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/prob.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/prob.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/prob.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/ptinsq.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ptinsq.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/ptinsq.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/ptinsq.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/rsnszk.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/rsnszk.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/rsnszk.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/rsnszk.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/tetrahedron.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/tetrahedron.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/tetrahedron.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/tetrahedron.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/trigsabs.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/trigsabs.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/trigsabs.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/trigsabs.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/trigssqs.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/trigssqs.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/trigssqs.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/trigssqs.f90
diff --git a/benchmark/rescue_idz/norma/fortran/tests/testsuite/vardim.f90 b/benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/vardim.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/tests/testsuite/vardim.f90
rename to benchmark/rescue_idz/230406/norma/fortran/tests/testsuite/vardim.f90
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/.fortls b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/.fortls
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/.fortls
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/README.txt b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/README.txt
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/README.txt
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/ffiles.txt b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/ffiles.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/ffiles.txt
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/ffiles.txt
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/geometry.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/geometry.f90
similarity index 99%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/geometry.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/geometry.f90
index 16c61eba06..3bab0711c7 100644
--- a/benchmark/rescue_idz/norma/fortran/uobyqa/geometry.f90
+++ b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_mod
!
! Started: February 2022
!
-! Last Modified: Tuesday, February 14, 2023 AM12:50:12
+! Last Modified: Tuesday, October 03, 2023 AM10:49:23
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -92,7 +92,7 @@ function setdrop_tr(kopt, ximproved, d, pl, rho, xpt) result(knew)
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
+! the trust-region trial point has not been included into XPT yet --- it can not be included
! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/initialize.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/initialize.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/initialize.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/initialize.f90
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/trustregion.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/trustregion.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/trustregion.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/trustregion.f90
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/uobyqa.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/uobyqa.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/uobyqa.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/uobyqa.f90
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/uobyqb.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/uobyqb.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/uobyqb.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/uobyqb.f90
diff --git a/benchmark/rescue_idz/norma/fortran/uobyqa/update.f90 b/benchmark/rescue_idz/230406/norma/fortran/uobyqa/update.f90
similarity index 100%
rename from benchmark/rescue_idz/norma/fortran/uobyqa/update.f90
rename to benchmark/rescue_idz/230406/norma/fortran/uobyqa/update.f90
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/newuoa_norma_mat.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/newuoa_norma_mat.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/newuoa_norma_mat.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/newuoa_norma_mat.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/calquad.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/calquad.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/calquad.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/calquad.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/consts.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/consts.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/consts.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/consts.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/fmsg.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/fmsg.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/fmsg.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/fmsg.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/geostep.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/geostep.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/geostep.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/geostep.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/infos.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/infos.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/infos.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/infos.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/inith.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/inith.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/inith.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/inith.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/initq.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/initq.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/initq.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/initq.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/initxf.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/initxf.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/initxf.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/initxf.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/newuob.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/newuob.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/newuob.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/newuob.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/preproc.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/preproc.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/preproc.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/preproc.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/retmsg.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/retmsg.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/retmsg.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/retmsg.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/rhomsg.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/rhomsg.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/rhomsg.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/rhomsg.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/setdrop_tr.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/setdrop_tr.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/setdrop_tr.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/setdrop_tr.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/shiftbase.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/shiftbase.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/shiftbase.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/shiftbase.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/trrad.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/trrad.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/trrad.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/trrad.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/trsapp.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/trsapp.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/trsapp.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/trsapp.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/tryqalt.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/tryqalt.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/tryqalt.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/tryqalt.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/updateh.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/updateh.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/updateh.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/updateh.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/updateq.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/updateq.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/updateq.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/updateq.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/verisize.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/verisize.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/verisize.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/verisize.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/verisym.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/verisym.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/verisym.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/verisym.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/vlagbeta.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/vlagbeta.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/+newuoa_norma_mat/private/vlagbeta.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/+newuoa_norma_mat/private/vlagbeta.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/README.txt b/benchmark/rescue_idz/230406/norma/matlab/interfaces/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/README.txt
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/README.txt
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/bobyqa_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/bobyqa_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/bobyqa_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/bobyqa_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/cobyla_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/cobyla_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/cobyla_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/cobyla_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/lincoa_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/lincoa_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/lincoa_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/lincoa_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/newuoa_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/newuoa_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/newuoa_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/newuoa_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/prima_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/prima_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/prima_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/prima_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/all_solvers.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/all_solvers.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/all_solvers.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/all_solvers.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/dbgstr.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/dbgstr.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/dbgstr.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/dbgstr.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/evalcon.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/evalcon.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/evalcon.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/evalcon.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/evalobj.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/evalobj.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/evalobj.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/evalobj.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/get_cstrv.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/get_cstrv.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/get_cstrv.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/get_cstrv.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/get_mexname.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/get_mexname.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/get_mexname.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/get_mexname.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/getmax.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/getmax.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/getmax.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/getmax.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/ischarstr.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/ischarstr.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/ischarstr.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/ischarstr.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isintegerscalar.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isintegerscalar.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isintegerscalar.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isintegerscalar.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/islogicalscalar.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/islogicalscalar.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/islogicalscalar.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/islogicalscalar.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isrealcolumn.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealcolumn.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isrealcolumn.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealcolumn.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isrealmatrix.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealmatrix.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isrealmatrix.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealmatrix.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isrealrow.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealrow.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isrealrow.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealrow.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isrealscalar.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealscalar.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isrealscalar.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealscalar.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/isrealvector.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealvector.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/isrealvector.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/isrealvector.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/maxint.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/maxint.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/maxint.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/maxint.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/package_info.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/package_info.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/package_info.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/package_info.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/postprima_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/postprima_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/postprima_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/postprima_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/preprima_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/preprima_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/preprima_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/preprima_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/project.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/project.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/project.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/project.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/private/rmempty.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/private/rmempty.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/private/rmempty.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/private/rmempty.m
diff --git a/benchmark/rescue_idz/norma/matlab/interfaces/uobyqa_norma.m b/benchmark/rescue_idz/230406/norma/matlab/interfaces/uobyqa_norma.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/interfaces/uobyqa_norma.m
rename to benchmark/rescue_idz/230406/norma/matlab/interfaces/uobyqa_norma.m
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/.fortls b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/.fortls
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/.fortls
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/README.txt b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/README.txt
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/README.txt
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/bobyqa_mex.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/bobyqa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/bobyqa_mex.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/bobyqa_mex.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/cbfun.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/cbfun.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/cbfun.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/cbfun.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/cobyla_mex.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/cobyla_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/cobyla_mex.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/cobyla_mex.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/debug.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/debug.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/debug.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/debug.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/fmxapi.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/fmxapi.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/fmxapi.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/fmxapi.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/lincoa_mex.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/lincoa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/lincoa_mex.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/lincoa_mex.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/newuoa_mex.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/newuoa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/newuoa_mex.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/newuoa_mex.F90
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/.fortls b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/.fortls
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/.fortls
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/.fortls
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/Makefile b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/Makefile
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/Makefile
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/Makefile
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.cobyla b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.common b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.common
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.common
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.common
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.lincoa b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.newuoa b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/old/Makefile.dev b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/makefiles/old/Makefile.dev
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/tests/s9src b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/s9src
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/tests/s9src
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/tests/s9src
diff --git a/benchmark/rescue_idz/norma/matlab/mex_gateways/uobyqa_mex.F90 b/benchmark/rescue_idz/230406/norma/matlab/mex_gateways/uobyqa_mex.F90
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/mex_gateways/uobyqa_mex.F90
rename to benchmark/rescue_idz/230406/norma/matlab/mex_gateways/uobyqa_mex.F90
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/README.txt b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/README.txt
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/README.txt
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/README.txt
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/add_save_path.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/add_save_path.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/add_save_path.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/add_save_path.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/all_precisions_possible.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_precisions_possible.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/all_precisions_possible.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_precisions_possible.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/all_solvers.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_solvers.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/all_solvers.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_solvers.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/all_variants_possible.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_variants_possible.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/all_variants_possible.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/all_variants_possible.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/clean_mex.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/clean_mex.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/clean_mex.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/clean_mex.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/compile.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/compile.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/compile.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/compile.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/copy_shared_tools.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/copy_shared_tools.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/copy_shared_tools.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/copy_shared_tools.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/copyfiles.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/copyfiles.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/copyfiles.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/copyfiles.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/create_all_precisions.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/create_all_precisions.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/create_all_precisions.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/create_all_precisions.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/create_all_variants.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/create_all_variants.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/create_all_variants.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/create_all_variants.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/dbgstr.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/dbgstr.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/dbgstr.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/dbgstr.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/del_str_ln.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/del_str_ln.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/del_str_ln.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/del_str_ln.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/files_with_wildcard.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/files_with_wildcard.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/files_with_wildcard.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/files_with_wildcard.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/freeform.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/freeform.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/freeform.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/freeform.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/get_mexname.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/get_mexname.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/get_mexname.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/get_mexname.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/getfintrf.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/getfintrf.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/getfintrf.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/getfintrf.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/interform.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/interform.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/interform.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/interform.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/isavailable.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/isavailable.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/isavailable.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/isavailable.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/ischarstr.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/ischarstr.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/ischarstr.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/ischarstr.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/islogicalscalar.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/islogicalscalar.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/islogicalscalar.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/islogicalscalar.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/isrealscalar.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/isrealscalar.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/isrealscalar.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/isrealscalar.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/parse_input.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/parse_input.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/parse_input.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/parse_input.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/rep_str.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/rep_str.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/rep_str.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/rep_str.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/try_mex_setup.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/try_mex_setup.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/try_mex_setup.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/try_mex_setup.m
diff --git a/benchmark/rescue_idz/norma/matlab/setup_tools/uninstall_prima.m b/benchmark/rescue_idz/230406/norma/matlab/setup_tools/uninstall_prima.m
similarity index 100%
rename from benchmark/rescue_idz/norma/matlab/setup_tools/uninstall_prima.m
rename to benchmark/rescue_idz/230406/norma/matlab/setup_tools/uninstall_prima.m
diff --git a/benchmark/rescue_idz/norma/setup.m b/benchmark/rescue_idz/230406/norma/setup.m
similarity index 100%
rename from benchmark/rescue_idz/norma/setup.m
rename to benchmark/rescue_idz/230406/norma/setup.m
diff --git a/benchmark/rescue_idz/note b/benchmark/rescue_idz/230406/note
similarity index 98%
rename from benchmark/rescue_idz/note
rename to benchmark/rescue_idz/230406/note
index eb495f897b..941c629c76 100644
--- a/benchmark/rescue_idz/note
+++ b/benchmark/rescue_idz/230406/note
@@ -6,7 +6,7 @@ performance marginally. The settings of the test are as follows.
3. Maximum number of linear constraints for LINCOA: 20000.
4. Tested features: plain, randomizex0-eps, perm, single, dnoise-1.0e-6, signif5, noise-1.0e-4.
5. Number of random tests when applicable: 3.
-6. RHOEND: 1.0e-8.
+6. RHOEND: 1.0e-10.
7. Maximum number of function evaluations: 500*dimension.
8. Tolerance for the convergence tests in the performance profiles: 10^{-i}, i = 1, ..., 16.
diff --git a/benchmark/rescue_idz/results/classical/classical_bobyqa_bobyqa_last.1_200.b.summary.230406_2009.pdf b/benchmark/rescue_idz/230406/results/classical/classical_bobyqa_bobyqa_last.1_200.b.summary.230406_2009.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_bobyqa_bobyqa_last.1_200.b.summary.230406_2009.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_bobyqa_bobyqa_last.1_200.b.summary.230406_2009.pdf
diff --git a/benchmark/rescue_idz/results/classical/classical_bobyqa_bobyqa_last.1_50.ub.summary.230406_2009.pdf b/benchmark/rescue_idz/230406/results/classical/classical_bobyqa_bobyqa_last.1_50.ub.summary.230406_2009.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_bobyqa_bobyqa_last.1_50.ub.summary.230406_2009.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_bobyqa_bobyqa_last.1_50.ub.summary.230406_2009.pdf
diff --git a/benchmark/rescue_idz/results/classical/classical_lincoa_lincoa_last.1_200.l.summary.230406_2010.pdf b/benchmark/rescue_idz/230406/results/classical/classical_lincoa_lincoa_last.1_200.l.summary.230406_2010.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_lincoa_lincoa_last.1_200.l.summary.230406_2010.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_lincoa_lincoa_last.1_200.l.summary.230406_2010.pdf
diff --git a/benchmark/rescue_idz/results/classical/classical_lincoa_lincoa_last.1_50.ubl.summary.230406_2009.pdf b/benchmark/rescue_idz/230406/results/classical/classical_lincoa_lincoa_last.1_50.ubl.summary.230406_2009.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_lincoa_lincoa_last.1_50.ubl.summary.230406_2009.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_lincoa_lincoa_last.1_50.ubl.summary.230406_2009.pdf
diff --git a/benchmark/rescue_idz/results/classical/classical_newuoa_newuoa_last.1_200.u.summary.230406_2010.pdf b/benchmark/rescue_idz/230406/results/classical/classical_newuoa_newuoa_last.1_200.u.summary.230406_2010.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_newuoa_newuoa_last.1_200.u.summary.230406_2010.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_newuoa_newuoa_last.1_200.u.summary.230406_2010.pdf
diff --git a/benchmark/rescue_idz/results/classical/classical_newuoa_newuoa_last.1_50.u.summary.230406_2009.pdf b/benchmark/rescue_idz/230406/results/classical/classical_newuoa_newuoa_last.1_50.u.summary.230406_2009.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/classical/classical_newuoa_newuoa_last.1_50.u.summary.230406_2009.pdf
rename to benchmark/rescue_idz/230406/results/classical/classical_newuoa_newuoa_last.1_50.u.summary.230406_2009.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_bobyqa_bobyqa_last.1_200.b.summary.230406_1606.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_bobyqa_bobyqa_last.1_200.b.summary.230406_1606.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_bobyqa_bobyqa_last.1_200.b.summary.230406_1606.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_bobyqa_bobyqa_last.1_200.b.summary.230406_1606.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_bobyqa_bobyqa_last.1_50.ub.summary.230406_1605.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_bobyqa_bobyqa_last.1_50.ub.summary.230406_1605.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_bobyqa_bobyqa_last.1_50.ub.summary.230406_1605.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_bobyqa_bobyqa_last.1_50.ub.summary.230406_1605.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_lincoa_lincoa_last.1_200.l.summary.230406_1606.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_lincoa_lincoa_last.1_200.l.summary.230406_1606.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_lincoa_lincoa_last.1_200.l.summary.230406_1606.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_lincoa_lincoa_last.1_200.l.summary.230406_1606.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_lincoa_lincoa_last.1_50.ubl.summary.230406_1608.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_lincoa_lincoa_last.1_50.ubl.summary.230406_1608.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_lincoa_lincoa_last.1_50.ubl.summary.230406_1608.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_lincoa_lincoa_last.1_50.ubl.summary.230406_1608.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_newuoa_newuoa_last.1_200.u.summary.230406_1607.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_newuoa_newuoa_last.1_200.u.summary.230406_1607.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_newuoa_newuoa_last.1_200.u.summary.230406_1607.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_newuoa_newuoa_last.1_200.u.summary.230406_1607.pdf
diff --git a/benchmark/rescue_idz/results/modernized/modernized_newuoa_newuoa_last.1_50.u.summary.230406_1605.pdf b/benchmark/rescue_idz/230406/results/modernized/modernized_newuoa_newuoa_last.1_50.u.summary.230406_1605.pdf
similarity index 100%
rename from benchmark/rescue_idz/results/modernized/modernized_newuoa_newuoa_last.1_50.u.summary.230406_1605.pdf
rename to benchmark/rescue_idz/230406/results/modernized/modernized_newuoa_newuoa_last.1_50.u.summary.230406_1605.pdf
diff --git a/benchmark/rescue_idz/setup.m b/benchmark/rescue_idz/230406/setup.m
similarity index 100%
rename from benchmark/rescue_idz/setup.m
rename to benchmark/rescue_idz/230406/setup.m
diff --git a/benchmark/rescue_idz/make_rescue_idz b/benchmark/rescue_idz/make_rescue_idz
new file mode 100755
index 0000000000..83f6ea926f
--- /dev/null
+++ b/benchmark/rescue_idz/make_rescue_idz
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+# This script generates a version of PRIMA to test the effect of RESCUE and IDZ in Powell's algorithms.
+
+# The directory where this script is located.
+THIS_DIR=$(dirname "$(readlink -f "$0")")
+
+# The directory where PRIMA is located.
+PRIMA_DIR="$THIS_DIR/../.."
+
+# The modified version of PRIMA will be created in $RESCUE_IDZ_DIR.
+STAMP="$(date +%y%m%d_%H%M%S)"
+RESCUE_IDZ_DIR="$THIS_DIR/$STAMP"
+echo "$RESCUE_IDZ_DIR" > "$THIS_DIR"/RESCUE_IDZ_DIR
+
+# If this script is not run on a GitHub-hosted runner of GitHub Actions, then ask whether to continue.
+if [ -z "$GITHUB_ACTIONS" ]; then
+ echo "This script will make a version of PRIMA to test the effect of RESCUE and IDZ in Powell's algorithms."
+ echo -e "It will create a new directory at\n\n $RESCUE_IDZ_DIR , \n\nwhich will contain a copy of PRIMA with modifications."
+ read -r -p "Do you want to continue? (y/n) " answer
+ if [[ ! "$answer" =~ ^[Yy]$ ]]; then
+ echo "Exiting."
+ exit 0
+ fi
+fi
+
+# Create the directory for the modified version of PRIMA.
+if [ -d "$RESCUE_IDZ_DIR" ]; then
+ echo "Warning: The directory $RESCUE_IDZ_DIR already exists. It will be removed." >&2
+ rm -rf "$RESCUE_IDZ_DIR"
+fi
+mkdir -p "$RESCUE_IDZ_DIR"
+
+# List of files and directories to copy from $PRIMA_DIR to $RESCUE_IDZ_DIR.
+FDLIST=(
+ "setup.m"
+ "fortran"
+ "matlab"
+ ".development/archnorma"
+)
+
+# Copy the files and directories from $PRIMA_DIR to $RESCUE_IDZ_DIR.
+echo -e "\nCopying files and directories from $PRIMA_DIR to $RESCUE_IDZ_DIR ...\n"
+for fd in "${FDLIST[@]}"; do
+ if [ -e "$PRIMA_DIR/$fd" ]; then
+ if [ "$(basename "$fd")" == "archnorma" ] ; then
+ mkdir -p "$RESCUE_IDZ_DIR/$(dirname "$fd")"
+ cp "$PRIMA_DIR/$fd" "$RESCUE_IDZ_DIR/$(dirname "$fd")/"
+ else
+ cp -r "$PRIMA_DIR/$fd" "$RESCUE_IDZ_DIR/"
+ fi
+ else
+ echo "Warning: $PRIMA_DIR/$fd does not exist. Skipping." >&2
+ fi
+done
+
+# Create the $RESCUE_IDZ_DIR/.development/norma directory so that archnorma will not complain.
+mkdir -p "$RESCUE_IDZ_DIR"/.development/norma
+
+# Create a README about the modified version of PRIMA.
+echo -e "\nCreating a README file in $RESCUE_IDZ_DIR ...\n"
+README_FILE="$RESCUE_IDZ_DIR/README.md"
+cat << EOF > "$README_FILE"
+This is a modified version of PRIMA to test the effect of RESCUE and IDZ in Powell's algorithms.
+It was created by the script
+$(realpath "$0")
+on $(date)
+from the version of PRIMA with git commit hash
+$(git -C "$PRIMA_DIR" rev-parse HEAD)
+EOF
+
+# Make a norma version of PRIMA. It will be used as the competitor for benchmarking against the modified version.
+echo -e "\nCreating a norma version of PRIMA in $RESCUE_IDZ_DIR/.development ...\n"
+bash "$RESCUE_IDZ_DIR"/.development/archnorma
+# Remove the archiva directory from the modified version of PRIMA as it is not needed for the tests.
+rm -rf "$RESCUE_IDZ_DIR"/.development/archiva
+
+# Create the modified version of PRIMA.
+echo -e "\nCreating a modified version of PRIMA in $RESCUE_IDZ_DIR ...\n"
+FLIST=(
+ "$RESCUE_IDZ_DIR"/fortran/newuoa/newuob.f90
+ "$RESCUE_IDZ_DIR"/fortran/lincoa/lincob.f90
+ "$RESCUE_IDZ_DIR"/fortran/bobyqa/bobyqb.f90
+ "$RESCUE_IDZ_DIR"/fortran/classical/newuoa/newuob.f
+ "$RESCUE_IDZ_DIR"/fortran/classical/lincoa/lincob.f
+ "$RESCUE_IDZ_DIR"/fortran/classical/lincoa/prelim.f
+ "$RESCUE_IDZ_DIR"/fortran/classical/bobyqa/bobyqb.f
+ "$RESCUE_IDZ_DIR"/matlab/tests/private/testcu.m
+ "$RESCUE_IDZ_DIR"/matlab/tests/private/get_solvers.m
+ "$RESCUE_IDZ_DIR"/matlab/tests/private/perfdata.m
+)
+for f in "${FLIST[@]}"; do
+ if [ -e "$f" ]; then
+ # Display the file being modified if we are on a GitHub-hosted runner.
+ if [ -n "$GITHUB_ACTIONS" ]; then
+ echo "Modifying file: $f"
+ fi
+ else
+ echo "Warning: $f does not exist. Skipping." >&2
+ continue
+ fi
+
+ # Modify the files in FLIST.
+ case "$(basename "$f")" in
+ # Set IDZ to 1 before and after the call to UPDATEH in the modernized NEWUOA and LINCOA.
+ newuob.f90|lincob.f90)
+ sed -i 's|\(^\s*\)\(call updateh(.*idz.*$\)|\1idz=1; \2; idz=1|' "$f"
+ ;;
+ # Avoid calling RESCUE in the modernized BOBYQA.
+ bobyqb.f90)
+ sed -i 's|if (to_rescue) then|if (.false.) then|' "$f"
+ ;;
+ # Set IDZ to 1 before and after the call to UPDATE in the classical NEWUOA and LINCOA.
+ newuob.f|lincob.f|prelim.f)
+ sed -i 's|\(^\s*[0-9]*\s*CALL UPDATE\s*(.*)\s*$\)| IDZ=1\n\1\n IDZ=1|' "$f"
+ ;;
+ # Avoid calling RESCUE in the classical BOBYQA.
+ bobyqb.f)
+ sed -i 's|IF (NF > NRESC) GOTO 190|!IF (NF > NRESC) GOTO 190|g' "$f"
+ ;;
+ # Ensure that the classical solvers are compiled if options.classical is set to true.
+ get_solvers.m)
+ sed -i "s/\(^\s*\)\(setup(solver, mexopts{is});\s*$\)/\1mexopts{is}.classical = mexopts{is}.classical || (isfield(options, 'classical') \&\& options.classical); \2/" "$f"
+ ;;
+ # Ensure that the classical solvers are tested if options.classical is set to true.
+ testcu.m)
+ sed -i "s/\(^\s*prob.options.classical = .*\);\s*$/\1 || (isfield(options, 'classical') \&\& options.classical);/g" "$f"
+ ;;
+ # Ensure that the solvers are correctly identified. Here, the norma version is the original
+ # version, and the other is the one without RESCUE or IDZ.
+ perfdata.m)
+ sed -i "s/solvers{is} = regexprep(solvers{is}, '_norma$', ' (norma)');/solvers{is} = regexprep(solvers{is}, '(oa$|qa$)', '\$1 (no rescue|idz)'); solvers{is} = regexprep(solvers{is}, '_norma$', '');/" "$f"
+ ;;
+ esac
+
+ # Display the modified file if we are on a GitHub-hosted runner.
+ if [ -n "$GITHUB_ACTIONS" ]; then
+ cat "$f"
+ fi
+done
+
+echo -e "\nModified version of PRIMA created at $RESCUE_IDZ_DIR.\n"
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index 911aa12ea7..2c62abf5e6 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -1,11 +1,19 @@
set(CMAKE_C_STANDARD 99)
+# Locate libm portably. On Linux, *BSD and macOS this resolves to the system libm and must be linked
+# explicitly into any executable that calls functions from (e.g. pow). Otherwise, if the
+# toolchain links with --as-needed and --no-copy-dt-needed-entries, the math functions will not be
+# found. On Windows, libm is part of the C runtime and does not need to be linked explicitly, so
+# MATH_LIBRARY will be NOTFOUND and the generator expression below will collapse to nothing.
+find_library(MATH_LIBRARY m)
+
add_library (primac cintrf.f90 cobyla_c.f90 lincoa_c.f90 bobyqa_c.f90 newuoa_c.f90 uobyqa_c.f90 prima.c)
if (WIN32)
set_target_properties(primac PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
endif()
+
target_include_directories (primac PUBLIC
$
$
@@ -15,9 +23,15 @@ set_target_properties(primac PROPERTIES POSITION_INDEPENDENT_CODE ON C_STANDARD
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(primac PUBLIC PRIMAC_STATIC)
+ # The line below caused issues when compiling prima_pybind on Windows with MinGW. We get errors
+ # about multiple definition of unwind_resume due to the inclusion of gcc_s.
+ # It's unclear why this was added as initial reason given in the issue (108) seems to work fine for me
+ if (NOT WIN32)
+ target_link_libraries (primac INTERFACE ${CMAKE_Fortran_IMPLICIT_LINK_LIBRARIES})
+ endif()
endif ()
-# export symbols
+# Export symbols on Windows. See more comments in fortran/CMakeLists.txt.
if (WIN32 AND BUILD_SHARED_LIBS)
target_sources(primac PRIVATE primac.def)
endif ()
@@ -26,7 +40,10 @@ install (FILES include/prima/prima.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/pri
macro (prima_add_c_test name)
add_executable (example_${name}_c_exe EXCLUDE_FROM_ALL examples/${name}/${name}_example.c)
- target_link_libraries (example_${name}_c_exe PRIVATE primac)
+ # Link libm explicitly on platforms where it is a separate library (Linux, *BSD, macOS).
+ # MATH_LIBRARY is NOTFOUND on Windows, where libm is part of the C runtime, so the generator
+ # expression collapses to nothing there.
+ target_link_libraries (example_${name}_c_exe PRIVATE primac $<$:${MATH_LIBRARY}>)
target_include_directories (example_${name}_c_exe PRIVATE ${CMAKE_SOURCE_DIR}/c/include)
if (PRIMA_ENABLE_EXAMPLES)
set_target_properties (example_${name}_c_exe PROPERTIES EXCLUDE_FROM_ALL FALSE)
@@ -34,7 +51,21 @@ macro (prima_add_c_test name)
if (WIN32)
set_target_properties(example_${name}_c_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
endif()
+
+ # Outside of CI we don't want to force people to run examples with gdb, so we test the executables by themselves.
+ # We want these to run in CI as well, because sometimes running with gdb masks an error, so we set them up
+ # before we set up the examples for CI
add_test (NAME example_${name}_c COMMAND example_${name}_c_exe)
+
+ # Within CI, we'd like to run with gdb so that if there's a segfault the logs will have a stacktrace we can use to investigate.
+ # Of course this can be run locally as well if you define CI in your environment.
+ if(NOT APPLE AND UNIX AND DEFINED ENV{CI}) # Apple security policy will not allow running gdb in CI
+ add_test (NAME example_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb example_${name}_c_exe)
+ elseif(WIN32 AND DEFINED ENV{CI})
+ # For Windows we need to provide the full path to the executable since it is installed to a different directory
+ add_test (NAME example_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/example_${name}_c_exe.exe)
+ endif()
+
add_dependencies(examples example_${name}_c_exe)
endmacro ()
diff --git a/c/README.txt b/c/README.md
similarity index 95%
rename from c/README.txt
rename to c/README.md
index 8cb883607d..f06009c71a 100644
--- a/c/README.txt
+++ b/c/README.md
@@ -2,4 +2,5 @@ This is the C interface for calling the Fortran version of PRIMA.
Contributed by Julien Schueller ( schueller@phimeca.com ) in September 2023.
+## Usage
See https://github.com/libprima/prima#c for the usage.
diff --git a/c/bobyqa_c.f90 b/c/bobyqa_c.f90
index 123b1b2fe8..6f318ec742 100644
--- a/c/bobyqa_c.f90
+++ b/c/bobyqa_c.f90
@@ -13,21 +13,24 @@ module bobyqa_c_mod
contains
-subroutine bobyqa_c(cobj_ptr, n, x, f, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, info) bind(C)
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR
-use, non_intrinsic :: cintrf_mod, only : COBJ
-use, non_intrinsic :: consts_mod, only : RP, IK
+subroutine bobyqa_c(cobj_ptr, data_ptr, n, x, f, xl, xu, nf, rhobeg, rhoend, &
+ & ftarget, maxfun, npt, iprint, callback_ptr, info) bind(C)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR, C_PTR, C_ASSOCIATED, C_F_PROCPOINTER, C_F_POINTER
use, non_intrinsic :: bobyqa_mod, only : bobyqa
+use, non_intrinsic :: cintrf_mod, only : COBJ, CCALLBACK
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: infnan_mod, only : is_nan
+use, non_intrinsic :: memory_mod, only : safealloc
implicit none
! Compulsory arguments
type(C_FUNPTR), intent(IN), value :: cobj_ptr
+type(C_PTR), intent(in), value :: data_ptr
integer(C_INT), intent(in), value :: n
-! We cannot use assumed-shape arrays for C interoperability
-real(C_DOUBLE), intent(inout) :: x(n)
+real(C_DOUBLE), intent(inout) :: x(n) ! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(out) :: f
-real(C_DOUBLE), intent(in) :: xl(n)
-real(C_DOUBLE), intent(in) :: xu(n)
+type(C_PTR), intent(in), value :: xl
+type(C_PTR), intent(in), value :: xu
integer(C_INT), intent(out) :: nf
real(C_DOUBLE), intent(in), value :: rhobeg
real(C_DOUBLE), intent(in), value :: rhoend
@@ -35,36 +38,81 @@ subroutine bobyqa_c(cobj_ptr, n, x, f, xl, xu, nf, rhobeg, rhoend, ftarget, maxf
integer(C_INT), intent(in), value :: maxfun
integer(C_INT), intent(in), value :: npt
integer(C_INT), intent(in), value :: iprint
+type(C_FUNPTR), intent(in), value :: callback_ptr
integer(C_INT), intent(out) :: info
! Local variables
integer(IK) :: info_loc
integer(IK) :: iprint_loc
-integer(IK) :: maxfun_loc
-integer(IK) :: npt_loc
integer(IK) :: nf_loc
+integer(IK), allocatable :: maxfun_loc
+integer(IK), allocatable :: npt_loc
+! The initialization below to null is necessary to avoid a bug with the newer Intel compiler ifx.
+! See https://fortran-lang.discourse.group/t/strange-issue-with-ifx-compiler-and-assume-recursion/7013
+! The bug was observed in all versions of ifx up to 2024.0.1. Once this bug is fixed we should
+! remove the initialization to null because it implies the 'save' attribute, which is undesirable.
+procedure(CCALLBACK), pointer :: cb_ptr => null()
+procedure(COBJ), pointer :: obj_ptr => null()
+real(C_DOUBLE), pointer :: xl_loc_interm(:)
+real(C_DOUBLE), pointer :: xu_loc_interm(:)
real(RP) :: f_loc
-real(RP) :: rhobeg_loc
-real(RP) :: rhoend_loc
real(RP) :: ftarget_loc
real(RP) :: x_loc(n)
-real(RP) :: xl_loc(n)
-real(RP) :: xu_loc(n)
+real(RP), allocatable :: rhobeg_loc
+real(RP), allocatable :: rhoend_loc
+real(RP), allocatable :: xl_loc(:)
+real(RP), allocatable :: xu_loc(:)
! Read the inputs and convert them to the Fortran side types
+
+! The following inputs correspond to compulsory arguments in the Fortran code.
x_loc = real(x, kind(x_loc))
-xl_loc = real(xl, kind(xl_loc))
-xu_loc = real(xu, kind(xu_loc))
-rhobeg_loc = real(rhobeg, kind(rhobeg))
-rhoend_loc = real(rhoend, kind(rhoend))
-ftarget_loc = real(ftarget, kind(ftarget))
-maxfun_loc = int(maxfun, kind(maxfun_loc))
-npt_loc = int(npt, kind(npt_loc))
+call c_f_procpointer(cobj_ptr, obj_ptr)
+
+! The following inputs correspond to optional arguments in the Fortran code.
+! Since C does not support optional arguments, we use NaN to represent an absent real scalar, 0 to
+! represent an absent integer scalar (all integer arguments are expected positive), and an
+! unassociated pointer to represent an absent array. In case of NaN, 0, or unassociated pointers,
+! the allocatable variables such as RHOBEG_LOC will be left uninitialized and hence unallocated, and
+! then treated as an absent argument when passed to the Fortran code.
+! See Sec. 9.7.1.3 (4) and 15.5.2.13 (1) of J3/24-007 (Fortran 2023 Interpretation Document).
+if (c_associated(xl)) then
+ call c_f_pointer(xl, xl_loc_interm, shape=[n])
+ call safealloc(xl_loc, int(n, IK))
+ xl_loc = real(xl_loc_interm, kind(xl_loc))
+end if
+if (c_associated(xu)) then
+ call c_f_pointer(xu, xu_loc_interm, shape=[n])
+ call safealloc(xu_loc, int(n, IK))
+ xu_loc = real(xu_loc_interm, kind(xu_loc))
+end if
+if (.not. is_nan(rhobeg)) then
+ rhobeg_loc = real(rhobeg, kind(rhobeg_loc))
+end if
+if (.not. is_nan(rhoend)) then
+ rhoend_loc = real(rhoend, kind(rhoend_loc))
+end if
+ftarget_loc = real(ftarget, kind(ftarget_loc))
+if (maxfun /= 0) then
+ maxfun_loc = int(maxfun, kind(maxfun_loc))
+end if
+if (npt /= 0) then
+ npt_loc = int(npt, kind(npt_loc))
+end if
iprint_loc = int(iprint, kind(iprint_loc))
! Call the Fortran code
-call bobyqa(calfun, x_loc, f_loc, xl=xl_loc, xu=xu_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
- & ftarget=ftarget_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, info=info_loc)
+if (c_associated(callback_ptr)) then
+ ! If a C callback function is provided, we convert it to a Fortran procedure pointer and capture
+ ! that pointer in the closure below.
+ call c_f_procpointer(callback_ptr, cb_ptr)
+ ! We then provide the closure to the algorithm.
+ call bobyqa(calfun, x_loc, f_loc, xl=xl_loc, xu=xu_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
+ & ftarget=ftarget_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, callback_fcn=callback_fcn, info=info_loc)
+else
+ call bobyqa(calfun, x_loc, f_loc, xl=xl_loc, xu=xu_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
+ & ftarget=ftarget_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, info=info_loc)
+end if
! Write the outputs
x = real(x_loc, kind(x))
@@ -72,22 +120,117 @@ subroutine bobyqa_c(cobj_ptr, n, x, f, xl, xu, nf, rhobeg, rhoend, ftarget, maxf
nf = int(nf_loc, kind(nf))
info = int(info_loc, kind(info))
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(npt_loc)) deallocate (npt_loc)
+if (allocated(maxfun_loc)) deallocate (maxfun_loc)
+if (allocated(rhoend_loc)) deallocate (rhoend_loc)
+if (allocated(rhobeg_loc)) deallocate (rhobeg_loc)
+if (allocated(xu_loc)) deallocate (xu_loc)
+if (allocated(xu_loc)) deallocate (xl_loc)
+
contains
!--------------------------------------------------------------------------------------------------!
! This subroutine defines `calfun` using the C function pointer with an internal subroutine.
! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across 4 out of 5 algorithms; COBYLA requires a slightly different
+! signature.
!--------------------------------------------------------------------------------------------------!
subroutine calfun(x_sub, f_sub)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE
use, non_intrinsic :: consts_mod, only : RP
-use, non_intrinsic :: cintrf_mod, only : evalcobj
implicit none
-real(RP), intent(in) :: x_sub(:)
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
real(RP), intent(out) :: f_sub
-call evalcobj(cobj_ptr, x_sub, f_sub)
+
+! Local variables
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE) :: f_sub_loc
+
+! Read the inputs and convert them to the types specified in COBJ
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+
+! Call the C objective function
+call obj_ptr(x_sub_loc, f_sub_loc, data_ptr)
+
+! Write the output
+f_sub = real(f_sub_loc, kind(f_sub))
+
end subroutine calfun
+
+!--------------------------------------------------------------------------------------------------!
+! This subroutine defines `callback_fcn` using the C function pointer with an internal subroutine.
+! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
+! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across all 5 algorithms.
+!--------------------------------------------------------------------------------------------------!
+subroutine callback_fcn(x_sub, f_sub, nf_sub, tr, cstrv_sub, nlconstr_sub, terminate)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_BOOL
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
+real(RP), intent(in) :: f_sub
+integer(IK), intent(in) :: nf_sub
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv_sub
+real(RP), intent(in), optional :: nlconstr_sub(:)
+
+! Outputs
+logical, intent(out), optional :: terminate
+
+! Local variables
+integer(C_INT) :: m_nlconstr
+integer(C_INT) :: n_sub_loc
+integer(C_INT) :: nf_sub_loc
+integer(C_INT) :: tr_loc
+logical(C_BOOL) :: terminate_loc
+real(C_DOUBLE) :: cstrv_sub_loc
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE), allocatable :: nlconstr_sub_loc(:)
+
+! Read the inputs and convert them to the types specified in CCALLBACK
+n_sub_loc = size(x_sub)
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+f_sub_loc = real(f_sub, kind(f_sub_loc))
+nf_sub_loc = int(nf_sub, kind(nf_sub_loc))
+tr_loc = int(tr, kind(tr_loc))
+
+! Set the constraint violation to a sensible default value if it is not provided.
+if (present(cstrv_sub)) then
+ cstrv_sub_loc = real(cstrv_sub, kind(cstrv_sub_loc))
+else
+ cstrv_sub_loc = 0.0_C_DOUBLE
+end if
+
+! Set the nonlinear constraints to a sensible default value if it is not provided.
+if (present(nlconstr_sub)) then
+ m_nlconstr = int(size(nlconstr_sub), C_INT)
+ call safealloc(nlconstr_sub_loc, int(m_nlconstr, IK))
+ nlconstr_sub_loc = real(nlconstr_sub, kind(nlconstr_sub_loc))
+else
+ m_nlconstr = 0_C_INT
+ nlconstr_sub_loc = [real(C_DOUBLE) ::]
+end if
+
+! Call the C callback function
+call cb_ptr(n_sub_loc, x_sub_loc, f_sub_loc, nf_sub_loc, tr_loc, cstrv_sub_loc, m_nlconstr, nlconstr_sub_loc, terminate_loc)
+
+! Write the output
+if (present(terminate)) then
+ terminate = logical(terminate_loc, kind(terminate))
+end if
+
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(nlconstr_sub_loc)) deallocate (nlconstr_sub_loc)
+
+end subroutine callback_fcn
+
end subroutine bobyqa_c
diff --git a/c/cintrf.f90 b/c/cintrf.f90
index bccb3d53a6..e66ef17178 100644
--- a/c/cintrf.f90
+++ b/c/cintrf.f90
@@ -7,86 +7,47 @@ module cintrf_mod
implicit none
private
-public :: COBJ, COBJCON, evalcobj, evalcobjcon
+public :: COBJ, COBJCON, CCALLBACK
abstract interface
- subroutine COBJ(x, f) bind(c)
- use, intrinsic :: iso_c_binding, only : C_DOUBLE
+ subroutine COBJ(x, f, data_ptr) bind(c)
+ use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_PTR
implicit none
! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(in) :: x(*)
real(C_DOUBLE), intent(out) :: f
+ type(C_PTR), intent(in), value :: data_ptr
end subroutine COBJ
- subroutine COBJCON(x, f, constr) bind(c)
- use, intrinsic :: iso_c_binding, only : C_DOUBLE
+ subroutine COBJCON(x, f, constr, data_ptr) bind(c)
+ use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_PTR
implicit none
! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(in) :: x(*)
real(C_DOUBLE), intent(out) :: f
real(C_DOUBLE), intent(out) :: constr(*)
+ type(C_PTR), intent(in), value :: data_ptr
end subroutine COBJCON
-end interface
-
-
-contains
-
-
-subroutine evalcobj(cobj_ptr, x, f)
-use, non_intrinsic :: consts_mod, only : RP
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_FUNPTR, C_F_PROCPOINTER
-implicit none
-type(C_FUNPTR), intent(in) :: cobj_ptr
-real(RP), intent(in) :: x(:)
-real(RP), intent(out) :: f
-
-! Local variables
-procedure(COBJ), pointer :: obj_ptr
-real(C_DOUBLE) :: x_loc(size(x))
-real(C_DOUBLE) :: f_loc
-
-! Read the inputs and convert them to the types specified in COBJ
-x_loc = real(x, kind(x_loc))
-call C_F_PROCPOINTER(cobj_ptr, obj_ptr)
-
-! Call the C objective function
-call obj_ptr(x_loc, f_loc)
-
-! Write the output
-f = real(f_loc, kind(f))
-
-end subroutine evalcobj
-
-
-subroutine evalcobjcon(cobjcon_ptr, x, f, constr)
-use, non_intrinsic :: consts_mod, only : RP
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_FUNPTR, C_F_PROCPOINTER
-implicit none
-type(C_FUNPTR), intent(in) :: cobjcon_ptr
-real(RP), intent(in) :: x(:)
-real(RP), intent(out) :: f
-real(RP), intent(out) :: constr(:)
-
-! Local variables
-procedure(COBJCON), pointer :: objcon_ptr
-real(C_DOUBLE) :: x_loc(size(x))
-real(C_DOUBLE) :: f_loc
-real(C_DOUBLE) :: constr_loc(size(constr))
-
-! Read the inputs and convert them to the types specified in COBJCON
-x_loc = real(x, kind(x_loc))
-call C_F_PROCPOINTER(cobjcon_ptr, objcon_ptr)
+ subroutine CCALLBACK(n, x, f, nf, tr, cstrv, m_nlcon, nlconstr, terminate) bind(c)
+ use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_BOOL, C_INT
+ implicit none
+ integer(C_INT), intent(in), value :: n
+ ! We cannot use assumed-shape arrays for C interoperability
+ real(C_DOUBLE), intent(in) :: x(*)
+ real(C_DOUBLE), intent(in), value :: f
+ integer(C_INT), intent(in), value :: nf
+ integer(C_INT), intent(in), value :: tr
+ real(C_DOUBLE), intent(in), value :: cstrv
+ integer(C_INT), intent(in), value :: m_nlcon
+ real(C_DOUBLE), intent(in) :: nlconstr(*)
+ logical(C_BOOL), intent(out) :: terminate
+ end subroutine CCALLBACK
-! Call the C objective function
-call objcon_ptr(x_loc, f_loc, constr_loc)
-! Write the output
-f = real(f_loc, kind(f))
-constr = real(constr_loc, kind(constr))
+end interface
-end subroutine evalcobjcon
end module cintrf_mod
diff --git a/c/cobyla_c.f90 b/c/cobyla_c.f90
index 88a1815918..36db0414e0 100644
--- a/c/cobyla_c.f90
+++ b/c/cobyla_c.f90
@@ -12,20 +12,22 @@ module cobyla_c_mod
contains
-subroutine cobyla_c(m_nlcon, cobjcon_ptr, n, x, f, cstrv, nlconstr, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &
- & nf, rhobeg, rhoend, ftarget, maxfun, iprint, info) bind(C)
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR
-use, non_intrinsic :: cintrf_mod, only : COBJCON
-use, non_intrinsic :: consts_mod, only : RP, IK
+subroutine cobyla_c(m_nlcon, cobjcon_ptr, data_ptr, n, x, f, cstrv, nlconstr, m_ineq, Aineq, bineq, m_eq, Aeq, beq, &
+ & xl, xu, f0, nlconstr0, nf, rhobeg, rhoend, ftarget, maxfun, iprint, ctol, callback_ptr, info) bind(C)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR, C_PTR, C_ASSOCIATED, C_F_PROCPOINTER, C_F_POINTER
+use, non_intrinsic :: cintrf_mod, only : COBJCON, CCALLBACK
use, non_intrinsic :: cobyla_mod, only : cobyla
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: infnan_mod, only : is_nan
+use, non_intrinsic :: memory_mod, only : safealloc
implicit none
! Compulsory arguments
integer(C_INT), intent(in), value :: m_nlcon
type(C_FUNPTR), intent(in), value :: cobjcon_ptr
+type(C_PTR), intent(in), value :: data_ptr
integer(C_INT), intent(in), value :: n
-! We cannot use assumed-shape arrays for C interoperability
-real(C_DOUBLE), intent(inout) :: x(n)
+real(C_DOUBLE), intent(inout) :: x(n) ! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(out) :: f
real(C_DOUBLE), intent(out) :: cstrv
real(C_DOUBLE), intent(out) :: nlconstr(m_nlcon)
@@ -35,59 +37,118 @@ subroutine cobyla_c(m_nlcon, cobjcon_ptr, n, x, f, cstrv, nlconstr, m_ineq, Aine
integer(C_INT), intent(in), value :: m_eq
real(C_DOUBLE), intent(in) :: Aeq(n, m_eq)
real(C_DOUBLE), intent(in) :: beq(m_eq)
-real(C_DOUBLE), intent(in) :: xl(n)
-real(C_DOUBLE), intent(in) :: xu(n)
+type(C_PTR), intent(in), value :: xl
+type(C_PTR), intent(in), value :: xu
+real(C_DOUBLE), intent(in), value :: f0
+type(C_PTR), intent(in), value :: nlconstr0
integer(C_INT), intent(out) :: nf
real(C_DOUBLE), intent(in), value :: rhobeg
real(C_DOUBLE), intent(in), value :: rhoend
real(C_DOUBLE), intent(in), value :: ftarget
integer(C_INT), intent(in), value :: maxfun
integer(C_INT), intent(in), value :: iprint
+real(C_DOUBLE), intent(in), value :: ctol
+type(C_FUNPTR), intent(in), value :: callback_ptr
integer(C_INT), intent(out) :: info
! Local variables
integer(IK) :: info_loc
integer(IK) :: iprint_loc
integer(IK) :: m_nlcon_loc
-integer(IK) :: maxfun_loc
integer(IK) :: nf_loc
-real(RP) :: Aineq_loc(m_ineq, n)
-real(RP) :: bineq_loc(m_ineq)
+integer(IK), allocatable :: maxfun_loc
+! The initialization below to null is necessary to avoid a bug with the newer Intel compiler ifx.
+! See https://fortran-lang.discourse.group/t/strange-issue-with-ifx-compiler-and-assume-recursion/7013
+! The bug was observed in all versions of ifx up to 2024.0.1. Once this bug is fixed we should
+! remove the initialization to null because it implies the 'save' attribute, which is undesirable.
+procedure(CCALLBACK), pointer :: cb_ptr => null()
+procedure(COBJCON), pointer :: objcon_ptr => null()
+real(C_DOUBLE), pointer :: nlconstr0_loc_interm(:)
+real(C_DOUBLE), pointer :: xl_loc_interm(:)
+real(C_DOUBLE), pointer :: xu_loc_interm(:)
real(RP) :: Aeq_loc(m_eq, n)
+real(RP) :: Aineq_loc(m_ineq, n)
real(RP) :: beq_loc(m_eq)
+real(RP) :: bineq_loc(m_ineq)
real(RP) :: cstrv_loc
-real(RP) :: nlconstr_loc(m_nlcon)
+real(RP) :: ctol_loc
real(RP) :: f_loc
-real(RP) :: rhobeg_loc
-real(RP) :: rhoend_loc
real(RP) :: ftarget_loc
+real(RP) :: nlconstr_loc(m_nlcon)
real(RP) :: x_loc(n)
-real(RP) :: xl_loc(n)
-real(RP) :: xu_loc(n)
+real(RP), allocatable :: f0_loc
+real(RP), allocatable :: nlconstr0_loc(:)
+real(RP), allocatable :: rhobeg_loc
+real(RP), allocatable :: rhoend_loc
+real(RP), allocatable :: xl_loc(:)
+real(RP), allocatable :: xu_loc(:)
! Read the inputs and convert them to the Fortran side types
! Note that `transpose` is needed when reading 2D arrays, since they are stored in the row-major
! order in c but column-major in Fortran.
+
+! The following inputs correspond to compulsory arguments in the Fortran code.
x_loc = real(x, kind(x_loc))
+m_nlcon_loc = int(m_nlcon, kind(m_nlcon_loc))
+call c_f_procpointer(cobjcon_ptr, objcon_ptr)
+
+! The following inputs correspond to optional arguments in the Fortran code.
+! Since C does not support optional arguments, we use NaN to represent an absent real scalar, 0 to
+! represent an absent integer scalar (all integer arguments are expected positive), and an
+! unassociated pointer to represent an absent array. In case of NaN, 0, or unassociated pointers,
+! the allocatable variables such as RHOBEG_LOC will be left uninitialized and hence unallocated, and
+! then treated as an absent argument when passed to the Fortran code.
+! See Sec. 9.7.1.3 (4) and 15.5.2.13 (1) of J3/24-007 (Fortran 2023 Interpretation Document).
Aineq_loc = real(transpose(Aineq), kind(Aineq_loc))
bineq_loc = real(bineq, kind(bineq_loc))
Aeq_loc = real(transpose(Aeq), kind(Aeq_loc))
beq_loc = real(beq, kind(beq_loc))
-xl_loc = real(xl, kind(xl_loc))
-xu_loc = real(xu, kind(xu_loc))
-rhobeg_loc = real(rhobeg, kind(rhobeg))
-rhoend_loc = real(rhoend, kind(rhoend))
-ftarget_loc = real(ftarget, kind(ftarget))
-maxfun_loc = int(maxfun, kind(maxfun_loc))
+if (c_associated(xl)) then
+ call c_f_pointer(xl, xl_loc_interm, shape=[n])
+ call safealloc(xl_loc, int(n, IK))
+ xl_loc = real(xl_loc_interm, kind(xl_loc))
+end if
+if (c_associated(xu)) then
+ call c_f_pointer(xu, xu_loc_interm, shape=[n])
+ call safealloc(xu_loc, int(n, IK))
+ xu_loc = real(xu_loc_interm, kind(xu_loc))
+end if
+if (c_associated(nlconstr0)) then
+ call c_f_pointer(nlconstr0, nlconstr0_loc_interm, shape=[m_nlcon])
+ call safealloc(nlconstr0_loc, int(m_nlcon, IK))
+ nlconstr0_loc = real(nlconstr0_loc_interm, kind(nlconstr0_loc))
+ !!! N.B.: We require that NLCONSTR0 and F0 are either both provided or both absent !!!
+ f0_loc = real(f0, kind(f0_loc))
+end if
+if (.not. is_nan(rhobeg)) then
+ rhobeg_loc = real(rhobeg, kind(rhobeg_loc))
+end if
+if (.not. is_nan(rhoend)) then
+ rhoend_loc = real(rhoend, kind(rhoend_loc))
+end if
+ftarget_loc = real(ftarget, kind(ftarget_loc))
+if (maxfun /= 0) then
+ maxfun_loc = int(maxfun, kind(maxfun_loc))
+end if
iprint_loc = int(iprint, kind(iprint_loc))
-m_nlcon_loc = int(m_nlcon, kind(m_nlcon_loc))
+ctol_loc = real(ctol, kind(ctol_loc))
! Call the Fortran code
-call cobyla(calcfc, m_nlcon_loc, x_loc, f_loc, cstrv=cstrv_loc, nlconstr=nlconstr_loc, &
- & Aineq=Aineq_loc, bineq=bineq_loc, Aeq=Aeq_loc, beq=beq_loc, &
- & xl=xl_loc, xu=xu_loc, nf=nf_loc, &
- & rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, maxfun=maxfun_loc, &
- & iprint=iprint_loc, info=info_loc)
+if (c_associated(callback_ptr)) then
+ ! If a C callback function is provided, we convert it to a Fortran procedure pointer and capture
+ ! that pointer in the closure below.
+ call c_f_procpointer(callback_ptr, cb_ptr)
+ ! We then provide the closure to the algorithm.
+ call cobyla(calcfc, m_nlcon_loc, x_loc, f_loc, cstrv=cstrv_loc, nlconstr=nlconstr_loc, Aineq=Aineq_loc, &
+ & bineq=bineq_loc, Aeq=Aeq_loc, beq=beq_loc, xl=xl_loc, xu=xu_loc, f0=f0_loc, nlconstr0=nlconstr0_loc, &
+ & nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, ctol=ctol_loc, maxfun=maxfun_loc, &
+ & iprint=iprint_loc, callback_fcn=callback_fcn, info=info_loc)
+else
+ call cobyla(calcfc, m_nlcon_loc, x_loc, f_loc, cstrv=cstrv_loc, nlconstr=nlconstr_loc, Aineq=Aineq_loc, &
+ & bineq=bineq_loc, Aeq=Aeq_loc, beq=beq_loc, xl=xl_loc, xu=xu_loc, f0=f0_loc, nlconstr0=nlconstr0_loc, &
+ & nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, ctol=ctol_loc, maxfun=maxfun_loc, &
+ & iprint=iprint_loc, info=info_loc)
+end if
! Write the outputs
x = real(x_loc, kind(x))
@@ -97,23 +158,120 @@ subroutine cobyla_c(m_nlcon, cobjcon_ptr, n, x, f, cstrv, nlconstr, m_ineq, Aine
info = int(info_loc, kind(info))
nlconstr = real(nlconstr_loc, kind(nlconstr))
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(maxfun_loc)) deallocate (maxfun_loc)
+if (allocated(rhoend_loc)) deallocate (rhoend_loc)
+if (allocated(rhobeg_loc)) deallocate (rhobeg_loc)
+if (allocated(xu_loc)) deallocate (xu_loc)
+if (allocated(xu_loc)) deallocate (xl_loc)
+
contains
!--------------------------------------------------------------------------------------------------!
! This subroutine defines `calcfc` using the C function pointer with an internal subroutine.
! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across 4 out of 5 algorithms; COBYLA requires a slightly different
+! signature.
!--------------------------------------------------------------------------------------------------!
subroutine calcfc(x_sub, f_sub, constr_sub)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE
use, non_intrinsic :: consts_mod, only : RP
-use, non_intrinsic :: cintrf_mod, only : evalcobjcon
implicit none
-real(RP), intent(in) :: x_sub(:)
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
real(RP), intent(out) :: f_sub
real(RP), intent(out) :: constr_sub(:)
-call evalcobjcon(cobjcon_ptr, x_sub, f_sub, constr_sub)
+
+! Local variables
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: constr_sub_loc(size(constr_sub))
+
+! Read the inputs and convert them to the types specified in COBJCON
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+
+! Call the C objective function
+call objcon_ptr(x_sub_loc, f_sub_loc, constr_sub_loc, data_ptr)
+
+! Write the output
+f_sub = real(f_sub_loc, kind(f_sub))
+constr_sub = real(constr_sub_loc, kind(constr_sub))
+
end subroutine calcfc
+
+!--------------------------------------------------------------------------------------------------!
+! This subroutine defines `callback_fcn` using the C function pointer with an internal subroutine.
+! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
+! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across all 5 algorithms.
+!--------------------------------------------------------------------------------------------------!
+subroutine callback_fcn(x_sub, f_sub, nf_sub, tr, cstrv_sub, nlconstr_sub, terminate)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_BOOL
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
+real(RP), intent(in) :: f_sub
+integer(IK), intent(in) :: nf_sub
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv_sub
+real(RP), intent(in), optional :: nlconstr_sub(:)
+
+! Outputs
+logical, intent(out), optional :: terminate
+
+! Local variables
+integer(C_INT) :: m_nlconstr
+integer(C_INT) :: n_sub_loc
+integer(C_INT) :: nf_sub_loc
+integer(C_INT) :: tr_loc
+logical(C_BOOL) :: terminate_loc
+real(C_DOUBLE) :: cstrv_sub_loc
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE), allocatable :: nlconstr_sub_loc(:)
+
+! Read the inputs and convert them to the types specified in CCALLBACK
+n_sub_loc = size(x_sub)
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+f_sub_loc = real(f_sub, kind(f_sub_loc))
+nf_sub_loc = int(nf_sub, kind(nf_sub_loc))
+tr_loc = int(tr, kind(tr_loc))
+
+! Set the constraint violation to a sensible default value if it is not provided.
+if (present(cstrv_sub)) then
+ cstrv_sub_loc = real(cstrv_sub, kind(cstrv_sub_loc))
+else
+ cstrv_sub_loc = 0.0_C_DOUBLE
+end if
+
+! Set the nonlinear constraints to a sensible default value if it is not provided.
+if (present(nlconstr_sub)) then
+ m_nlconstr = int(size(nlconstr_sub), C_INT)
+ call safealloc(nlconstr_sub_loc, int(m_nlconstr, IK))
+ nlconstr_sub_loc = real(nlconstr_sub, kind(nlconstr_sub_loc))
+else
+ m_nlconstr = 0_C_INT
+ nlconstr_sub_loc = [real(C_DOUBLE) ::]
+end if
+
+! Call the C callback function
+call cb_ptr(n_sub_loc, x_sub_loc, f_sub_loc, nf_sub_loc, tr_loc, cstrv_sub_loc, m_nlconstr, nlconstr_sub_loc, terminate_loc)
+
+! Write the output
+if (present(terminate)) then
+ terminate = logical(terminate_loc, kind(terminate))
+end if
+
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(nlconstr_sub_loc)) deallocate (nlconstr_sub_loc)
+
+end subroutine callback_fcn
+
+
end subroutine cobyla_c
diff --git a/c/examples/README.md b/c/examples/README.md
new file mode 100644
index 0000000000..fc15593235
--- /dev/null
+++ b/c/examples/README.md
@@ -0,0 +1,6 @@
+The files in these directories show how to use the C API. Each example is
+standalone. The examples use a trivial objective function who optimal point
+and value are clear from looking at it, although examples involving constrained
+or bounded optimization will make sure to use active constraints (in order to
+demonstrate their usage) and so will not arrive at the same optimal point as
+the unconstrained problem.
\ No newline at end of file
diff --git a/c/examples/bobyqa/CMakeLists.txt b/c/examples/bobyqa/CMakeLists.txt
index 6b8246f800..51f2b344c6 100644
--- a/c/examples/bobyqa/CMakeLists.txt
+++ b/c/examples/bobyqa/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
-project(bobyqa_example C Fortran)
+project(bobyqa_example C)
find_package(PRIMA CONFIG REQUIRED)
add_executable(bobyqa_example bobyqa_example.c)
-target_link_libraries(bobyqa_example prima::primac)
+target_link_libraries(bobyqa_example prima::primac m)
install(TARGETS bobyqa_example DESTINATION bin)
diff --git a/c/examples/bobyqa/bobyqa_example.c b/c/examples/bobyqa/bobyqa_example.c
index 9d8dbc4e99..b73b1dc4df 100644
--- a/c/examples/bobyqa/bobyqa_example.c
+++ b/c/examples/bobyqa/bobyqa_example.c
@@ -1,34 +1,72 @@
// An example to illustrate the use of BOBYQA.
+
#include "prima/prima.h"
-#include
#include
+#include
+
+
+// Objective function
+static void fun(const double x[], double *const f, const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = pow(x1-5, 2) + pow(x2-4, 2);
+ (void)data;
+}
+
-static void fun(const double x[], double *f)
+// Callback function
+static void callback(const int n, const double x[], const double f, const int nf, const int tr, const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate)
{
- const double x1 = x[0];
- const double x2 = x[1];
- *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
+ (void)n;
+ (void)cstrv;
+ (void)m_nlcon;
+ (void)nlconstr;
+ printf("Best point so far: x = {%g, %g}, f = %g, nf = %d, tr = %d\n", x[0], x[1], f, nf, tr);
+ *terminate = 0;
}
+
+// Main function
int main(int argc, char * argv[])
{
- (void)argc;
- (void)argv;
- const int n = 2;
- double x[2] = {0.0, 0.0};
- double f = 0.0;
- double xl[2] = {-6.0, -6.0};
- double xu[2] = {6.0, 6.0};
- const double rhobeg = 1.0;
- const double rhoend = 1e-3;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_EXIT;
- const int maxfun = 200*n;
- const int npt = 2*n+1;
- int nf = 0;
- const int rc = prima_bobyqa(&fun, n, x, &f, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint);
- const char *msg = prima_get_rc_string(rc);
- printf("x*={%g, %g} rc=%d msg='%s' evals=%d\n", x[0], x[1], rc, msg, nf);
- return (fabs(x[0]-3)>2e-2 || fabs(x[1]-2)>2e-2);
+ (void)argc;
+ (void)argv;
+ const int n = 2;
+ double x0[2] = {0.0, 0.0};
+
+ // Set up the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.x0 = x0;
+ problem.calfun = &fun;
+ // We define an upper bound that will be active in order to demonstrate the usage.
+ double xl[] = {-1.0, -1.0};
+ double xu[] = {4.5, 4.5};
+ problem.xl = xl;
+ problem.xu = xu;
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_EXIT;
+ options.rhoend = 1e-6;
+ options.maxfun = 500*n;
+ options.callback = &callback;
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(PRIMA_BOBYQA, problem, options, &result);
+
+ // Print the result
+ printf("x* = {%g, %g}, rc = %d, msg = '%s', evals = %d\n", result.x[0], result.x[1], rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 4.5) > 2e-2 || fabs(result.x[1] - 4) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
}
diff --git a/c/examples/cobyla/CMakeLists.txt b/c/examples/cobyla/CMakeLists.txt
index 63604fed84..02fb42ff9e 100644
--- a/c/examples/cobyla/CMakeLists.txt
+++ b/c/examples/cobyla/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
-project(cobyla_example C Fortran)
+project(cobyla_example C)
find_package(PRIMA CONFIG REQUIRED)
add_executable(cobyla_example cobyla_example.c)
-target_link_libraries(cobyla_example prima::primac)
+target_link_libraries(cobyla_example prima::primac m)
install(TARGETS cobyla_example DESTINATION bin)
diff --git a/c/examples/cobyla/cobyla_example.c b/c/examples/cobyla/cobyla_example.c
index 42100afdca..f0dd1b36b5 100644
--- a/c/examples/cobyla/cobyla_example.c
+++ b/c/examples/cobyla/cobyla_example.c
@@ -1,48 +1,81 @@
// An example to illustrate the use of COBYLA.
+
#include "prima/prima.h"
-#include
#include
+#include
+#include
+
+
+#define M_NLCON 1
+#define PROVIDE_INITIAL_F_AND_NLCONSTR 1
+
+
+// Objective & constraint function
+static void fun(const double x[], double *const f, double constr[], const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = pow(x1-5, 2) + pow(x2-4, 2);
+ constr[0] = pow(x1, 2) - 9; // Constraint: x1^2 - 9 <= 0
+ (void)data;
+}
-static void fun(const double x[], double *f, double constr[])
+
+// Callback function
+static void callback(const int n, const double x[], const double f, const int nf, const int tr, const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate)
{
- const double x1 = x[0];
- const double x2 = x[1];
- *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
- constr[0] = x1*x1 + x2*x2 - 13;// ||x||^2<=13
+ (void)n;
+ (void)m_nlcon;
+ printf("Best point so far: x = {%g, %g}, f = %g, cstrv = %g, nlconstr = {%g}, nf = %d, tr = %d\n", x[0], x[1], f, cstrv, nlconstr[0], nf, tr);
+ *terminate = 0;
}
+
+// Main function
int main(int argc, char * argv[])
{
- (void)argc;
- (void)argv;
- const int m_nlcon = 1;
- const int n = 2;
- double x[2] = {0.0, 0.0};
- double f = 0.0;
- double cstrv = 0.0;
- double nlconstr[m_nlcon];
- // x1<=4, x2<=3, x1+x2<=10
- const int m_ineq = 3;
- double Aineq[3*2] = {1.0, 0.0,
- 0.0, 1.0,
- 1.0, 1.0};
- double bineq[3] = {4.0,
- 3.0,
- 10.0};
- const int m_eq = 0;
- double *Aeq = NULL;
- double *beq = NULL;
- double xl[2] = {-6.0, -6.0};
- double xu[2] = {6.0, 6.0};
- const double rhobeg = 1.0;
- const double rhoend = 1e-3;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_EXIT;
- const int maxfun = 200*n;
- int nf = 0;
- const int rc = prima_cobyla(m_nlcon, &fun, n, x, &f, &cstrv, nlconstr, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, iprint);
- const char *msg = prima_get_rc_string(rc);
- printf("x*={%g, %g} f*=%g cstrv=%g nlconstr=%g rc=%d msg='%s' evals=%d\n", x[0], x[1], f, cstrv, nlconstr[0], rc, msg, nf);
- return (fabs(x[0]-3)>2e-2 || fabs(x[1]-2)>2e-2);
+ (void)argc;
+ (void)argv;
+ const int n = 2;
+ double x0[2] = {0.0, 0.0};
+
+ // Set up the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.x0 = x0;
+ problem.m_nlcon = M_NLCON;
+ problem.calcfc = &fun;
+ // Provide the initial values of the objective function and the nonlinear constraints.
+ // This is OPTIONAL, and end users should NOT do it in general. Here, we do it for testing.
+ // problem.f0 and problem.nlconstr0 are used when interfacing with Python and Julia etc.
+ // They are not designed for C users.See the documentation of `prima_problem_t` for details.
+#if PROVIDE_INITIAL_F_AND_NLCONSTR
+ double nlconstr0[M_NLCON] = {0};
+ fun(x0, &(problem.f0), nlconstr0, NULL);
+ problem.nlconstr0 = nlconstr0;
+#endif
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_EXIT;
+ options.rhoend = 1e-6;
+ options.maxfun = 500*n;
+ options.callback = &callback;
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(PRIMA_COBYLA, problem, options, &result);
+
+ // Print the result
+ printf("x* = {%g, %g}, f* = %g, cstrv = %g, nlconstr = {%g}, rc = %d, msg = '%s', evals = %d\n", result.x[0], result.x[1], result.f, result.cstrv, result.nlconstr[0], rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 3) > 2e-2 || fabs(result.x[1] - 4) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
}
diff --git a/c/examples/lincoa/CMakeLists.txt b/c/examples/lincoa/CMakeLists.txt
index a048f89ce0..2421212bfe 100644
--- a/c/examples/lincoa/CMakeLists.txt
+++ b/c/examples/lincoa/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
-project(lincoa_example C Fortran)
+project(lincoa_example C)
find_package(PRIMA CONFIG REQUIRED)
add_executable(lincoa_example lincoa_example.c)
-target_link_libraries(lincoa_example prima::primac)
+target_link_libraries(lincoa_example prima::primac m)
install(TARGETS lincoa_example DESTINATION bin)
diff --git a/c/examples/lincoa/lincoa_example.c b/c/examples/lincoa/lincoa_example.c
index c275167da1..f341dce591 100644
--- a/c/examples/lincoa/lincoa_example.c
+++ b/c/examples/lincoa/lincoa_example.c
@@ -1,46 +1,73 @@
// An example to illustrate the use of LINCOA.
+
#include "prima/prima.h"
-#include
#include
+#include
+
+
+// Objective function
+static void fun(const double x[], double *const f, const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = pow(x1-5, 2) + pow(x2-4, 2);
+ (void)data;
+}
+
-static void fun(const double x[], double *f)
+// Callback function
+static void callback(const int n, const double x[], const double f, const int nf, const int tr, const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate)
{
- const double x1 = x[0];
- const double x2 = x[1];
- *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
+ (void)n;
+ (void)m_nlcon;
+ (void)nlconstr;
+ printf("Best point so far: x = {%g, %g}, f = %g, cstrv = %g, nf = %d, tr = %d\n", x[0], x[1], f, cstrv, nf, tr);
+ *terminate = 0;
}
+
+// Main function
int main(int argc, char * argv[])
{
- (void)argc;
- (void)argv;
- const int n = 2;
- double x[2] = {0.0, 0.0};
- double f = 0.0;
- double cstrv = 0.0;
- // x1<=4, x2<=3, x1+x2<=10
- const int m_ineq = 3;
- double Aineq[3*2] = {1.0, 0.0,
- 0.0, 1.0,
- 1.0, 1.0};
- double bineq[3] = {4.0,
- 3.0,
- 10.0};
- const int m_eq = 0;
- double *Aeq = NULL;
- double *beq = NULL;
- double xl[2] = {-6.0, -6.0};
- double xu[2] = {6.0, 6.0};
- const double rhobeg = 1.0;
- const double rhoend = 1e-3;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_EXIT;
- const int maxfun = 200*n;
- const int npt = 2*n+1;
- int nf = 0;
- const int rc = prima_lincoa(&fun, n, x, &f, &cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint);
- const char *msg = prima_get_rc_string(rc);
- printf("x*={%g, %g} f*=%g cstrv=%g rc=%d msg='%s' evals=%d\n", x[0], x[1], f, cstrv, rc, msg, nf);
- return (fabs(x[0]-3)>2e-2 || fabs(x[1]-2)>2e-2);
+ (void)argc;
+ (void)argv;
+ const int n = 2;
+ double x0[2] = {0.0, 0.0};
+
+ // Set up the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.calfun = &fun;
+ problem.x0 = x0;
+ // We define linear constraints that will be active in order to demonstrate the usage.
+ // The constraint is x1 + x2 <= 8.
+ problem.m_ineq = 1;
+ double Aineq[1*2] = {1.0, 1.0};
+ double bineq[1] = {8.0};
+ problem.Aineq = Aineq;
+ problem.bineq = bineq;
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_EXIT;
+ options.rhoend = 1e-6;
+ options.maxfun = 500*n;
+ options.callback = &callback;
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(PRIMA_LINCOA, problem, options, &result);
+
+ // Print the result
+ printf("x* = {%g, %g}, f* = %g, cstrv = %g, rc = %d, msg = '%s', evals = %d\n", result.x[0], result.x[1], result.f, result.cstrv, rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 4.5) > 2e-2 || fabs(result.x[1] - 3.5) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
}
diff --git a/c/examples/newuoa/CMakeLists.txt b/c/examples/newuoa/CMakeLists.txt
index b38e3ea498..dbcec6b8f3 100644
--- a/c/examples/newuoa/CMakeLists.txt
+++ b/c/examples/newuoa/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
-project(newuoa_example C Fortran)
+project(newuoa_example C)
find_package(PRIMA CONFIG REQUIRED)
add_executable(newuoa_example newuoa_example.c)
-target_link_libraries(newuoa_example prima::primac)
+target_link_libraries(newuoa_example prima::primac m)
install(TARGETS newuoa_example DESTINATION bin)
diff --git a/c/examples/newuoa/newuoa_example.c b/c/examples/newuoa/newuoa_example.c
index 10596dbc06..21356d1a2d 100644
--- a/c/examples/newuoa/newuoa_example.c
+++ b/c/examples/newuoa/newuoa_example.c
@@ -1,32 +1,67 @@
// An example to illustrate the use of NEWUOA.
+
#include "prima/prima.h"
-#include
#include
+#include
+
+
+// Objective function
+static void fun(const double x[], double *const f, const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = pow(x1-5, 2) + pow(x2-4, 2);
+ (void)data;
+}
+
-static void fun(const double x[], double *f)
+// Callback function
+static void callback(const int n, const double x[], const double f, const int nf, const int tr, const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate)
{
- const double x1 = x[0];
- const double x2 = x[1];
- *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
+ (void)n;
+ (void)cstrv;
+ (void)m_nlcon;
+ (void)nlconstr;
+ printf("Best point so far: x = {%g, %g}, f = %g, nf = %d, tr = %d\n", x[0], x[1], f, nf, tr);
+ *terminate = 0;
}
+
+// Main function
int main(int argc, char * argv[])
{
- (void)argc;
- (void)argv;
- const int n = 2;
- double x[2] = {0.0, 0.0};
- double f = 0.0;
- const double rhobeg = 1.0;
- const double rhoend = 1e-3;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_EXIT;
- const int maxfun = 200*n;
- const int npt = 2*n+1;
- int nf = 0;
- const int rc = prima_newuoa(&fun, n, x, &f, &nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint);
- const char *msg = prima_get_rc_string(rc);
- printf("x*={%g, %g} rc=%d msg='%s' evals=%d\n", x[0], x[1], rc, msg, nf);
- return (fabs(x[0]-3)>2e-2 || fabs(x[1]-2)>2e-2);
+ (void)argc;
+ (void)argv;
+ const int n = 2;
+ double x0[2] = {0.0, 0.0};
+
+ // Set up the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.calfun = &fun;
+ problem.x0 = x0;
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_EXIT;
+ options.rhoend = 1e-6;
+ options.maxfun = 500*n;
+ options.callback = &callback;
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(PRIMA_NEWUOA, problem, options, &result);
+
+ // Print the result
+ printf("x* = {%g, %g}, rc = %d, msg = '%s', evals = %d\n", result.x[0], result.x[1], rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 5) > 2e-2 || fabs(result.x[1] - 4) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
}
diff --git a/c/examples/uobyqa/CMakeLists.txt b/c/examples/uobyqa/CMakeLists.txt
index 274e56149a..e73706f72f 100644
--- a/c/examples/uobyqa/CMakeLists.txt
+++ b/c/examples/uobyqa/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
-project(uobyqa_example C Fortran)
+project(uobyqa_example C)
find_package(PRIMA CONFIG REQUIRED)
add_executable(uobyqa_example uobyqa_example.c)
-target_link_libraries(uobyqa_example prima::primac)
+target_link_libraries(uobyqa_example prima::primac m)
install(TARGETS uobyqa_example DESTINATION bin)
diff --git a/c/examples/uobyqa/uobyqa_example.c b/c/examples/uobyqa/uobyqa_example.c
index 56d18460e7..79c18c39b2 100644
--- a/c/examples/uobyqa/uobyqa_example.c
+++ b/c/examples/uobyqa/uobyqa_example.c
@@ -1,31 +1,67 @@
// An example to illustrate the use of UOBYQA.
+
#include "prima/prima.h"
-#include
#include
+#include
+
+
+// Objective function
+static void fun(const double x[], double *const f, const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = pow(x1-5, 2) + pow(x2-4, 2);
+ (void)data;
+}
+
-static void fun(const double x[], double *f)
+// Callback function
+static void callback(const int n, const double x[], const double f, const int nf, const int tr, const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate)
{
- const double x1 = x[0];
- const double x2 = x[1];
- *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
+ (void)n;
+ (void)cstrv;
+ (void)m_nlcon;
+ (void)nlconstr;
+ printf("Best point so far: x = {%g, %g}, f = %g, nf = %d, tr = %d\n", x[0], x[1], f, nf, tr);
+ *terminate = 0;
}
+
+// Main function
int main(int argc, char * argv[])
{
- (void)argc;
- (void)argv;
- const int n = 2;
- double x[2] = {0.0, 0.0};
- double f = 0.0;
- const double rhobeg = 1.0;
- const double rhoend = 1e-3;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_EXIT;
- const int maxfun = 200*n;
- int nf = 0;
- const int rc = prima_uobyqa(&fun, n, x, &f, &nf, rhobeg, rhoend, ftarget, maxfun, iprint);
- const char *msg = prima_get_rc_string(rc);
- printf("x*={%g, %g} rc=%d msg='%s' evals=%d\n", x[0], x[1], rc, msg, nf);
- return (fabs(x[0]-3)>2e-2 || fabs(x[1]-2)>2e-2);
+ (void)argc;
+ (void)argv;
+ const int n = 2;
+ double x0[2] = {0.0, 0.0};
+
+ // Set up the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.calfun = &fun;
+ problem.x0 = x0;
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_EXIT;
+ options.rhoend = 1e-6;
+ options.maxfun = 500*n;
+ options.callback = &callback;
+
+ // Run the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(PRIMA_UOBYQA, problem, options, &result);
+
+ // Print the result
+ printf("x* = {%g, %g}, rc = %d, msg = '%s', evals = %d\n", result.x[0], result.x[1], rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 5) > 2e-2 || fabs(result.x[1] - 4) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
}
diff --git a/c/include/prima/prima.h b/c/include/prima/prima.h
index c81f8d6204..6755765729 100644
--- a/c/include/prima/prima.h
+++ b/c/include/prima/prima.h
@@ -3,132 +3,298 @@
#ifndef PRIMA_H
#define PRIMA_H
+#include
+
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
-# define PRIMAC_HELPER_DLL_IMPORT __declspec(dllimport)
-# define PRIMAC_HELPER_DLL_EXPORT __declspec(dllexport)
+#define PRIMAC_HELPER_DLL_IMPORT __declspec(dllimport)
+#define PRIMAC_HELPER_DLL_EXPORT __declspec(dllexport)
#else
-# define PRIMAC_HELPER_DLL_IMPORT
-# define PRIMAC_HELPER_DLL_EXPORT
+#define PRIMAC_HELPER_DLL_IMPORT
+#define PRIMAC_HELPER_DLL_EXPORT
#endif
#ifdef PRIMAC_STATIC
-# define PRIMAC_API
+#define PRIMAC_API
#elif defined primac_EXPORTS
-# define PRIMAC_API PRIMAC_HELPER_DLL_EXPORT
+#define PRIMAC_API PRIMAC_HELPER_DLL_EXPORT
#else
-# define PRIMAC_API PRIMAC_HELPER_DLL_IMPORT
+#define PRIMAC_API PRIMAC_HELPER_DLL_IMPORT
#endif
-/*
- * Verbosity level
- */
+
+// Possible algorithms
typedef enum {
- PRIMA_MSG_NONE = 0, /* No messages */
- PRIMA_MSG_EXIT = 1, /* Exit reasons */
- PRIMA_MSG_RHO = 2, /* Rho changes */
- PRIMA_MSG_FEVL = 3, /* The object/constraint functions get evaluated */
-} prima_message;
+ PRIMA_UOBYQA, // Unconstrained
+ PRIMA_NEWUOA, // Unconstrained
+ PRIMA_BOBYQA, // Bound-constrained
+ PRIMA_LINCOA, // Linearly constrained
+ PRIMA_COBYLA, // Nonlinearly constrained
+} prima_algorithm_t;
+
+
+// Verbosity level
+typedef enum {
+ PRIMA_MSG_NONE = 0, // Do not print any message
+ PRIMA_MSG_EXIT = 1, // Print a message at exit
+ PRIMA_MSG_RHO = 2, // Print a message when rho changes
+ PRIMA_MSG_FEVL = 3, // Print a message when the object/constraint functions get evaluated
+} prima_message_t;
+
+
+// Possible return values
+typedef enum {
+ PRIMA_RC_DFT = 0,
+ PRIMA_SMALL_TR_RADIUS = 0,
+ PRIMA_FTARGET_ACHIEVED = 1,
+ PRIMA_TRSUBP_FAILED = 2,
+ PRIMA_MAXFUN_REACHED = 3,
+ PRIMA_MAXTR_REACHED = 20,
+ PRIMA_NAN_INF_X = -1,
+ PRIMA_NAN_INF_F = -2,
+ PRIMA_NAN_INF_MODEL = -3,
+ PRIMA_NO_SPACE_BETWEEN_BOUNDS = 6,
+ PRIMA_DAMAGING_ROUNDING = 7,
+ PRIMA_ZERO_LINEAR_CONSTRAINT = 8,
+ PRIMA_CALLBACK_TERMINATE = 30,
+ PRIMA_INVALID_INPUT = 100,
+ PRIMA_ASSERTION_FAILS = 101,
+ PRIMA_VALIDATION_FAILS = 102,
+ PRIMA_MEMORY_ALLOCATION_FAILS = 103,
+ PRIMA_NULL_OPTIONS = 110,
+ PRIMA_NULL_PROBLEM = 111,
+ PRIMA_NULL_X0 = 112,
+ PRIMA_NULL_RESULT = 113,
+ PRIMA_NULL_FUNCTION = 114,
+ PRIMA_RESULT_INITIALIZED = 115,
+} prima_rc_t;
-/*
- * Possible return values
- */
-typedef enum
-{
- PRIMA_SMALL_TR_RADIUS = 0,
- PRIMA_FTARGET_ACHIEVED = 1,
- PRIMA_TRSUBP_FAILED = 2,
- PRIMA_MAXFUN_REACHED = 3,
- PRIMA_MAXTR_REACHED = 20,
- PRIMA_NAN_INF_X = -1,
- PRIMA_NAN_INF_F = -2,
- PRIMA_NAN_INF_MODEL = -3,
- PRIMA_NO_SPACE_BETWEEN_BOUNDS = 6,
- PRIMA_DAMAGING_ROUNDING = 7,
- PRIMA_ZERO_LINEAR_CONSTRAINT = 8,
- PRIMA_INVALID_INPUT = 100,
- PRIMA_ASSERTION_FAILS = 101,
- PRIMA_VALIDATION_FAILS = 102,
- PRIMA_MEMORY_ALLOCATION_FAILS = 103,
-} prima_rc;
/*
- * Return code string
+ * Objective function required by UOBYQA, NEWUOA, BOBYQA, and LINCOA
+ * x : on input, the vector of variables (should not be modified)
+ * f : on output, the value of the function
+ * a NaN value can be passed to signal an evaluation error
+ * data : user data
*/
-PRIMAC_API
-const char *prima_get_rc_string(int rc);
+typedef void (*prima_obj_t)(const double x[], double *const f, const void *data);
+
/*
- * A function as required by solvers
- *
- * x : on input, then vector of variables (should not be modified)
+ * Objective & constraint function required by COBYLA
+ * x : on input, the vector of variables (should not be modified)
* f : on output, the value of the function
- * constr : on output, the value of the constraints (of size m_nlcon)
- * only for cobyla
-*/
-typedef void (*prima_obj)(const double x[], double *f);
-typedef void (*prima_objcon)(const double x[], double *f, double constr[]);
+ * a NaN value can be passed to signal an evaluation error
+ * constr : on output, the value of the constraints (of size m_nlcon),
+ * with the constraints being constr <= 0
+ * NaN values can be passed to signal evaluation errors
+ * data : user data
+ */
+typedef void (*prima_objcon_t)(const double x[], double *const f, double constr[], const void *data);
+
/*
- * calfun : function to minimize (see prima_obj)
- * n : number of variables (>=0)
- * x : on input, initial estimate
- * on output, the solution
- * f : objective value (output)
- * nf : number of objective function calls (output)
- * rhobeg : a reasonable initial change to the variables
- * rhoend : required accuracy for the variables
- * ftarget : target function value; optimization stops when f <= ftarget for a feasible point,
- * can be set to -INFINITY to disable
- * maxfun : maximum number of function evaluations
- * npt : number of points in the interpolation set, n+2<=npt<=(n+1)(n+2)/2, recommended: 2*n+1
- * iprint : verbosity level, see the prima_message enum
- * m_nlcon : number of non-linear constraints (>=0)
- * calcfc : function to minimize and constraints (see prima_objcon)
- * cstrv : constraint violation (output)
- * nlconstr : non-linear constraint values of size m_nlcon (output)
- * m_ineq, Aineq, bineq : Aineq*x <= bineq constraint
- * Aineq is an m_ineq-by-n matrix stored in row-major order (line by line)
- * bineq is of size m_ineq
- * m_eq, Aeq, beq : Aeq*x = beq constraint
- * Aeq is an m_eq-by-n matrix stored in row-major order (line by line)
- * beq is of size m_eq
- * xl, xu : x lower & upper bounds, of size n
- *
- * return : see prima_rc enum for return codes
+ * Callback function to report algorithm progress
+ * n : number of variables
+ * x : the current best point
+ * f : the function value of the current best point
+ * nf : number of function evaluations
+ * tr : number of trust-region iterations
+ * cstrv : the constraint violation of the current best point (LINCOA and COBYLA only)
+ * m_nlcon : number of nonlinear constraints (COBYLA only)
+ * nlconstr : nonlinear constraint values of the current best point (COBYLA only)
+ * terminate : a boolean to ask for termination
*/
+typedef void (*prima_callback_t)(const int n, const double x[], const double f, const int nf, const int tr,
+ const double cstrv, const int m_nlcon, const double nlconstr[], bool *const terminate);
+
+
+// Structure to hold the problem
+// In the following, "Default" refers to the value set by `prima_init_problem`.
+typedef struct {
+
+ // n: number of variables, n >= 1
+ // Default: 0
+ int n;
+
+ // calfun: pointer to the objective function to minimize
+ // Should not be NULL for UOBYQA, NEWUOA, BOBYQA, and LINCOA; should be NULL for COBYLA
+ // Default: NULL
+ prima_obj_t calfun;
+
+ // calcfc: pointer to the objective & constraint function to minimize
+ // Should not be NULL for COBYLA; should be NULL for UOBYQA, NEWUOA, BOBYQA, and LINCOA
+ // Default: NULL
+ prima_objcon_t calcfc;
+
+ // starting point
+ // Should not be NULL
+ // Default: NULL
+ double *x0;
+
+ // Bound constraints: xl <= x <= xu
+ // Should be NULL for UOBYQA and NEWUOA
+ // Default: xl = NULL and xu = NULL
+ double *xl;
+ double *xu;
+
+ // Linear inequality constraints: Aineq*x <= bineq
+ // Aineq is an m_ineq-by-n matrix stored in row-major order (row by row)
+ // bineq is of size m_ineq
+ // Should be m_ineq = 0, Aineq = NULL, and bineq = NULL for UOBYQA, NEWUOA, and BOBYQA
+ // Default: m_ineq = 0, Aineq = NULL, and bineq = NULL
+ int m_ineq;
+ double *Aineq;
+ double *bineq;
+
+ // Linear equality constraints: Aeq*x = beq
+ // Aeq is an m_eq-by-n matrix stored in row-major order (row by row)
+ // beq is of size m_eq
+ // Should be m_eq = 0, Aeq = NULL, and beq = NULL for UOBYQA, NEWUOA, and BOBYQA
+ // Default: m_eq = 0, Aeq = NULL, and beq = NULL
+ int m_eq;
+ double *Aeq;
+ double *beq;
+
+ // m_nlcon: number of nonlinear constraints defined by calcfc
+ // Should be 0 for UOBYQA, NEWUOA, BOBYQA, and LINCOA
+ // Default: 0
+ int m_nlcon;
+
+ // f0, nlconstr0: initial objective function value and constraint values (COBYLA only)
+ // It should ONLY be used when interfacing with high-level languages such as MATLAB/Python/
+ // Julia/R. In these languages, instead of asking the user to provide m_nlcon, we should
+ // evaluate the constraints at the initial point to get m_nlcon; we evaluate the objective
+ // function at the initial point as well to keep the objective and constraint evaluations
+ // synchronized; after this, we pass the initial object and constraint values to the solver
+ // via f0 and nlconstr0 to avoid re-evaluating them, as the evaluations are expensive.
+ // C end users should leave f0 and nlconstr0 as the default set by prima_init_problem and
+ // set m_nlcon to the number of nonlinear constraints.
+ // Default: f0 = NAN and nlconstr0 = NULL
+ double f0;
+ double *nlconstr0;
+
+} prima_problem_t;
-PRIMAC_API
-int prima_bobyqa(const prima_obj calfun, const int n, double x[], double *f,
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint);
+// Function to initialize the problem
PRIMAC_API
-int prima_newuoa(const prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint);
+prima_rc_t prima_init_problem(prima_problem_t *const problem, const int n);
+
+// Structure to hold the options
+// In the following, "Default" refers to the value set by `prima_init_options`.
+typedef struct {
+
+ // rhobeg: a reasonable initial change to the variables
+ // Default: NaN, which will be interpreted in Fortran as not present, in which case a default
+ // value will be used
+ double rhobeg;
+
+ // rhoend: required accuracy for the variables
+ // Default: NaN, which will be interpreted in Fortran as not present, in which case a default
+ // value will be used
+ double rhoend;
+
+ // maxfun: maximum number of function evaluations
+ // Default: 0, which will be interpreted in Fortran as not present, in which case a default value
+ // will be used
+ int maxfun;
+
+ // iprint: verbosity level (see the prima_message_t enum)
+ // Default: PRIMA_MSG_NONE, which means that no message will be printed
+ int iprint;
+
+ // ftarget: target function value; solver stops when f <= ftarget for a feasible point
+ // Default: -Inf
+ double ftarget;
+
+ // npt: number of points in the interpolation set for NEWUOA, BOBYQA, and LINCOA
+ // Should satisfy n+2 <= npt <= (n+1)(n+2)/2
+ // It will be ignored by UOBYQA or COBYLA if provided
+ // Default: 0, which will be interpreted by Fortran as not present, in which case a default value
+ // based on the algorithm that will be used
+ int npt;
+
+ // ctol: tolerance for the constraint violation (COBYLA and LINCOA only)
+ // ctol is the tolerance of constraint violation. x is considered feasible if cstrv(x) <= ctol.
+ // N.B.: 1. ctol is absolute, not relative.
+ // 2. ctol is used for choosing the returned x. It does not affect the iterations of the algorithm.
+ // Default: NaN, which will be interpreted in Fortran as not present, in which case a default value
+ // of sqrt(machine epsilon) will be used.
+ double ctol;
+
+ // data: user data, will be passed through the objective function
+ // Default: NULL
+ void *data;
+
+ // callback: pointer to the callback function to report algorithm progress
+ // Default: NULL, which means that no callback will be called
+ prima_callback_t callback;
+
+} prima_options_t;
+
+
+// Function to initialize the options
PRIMAC_API
-int prima_uobyqa(const prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint);
+prima_rc_t prima_init_options(prima_options_t *const options);
+
+
+// Structure to hold the result
+// prima_minimize will allocate the memory for x and nlconstr (if needed),
+// and as such the user is expected to free the memory using prima_free_result
+// once they are done using the contents of the result (or have saved the contents).
+typedef struct {
+ // x: returned point
+ double *x;
+
+ // f: objective function value at the returned point
+ double f;
+
+ // cstrv: constraint violation at the returned point (COBYLA and LINCOA only)
+ double cstrv;
+
+ // nlconstr: nonlinear constraint values at the returned point, of size m_nlcon (COBYLA only)
+ double *nlconstr;
+
+ // nf: number of function evaluations
+ int nf;
+
+ // status: return code
+ prima_rc_t status;
+
+ // success: whether the solver returned normally or ran into abnormal conditions
+ bool success;
+
+ // message: exit message
+ const char *message;
+
+} prima_result_t;
+
+
+// Function to free the result
PRIMAC_API
-int prima_cobyla(const int m_nlcon, const prima_objcon calcfc, const int n, double x[], double *f,
- double *cstrv, double nlconstr[],
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint);
+prima_rc_t prima_free_result(prima_result_t *const result);
+
+/*
+ * The function that does the minimization using a PRIMA solver.
+ * After using (or saving) the result, the user is expected to call
+ * prima_free_result, regardless of the return value of prima_minimize,
+ * in order to avoid memory leaks.
+ * algorithm : optimization algorithm (see prima_algorithm_t)
+ * problem : optimization problem (see prima_problem_t)
+ * options : optimization options (see prima_options_t)
+ * result : optimization result (see prima_result_t)
+ * return : see prima_rc_t enum for return codes
+ */
PRIMAC_API
-int prima_lincoa(const prima_obj calfun, const int n, double x[], double *f,
- double *cstrv,
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint);
+prima_rc_t prima_minimize(const prima_algorithm_t algorithm, const prima_problem_t problem, const prima_options_t options, prima_result_t *const result);
+
#ifdef __cplusplus
}
diff --git a/c/lincoa_c.f90 b/c/lincoa_c.f90
index 5a1c46b72e..40864846d4 100644
--- a/c/lincoa_c.f90
+++ b/c/lincoa_c.f90
@@ -13,19 +13,21 @@ module lincoa_c_mod
contains
-subroutine lincoa_c(cobj_ptr, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &
- & nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, info) bind(C)
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR
-use, non_intrinsic :: cintrf_mod, only : COBJ
+subroutine lincoa_c(cobj_ptr, data_ptr, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &
+ & nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, ctol, callback_ptr, info) bind(C)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR, C_PTR, C_ASSOCIATED, C_F_PROCPOINTER, C_F_POINTER
+use, non_intrinsic :: cintrf_mod, only : COBJ, CCALLBACK
use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: infnan_mod, only : is_nan
use, non_intrinsic :: lincoa_mod, only : lincoa
+use, non_intrinsic :: memory_mod, only : safealloc
implicit none
! Compulsory arguments
type(C_FUNPTR), intent(IN), value :: cobj_ptr
+type(C_PTR), intent(in), value :: data_ptr
integer(C_INT), intent(in), value :: n
-! We cannot use assumed-shape arrays for C interoperability
-real(C_DOUBLE), intent(inout) :: x(n)
+real(C_DOUBLE), intent(inout) :: x(n) ! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(out) :: f
real(C_DOUBLE), intent(out) :: cstrv
integer(C_INT), intent(in), value :: m_ineq
@@ -34,8 +36,8 @@ subroutine lincoa_c(cobj_ptr, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, b
integer(C_INT), intent(in), value :: m_eq
real(C_DOUBLE), intent(in) :: Aeq(n, m_eq)
real(C_DOUBLE), intent(in) :: beq(m_eq)
-real(C_DOUBLE), intent(in) :: xl(n)
-real(C_DOUBLE), intent(in) :: xu(n)
+type(C_PTR), intent(in), value :: xl
+type(C_PTR), intent(in), value :: xu
integer(C_INT), intent(out) :: nf
real(C_DOUBLE), intent(in), value :: rhobeg
real(C_DOUBLE), intent(in), value :: rhoend
@@ -43,50 +45,99 @@ subroutine lincoa_c(cobj_ptr, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, b
integer(C_INT), intent(in), value :: maxfun
integer(C_INT), intent(in), value :: npt
integer(C_INT), intent(in), value :: iprint
+real(C_DOUBLE), intent(in), value :: ctol
+type(C_FUNPTR), intent(in), value :: callback_ptr
integer(C_INT), intent(out) :: info
! Local variables
integer(IK) :: info_loc
integer(IK) :: iprint_loc
-integer(IK) :: maxfun_loc
-integer(IK) :: npt_loc
integer(IK) :: nf_loc
-real(RP) :: Aineq_loc(m_ineq, n)
-real(RP) :: bineq_loc(m_ineq)
+integer(IK), allocatable :: maxfun_loc
+integer(IK), allocatable :: npt_loc
+! The initialization below to null is necessary to avoid a bug with the newer Intel compiler ifx.
+! See https://fortran-lang.discourse.group/t/strange-issue-with-ifx-compiler-and-assume-recursion/7013
+! The bug was observed in all versions of ifx up to 2024.0.1. Once this bug is fixed we should
+! remove the initialization to null because it implies the 'save' attribute, which is undesirable.
+procedure(CCALLBACK), pointer :: cb_ptr => null()
+procedure(COBJ), pointer :: obj_ptr => null()
+real(C_DOUBLE), pointer :: xl_loc_interm(:)
+real(C_DOUBLE), pointer :: xu_loc_interm(:)
real(RP) :: Aeq_loc(m_eq, n)
+real(RP) :: Aineq_loc(m_ineq, n)
real(RP) :: beq_loc(m_eq)
+real(RP) :: bineq_loc(m_ineq)
real(RP) :: cstrv_loc
+real(RP) :: ctol_loc
real(RP) :: f_loc
-real(RP) :: rhobeg_loc
-real(RP) :: rhoend_loc
real(RP) :: ftarget_loc
real(RP) :: x_loc(n)
-real(RP) :: xl_loc(n)
-real(RP) :: xu_loc(n)
+real(RP), allocatable :: rhobeg_loc
+real(RP), allocatable :: rhoend_loc
+real(RP), allocatable :: xl_loc(:)
+real(RP), allocatable :: xu_loc(:)
! Read the inputs and convert them to the Fortran side types
! Note that `transpose` is needed when reading 2D arrays, since they are stored in the row-major
! order in c but column-major in Fortran.
+
+! The following inputs correspond to compulsory arguments in the Fortran code.
x_loc = real(x, kind(x_loc))
+call c_f_procpointer(cobj_ptr, obj_ptr)
+
+! The following inputs correspond to optional arguments in the Fortran code.
+! Since C does not support optional arguments, we use NaN to represent an absent real scalar, 0 to
+! represent an absent integer scalar (all integer arguments are expected positive), and an
+! unassociated pointer to represent an absent array. In case of NaN, 0, or unassociated pointers,
+! the allocatable variables such as RHOBEG_LOC will be left uninitialized and hence unallocated, and
+! then treated as an absent argument when passed to the Fortran code.
+! See Sec. 9.7.1.3 (4) and 15.5.2.13 (1) of J3/24-007 (Fortran 2023 Interpretation Document).
Aineq_loc = real(transpose(Aineq), kind(Aineq_loc))
bineq_loc = real(bineq, kind(bineq_loc))
Aeq_loc = real(transpose(Aeq), kind(Aeq_loc))
beq_loc = real(beq, kind(beq_loc))
-xl_loc = real(xl, kind(xl_loc))
-xu_loc = real(xu, kind(xu_loc))
-rhobeg_loc = real(rhobeg, kind(rhobeg))
-rhoend_loc = real(rhoend, kind(rhoend))
-ftarget_loc = real(ftarget, kind(ftarget))
-maxfun_loc = int(maxfun, kind(maxfun_loc))
-npt_loc = int(npt, kind(npt_loc))
+if (c_associated(xl)) then
+ call c_f_pointer(xl, xl_loc_interm, shape=[n])
+ call safealloc(xl_loc, int(n, IK))
+ xl_loc = real(xl_loc_interm, kind(xl_loc))
+end if
+if (c_associated(xu)) then
+ call c_f_pointer(xu, xu_loc_interm, shape=[n])
+ call safealloc(xu_loc, int(n, IK))
+ xu_loc = real(xu_loc_interm, kind(xu_loc))
+end if
+if (.not. is_nan(rhobeg)) then
+ rhobeg_loc = real(rhobeg, kind(rhobeg_loc))
+end if
+if (.not. is_nan(rhoend)) then
+ rhoend_loc = real(rhoend, kind(rhoend_loc))
+end if
+ftarget_loc = real(ftarget, kind(ftarget_loc))
+if (maxfun /= 0) then
+ maxfun_loc = int(maxfun, kind(maxfun_loc))
+end if
+if (npt /= 0) then
+ npt_loc = int(npt, kind(npt_loc))
+end if
iprint_loc = int(iprint, kind(iprint_loc))
+ctol_loc = real(ctol, kind(ctol_loc))
! Call the Fortran code
-call lincoa(calfun, x_loc, f_loc, cstrv=cstrv_loc, &
- & Aineq=Aineq_loc, bineq=bineq_loc, Aeq=Aeq_loc, beq=beq_loc, &
- & xl=xl_loc, xu=xu_loc, nf=nf_loc, &
- & rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
- & ftarget=ftarget_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, info=info_loc)
+if (c_associated(callback_ptr)) then
+ ! If a C callback function is provided, we convert it to a Fortran procedure pointer and capture
+ ! that pointer in the closure below.
+ call c_f_procpointer(callback_ptr, cb_ptr)
+ ! We then provide the closure to the algorithm.
+ call lincoa(calfun, x_loc, f_loc, cstrv=cstrv_loc, Aineq=Aineq_loc, bineq=bineq_loc, Aeq=Aeq_loc, &
+ & beq=beq_loc, xl=xl_loc, xu=xu_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
+ & ftarget=ftarget_loc, ctol=ctol_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, &
+ & callback_fcn=callback_fcn, info=info_loc)
+else
+ call lincoa(calfun, x_loc, f_loc, cstrv=cstrv_loc, Aineq=Aineq_loc, bineq=bineq_loc, Aeq=Aeq_loc, &
+ & beq=beq_loc, xl=xl_loc, xu=xu_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, &
+ & ftarget=ftarget_loc, ctol=ctol_loc, maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, &
+ & info=info_loc)
+end if
! Write the outputs
x = real(x_loc, kind(x))
@@ -95,22 +146,117 @@ subroutine lincoa_c(cobj_ptr, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, b
nf = int(nf_loc, kind(nf))
info = int(info_loc, kind(info))
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(npt_loc)) deallocate (npt_loc)
+if (allocated(maxfun_loc)) deallocate (maxfun_loc)
+if (allocated(rhoend_loc)) deallocate (rhoend_loc)
+if (allocated(rhobeg_loc)) deallocate (rhobeg_loc)
+if (allocated(xu_loc)) deallocate (xu_loc)
+if (allocated(xl_loc)) deallocate (xl_loc)
+
contains
!--------------------------------------------------------------------------------------------------!
! This subroutine defines `calfun` using the C function pointer with an internal subroutine.
! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across 4 out of 5 algorithms; COBYLA requires a slightly different
+! signature.
!--------------------------------------------------------------------------------------------------!
subroutine calfun(x_sub, f_sub)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE
use, non_intrinsic :: consts_mod, only : RP
-use, non_intrinsic :: cintrf_mod, only : evalcobj
implicit none
-real(RP), intent(in) :: x_sub(:)
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
real(RP), intent(out) :: f_sub
-call evalcobj(cobj_ptr, x_sub, f_sub)
+
+! Local variables
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE) :: f_sub_loc
+
+! Read the inputs and convert them to the types specified in COBJ
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+
+! Call the C objective function
+call obj_ptr(x_sub_loc, f_sub_loc, data_ptr)
+
+! Write the output
+f_sub = real(f_sub_loc, kind(f_sub))
+
end subroutine calfun
+
+!--------------------------------------------------------------------------------------------------!
+! This subroutine defines `callback_fcn` using the C function pointer with an internal subroutine.
+! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
+! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across all 5 algorithms.
+!--------------------------------------------------------------------------------------------------!
+subroutine callback_fcn(x_sub, f_sub, nf_sub, tr, cstrv_sub, nlconstr_sub, terminate)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_BOOL
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
+real(RP), intent(in) :: f_sub
+integer(IK), intent(in) :: nf_sub
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv_sub
+real(RP), intent(in), optional :: nlconstr_sub(:)
+
+! Outputs
+logical, intent(out), optional :: terminate
+
+! Local variables
+integer(C_INT) :: m_nlconstr
+integer(C_INT) :: n_sub_loc
+integer(C_INT) :: nf_sub_loc
+integer(C_INT) :: tr_loc
+logical(C_BOOL) :: terminate_loc
+real(C_DOUBLE) :: cstrv_sub_loc
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE), allocatable :: nlconstr_sub_loc(:)
+
+! Read the inputs and convert them to the types specified in CCALLBACK
+n_sub_loc = size(x_sub)
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+f_sub_loc = real(f_sub, kind(f_sub_loc))
+nf_sub_loc = int(nf_sub, kind(nf_sub_loc))
+tr_loc = int(tr, kind(tr_loc))
+
+! Set the constraint violation to a sensible default value if it is not provided.
+if (present(cstrv_sub)) then
+ cstrv_sub_loc = real(cstrv_sub, kind(cstrv_sub_loc))
+else
+ cstrv_sub_loc = 0.0_C_DOUBLE
+end if
+
+! Set the nonlinear constraints to a sensible default value if it is not provided.
+if (present(nlconstr_sub)) then
+ m_nlconstr = int(size(nlconstr_sub), C_INT)
+ call safealloc(nlconstr_sub_loc, int(m_nlconstr, IK))
+ nlconstr_sub_loc = real(nlconstr_sub, kind(nlconstr_sub_loc))
+else
+ m_nlconstr = 0_C_INT
+ nlconstr_sub_loc = [real(C_DOUBLE) ::]
+end if
+
+! Call the C callback function
+call cb_ptr(n_sub_loc, x_sub_loc, f_sub_loc, nf_sub_loc, tr_loc, cstrv_sub_loc, m_nlconstr, nlconstr_sub_loc, terminate_loc)
+
+! Write the output
+if (present(terminate)) then
+ terminate = logical(terminate_loc, kind(terminate))
+end if
+
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(nlconstr_sub_loc)) deallocate (nlconstr_sub_loc)
+
+end subroutine callback_fcn
+
end subroutine lincoa_c
diff --git a/c/newuoa_c.f90 b/c/newuoa_c.f90
index 59a05903b8..21f1eb8efe 100644
--- a/c/newuoa_c.f90
+++ b/c/newuoa_c.f90
@@ -12,18 +12,19 @@ module newuoa_c_mod
contains
-subroutine newuoa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, info) bind(C)
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR
-use, non_intrinsic :: cintrf_mod, only : COBJ
+subroutine newuoa_c(cobj_ptr, data_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, callback_ptr, info) bind(C)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR, C_PTR, C_ASSOCIATED, C_F_PROCPOINTER
+use, non_intrinsic :: cintrf_mod, only : COBJ, CCALLBACK
use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: infnan_mod, only : is_nan
use, non_intrinsic :: newuoa_mod, only : newuoa
implicit none
! Compulsory arguments
type(C_FUNPTR), intent(IN), value :: cobj_ptr
+type(C_PTR), intent(in), value :: data_ptr
integer(C_INT), intent(in), value :: n
-! We cannot use assumed-shape arrays for C interoperability
-real(C_DOUBLE), intent(inout) :: x(n)
+real(C_DOUBLE), intent(inout) :: x(n) ! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(out) :: f
integer(C_INT), intent(out) :: nf
real(C_DOUBLE), intent(in), value :: rhobeg
@@ -32,32 +33,67 @@ subroutine newuoa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt,
integer(C_INT), intent(in), value :: maxfun
integer(C_INT), intent(in), value :: npt
integer(C_INT), intent(in), value :: iprint
+type(C_FUNPTR), intent(in), value :: callback_ptr
integer(C_INT), intent(out) :: info
! Local variables
integer(IK) :: info_loc
integer(IK) :: iprint_loc
-integer(IK) :: maxfun_loc
-integer(IK) :: npt_loc
integer(IK) :: nf_loc
+integer(IK), allocatable :: maxfun_loc
+integer(IK), allocatable :: npt_loc
+! The initialization below to null is necessary to avoid a bug with the newer Intel compiler ifx.
+! See https://fortran-lang.discourse.group/t/strange-issue-with-ifx-compiler-and-assume-recursion/7013
+! The bug was observed in all versions of ifx up to 2024.0.1. Once this bug is fixed we should
+! remove the initialization to null because it implies the 'save' attribute, which is undesirable.
+procedure(CCALLBACK), pointer :: cb_ptr => null()
+procedure(COBJ), pointer :: obj_ptr => null()
real(RP) :: f_loc
-real(RP) :: rhobeg_loc
-real(RP) :: rhoend_loc
real(RP) :: ftarget_loc
real(RP) :: x_loc(n)
+real(RP), allocatable :: rhobeg_loc
+real(RP), allocatable :: rhoend_loc
! Read the inputs and convert them to the Fortran side types
+
+! The following inputs correspond to compulsory arguments in the Fortran code.
x_loc = real(x, kind(x_loc))
-rhobeg_loc = real(rhobeg, kind(rhobeg))
-rhoend_loc = real(rhoend, kind(rhoend))
-ftarget_loc = real(ftarget, kind(ftarget))
-maxfun_loc = int(maxfun, kind(maxfun_loc))
-npt_loc = int(npt, kind(npt_loc))
+call c_f_procpointer(cobj_ptr, obj_ptr)
+
+! The following inputs correspond to optional arguments in the Fortran code.
+! Since C does not support optional arguments, we use NaN to represent an absent real scalar, 0 to
+! represent an absent integer scalar (all integer arguments are expected positive), and an
+! unassociated pointer to represent an absent array. In case of NaN, 0, or unassociated pointers,
+! the allocatable variables such as RHOBEG_LOC will be left uninitialized and hence unallocated, and
+! then treated as an absent argument when passed to the Fortran code.
+! See Sec. 9.7.1.3 (4) and 15.5.2.13 (1) of J3/24-007 (Fortran 2023 Interpretation Document).
+if (.not. is_nan(rhobeg)) then
+ rhobeg_loc = real(rhobeg, kind(rhobeg_loc))
+end if
+if (.not. is_nan(rhoend)) then
+ rhoend_loc = real(rhoend, kind(rhoend_loc))
+end if
+ftarget_loc = real(ftarget, kind(ftarget_loc))
+if (maxfun /= 0) then
+ maxfun_loc = int(maxfun, kind(maxfun_loc))
+end if
+if (npt /= 0) then
+ npt_loc = int(npt, kind(npt_loc))
+end if
iprint_loc = int(iprint, kind(iprint_loc))
! Call the Fortran code
-call newuoa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
- & maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, info=info_loc)
+if (c_associated(callback_ptr)) then
+ ! If a C callback function is provided, we convert it to a Fortran procedure pointer and capture
+ ! that pointer in the closure below.
+ call c_f_procpointer(callback_ptr, cb_ptr)
+ ! We then provide the closure to the algorithm.
+ call newuoa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
+ & maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, callback_fcn=callback_fcn, info=info_loc)
+else
+ call newuoa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
+ & maxfun=maxfun_loc, npt=npt_loc, iprint=iprint_loc, info=info_loc)
+end if
! Write the outputs
x = real(x_loc, kind(x))
@@ -65,22 +101,115 @@ subroutine newuoa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt,
nf = int(nf_loc, kind(nf))
info = int(info_loc, kind(info))
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(npt_loc)) deallocate (npt_loc)
+if (allocated(maxfun_loc)) deallocate (maxfun_loc)
+if (allocated(rhoend_loc)) deallocate (rhoend_loc)
+if (allocated(rhobeg_loc)) deallocate (rhobeg_loc)
+
contains
!--------------------------------------------------------------------------------------------------!
! This subroutine defines `calfun` using the C function pointer with an internal subroutine.
! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across 4 out of 5 algorithms; COBYLA requires a slightly different
+! signature.
!--------------------------------------------------------------------------------------------------!
subroutine calfun(x_sub, f_sub)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE
use, non_intrinsic :: consts_mod, only : RP
-use, non_intrinsic :: cintrf_mod, only : evalcobj
implicit none
-real(RP), intent(in) :: x_sub(:)
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
real(RP), intent(out) :: f_sub
-call evalcobj(cobj_ptr, x_sub, f_sub)
+
+! Local variables
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE) :: f_sub_loc
+
+! Read the inputs and convert them to the types specified in COBJ
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+
+! Call the C objective function
+call obj_ptr(x_sub_loc, f_sub_loc, data_ptr)
+
+! Write the output
+f_sub = real(f_sub_loc, kind(f_sub))
+
end subroutine calfun
+
+!--------------------------------------------------------------------------------------------------!
+! This subroutine defines `callback_fcn` using the C function pointer with an internal subroutine.
+! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
+! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across all 5 algorithms.
+!--------------------------------------------------------------------------------------------------!
+subroutine callback_fcn(x_sub, f_sub, nf_sub, tr, cstrv_sub, nlconstr_sub, terminate)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_BOOL
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
+real(RP), intent(in) :: f_sub
+integer(IK), intent(in) :: nf_sub
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv_sub
+real(RP), intent(in), optional :: nlconstr_sub(:)
+
+! Outputs
+logical, intent(out), optional :: terminate
+
+! Local variables
+integer(C_INT) :: m_nlconstr
+integer(C_INT) :: n_sub_loc
+integer(C_INT) :: nf_sub_loc
+integer(C_INT) :: tr_loc
+logical(C_BOOL) :: terminate_loc
+real(C_DOUBLE) :: cstrv_sub_loc
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE), allocatable :: nlconstr_sub_loc(:)
+
+! Read the inputs and convert them to the types specified in CCALLBACK
+n_sub_loc = size(x_sub)
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+f_sub_loc = real(f_sub, kind(f_sub_loc))
+nf_sub_loc = int(nf_sub, kind(nf_sub_loc))
+tr_loc = int(tr, kind(tr_loc))
+
+! Set the constraint violation to a sensible default value if it is not provided.
+if (present(cstrv_sub)) then
+ cstrv_sub_loc = real(cstrv_sub, kind(cstrv_sub_loc))
+else
+ cstrv_sub_loc = 0.0_C_DOUBLE
+end if
+
+! Set the nonlinear constraints to a sensible default value if it is not provided.
+if (present(nlconstr_sub)) then
+ m_nlconstr = int(size(nlconstr_sub), C_INT)
+ call safealloc(nlconstr_sub_loc, int(m_nlconstr, IK))
+ nlconstr_sub_loc = real(nlconstr_sub, kind(nlconstr_sub_loc))
+else
+ m_nlconstr = 0_C_INT
+ nlconstr_sub_loc = [real(C_DOUBLE) ::]
+end if
+
+! Call the C callback function
+call cb_ptr(n_sub_loc, x_sub_loc, f_sub_loc, nf_sub_loc, tr_loc, cstrv_sub_loc, m_nlconstr, nlconstr_sub_loc, terminate_loc)
+
+! Write the output
+if (present(terminate)) then
+ terminate = logical(terminate_loc, kind(terminate))
+end if
+
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(nlconstr_sub_loc)) deallocate (nlconstr_sub_loc)
+
+end subroutine callback_fcn
+
end subroutine newuoa_c
diff --git a/c/prima.c b/c/prima.c
index bf8d3478cf..8cef31b7f3 100644
--- a/c/prima.c
+++ b/c/prima.c
@@ -1,108 +1,273 @@
-/* Dedicated to the late Professor M. J. D. Powell FRS (1936--2015). */
+// Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
+
#include "prima/prima.h"
+#include
+#include
+#include
+#include
+#include
-/* implemented in fortran (*_c.f90) */
-int cobyla_c(const int m_nlcon, const prima_objcon calcfc, const int n, double x[], double *f, double *cstrv, double nlconstr[],
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, int *info);
-int bobyqa_c(prima_obj calfun, const int n, double x[], double *f, const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, int *info);
-int newuoa_c(prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, int *info);
-int uobyqa_c(prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, int *info);
-int lincoa_c(prima_obj calfun, const int n, double x[], double *f,
- double *cstrv,
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, int *info);
-
-/* these functions just call the fortran compatibility layer and return the status code */
-int prima_cobyla(const int m_nlcon, const prima_objcon calcfc, const int n, double x[], double *f, double *cstrv, double nlconstr[],
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint)
+
+/*
+ * A NOTE ON DEFAULT VALUES IN OPTIONS AND PROBLEM STRUCTURES
+ *
+ * Certain values of the variables in the options and problems structures
+ * are interpreted by the Fortran code as "not present". This is not by default,
+ * it is done by us intentionally so that we may signal to the Fortran code that
+ * these values were not provided. This is so that the Fortran code may then properly
+ * set the default values for those variables.
+ *
+ * In order to accomplish this we take advantage of a certain part of the Fortran
+ * standard that basically says that if an allocatable value which has not been
+ * allocated is passed to a procedure, `present` will return false.
+ *
+ * Our convention is as follows
+ * double - NaN is interpreted as not present
+ * int - 0 is interpreted as not present (as of 20240124 all ints are expected nonnegative)
+ * pointer - NULL is interpreted as not present
+ *
+ * If variables are added to options/problems that are optional, the algorithm_c.f90 files must
+ * be updated to treat the default values appropriately. For examples see rhobeg/rhoend (double),
+ * maxfun/npt(int), and xl/xu (array/pointer).
+ */
+
+
+// Function to initialize the problem
+prima_rc_t prima_init_problem(prima_problem_t *const problem, const int n)
+{
+ if (!problem)
+ return PRIMA_NULL_PROBLEM;
+
+ memset(problem, 0, sizeof(prima_problem_t));
+ problem->n = n;
+ problem->f0 = NAN;
+ return PRIMA_RC_DFT;
+}
+
+
+// Function to initialize the options
+prima_rc_t prima_init_options(prima_options_t *const options)
{
- int info = 0;
- cobyla_c(m_nlcon, calcfc, n, x, f, cstrv, nlconstr, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, iprint, &info);
- return info;
+ if (!options)
+ return PRIMA_NULL_OPTIONS;
+
+ memset(options, 0, sizeof(prima_options_t));
+ options->rhobeg = NAN; // Will be interpreted by Fortran as not present
+ options->rhoend = NAN; // Will be interpreted by Fortran as not present
+ options->iprint = PRIMA_MSG_NONE;
+ options->ftarget = -INFINITY;
+ options->ctol = sqrt(DBL_EPSILON);
+ return PRIMA_RC_DFT;
}
-int prima_bobyqa(const prima_obj calfun, const int n, double x[], double *f, const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint)
+
+// Function to check whether the problem matches the algorithm
+prima_rc_t prima_check_problem(const prima_problem_t problem, const prima_algorithm_t algorithm)
{
- int info = 0;
- bobyqa_c(calfun, n, x, f, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &info);
- return info;
+ if (!problem.x0)
+ return PRIMA_NULL_X0;
+
+ if ((algorithm == PRIMA_COBYLA && !problem.calcfc) || (algorithm != PRIMA_COBYLA && !problem.calfun))
+ return PRIMA_NULL_FUNCTION;
+
+ return PRIMA_RC_DFT;
}
-int prima_newuoa(const prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint)
+
+// Function to initialize the result
+prima_rc_t prima_init_result(prima_result_t *const result, const prima_problem_t problem)
{
- int info = 0;
- newuoa_c(calfun, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &info);
- return info;
+ if (!result)
+ return PRIMA_NULL_RESULT;
+
+ memset(result, 0, sizeof(prima_result_t));
+
+ // f: objective function value at the returned point
+ result->f = NAN;
+
+ // cstrv: constraint violation at the returned point (COBYLA and LINCOA only)
+ result->cstrv = NAN;
+
+ // nf: number of function evaluations
+ result->nf = 0;
+
+ // status: return code
+ result->status = PRIMA_RESULT_INITIALIZED;
+
+ // message: exit message
+ result->message = NULL;
+
+ // x: returned point
+ result->x = (double*)malloc(problem.n * sizeof(double));
+ if (!result->x)
+ return PRIMA_MEMORY_ALLOCATION_FAILS;
+ for (int i = 0; i < problem.n; i++)
+ result->x[i] = NAN;
+
+ // nlconstr: nonlinear constraint values at the returned point, of size m_nlcon (COBYLA only)
+ result->nlconstr = (double*)malloc(problem.m_nlcon * sizeof(double));
+ if (!result->nlconstr) {
+ free(result->x);
+ return PRIMA_MEMORY_ALLOCATION_FAILS;
+ }
+ for (int i = 0; i < problem.m_nlcon; i++)
+ result->nlconstr[i] = NAN;
+
+ return PRIMA_RC_DFT;
}
-int prima_uobyqa(const prima_obj calfun, const int n, double x[], double *f,
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint)
+
+// Function to free the result
+prima_rc_t prima_free_result(prima_result_t *const result)
{
- int info = 0;
- uobyqa_c(calfun, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, &info);
- return info;
+ if (!result)
+ return PRIMA_NULL_RESULT;
+
+ if (result->nlconstr) {
+ free(result->nlconstr);
+ result->nlconstr = NULL;
+ }
+ if (result->x) {
+ free(result->x);
+ result->x = NULL;
+ }
+ return PRIMA_RC_DFT;
}
-int prima_lincoa(const prima_obj calfun, const int n, double x[], double *f, double *cstrv,
- const int m_ineq, const double Aineq[], const double bineq[],
- const int m_eq, const double Aeq[], const double beq[],
- const double xl[], const double xu[],
- int *nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint)
+
+// Function to get the string corresponding to the return code
+const char *prima_get_rc_string(const prima_rc_t rc)
{
- int info = 0;
- lincoa_c(calfun, n, x, f, cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &info);
- return info;
+ switch (rc) {
+ case PRIMA_SMALL_TR_RADIUS:
+ return "Trust region radius reaches its lower bound";
+ case PRIMA_FTARGET_ACHIEVED:
+ return "The target function value is reached";
+ case PRIMA_TRSUBP_FAILED:
+ return "A trust region step failed to reduce the model";
+ case PRIMA_MAXFUN_REACHED:
+ return "Maximum number of function evaluations reached";
+ case PRIMA_MAXTR_REACHED:
+ return "Maximum number of trust region iterations reached";
+ case PRIMA_NAN_INF_X:
+ return "The input X contains NaN of Inf";
+ case PRIMA_NAN_INF_F:
+ return "The objective or constraint functions return NaN or +Inf";
+ case PRIMA_NAN_INF_MODEL:
+ return "NaN or Inf occurs in the model";
+ case PRIMA_NO_SPACE_BETWEEN_BOUNDS:
+ return "No space between bounds";
+ case PRIMA_DAMAGING_ROUNDING:
+ return "Rounding errors are becoming damaging";
+ case PRIMA_ZERO_LINEAR_CONSTRAINT:
+ return "One of the linear constraints has a zero gradient";
+ case PRIMA_CALLBACK_TERMINATE:
+ return "Callback function requested termination of optimization";
+ case PRIMA_INVALID_INPUT:
+ return "Invalid input";
+ case PRIMA_ASSERTION_FAILS:
+ return "Assertion fails";
+ case PRIMA_VALIDATION_FAILS:
+ return "Validation fails";
+ case PRIMA_MEMORY_ALLOCATION_FAILS:
+ return "Memory allocation fails";
+ case PRIMA_NULL_OPTIONS:
+ return "NULL options";
+ case PRIMA_NULL_PROBLEM:
+ return "NULL problem";
+ case PRIMA_NULL_X0:
+ return "NULL x0";
+ case PRIMA_NULL_RESULT:
+ return "NULL result";
+ case PRIMA_NULL_FUNCTION:
+ return "NULL function";
+ default:
+ return "Invalid return code";
+ }
}
-const char *prima_get_rc_string(const int rc)
+
+// Functions implemented in Fortran (*_c.f90)
+int cobyla_c(const int m_nlcon, const prima_objcon_t calcfc, const void *data, const int n, double x[], double *const f, double *const cstrv, double nlconstr[],
+ const int m_ineq, const double Aineq[], const double bineq[],
+ const int m_eq, const double Aeq[], const double beq[],
+ const double xl[], const double xu[],
+ const double f0, const double nlconstr0[],
+ int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const double ctol,
+ const prima_callback_t callback, int *const info);
+
+int bobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f, const double xl[], const double xu[],
+ int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info);
+
+int newuoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f,
+ int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const prima_callback_t callback, int *const info);
+
+int uobyqa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f,
+ int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int iprint, const prima_callback_t callback, int *const info);
+
+int lincoa_c(prima_obj_t calfun, const void *data, const int n, double x[], double *const f,
+ double *const cstrv, const int m_ineq, const double Aineq[], const double bineq[],
+ const int m_eq, const double Aeq[], const double beq[], const double xl[], const double xu[],
+ int *const nf, const double rhobeg, const double rhoend, const double ftarget, const int maxfun, const int npt, const int iprint, const double ctol,
+ const prima_callback_t callback, int *const info);
+
+
+// The function that does the minimization using a PRIMA solver
+prima_rc_t prima_minimize(const prima_algorithm_t algorithm, const prima_problem_t problem, const prima_options_t options, prima_result_t *const result)
{
- switch (rc)
- {
- case PRIMA_SMALL_TR_RADIUS:
- return "Trust region radius reaches its lower bound";
- case PRIMA_FTARGET_ACHIEVED:
- return "The target function value is reached";
- case PRIMA_TRSUBP_FAILED:
- return "A trust region step failed to reduce the model";
- case PRIMA_MAXFUN_REACHED:
- return "Maximum number of function evaluations reached";
- case PRIMA_MAXTR_REACHED:
- return "Maximum number of trust region iterations reached";
- case PRIMA_NAN_INF_X:
- return "The input X contains NaN of Inf";
- case PRIMA_NAN_INF_F:
- return "The objective or constraint functions return NaN or +Inf";
- case PRIMA_NAN_INF_MODEL:
- return "NaN or Inf occurs in the model";
- case PRIMA_NO_SPACE_BETWEEN_BOUNDS:
- return "No space between bounds";
- case PRIMA_DAMAGING_ROUNDING:
- return "Rounding errors are becoming damaging";
- case PRIMA_ZERO_LINEAR_CONSTRAINT:
- return "One of the linear constraints has a zero gradient";
- case PRIMA_INVALID_INPUT:
- return "Invalid input";
- case PRIMA_ASSERTION_FAILS:
- return "Assertion fails";
- case PRIMA_VALIDATION_FAILS:
- return "Validation fails";
- case PRIMA_MEMORY_ALLOCATION_FAILS:
- return "Memory allocation failed";
- default:
- return "Invalid return code";
- }
+ prima_rc_t info = prima_init_result(result, problem);
+
+ if (info == PRIMA_RC_DFT)
+ info = prima_check_problem(problem, algorithm);
+
+ if (info == PRIMA_RC_DFT) {
+ // We copy x0 into result->x only after prima_check_problem has succeeded,
+ // so that if prima_check_problem failed, result->x will not contained a
+ // seemingly valid value.
+ for (int i = 0; i < problem.n; i++) {
+ result->x[i] = problem.x0[i];
+ }
+
+ switch (algorithm) {
+ case PRIMA_BOBYQA:
+ bobyqa_c(problem.calfun, options.data, problem.n, result->x, &(result->f), problem.xl, problem.xu, &(result->nf), options.rhobeg, options.rhoend, options.ftarget, options.maxfun, options.npt, options.iprint, options.callback, &info);
+ result->cstrv = 0.0;
+ break;
+
+ case PRIMA_COBYLA:
+ cobyla_c(problem.m_nlcon, problem.calcfc, options.data, problem.n, result->x, &(result->f), &(result->cstrv), result->nlconstr,
+ problem.m_ineq, problem.Aineq, problem.bineq, problem.m_eq, problem.Aeq, problem.beq,
+ problem.xl, problem.xu, problem.f0, problem.nlconstr0, &(result->nf), options.rhobeg, options.rhoend, options.ftarget, options.maxfun, options.iprint, options.ctol, options.callback, &info);
+ break;
+
+ case PRIMA_LINCOA:
+ lincoa_c(problem.calfun, options.data, problem.n, result->x, &(result->f), &(result->cstrv),
+ problem.m_ineq, problem.Aineq, problem.bineq, problem.m_eq, problem.Aeq, problem.beq,
+ problem.xl, problem.xu, &(result->nf), options.rhobeg, options.rhoend, options.ftarget, options.maxfun, options.npt, options.iprint, options.ctol, options.callback, &info);
+ break;
+
+ case PRIMA_NEWUOA:
+ newuoa_c(problem.calfun, options.data, problem.n, result->x, &(result->f), &(result->nf), options.rhobeg, options.rhoend, options.ftarget, options.maxfun, options.npt, options.iprint, options.callback, &info);
+ result->cstrv = 0.0;
+ break;
+
+ case PRIMA_UOBYQA:
+ uobyqa_c(problem.calfun, options.data, problem.n, result->x, &(result->f), &(result->nf), options.rhobeg, options.rhoend, options.ftarget, options.maxfun, options.iprint, options.callback, &info);
+ result->cstrv = 0.0;
+ break;
+
+ default:
+ return PRIMA_INVALID_INPUT;
+ }
+ } else {
+ return info;
+ }
+
+ result->status = info;
+ result->success = ((result->status == PRIMA_SMALL_TR_RADIUS && result->cstrv <= options.ctol) ||
+ (result->status == PRIMA_FTARGET_ACHIEVED));
+ result->message = prima_get_rc_string(info);
+
+ return PRIMA_RC_DFT;
}
diff --git a/c/tests/CMakeLists.txt b/c/tests/CMakeLists.txt
index 1948b00a19..e205e88a9e 100644
--- a/c/tests/CMakeLists.txt
+++ b/c/tests/CMakeLists.txt
@@ -1,15 +1,41 @@
-add_executable(stress_c_exe EXCLUDE_FROM_ALL stress.c)
-if (PRIMA_ENABLE_TESTING)
- set_target_properties (stress_c_exe PROPERTIES EXCLUDE_FROM_ALL FALSE)
-endif ()
-if (WIN32)
- set_target_properties(stress_c_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
-endif()
-target_link_libraries(stress_c_exe PRIVATE primac)
-add_dependencies(tests stress_c_exe)
-
-add_test(NAME bobyqa_large_c COMMAND stress_c_exe bobyqa)
-add_test(NAME cobyla_large_c COMMAND stress_c_exe cobyla)
-add_test(NAME lincoa_large_c COMMAND stress_c_exe lincoa)
-add_test(NAME newuoa_large_c COMMAND stress_c_exe newuoa)
-add_test(NAME uobyqa_large_c COMMAND stress_c_exe uobyqa)
+
+macro (prima_add_c_test_multi name)
+ add_executable(${name}_c_exe EXCLUDE_FROM_ALL ${name}.c)
+ if (PRIMA_ENABLE_TESTING)
+ set_target_properties (${name}_c_exe PROPERTIES EXCLUDE_FROM_ALL FALSE)
+ endif ()
+ if (WIN32)
+ set_target_properties(${name}_c_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
+ endif()
+ target_link_libraries(${name}_c_exe PRIVATE primac)
+ add_dependencies(tests ${name}_c_exe)
+ # Outside of CI we don't want to force people to run tests with gdb, so we test the executables by themselves.
+ # We want these to run in CI as well, because sometimes running with gdb masks an error, so we set them up
+ # before we set up the tests for CI
+ add_test(NAME bobyqa_${name}_c COMMAND ${name}_c_exe bobyqa)
+ add_test(NAME cobyla_${name}_c COMMAND ${name}_c_exe cobyla)
+ add_test(NAME lincoa_${name}_c COMMAND ${name}_c_exe lincoa)
+ add_test(NAME newuoa_${name}_c COMMAND ${name}_c_exe newuoa)
+ add_test(NAME uobyqa_${name}_c COMMAND ${name}_c_exe uobyqa)
+
+ # Within CI, we'd like to run with gdb so that if there's a segfault the logs will have a stacktrace we can use to investigate.
+ # Of course this can be run locally as well if you define CI in your environment.
+ if(NOT APPLE AND UNIX AND DEFINED ENV{CI}) # Apple security policy will not allow running gdb in CI
+ add_test(NAME bobyqa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${name}_c_exe bobyqa)
+ add_test(NAME cobyla_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${name}_c_exe cobyla)
+ add_test(NAME lincoa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${name}_c_exe lincoa)
+ add_test(NAME newuoa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${name}_c_exe newuoa)
+ add_test(NAME uobyqa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${name}_c_exe uobyqa)
+ elseif(WIN32 AND DEFINED ENV{CI})
+ # For Windows we need to provide the full path to the executable since it is installed to a different directory
+ add_test(NAME bobyqa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${name}_c_exe.exe bobyqa)
+ add_test(NAME cobyla_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${name}_c_exe.exe cobyla)
+ add_test(NAME lincoa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${name}_c_exe.exe lincoa)
+ add_test(NAME newuoa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${name}_c_exe.exe newuoa)
+ add_test(NAME uobyqa_${name}_c_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb --args ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${name}_c_exe.exe uobyqa)
+ endif()
+
+endmacro ()
+
+prima_add_c_test_multi(stress)
+prima_add_c_test_multi(data)
diff --git a/c/tests/data.c b/c/tests/data.c
new file mode 100644
index 0000000000..ef15df207f
--- /dev/null
+++ b/c/tests/data.c
@@ -0,0 +1,149 @@
+// A test for the `data` argument in the objective/constraint callback function.
+
+#include
+#include
+#include
+#include
+
+// Make PRIMA available
+#include "prima/prima.h"
+
+#define M_NLCON 1
+
+const int n = 2;
+int debug = 0;
+static int int_data = 0xff;
+void * data_ref = &int_data;
+
+// Objective function for unconstrained, bound constrained, and linearly-constrained problems
+static void fun(const double x[], double *const f, const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2)+0.1*(x1+x2)-10;
+
+ static int nf = 0;
+ if (debug) {
+ ++ nf;
+ printf("Number of function evaluations = %d\n", nf);
+ }
+
+ // Check whether data is OK
+ if (data != data_ref) {
+ printf("Invalid data!\n");
+ *f = NAN;
+ }
+}
+
+// Objective & constraint function for nonlinearly-constrained problems
+static void fun_con(const double x[], double *const f, double constr[], const void *data)
+{
+ const double x1 = x[0];
+ const double x2 = x[1];
+ *f = 5*(x1-3)*(x1-3)+7*(x2-2)*(x2-2) + 0.1*(x1+x2) - 10;
+ constr[0] = x1*x1 + x2*x2 - 13; // ||x||^2<=13
+
+ static int nf = 0;
+ if (debug) {
+ ++ nf;
+ printf("Number of function evaluations = %d\n", nf);
+ }
+
+ // Check whether data is OK
+ if (data != data_ref) {
+ printf("Invalid data!\n");
+ *f = NAN;
+ }
+}
+
+// Main function
+int main(int argc, char * argv[])
+{
+ char *algo = "uobyqa";
+ prima_algorithm_t algorithm = PRIMA_UOBYQA;
+ if (argc > 1)
+ algo = argv[1];
+ printf("Algorithm = %s\n", algo);
+
+ if (argc > 2)
+ debug = (strcmp(argv[2], "debug") == 0);
+ printf("Debug = %d\n", debug);
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_RHO;
+ options.maxfun = 500*n;
+ options.data = data_ref;
+
+ // Data for the problem
+ double x0[] = {0.0,
+ 0.0};
+ double xl[] = {-6.0,
+ -6.0};
+ double xu[] = {6.0,
+ 6.0};
+ double Aineq[3*2] = {1.0, 0.0,
+ 0.0, 1.0,
+ 1.0, 1.0};
+ double bineq[3] = {4.0,
+ 3.0,
+ 10.0};
+
+ // Define the algorithm and the problem according to `algo`
+ prima_problem_t problem;
+ prima_init_problem(&problem, n);
+ problem.x0 = x0;
+ if(strcmp(algo, "uobyqa") == 0) {
+ algorithm = PRIMA_UOBYQA;
+ problem.calfun = &fun;
+ }
+ else if(strcmp(algo, "newuoa") == 0) {
+ algorithm = PRIMA_NEWUOA;
+ problem.calfun = &fun;
+ }
+ else if(strcmp(algo, "bobyqa") == 0) {
+ algorithm = PRIMA_BOBYQA;
+ problem.calfun = &fun;
+ problem.xl = xl;
+ problem.xu = xu;
+ }
+ else if(strcmp(algo, "lincoa") == 0) {
+ algorithm = PRIMA_LINCOA;
+ problem.calfun = &fun;
+ problem.xl = xl;
+ problem.xu = xu;
+ problem.m_ineq = 3;
+ problem.Aineq = Aineq;
+ problem.bineq = bineq;
+ }
+ else if(strcmp(algo, "cobyla") == 0) {
+ algorithm = PRIMA_COBYLA;
+ problem.m_nlcon = M_NLCON;
+ problem.calcfc = &fun_con;
+ problem.xl = xl;
+ problem.xu = xu;
+ problem.m_ineq = 3;
+ problem.Aineq = Aineq;
+ problem.bineq = bineq;
+ }
+ else {
+ printf("Invalid algorithm %s!\n", algo);
+ return 1;
+ }
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(algorithm, problem, options, &result);
+
+ // Print the result
+ printf("f* = %g, cstrv = %g, nlconstr = {%g}, rc = %d, msg = '%s', evals = %d\n", result.f, result.cstrv, result.nlconstr ? result.nlconstr[0] : 0.0, rc, result.message, result.nf);
+
+ // Check the result
+ int success = (fabs(result.x[0] - 3) > 2e-2 || fabs(result.x[1] - 2) > 2e-2);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return success;
+}
diff --git a/c/tests/stress.c b/c/tests/stress.c
index b0811c61b9..d66d685046 100644
--- a/c/tests/stress.c
+++ b/c/tests/stress.c
@@ -1,144 +1,175 @@
-// A stress test on excessively large problems.
+// A stress test on excessively large problems
-#include "prima/prima.h"
-#include
#include
+#include
#include
#include
#include
-const int n_max = 2000;
+// Make PRIMA available
+#include "prima/prima.h"
+
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#define N_MAX 2000
+#define M_INEQ_MAX 1000
+#define M_NLCON 200
+
int n = 0;
-const int m_ineq_max = 1000;
int m_ineq = 0;
-const int m_nlcon = 10;
+const double alpha = 4.0;
int debug = 0;
static double random_gen(double a, double b)
{
- return a + rand() * (b - a) / RAND_MAX;
+ return a + rand() * (b - a) / RAND_MAX;
}
-static void fun(const double x[], double *f)
+// Objective function for unconstrained, bound constrained, and linearly-constrained problems
+static void fun(const double x[], double *const f, const void *data)
{
- // min x^T*x
- *f = 0.0;
- for (int i = 0; i < n; ++ i)
- *f += x[i]*x[i];
-
- static int count = 0;
- if (debug)
- {
- ++ count;
- printf("count=%d\n", count);
- }
+ // Objective: Rosenbrock function
+ *f = 0.0;
+ for (int i = 0; i < n-1; ++ i)
+ *f += (x[i] - 1.0) * (x[i] - 1.0) + alpha * (x[i+1] - x[i]*x[i]) * (x[i+1] - x[i]*x[i]);
+
+ static int nf = 0;
+ if (debug) {
+ ++ nf;
+ printf("Number of function evaluations = %d\n", nf);
+ }
+ (void)data;
}
-static void fun_con(const double x[], double *f, double constr[])
+// Objective & constraint function for nonlinearly-constrained problems
+static void fun_con(const double x[], double *const f, double constr[], const void *data)
{
- // min x^T*x
- *f = 0.0;
- for (int i = 0; i < n; ++ i)
- *f += x[i]*x[i];
- // x_j^2-x_j<=1
- for (int j = 0; j < m_nlcon; ++ j)
- constr[j] = x[j]*x[j] - x[j] - 1.0;
-
- static int count = 0;
- if (debug)
- {
- ++ count;
- printf("count=%d\n", count);
- }
+ // Objective: Rosenbrock function
+ *f = 0.0;
+ for (int i = 0; i < n-1; ++ i)
+ *f += (x[i] - 1.0) * (x[i] - 1.0) + alpha * (x[i+1] - x[i]*x[i]) * (x[i+1] - x[i]*x[i]);
+
+ // Constraint: x_{i+1} <= x_i^2
+ for (int i = 0; i < MIN(M_NLCON, n-1); ++ i)
+ constr[i] = x[i+1] - x[i] * x[i];
+
+ static int nf = 0;
+ if (debug) {
+ ++ nf;
+ printf("Number of function evaluations = %d\n", nf);
+ }
+ (void)data;
+}
+
+// A function generating a seed that alters weekly
+unsigned int get_random_seed(void) {
+ // Set the random seed to year/week
+ char buf[10] = {0};
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+ int rc = strftime(buf, 10, "%y%W", tmp);
+ if (!rc)
+ return 42;
+ else
+ return atoi(buf);
}
+// Main function
int main(int argc, char * argv[])
{
- char *algo = "bobyqa";
- if (argc > 1)
- algo = argv[1];
- printf("algo=%s\n", algo);
-
- if (argc > 2)
- debug = (strcmp(argv[2], "debug") == 0);
- printf("debug=%d\n", debug);
-
- // set seed to year/week
- char buf[10] = {0};
- time_t t = time(NULL);
- struct tm *tmp = localtime(&t);
- int rc = strftime(buf, 10, "%y%W", tmp);
- if (!rc)
- return 1;
- unsigned seed = atoi(buf);
- printf("seed=%d\n", seed);
- srand(seed);
-
- double x[n_max];
- double xl[n_max];
- double xu[n_max];
- double f = 0.0;
- double cstrv = 0.0;
- double nlconstr[m_nlcon];
- double *Aineq = malloc(n_max*m_ineq_max*sizeof(double));
- double bineq[m_ineq_max];
- const int m_eq = 0;
- double *Aeq = NULL;
- double *beq = NULL;
- const double rhobeg = 1.0;
- const double rhoend = 1e-7;
- const double ftarget = -INFINITY;
- const int iprint = PRIMA_MSG_RHO;
- const int maxfun = 500*n_max;
-
- for (int j = 0; j < m_ineq_max; ++ j)
- bineq[j] = random_gen(-1.0, 1.0);
- for (int j = 0; j < m_nlcon; ++ j)
- nlconstr[j] = 0.0;
-
- for (int i = 0; i < n_max; ++ i)
- {
- for (int j = 0; j < m_ineq; ++ j)
- Aineq[j*n_max+i] = random_gen(-1.0, 1.0);
- x[i] = 0.0;
- xl[i] = 0.0;
- xu[i] = 1.0;
- }
- int nf = 0;
- if(strcmp(algo, "bobyqa") == 0)
- {
- n = 1600;
- rc = prima_bobyqa(&fun, n, x, &f, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, 2*n+1, iprint);
- }
- else if(strcmp(algo, "cobyla") == 0)
- {
- n = 800;
- m_ineq = 800;
- rc = prima_cobyla(m_nlcon, &fun_con, n, x, &f, &cstrv, nlconstr, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, iprint);
- }
- else if(strcmp(algo, "lincoa") == 0)
- {
- n = 1000;
- m_ineq = 1000;
- rc = prima_lincoa(&fun, n, x, &f, &cstrv, m_ineq, Aineq, bineq, m_eq, Aeq, beq, xl, xu, &nf, rhobeg, rhoend, ftarget, maxfun, 2*n+1, iprint);
- }
- else if(strcmp(algo, "newuoa") == 0)
- {
- n = 1600;
- rc = prima_newuoa(&fun, n, x, &f, &nf, rhobeg, rhoend, ftarget, maxfun, 2*n+1, iprint);
- }
- else if(strcmp(algo, "uobyqa") == 0)
- {
- n = 160;
- rc = prima_uobyqa(&fun, n, x, &f, &nf, rhobeg, rhoend, ftarget, maxfun, iprint);
- }
- else
- {
- printf("incorrect algo\n");
- return 1;
- }
- const char *msg = prima_get_rc_string(rc);
-
- printf("f*=%g cstrv=%g nlconstr=%g rc=%d msg='%s' evals=%d\n", f, cstrv, nlconstr[0], rc, msg, nf);
- return 0;
+ char *algo = "uobyqa";
+ prima_algorithm_t algorithm = PRIMA_UOBYQA;
+ if (argc > 1)
+ algo = argv[1];
+ printf("Algorithm = %s\n", algo);
+
+ if (argc > 2)
+ debug = (strcmp(argv[2], "debug") == 0);
+ printf("Debug = %d\n", debug);
+
+ unsigned int seed = get_random_seed();
+ printf("Random seed = %d\n", seed);
+ srand(seed);
+
+ // Set up the options
+ prima_options_t options;
+ prima_init_options(&options);
+ options.iprint = PRIMA_MSG_RHO;
+ options.maxfun = 500*N_MAX;
+
+ // Data for the problem
+ double x0[N_MAX];
+ double xl[N_MAX];
+ double xu[N_MAX];
+ for (int i = 0; i < N_MAX; ++ i) {
+ x0[i] = random_gen(-1.0, 1.0);
+ xl[i] = random_gen(-2.0, -1.0);
+ xu[i] = random_gen(1.0, 2.0);
+ }
+ double *Aineq = malloc(N_MAX*M_INEQ_MAX*sizeof(double));
+ for (int i = 0; i < N_MAX; ++ i) {
+ for (int j = 0; j < m_ineq; ++ j)
+ Aineq[j*N_MAX+i] = random_gen(-1.0, 1.0);
+ }
+ double bineq[M_INEQ_MAX];
+ for (int j = 0; j < M_INEQ_MAX; ++ j)
+ bineq[j] = random_gen(-1.0, 1.0);
+
+ // Define the algorithm and the problem according to `algo`
+ prima_problem_t problem;
+ prima_init_problem(&problem, N_MAX);
+ problem.x0 = x0;
+ if(strcmp(algo, "uobyqa") == 0) {
+ algorithm = PRIMA_UOBYQA;
+ problem.n = 100;
+ problem.calfun = &fun;
+ }
+ else if(strcmp(algo, "newuoa") == 0) {
+ algorithm = PRIMA_NEWUOA;
+ problem.n = 1600;
+ problem.calfun = &fun;
+ }
+ else if(strcmp(algo, "bobyqa") == 0) {
+ algorithm = PRIMA_BOBYQA;
+ problem.n = 1600;
+ problem.calfun = &fun;
+ problem.xl = xl;
+ problem.xu = xu;
+ }
+ else if(strcmp(algo, "lincoa") == 0) {
+ algorithm = PRIMA_LINCOA;
+ problem.n = 1000;
+ problem.m_ineq = 1000;
+ problem.calfun = &fun;
+ problem.xl = xl;
+ problem.xu = xu;
+ problem.Aineq = Aineq;
+ problem.bineq = bineq;
+ }
+ else if(strcmp(algo, "cobyla") == 0) {
+ algorithm = PRIMA_COBYLA;
+ problem.n = 800;
+ problem.m_nlcon = M_NLCON;
+ problem.m_ineq = 600;
+ problem.calcfc = &fun_con;
+ problem.xl = xl;
+ problem.xu = xu;
+ problem.Aineq = Aineq;
+ problem.bineq = bineq;
+ }
+ else {
+ printf("Invalid algorithm %s!\n", algo);
+ return 1;
+ }
+
+ // Call the solver
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(algorithm, problem, options, &result);
+
+ // Print the result
+ printf("f* = %g, cstrv = %g, nlconstr = {%g}, rc = %d, msg = '%s', evals = %d\n", result.f, result.cstrv, result.nlconstr ? result.nlconstr[0] : 0.0, rc, result.message, result.nf);
+
+ // Free the result
+ prima_free_result(&result);
+
+ return 0;
}
diff --git a/c/uobyqa_c.f90 b/c/uobyqa_c.f90
index 1cac6efd27..c38f01dcf9 100644
--- a/c/uobyqa_c.f90
+++ b/c/uobyqa_c.f90
@@ -12,18 +12,19 @@ module uobyqa_c_mod
contains
-subroutine uobyqa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, info) bind(C)
-use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR
-use, non_intrinsic :: cintrf_mod, only : COBJ
+subroutine uobyqa_c(cobj_ptr, data_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, callback_ptr, info) bind(C)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_FUNPTR, C_PTR, C_ASSOCIATED, C_F_PROCPOINTER
+use, non_intrinsic :: cintrf_mod, only : COBJ, CCALLBACK
use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: infnan_mod, only : is_nan
use, non_intrinsic :: uobyqa_mod, only : uobyqa
implicit none
! Compulsory arguments
type(C_FUNPTR), intent(IN), value :: cobj_ptr
+type(C_PTR), intent(in), value :: data_ptr
integer(C_INT), intent(in), value :: n
-! We cannot use assumed-shape arrays for C interoperability
-real(C_DOUBLE), intent(inout) :: x(n)
+real(C_DOUBLE), intent(inout) :: x(n) ! We cannot use assumed-shape arrays for C interoperability
real(C_DOUBLE), intent(out) :: f
integer(C_INT), intent(out) :: nf
real(C_DOUBLE), intent(in), value :: rhobeg
@@ -31,30 +32,63 @@ subroutine uobyqa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, ipri
real(C_DOUBLE), intent(in), value :: ftarget
integer(C_INT), intent(in), value :: maxfun
integer(C_INT), intent(in), value :: iprint
+type(C_FUNPTR), intent(in), value :: callback_ptr
integer(C_INT), intent(out) :: info
! Local variables
integer(IK) :: info_loc
integer(IK) :: iprint_loc
-integer(IK) :: maxfun_loc
integer(IK) :: nf_loc
+integer(IK), allocatable :: maxfun_loc
+! The initialization below to null is necessary to avoid a bug with the newer Intel compiler ifx.
+! See https://fortran-lang.discourse.group/t/strange-issue-with-ifx-compiler-and-assume-recursion/7013
+! The bug was observed in all versions of ifx up to 2024.0.1. Once this bug is fixed we should
+! remove the initialization to null because it implies the 'save' attribute, which is undesirable.
+procedure(CCALLBACK), pointer :: cb_ptr => null()
+procedure(COBJ), pointer :: obj_ptr => null()
real(RP) :: f_loc
-real(RP) :: rhobeg_loc
-real(RP) :: rhoend_loc
real(RP) :: ftarget_loc
real(RP) :: x_loc(n)
+real(RP), allocatable :: rhobeg_loc
+real(RP), allocatable :: rhoend_loc
! Read the inputs and convert them to the Fortran side types
+
+! The following inputs correspond to compulsory arguments in the Fortran code.
x_loc = real(x, kind(x_loc))
-rhobeg_loc = real(rhobeg, kind(rhobeg))
-rhoend_loc = real(rhoend, kind(rhoend))
-ftarget_loc = real(ftarget, kind(ftarget))
-maxfun_loc = int(maxfun, kind(maxfun_loc))
+call c_f_procpointer(cobj_ptr, obj_ptr)
+
+! The following inputs correspond to optional arguments in the Fortran code.
+! Since C does not support optional arguments, we use NaN to represent an absent real scalar, 0 to
+! represent an absent integer scalar (all integer arguments are expected positive), and an
+! unassociated pointer to represent an absent array. In case of NaN, 0, or unassociated pointers,
+! the allocatable variables such as RHOBEG_LOC will be left uninitialized and hence unallocated, and
+! then treated as an absent argument when passed to the Fortran code.
+! See Sec. 9.7.1.3 (4) and 15.5.2.13 (1) of J3/24-007 (Fortran 2023 Interpretation Document).
+if (.not. is_nan(rhobeg)) then
+ rhobeg_loc = real(rhobeg, kind(rhobeg_loc))
+end if
+if (.not. is_nan(rhoend)) then
+ rhoend_loc = real(rhoend, kind(rhoend_loc))
+end if
+ftarget_loc = real(ftarget, kind(ftarget_loc))
+if (maxfun /= 0) then
+ maxfun_loc = int(maxfun, kind(maxfun_loc))
+end if
iprint_loc = int(iprint, kind(iprint_loc))
! Call the Fortran code
-call uobyqa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
- & maxfun=maxfun_loc, iprint=iprint_loc, info=info_loc)
+if (c_associated(callback_ptr)) then
+ ! If a C callback function is provided, we convert it to a Fortran procedure pointer and capture
+ ! that pointer in the closure below.
+ call c_f_procpointer(callback_ptr, cb_ptr)
+ ! We then provide the closure to the algorithm.
+ call uobyqa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
+ & maxfun=maxfun_loc, iprint=iprint_loc, callback_fcn=callback_fcn, info=info_loc)
+else
+ call uobyqa(calfun, x_loc, f_loc, nf=nf_loc, rhobeg=rhobeg_loc, rhoend=rhoend_loc, ftarget=ftarget_loc, &
+ & maxfun=maxfun_loc, iprint=iprint_loc, info=info_loc)
+end if
! Write the outputs
x = real(x_loc, kind(x))
@@ -62,22 +96,114 @@ subroutine uobyqa_c(cobj_ptr, n, x, f, nf, rhobeg, rhoend, ftarget, maxfun, ipri
nf = int(nf_loc, kind(nf))
info = int(info_loc, kind(info))
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(maxfun_loc)) deallocate (maxfun_loc)
+if (allocated(rhoend_loc)) deallocate (rhoend_loc)
+if (allocated(rhobeg_loc)) deallocate (rhobeg_loc)
+
contains
!--------------------------------------------------------------------------------------------------!
! This subroutine defines `calfun` using the C function pointer with an internal subroutine.
! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across 4 out of 5 algorithms; COBYLA requires a slightly different
+! signature.
!--------------------------------------------------------------------------------------------------!
subroutine calfun(x_sub, f_sub)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE
use, non_intrinsic :: consts_mod, only : RP
-use, non_intrinsic :: cintrf_mod, only : evalcobj
implicit none
-real(RP), intent(in) :: x_sub(:)
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
real(RP), intent(out) :: f_sub
-call evalcobj(cobj_ptr, x_sub, f_sub)
+
+! Local variables
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE) :: f_sub_loc
+
+! Read the inputs and convert them to the types specified in COBJ
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+
+! Call the C objective function
+call obj_ptr(x_sub_loc, f_sub_loc, data_ptr)
+
+! Write the output
+f_sub = real(f_sub_loc, kind(f_sub))
+
end subroutine calfun
+
+!--------------------------------------------------------------------------------------------------!
+! This subroutine defines `callback_fcn` using the C function pointer with an internal subroutine.
+! This allows to avoid passing the C function pointer by a module variable, which is thread-unsafe.
+! A possible security downside is that the compiler must allow for an executable stack.
+! This subroutine is identical across all 5 algorithms.
+!--------------------------------------------------------------------------------------------------!
+subroutine callback_fcn(x_sub, f_sub, nf_sub, tr, cstrv_sub, nlconstr_sub, terminate)
+use, intrinsic :: iso_c_binding, only : C_DOUBLE, C_INT, C_BOOL
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x_sub(:) ! We name some variables _sub to avoid masking the parent variables
+real(RP), intent(in) :: f_sub
+integer(IK), intent(in) :: nf_sub
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv_sub
+real(RP), intent(in), optional :: nlconstr_sub(:)
+
+! Outputs
+logical, intent(out), optional :: terminate
+
+! Local variables
+integer(C_INT) :: m_nlconstr
+integer(C_INT) :: n_sub_loc
+integer(C_INT) :: nf_sub_loc
+integer(C_INT) :: tr_loc
+logical(C_BOOL) :: terminate_loc
+real(C_DOUBLE) :: cstrv_sub_loc
+real(C_DOUBLE) :: f_sub_loc
+real(C_DOUBLE) :: x_sub_loc(size(x_sub))
+real(C_DOUBLE), allocatable :: nlconstr_sub_loc(:)
+
+! Read the inputs and convert them to the types specified in CCALLBACK
+n_sub_loc = size(x_sub)
+x_sub_loc = real(x_sub, kind(x_sub_loc))
+f_sub_loc = real(f_sub, kind(f_sub_loc))
+nf_sub_loc = int(nf_sub, kind(nf_sub_loc))
+tr_loc = int(tr, kind(tr_loc))
+
+! Set the constraint violation to a sensible default value if it is not provided.
+if (present(cstrv_sub)) then
+ cstrv_sub_loc = real(cstrv_sub, kind(cstrv_sub_loc))
+else
+ cstrv_sub_loc = 0.0_C_DOUBLE
+end if
+
+! Set the nonlinear constraints to a sensible default value if it is not provided.
+if (present(nlconstr_sub)) then
+ m_nlconstr = int(size(nlconstr_sub), C_INT)
+ call safealloc(nlconstr_sub_loc, int(m_nlconstr, IK))
+ nlconstr_sub_loc = real(nlconstr_sub, kind(nlconstr_sub_loc))
+else
+ m_nlconstr = 0_C_INT
+ nlconstr_sub_loc = [real(C_DOUBLE) ::]
+end if
+
+! Call the C callback function
+call cb_ptr(n_sub_loc, x_sub_loc, f_sub_loc, nf_sub_loc, tr_loc, cstrv_sub_loc, m_nlconstr, nlconstr_sub_loc, terminate_loc)
+
+! Write the output
+if (present(terminate)) then
+ terminate = logical(terminate_loc, kind(terminate))
+end if
+
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+if (allocated(nlconstr_sub_loc)) deallocate (nlconstr_sub_loc)
+
+end subroutine callback_fcn
+
end subroutine uobyqa_c
diff --git a/fortran/CMakeLists.txt b/fortran/CMakeLists.txt
index 8c03b1dd6e..20f39f9e2d 100644
--- a/fortran/CMakeLists.txt
+++ b/fortran/CMakeLists.txt
@@ -19,6 +19,7 @@ add_library (primaf
common/redrho.f90
common/memory.F90
common/selectx.f90
+ common/huge.F90
common/infnan.F90
common/shiftbase.f90
common/infos.f90
@@ -63,6 +64,7 @@ target_compile_definitions (primaf PUBLIC "PRIMA_INTEGER_KIND=${PRIMA_INTEGER_KI
set_target_properties(primaf PROPERTIES
POSITION_INDEPENDENT_CODE ON
Fortran_MODULE_DIRECTORY mod)
+target_include_directories (primaf PRIVATE common)
target_include_directories (primaf PUBLIC
$
$)
@@ -101,23 +103,42 @@ endif()
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/mod DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/prima)
-macro (prima_add_f_test name)
- add_executable (example_${name}_fortran_exe EXCLUDE_FROM_ALL examples/${name}/${name}_example.f90)
- target_link_libraries (example_${name}_fortran_exe PRIVATE primaf)
- target_include_directories (example_${name}_fortran_exe PRIVATE ${CMAKE_BINARY_DIR}/fortran)
- set_target_properties(example_${name}_fortran_exe PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/${name}/mod)
+macro (prima_add_f_test name number)
+ add_executable (example_${name}_fortran_${number}_exe EXCLUDE_FROM_ALL examples/${name}/${name}_example_${number}.f90)
+ target_link_libraries (example_${name}_fortran_${number}_exe PRIVATE primaf)
+ target_include_directories (example_${name}_fortran_${number}_exe PRIVATE ${CMAKE_BINARY_DIR}/fortran)
+ set_target_properties(example_${name}_fortran_${number}_exe PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/${name}_${number}/mod)
if (WIN32)
- set_target_properties(example_${name}_fortran_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
+ set_target_properties(example_${name}_fortran_${number}_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
endif()
if (PRIMA_ENABLE_EXAMPLES)
- set_target_properties (example_${name}_fortran_exe PROPERTIES EXCLUDE_FROM_ALL FALSE)
+ set_target_properties (example_${name}_fortran_${number}_exe PROPERTIES EXCLUDE_FROM_ALL FALSE)
endif ()
- add_test (NAME example_${name}_fortran COMMAND example_${name}_fortran_exe)
- add_dependencies(examples example_${name}_fortran_exe)
+
+ # Outside of CI we don't want to force people to run examples with gdb, so we test the executables by themselves.
+ # We want these to run in CI as well, because sometimes running with gdb masks an error, so we set them up
+ # before we set up the examples for CI
+ add_test (NAME example_${name}_fortran_${number} COMMAND example_${name}_fortran_${number}_exe)
+
+ # Within CI, we'd like to run with gdb so that if there's a segfault the logs will have a stacktrace we can use to investigate.
+ # Of course this can be run locally as well if you define CI in your environment.
+ if(NOT APPLE AND UNIX AND DEFINED ENV{CI}) # Apple security policy will not allow running gdb in CI
+ add_test (NAME example_${name}_fortran_${number}_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb example_${name}_fortran_${number}_exe)
+ elseif(WIN32 AND DEFINED ENV{CI})
+ # For Windows we need to provide the full path to the executable since it is installed to a different directory
+ add_test (NAME example_${name}_fortran_${number}_with_gdb COMMAND gdb -return-child-result -batch --command=${CMAKE_BINARY_DIR}/cmdfile.gdb ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/example_${name}_fortran_${number}_exe.exe)
+ endif()
+
+ add_dependencies(examples example_${name}_fortran_${number}_exe)
endmacro ()
-prima_add_f_test (cobyla)
-prima_add_f_test (bobyqa)
-prima_add_f_test (newuoa)
-prima_add_f_test (uobyqa)
-prima_add_f_test (lincoa)
+prima_add_f_test (cobyla 1)
+prima_add_f_test (cobyla 2)
+prima_add_f_test (bobyqa 1)
+prima_add_f_test (bobyqa 2)
+prima_add_f_test (newuoa 1)
+prima_add_f_test (newuoa 2)
+prima_add_f_test (uobyqa 1)
+prima_add_f_test (uobyqa 2)
+prima_add_f_test (lincoa 1)
+prima_add_f_test (lincoa 2)
diff --git a/fortran/bobyqa/bobyqa.f90 b/fortran/bobyqa/bobyqa.f90
index 297109a872..1621a5303f 100644
--- a/fortran/bobyqa/bobyqa.f90
+++ b/fortran/bobyqa/bobyqa.f90
@@ -25,7 +25,7 @@ module bobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Friday, September 15, 2023 PM09:46:32
+! Last Modified: Thursday, February 22, 2024 PM03:30:31
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -36,12 +36,12 @@ module bobyqa_mod
contains
-subroutine bobyqa(calfun, x, f, &
- & xl, xu, &
+subroutine bobyqa(calfun, x, &
+ & f, xl, xu, &
& nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &
- & eta1, eta2, gamma1, gamma2, xhist, fhist, maxhist, honour_x0, info)
+ & eta1, eta2, gamma1, gamma2, xhist, fhist, maxhist, honour_x0, callback_fcn, info)
!--------------------------------------------------------------------------------------------------!
-! Among all the arguments, only CALFUN, X, and F are obligatory. The others are OPTIONAL and you can
+! Among all the arguments, only CALFUN and X are obligatory. The others are OPTIONAL and you can
! neglect them unless you are familiar with the algorithm. Any unspecified optional input will take
! the default value detailed below. For instance, we may invoke the solver as follows.
!
@@ -51,7 +51,7 @@ subroutine bobyqa(calfun, x, f, &
! or
!
! ! First define CALFUN, X, and XL, and then do the following.
-! call bobyqa(calfun, x, f, xl = xl, rhobeg = 0.5D0, rhoend = 1.0D-3, maxfun = 100)
+! call bobyqa(calfun, x, f, xl = xl, rhobeg = 1.0D0, rhoend = 1.0D-6)
!
! See examples/bobyqa_exmp.f90 for a concrete example.
!
@@ -89,8 +89,11 @@ subroutine bobyqa(calfun, x, f, &
! effectively means there is no lower bound for the corresponding entry of X. The value of
! BOUNDMAX is 0.25*HUGE(X), which is about 8.6E37 for single precision and 4.5E307 for double
! precision. XU is similar.
-! N.B.: It is required that XU - XL > 2*EPSILON(X), which is about 2.4E-7 for single precision and
+! N.B.:
+! 1. It is required that XU - XL > 2*EPSILON(X), which is about 2.4E-7 for single precision and
! 4.5E-16 for double precision. Otherwise, the solver will return after printing a warning.
+! 2. Why don't we set BOUNDMAX to REALMAX? Because we want to avoid overflow when calculating
+! XU - XL and when defining/updating SU and SL. This is not a problem in MATLAB/Python/Julia/R.
!
! NF
! Output, INTEGER(IK) scalar.
@@ -162,11 +165,14 @@ subroutine bobyqa(calfun, x, f, &
! Use *HIST with caution! (N.B.: the algorithm is NOT designed for large problems).
!
! HONOUR_X0
-! Input, LOGICAL scalar, default: .true. if RHOBEG is absent, and .false. if RHOBEG is present.
-! HONOUR_X0 indicates whether to respect the user-defined X0 or not. The BOBYQA algorithm requires
-! that the distance between X0 and the inactive bounds is at least RHOBEG. X0 or RHOBEG is revised
-! if this requirement is not met. If HONOUR_X0 == TRUE, revise RHOBEG if needed; otherwise, revise
-! X0 if needed. See the PREPROC subroutine for more information.
+! Input, LOGICAL scalar, default: it is .false. if RHOBEG is present and 0 < RHOBEG < Inf, and it
+! is .true. otherwise. HONOUR_X0 indicates whether to respect the user-defined X0 or not.
+! BOBYQA requires that the distance between X0 and the inactive bounds is at least RHOBEG. X0 or
+! RHOBEG is revised if this requirement is not met. If HONOUR_X0 == TRUE, revise RHOBEG if needed;
+! otherwise, revise X0 if needed. See the PREPROC subroutine for more information.
+!
+! CALLBACK_FCN
+! Input, function to report progress and optionally request termination.
!
! INFO
! Output, INTEGER(IK) scalar.
@@ -198,7 +204,7 @@ subroutine bobyqa(calfun, x, f, &
use, non_intrinsic :: infos_mod, only : NO_SPACE_BETWEEN_BOUNDS
use, non_intrinsic :: linalg_mod, only : trueloc
use, non_intrinsic :: memory_mod, only : safealloc
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: preproc_mod, only : preproc
use, non_intrinsic :: string_mod, only : num2str
@@ -210,9 +216,9 @@ subroutine bobyqa(calfun, x, f, &
! Compulsory arguments
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
real(RP), intent(inout) :: x(:) ! X(N)
-real(RP), intent(out) :: f
! Optional inputs
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in), optional :: iprint
integer(IK), intent(in), optional :: maxfun
integer(IK), intent(in), optional :: maxhist
@@ -231,8 +237,9 @@ subroutine bobyqa(calfun, x, f, &
! Optional outputs
integer(IK), intent(out), optional :: info
integer(IK), intent(out), optional :: nf
-real(RP), intent(out), allocatable, optional :: fhist(:) ! FHIST(MAXFHIST)
-real(RP), intent(out), allocatable, optional :: xhist(:, :) ! XHIST(N, MAXXHIST)
+real(RP), intent(out), optional :: f
+real(RP), intent(out), optional, allocatable :: fhist(:) ! FHIST(MAXFHIST)
+real(RP), intent(out), optional, allocatable :: xhist(:, :) ! XHIST(N, MAXXHIST)
! Local variables
character(len=*), parameter :: solver = 'BOBYQA'
@@ -250,6 +257,7 @@ subroutine bobyqa(calfun, x, f, &
logical :: honour_x0_loc
real(RP) :: eta1_loc
real(RP) :: eta2_loc
+real(RP) :: f_loc
real(RP) :: ftarget_loc
real(RP) :: gamma1_loc
real(RP) :: gamma2_loc
@@ -276,17 +284,19 @@ subroutine bobyqa(calfun, x, f, &
! Read the inputs
+xl_loc = -BOUNDMAX
if (present(xl)) then
- xl_loc = xl
-else
- xl_loc = -BOUNDMAX
+ if (size(xl) > 0) then
+ xl_loc = xl
+ end if
end if
xl_loc(trueloc(is_nan(xl_loc) .or. xl_loc < -BOUNDMAX)) = -BOUNDMAX
+xu_loc = BOUNDMAX
if (present(xu)) then
- xu_loc = xu
-else
- xu_loc = BOUNDMAX
+ if (size(xu) > 0) then
+ xu_loc = xu
+ end if
end if
xu_loc(trueloc(is_nan(xu_loc) .or. xu_loc > BOUNDMAX)) = BOUNDMAX
@@ -327,7 +337,7 @@ subroutine bobyqa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -346,8 +356,8 @@ subroutine bobyqa(calfun, x, f, &
if (present(npt)) then
npt_loc = npt
-elseif (maxfun_loc >= 1) then
- npt_loc = max(n + 2_IK, min(maxfun_loc - 1_IK, 2_IK * n + 1_IK))
+elseif (maxfun_loc >= n + 3_IK) then ! Take MAXFUN into account if it is valid.
+ npt_loc = min(maxfun_loc - 1_IK, 2_IK * n + 1_IK)
else
npt_loc = 2_IK * n + 1_IK
end if
@@ -395,12 +405,15 @@ subroutine bobyqa(calfun, x, f, &
end if
has_rhobeg = present(rhobeg)
+honour_x0_loc = .true.
if (present(honour_x0)) then
honour_x0_loc = honour_x0
-else
- honour_x0_loc = (.not. has_rhobeg)
+else if (has_rhobeg) then
+ ! HONOUR_X0 is FALSE if user provides a valid RHOBEG. Is this the best choice?
+ honour_x0_loc = (.not. (is_finite(rhobeg) .and. rhobeg > 0))
end if
+
! Preprocess the inputs in case some of them are invalid. It does nothing if all inputs are valid.
call preproc(solver, n, iprint_loc, maxfun_loc, maxhist_loc, ftarget_loc, rhobeg_loc, rhoend_loc, &
& npt=npt_loc, eta1=eta1_loc, eta2=eta2_loc, gamma1=gamma1_loc, gamma2=gamma2_loc, &
@@ -414,13 +427,23 @@ subroutine bobyqa(calfun, x, f, &
!-------------------- Call BOBYQB, which performs the real calculations. --------------------------!
-call bobyqb(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, &
- & gamma1_loc, gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, f, &
- & fhist_loc, xhist_loc, info_loc)
+if (present(callback_fcn)) then
+ call bobyqb(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, &
+ & gamma1_loc, gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, f_loc, &
+ & fhist_loc, xhist_loc, info_loc, callback_fcn)
+else
+ call bobyqb(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, &
+ & gamma1_loc, gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, f_loc, &
+ & fhist_loc, xhist_loc, info_loc)
+end if
!--------------------------------------------------------------------------------------------------!
! Write the outputs.
+if (present(f)) then
+ f = f_loc
+end if
+
if (present(nf)) then
nf = nf_loc
end if
@@ -464,7 +487,7 @@ subroutine bobyqa(calfun, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
! Postconditions
@@ -502,7 +525,7 @@ subroutine bobyqa(calfun, x, f, &
if (present(fhist)) then
call assert(size(fhist) == nhist, 'SIZE(FHIST) == NHIST', srname)
call assert(.not. any(is_nan(fhist) .or. is_posinf(fhist)), 'FHIST does not contain NaN/+Inf', srname)
- call assert(.not. any(fhist < f), 'F is the smallest in FHIST', srname)
+ call assert(.not. any(fhist < f_loc), 'F is the smallest in FHIST', srname)
end if
end if
diff --git a/fortran/bobyqa/bobyqb.f90 b/fortran/bobyqa/bobyqb.f90
index d7cb78362a..9d14c9d2a8 100644
--- a/fortran/bobyqa/bobyqb.f90
+++ b/fortran/bobyqa/bobyqb.f90
@@ -32,7 +32,7 @@ module bobyqb_mod
!
! Started: February 2022
!
-! Last Modified: Friday, August 04, 2023 PM09:55:47
+! Last Modified: Wed 08 Apr 2026 06:38:40 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -44,7 +44,7 @@ module bobyqb_mod
subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamma2, rhobeg, rhoend, &
- & xl, xu, x, nf, f, fhist, xhist, info)
+ & xl, xu, x, nf, f, fhist, xhist, info, callback_fcn)
!--------------------------------------------------------------------------------------------------!
! This subroutine performs the major calculations of BOBYQA.
!
@@ -82,10 +82,11 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist, rangehist
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
-use, non_intrinsic :: infos_mod, only : INFO_DFT, SMALL_TR_RADIUS, MAXTR_REACHED
+use, non_intrinsic :: infos_mod, only : INFO_DFT, SMALL_TR_RADIUS, MAXTR_REACHED, DAMAGING_ROUNDING,&
+ & NAN_INF_MODEL, CALLBACK_TERMINATE
use, non_intrinsic :: linalg_mod, only : norm
use, non_intrinsic :: message_mod, only : retmsg, rhomsg, fmsg
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: powalg_mod, only : quadinc, calden, calvlag!, errquad
use, non_intrinsic :: ratio_mod, only : redrat
use, non_intrinsic :: redrho_mod, only : redrho
@@ -103,6 +104,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Inputs
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfun
integer(IK), intent(in) :: npt
@@ -148,8 +150,11 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
logical :: close_itpset
logical :: improve_geo
logical :: reduce_rho
+logical :: rescued
logical :: shortd
logical :: small_trrad
+logical :: terminate
+logical :: to_rescue
logical :: trfail
logical :: ximproved
real(RP) :: bmat(size(x), npt + size(x))
@@ -180,6 +185,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
real(RP) :: xosav(size(x))
real(RP) :: xpt(size(x), npt)
real(RP) :: zmat(npt, npt - size(x) - 1)
+real(RP), parameter :: trtol = 1.0E-2_RP ! Convergence tolerance of trust-region subproblem solver
! Sizes.
n = int(size(x), kind(n))
@@ -199,8 +205,8 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call assert(size(xl) == n .and. size(xu) == n, 'SIZE(XL) == N == SIZE(XU)', srname)
call assert(all(rhobeg <= (xu - xl) / TWO), 'RHOBEG <= MINVAL(XU-XL)/2', srname)
call assert(all(is_finite(x)), 'X is finite', srname)
- call assert(all(x >= xl .and. (x <= xl .or. x >= xl + rhobeg)), 'X == XL or X >= XL + RHOBEG', srname)
- call assert(all(x <= xu .and. (x >= xu .or. x <= xu - rhobeg)), 'X == XU or X >= XU - RHOBEG', srname)
+ call assert(all(x >= xl .and. (x <= xl .or. x - xl >= rhobeg)), 'X == XL or X - XL >= RHOBEG', srname)
+ call assert(all(x <= xu .and. (x >= xu .or. xu - x >= rhobeg)), 'X == XU or XU - X >= RHOBEG', srname)
call assert(maxhist >= 0 .and. maxhist <= maxfun, '0 <= MAXHIST <= MAXFUN', srname)
call assert(size(xhist, 1) == n .and. maxxhist * (maxxhist - maxhist) == 0, &
& 'SIZE(XHIST, 1) == N, SIZE(XHIST, 2) == 0 or MAXHIST', srname)
@@ -215,10 +221,33 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call initxf(calfun, iprint, maxfun, ftarget, rhobeg, xl, xu, x, ij, kopt, nf, fhist, fval, &
& sl, su, xbase, xhist, xpt, subinfo)
+! Report the current best value, and check if user asks for early termination.
+terminate = .false.
+if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, 0_IK, terminate=terminate)
+ if (terminate) then
+ subinfo = CALLBACK_TERMINATE
+ end if
+end if
+
! Initialize X and F according to KOPT.
x = xinbd(xbase, xpt(:, kopt), xl, xu, sl, su) ! In precise arithmetic, X = XBASE + XOPT.
f = fval(kopt)
+! Finish the initialization if INITXF completed normally and CALLBACK did not request termination;
+! otherwise, do not proceed, as XPT etc may be uninitialized, leading to errors or exceptions.
+if (subinfo == INFO_DFT) then
+ ! Initialize [BMAT, ZMAT], representing inverse of KKT matrix of the interpolation system.
+ call inith(ij, xpt, bmat, zmat)
+
+ ! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
+ ! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
+ call initq(ij, fval, xpt, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ subinfo = NAN_INF_MODEL
+ end if
+end if
+
! Check whether to return due to abnormal cases that may occur during the initialization.
if (subinfo /= INFO_DFT) then
info = subinfo
@@ -226,19 +255,26 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call rangehist(nf, xhist, fhist)
! Print a return message according to IPRINT.
call retmsg(solver, info, iprint, nf, f, x)
+ ! Postconditions
+ if (DEBUGGING) then
+ call assert(nf <= maxfun, 'NF <= MAXFUN', srname)
+ call assert(size(x) == n .and. .not. any(is_nan(x)), 'SIZE(X) == N, X does not contain NaN', srname)
+ call assert(all(x >= xl) .and. all(x <= xu), 'XL <= X <= XU', srname)
+ call assert(.not. (is_nan(f) .or. is_posinf(f)), 'F is not NaN/+Inf', srname)
+ call assert(size(xhist, 1) == n .and. size(xhist, 2) == maxxhist, 'SIZE(XHIST) == [N, MAXXHIST]', srname)
+ call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
+ ! The last calculated X can be Inf (finite + finite can be Inf numerically).
+ do k = 1, min(nf, maxxhist)
+ call assert(all(xhist(:, k) >= xl) .and. all(xhist(:, k) <= xu), 'XL <= XHIST <= XU', srname)
+ end do
+ call assert(size(fhist) == maxfhist, 'SIZE(FHIST) == MAXFHIST', srname)
+ call assert(.not. any(is_nan(fhist(1:min(nf, maxfhist))) .or. is_posinf(fhist(1:min(nf, maxfhist)))), &
+ & 'FHIST does not contain NaN/+Inf', srname)
+ call assert(.not. any(fhist(1:min(nf, maxfhist)) < f), 'F is the smallest in FHIST', srname)
+ end if
return
end if
-! Initialize [BMAT, ZMAT], representing the inverse of the KKT matrix of the interpolation system.
-call inith(ij, xpt, bmat, zmat)
-
-! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
-! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
-call initq(ij, fval, xpt, gopt, hq, pq)
-
-! After initializing GOPT, HQ, PQ, BMAT, ZMAT, one can also choose to return if these arrays contain
-! NaN. We do not do it here. The code will continue to run and possibly recovers by geometry steps.
-
! Set some more initial values.
! We must initialize RATIO. Otherwise, when SHORTD = TRUE, compilers may raise a run-time error that
! RATIO is undefined. But its value will not be used: when SHORTD = FALSE, its value will be
@@ -248,6 +284,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
rho = rhobeg
delta = rho
ebound = ZERO
+rescued = .false.
shortd = .false.
trfail = .false.
ratio = -ONE
@@ -266,10 +303,16 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! than setting directly DELTA = MAX(NEW_DELTA, RHO).
gamma3 = max(ONE, min(0.75_RP * gamma2, 1.5_RP))
-! MAXTR is the maximal number of trust-region iterations. Each trust-region iteration takes 1 or 2
-! function evaluations unless the trust-region step is short or fails to reduce the trust-region
-! model but the geometry step is not invoked. Thus the following MAXTR is unlikely to be reached.
-maxtr = max(maxfun, 2_IK * maxfun) ! MAX: precaution against overflow, which will make 2*MAXFUN < 0.
+! MAXTR is the maximal number of trust-region iterations. Here, we set it to HUGE(MAXTR) - 1 so that
+! the algorithm will not terminate due to MAXTR. However, this may not be allowed in other languages
+! such as MATLAB. In that case, we can set MAXTR to 10*MAXFUN, which is unlikely to reach because
+! each trust-region iteration takes 1 or 2 function evaluations unless the trust-region step is short
+! or fails to reduce the trust-region model but the geometry step is not invoked.
+! N.B.: Do NOT set MAXTR to HUGE(MAXTR), as it may cause overflow and infinite cycling in the DO
+! loop. See
+! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+maxtr = huge(maxtr) - 1_IK !!MATLAB: maxtr = 10 * maxfun;
info = MAXTR_REACHED
! Begin the iterative procedure.
@@ -280,18 +323,18 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! BOBYQA never sets IMPROVE_GEO and REDUCE_RHO to TRUE simultaneously.
do tr = 1, maxtr
! Generate the next trust region step D.
- call trsbox(delta, gopt, hq, pq, sl, su, xpt(:, kopt), xpt, crvmin, d)
+ call trsbox(delta, gopt, hq, pq, sl, su, trtol, xpt(:, kopt), xpt, crvmin, d)
dnorm = min(delta, norm(d))
- shortd = (dnorm < HALF * rho)
+ shortd = (dnorm <= HALF * rho) ! `<=` works better than `<` in case of underflow.
! Set QRED to the reduction of the quadratic model when the move D is made from XOPT. QRED
- ! should be positive If it is nonpositive due to rounding errors, we will not take this step.
+ ! should be positive. If it is nonpositive due to rounding errors, we will not take this step.
qred = -quadinc(d, xpt, gopt, pq, hq) ! QRED = Q(XOPT) - Q(XOPT + D)
- trfail = (.not. qred > 1.0E-5_RP * rho**2) ! QRED is tiny/negative or NaN.
+ trfail = (.not. qred > 1.0E-6 * rho**2) ! QRED is tiny/negative or NaN.
! When D is short, make a choice between reducing RHO and improving the geometry depending
! on whether or not our work with the current RHO seems complete. RHO is reduced if the
- ! errors in the quadratic model at the last three interpolation points compare favourably
+ ! errors in the quadratic model at the recent interpolation points compare favourably
! with predictions of likely improvements to the model within distance HALF*RHO of XOPT.
! Why do we reduce RHO when SHORTD is true and the entries of MODERR_REC and DNORM_REC are all
! small? The reason is well explained by the BOBYQA paper in the paragraphs surrounding
@@ -312,6 +355,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
x = xinbd(xbase, xpt(:, kopt) + d, xl, xu, sl, su) ! X = XBASE + XOPT + D without rounding.
call evaluate(calfun, x, f)
nf = nf + 1_IK
+ rescued = .false. ! Set RESCUED to FALSE after evaluating F at a new point.
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Trust region', iprint, nf, delta, f, x)
@@ -326,10 +370,10 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
end if
! Update DNORM_REC and MODERR_REC.
- ! DNORM_REC records the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC records the DNORM of the recent function evaluations with the current RHO.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC records the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC records the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) + qred
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
@@ -347,22 +391,28 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Call RESCUE if rounding errors have damaged the denominator corresponding to D.
! RESCUE is invoked sometimes though not often after a trust-region step, and it does
- ! improve the performance, especially when pursing high-precision solutions..
+ ! improve the performance, especially when pursing high-precision solutions.
vlag = calvlag(kopt, bmat, d, xpt, zmat)
den = calden(kopt, bmat, d, xpt, zmat)
- if (ximproved .and. .not. (is_finite(sum(abs(vlag))) .and. any(den > maxval(vlag(1:npt)**2)))) then
- ! Below are some alternatives conditions for calling RESCUE. They perform fairly well.
- ! !if (.false.) then ! Do not call RESCUE at all.
- ! !if (ximproved .and. .not. any(den > 0.25_RP * maxval(vlag(1:npt)**2))) then
- ! !if (ximproved .and. .not. any(den > HALF * maxval(vlag(1:npt)**2))) then
- ! !if (.not. any(den > HALF * maxval(vlag(1:npt)**2))) then ! Powell's code.
- ! !if (.not. any(den > maxval(vlag(1:npt)**2))) then
+ to_rescue = (ximproved .and. .not. (is_finite(sum(abs(vlag))) .and. any(den > maxval(vlag(1:npt)**2))))
+ ! Below are some alternatives conditions for calling RESCUE. They perform fairly well.
+ ! !to_rescue = .false. ! Do not call RESCUE at all.
+ ! !to_rescue = (ximproved .and. .not. any(den > 0.25_RP * maxval(vlag(1:npt)**2)))
+ ! !to_rescue = (ximproved .and. .not. any(den > HALF * maxval(vlag(1:npt)**2)))
+ ! !to_rescue = (.not. any(den > HALF * maxval(vlag(1:npt)**2))) ! Powell's code.
+ ! !to_rescue = (.not. any(den > maxval(vlag(1:npt)**2)))
+ if (to_rescue) then
+ if (rescued) then
+ info = DAMAGING_ROUNDING ! The last RESCUE did not improve the situation.
+ exit
+ end if
call rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt, nf, fhist, &
& fval, gopt, hq, pq, sl, su, xbase, xhist, xpt, bmat, zmat, subinfo)
if (subinfo /= INFO_DFT) then
info = subinfo
exit
end if
+ rescued = .true.
dnorm_rec = REALMAX
moderr_rec = REALMAX
@@ -391,6 +441,10 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Try whether to replace the new quadratic model with the alternative model, namely the
! least Frobenius norm interpolant.
call tryqalt(bmat, fval - fval(kopt), ratio, sl, su, xpt(:, kopt), xpt, zmat, itest, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
end if
end if ! End of IF (SHORTD .OR. TRFAIL). The normal trust-region calculation ends.
@@ -417,6 +471,8 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! !close_itpset = all(distsq <= 4.0_RP * delta**2) ! Powell's NEWUOA code.
! !close_itpset = all(distsq <= max(delta**2, 4.0_RP * rho**2)) ! Powell's LINCOA code.
! ADEQUATE_GEO: Is the geometry of the interpolation set "adequate"?
+ ! N.B. (Zaikun 20240314): Even if RESCUE has just been called (RESCUED = TRUE), the geometry may
+ ! still be inadequate/improvable if XPT contains points far away from XOPT.
adequate_geo = (shortd .and. accurate_mod) .or. close_itpset
! SMALL_TRRAD: Is the trust-region radius small? This indicator seems not impactive in practice.
small_trrad = (max(delta, dnorm) <= rho) ! Powell's code. See also (6.7) of the BOBYQA paper.
@@ -489,14 +545,19 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! KNEW_GEO, the step D will become improper as it was chosen according to the old KNEW_GEO.
vlag = calvlag(kopt, bmat, d, xpt, zmat)
den = calden(kopt, bmat, d, xpt, zmat)
- if (.not. (is_finite(sum(abs(vlag))) .and. den(knew_geo) > HALF * vlag(knew_geo)**2)) then
+ to_rescue = (.not. (is_finite(sum(abs(vlag))) .and. den(knew_geo) > HALF * vlag(knew_geo)**2))
+ if (to_rescue) then
+ if (rescued) then
+ info = DAMAGING_ROUNDING ! The last RESCUE did not improve the situation.
+ exit
+ end if
call rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt, nf, fhist, &
& fval, gopt, hq, pq, sl, su, xbase, xhist, xpt, bmat, zmat, subinfo)
if (subinfo /= INFO_DFT) then
info = subinfo
exit
end if
-
+ rescued = .true.
dnorm_rec = REALMAX
moderr_rec = REALMAX
else
@@ -504,6 +565,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
x = xinbd(xbase, xpt(:, kopt) + d, xl, xu, sl, su) ! X = XBASE + XOPT + D without rounding.
call evaluate(calfun, x, f)
nf = nf + 1_IK
+ rescued = .false. ! Set RESCUED to FALSE after evaluating F at a new point.
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Geometry', iprint, nf, delbar, f, x)
@@ -518,13 +580,13 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
end if
! Update DNORM_REC and MODERR_REC.
- ! DNORM_REC records the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC records the DNORM of the recent function evaluations with the current RHO.
! Powell's code does not update DNORM. Therefore, DNORM is the length of the last
! trust-region trial step, inconsistent with MODERR_REC. The same problem exists in NEWUOA.
dnorm = min(delbar, norm(d))
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC records the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC records the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) - quadinc(d, xpt, gopt, pq, hq) ! QRED = Q(XOPT) - Q(XOPT + D)
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
@@ -538,6 +600,10 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call updateh(knew_geo, kopt, d, xpt, bmat, zmat)
call updatexf(knew_geo, ximproved, f, max(sl, min(su, xosav + d)), kopt, fval, xpt)
call updateq(knew_geo, ximproved, bmat, d, moderr, xdrop, xosav, xpt, zmat, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
end if
end if ! End of IF (IMPROVE_GEO). The procedure of improving geometry ends.
@@ -552,7 +618,7 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
rho = redrho(rho, rhoend)
! Print a message about the reduction of RHO according to IPRINT.
call rhomsg(solver, iprint, nf, delta, fval(kopt), rho, xbase + xpt(:, kopt))
- ! DNORM_REC and MODERR_REC are corresponding to the latest 3 function evaluations with
+ ! DNORM_REC and MODERR_REC are corresponding to the recent function evaluations with
! the current RHO. Update them after reducing RHO.
dnorm_rec = REALMAX
moderr_rec = REALMAX
@@ -570,10 +636,20 @@ subroutine bobyqb(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call shiftbase(kopt, xbase, xpt, zmat, bmat, pq, hq)
xbase = max(xl, min(xu, xbase))
end if
+
+ ! Report the current best value, and check if user asks for early termination.
+ if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, tr, terminate=terminate)
+ if (terminate) then
+ info = CALLBACK_TERMINATE
+ exit
+ end if
+ end if
+
end do ! End of DO TR = 1, MAXTR. The iterative procedure ends.
! Return from the calculation, after trying the Newton-Raphson step if it has not been tried yet.
-if (info == SMALL_TR_RADIUS .and. shortd .and. nf < maxfun) then
+if (info == SMALL_TR_RADIUS .and. shortd .and. dnorm > TENTH * rhoend .and. nf < maxfun) then
x = xinbd(xbase, xpt(:, kopt) + d, xl, xu, sl, su) ! In precise arithmetic, X = XBASE + XOPT + D.
call evaluate(calfun, x, f)
nf = nf + 1_IK
diff --git a/fortran/bobyqa/flint b/fortran/bobyqa/flint
index 764125c9f1..75b810d749 120000
--- a/fortran/bobyqa/flint
+++ b/fortran/bobyqa/flint
@@ -1 +1 @@
-../common/flint
\ No newline at end of file
+../tests/tools/flint
\ No newline at end of file
diff --git a/fortran/bobyqa/geometry.f90 b/fortran/bobyqa/geometry.f90
index cfee3146e4..863d7a16fc 100644
--- a/fortran/bobyqa/geometry.f90
+++ b/fortran/bobyqa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_bobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Monday, August 07, 2023 AM03:57:28
+! Last Modified: Tue 10 Feb 2026 02:07:34 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -41,7 +41,7 @@ function setdrop_tr(kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result(knew
use, non_intrinsic :: consts_mod, only : RP, IK, ONE, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-use, non_intrinsic :: linalg_mod, only : issymmetric
+use, non_intrinsic :: linalg_mod, only : issymmetric, trueloc
use, non_intrinsic :: powalg_mod, only : calden
implicit none
@@ -99,14 +99,16 @@ function setdrop_tr(kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result(knew
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included into XPT yet --- it can not be included
-! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
-! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
-! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
+! the trust-region trial point has not been included into XPT yet --- it cannot be included without
+! knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code picks KNEW
+! based on the distance to the un-updated "optimal point", which is unreasonable. This has been
+! corrected in our implementation of LINCOA, yet it does not boost the performance.
if (ximproved) then
distsq = sum((xpt - spread(xpt(:, kopt) + d, dim=2, ncopies=npt))**2, dim=1)
+ !!MATLAB: distsq = sum((xpt - (xpt(:, kopt) + d)).^2) % d should be a column! Implicit expansion
else
distsq = sum((xpt - spread(xpt(:, kopt), dim=2, ncopies=npt))**2, dim=1)
+ !!MATLAB: distsq = sum((xpt - xpt(:, kopt)).^2) % Implicit expansion
end if
weight = max(ONE, distsq / rho**2)**4
@@ -137,22 +139,24 @@ function setdrop_tr(kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result(knew
score(kopt) = -ONE
end if
-! The following IF works slightly better than `IF (ANY(SCORE > 0))` from Powell's BOBYQA and LINCOA
-! code.
+! SCORE(K) = NaN implies DEN(K) = NaN. We exclude such K as we want DEN to be big.
+score(trueloc(is_nan(score))) = -ONE
+
+knew = 0
+! The following IF works slightly better than `IF (ANY(SCORE > 0))` from Powell's BOBYQA/LINCOA code.
if (any(score > 1) .or. (ximproved .and. any(score > 0))) then ! Powell's UOBYQA and NEWUOA code.
! See (6.1) of the BOBYQA paper for the definition of KNEW in this case.
- ! SCORE(K) = NaN implies DEN(K) = NaN. We exclude such K as we want DEN to be big.
- knew = int(maxloc(score, mask=(.not. is_nan(score)), dim=1), kind(knew))
- !!MATLAB: [~, knew] = max(score, [], 'omitnan');
-elseif (ximproved) then
- ! Powell's code does not include the following instructions. With Powell's code, if DEN consists
- ! of only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the
- ! following value, to make sure that the new trial point is included in the interpolation set.
- ! However, the updating subroutine will likely need to skip the update of the Lagrange
- ! polynomials (i.e., H), or they would be destroyed by the NaNs.
+ knew = int(maxloc(score, dim=1), kind(knew))
+ !!MATLAB: [~, knew] = max(score);
+end if
+
+! Powell's code does not include the following instructions. With Powell's code, if DEN consists of
+! only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the following value,
+! to make sure that the new trial point is included in the interpolation set. However, the updating
+! subroutine will likely need to skip the update of the Lagrange polynomials (i.e., H), or they
+! would be destroyed by the NaNs.
+if ((ximproved .and. knew == 0) .or. knew < 0) then ! KNEW < 0 is impossible in theory.
knew = int(maxloc(distsq, dim=1), kind(knew))
-else
- knew = 0
end if
!====================!
@@ -185,8 +189,8 @@ function geostep(knew, kopt, bmat, delbar, sl, su, xpt, zmat) result(d)
! satisfies the SL, SU and trust region bounds and it should provide a large denominator in the
! next call of UPDATE. The step XLINE-XOPT from XOPT is restricted to moves along the straight
! lines through XOPT and another interpolation point.
-! XCAUCHY also provides a large value of the modulus of the KNEW-th Lagrange function subject to
-! the constraints that have been mentioned, its main difference from XLINE being that XCAUCHY-XOPT
+! XCAUCHY provides a large value of the modulus of the KNEW-th Lagrange function subject to the
+! constraints that have been mentioned, its main difference from XLINE being that XCAUCHY-XOPT
! is a bound-constrained version of the Cauchy step within the trust region.
!--------------------------------------------------------------------------------------------------!
@@ -307,7 +311,7 @@ function geostep(knew, kopt, bmat, delbar, sl, su, xpt, zmat) result(d)
! In case GLAG contains NaN, set D to a displacement from XOPT to XPT(:, KNEW) and return. Powell's
! code does not have this, and D may be NaN in the end. Note that it is crucial to ensure that a
! geometry step is nonzero.
-if (is_nan(sum(abs(glag)))) then
+if (.not. is_finite(sum(abs(glag)))) then
d = xpt(:, knew) - xopt
d = min(HALF, delbar / norm(d)) * d ! Since XPT respects the bounds, so does XOPT + D.
return
@@ -473,6 +477,8 @@ function geostep(knew, kopt, bmat, delbar, sl, su, xpt, zmat) result(d)
end if
! Calculate DENOM for the current choice of D. Indeed, only DEN_LINE(KNEW) is needed.
+! Zaikun 20250907: It was observed numerically that D could be ZERO here (i.e., XLINE = XOPT).
+! Should this be impossible in theory?
d = xline - xopt
den_line = calden(kopt, bmat, d, xpt, zmat)
@@ -511,7 +517,7 @@ function geostep(knew, kopt, bmat, delbar, sl, su, xpt, zmat) result(d)
! In Powell's code, the subroutine returns immediately if GGFREE is 0. However, GGFREE depends
! on GLAG, which in turn depends on UPHILL. It can happen that GGFREE is 0 when UPHILL = 0 but
! not so when UPHILL= 1. Thus we skip the iteration for the current UPHILL but do not return.
- if (ggfree <= 0) then
+ if (ggfree <= 0 .or. is_nan(ggfree)) then
cycle
end if
@@ -582,6 +588,13 @@ function geostep(knew, kopt, bmat, delbar, sl, su, xpt, zmat) result(d)
d = s
end if
+! In case D is zero or contains Inf/NaN, replace it with a displacement from XPT(:, KNEW) to XOPT.
+! Powell's code does not have this. Note that it is crucial to ensure that a geometry step is nonzero.
+if (sum(abs(d)) <= 0 .or. .not. is_finite(sum(abs(d)))) then
+ d = xpt(:, knew) - xopt
+ d = min(HALF, delbar / norm(d)) * d ! Since XPT respects the bounds, so does XOPT + D.
+end if
+
!====================!
! Calculation ends !
!====================!
diff --git a/fortran/bobyqa/initialize.f90 b/fortran/bobyqa/initialize.f90
index 9a6d92cd0a..995e96798c 100644
--- a/fortran/bobyqa/initialize.f90
+++ b/fortran/bobyqa/initialize.f90
@@ -8,7 +8,7 @@ module initialize_bobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Monday, August 07, 2023 AM03:57:17
+! Last Modified: Tue 10 Feb 2026 01:53:25 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -110,8 +110,8 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, xl, xu, x0, ij, kopt,
call assert(size(sl) == n .and. size(su) == n, 'SIZE(SL) == N == SIZE(SU)', srname)
call assert(size(xl) == n .and. size(xu) == n, 'SIZE(XL) == N == SIZE(XU)', srname)
call assert(size(x0) == n .and. all(is_finite(x0)), 'SIZE(X0) == N, X0 is finite', srname)
- call assert(all(x0 >= xl .and. (x0 <= xl .or. x0 >= xl + rhobeg)), 'X0 == XL or X0 >= XL + RHOBEG', srname)
- call assert(all(x0 <= xu .and. (x0 >= xu .or. x0 <= xu - rhobeg)), 'X0 == XU or X0 >= XU - RHOBEG', srname)
+ call assert(all(x0 >= xl .and. (x0 <= xl .or. x0 - xl >= rhobeg)), 'X0 == XL or X0 - XL >= RHOBEG', srname)
+ call assert(all(x0 <= xu .and. (x0 >= xu .or. xu - x0 >= rhobeg)), 'X0 == XU or XU - X0 >= RHOBEG', srname)
call assert(size(xbase) == n, 'SIZE(XBASE) == N', srname)
call assert(size(xhist, 1) == n .and. maxxhist * (maxxhist - maxhist) == 0, &
& 'SIZE(XHIST, 1) == N, SIZE(XHIST, 2) == 0 or MAXHIST', srname)
@@ -167,7 +167,9 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, xl, xu, x0, ij, kopt,
! Initialize XHIST, FHIST, and FVAL. Otherwise, compilers may complain that they are not
! (completely) initialized if the initialization aborts due to abnormality (see CHECKEXIT).
-! Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! N.B.: 1. Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! 2. Do not initialize the models if the current initialization aborts due to abnormality. Otherwise,
+! errors or exceptions may occur, as FVAL and XPT etc are uninitialized.
xhist = -REALMAX
fhist = REALMAX
fval = REALMAX
@@ -294,6 +296,11 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, xl, xu, x0, ij, kopt,
call assert(.not. any(evaluated .and. fval < fval(kopt)), 'FVAL(KOPT) = MINVAL(FVAL)', srname)
call assert(size(fhist) == maxfhist, 'SIZE(FHIST) == MAXFHIST', srname)
call assert(size(xhist, 1) == n .and. size(xhist, 2) == maxxhist, 'SIZE(XHIST) == [N, MAXXHIST]', srname)
+ call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
+ ! The last calculated X can be Inf (finite + finite can be Inf numerically).
+ do k = 1, min(nf, maxxhist)
+ call assert(all(xhist(:, k) >= xl) .and. all(xhist(:, k) <= xu), 'XL <= XHIST <= XU', srname)
+ end do
end if
end subroutine initxf
@@ -407,7 +414,7 @@ subroutine initq(ij, fval, xpt, gopt, hq, pq, info)
pq = ZERO
if (present(info)) then
- if (is_nan(sum(abs(gopt)) + sum(abs(hq)))) then
+ if (any(is_nan(gopt)) .or. any(is_nan(hq))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -435,12 +442,12 @@ subroutine inith(ij, xpt, bmat, zmat, info)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, HALF, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, HALF, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : issymmetric, diag
-use, non_intrinsic :: powalg_mod, only : errh
+!use, non_intrinsic :: powalg_mod, only : errh
implicit none
@@ -523,7 +530,7 @@ subroutine inith(ij, xpt, bmat, zmat, info)
end do
if (present(info)) then
- if (is_nan(sum(abs(bmat)) + sum(abs(zmat)))) then
+ if (any(is_nan(bmat)) .or. any(is_nan(zmat))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -540,8 +547,8 @@ subroutine inith(ij, xpt, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
& 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
- call assert(errh(1_IK, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS), &
- & '[IDZ, BMA, ZMAT] represents H = W^{-1}', srname)
+ !call assert(errh(1_IK, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS) .or. &
+ ! & precision(0.0_RP) < precision(0.0D0), '[BMA, ZMAT] represents H = W^{-1}', srname)
end if
end subroutine inith
diff --git a/fortran/bobyqa/mlint b/fortran/bobyqa/mlint
index 13013f2f37..9dd46ff833 120000
--- a/fortran/bobyqa/mlint
+++ b/fortran/bobyqa/mlint
@@ -1 +1 @@
-../common/mlint
\ No newline at end of file
+../tests/tools/mlint
\ No newline at end of file
diff --git a/fortran/bobyqa/rescue.f90 b/fortran/bobyqa/rescue.f90
index 3863b1a3b8..382d47fb5b 100644
--- a/fortran/bobyqa/rescue.f90
+++ b/fortran/bobyqa/rescue.f90
@@ -18,7 +18,7 @@ module rescue_mod
!
! Started: February 2022
!
-! Last Modified: Wednesday, June 21, 2023 PM08:32:09
+! Last Modified: Thu 14 Aug 2025 07:34:53 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -102,6 +102,7 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
use, non_intrinsic :: message_mod, only : fmsg
use, non_intrinsic :: pintrf_mod, only : OBJ
use, non_intrinsic :: powalg_mod, only : hess_mul, setij
+use, non_intrinsic :: string_mod, only : num2str
use, non_intrinsic :: xinbd_mod, only : xinbd
implicit none
@@ -141,6 +142,7 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
integer(IK) :: ip
integer(IK) :: iq
integer(IK) :: iter
+integer(IK) :: j
integer(IK) :: k
integer(IK) :: kbase
integer(IK) :: korig
@@ -160,6 +162,7 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
real(RP) :: den(size(xpt, 2))
real(RP) :: f
real(RP) :: fbase
+real(RP) :: hcol(size(bmat, 2))
real(RP) :: hdiag(size(xpt, 2))
real(RP) :: moderr
real(RP) :: pqinc(size(xpt, 2))
@@ -174,6 +177,7 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
real(RP) :: vquad
real(RP) :: wmv(size(xpt, 1) + size(xpt, 2))
real(RP) :: x(size(xpt, 1))
+real(RP) :: xnew(size(xpt, 1))
real(RP) :: xopt(size(xpt, 1))
real(RP) :: xp
real(RP) :: xq
@@ -207,6 +211,10 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
call assert(all(xbase >= xl .and. xbase <= xu), 'XL <= XBASE <= XU', srname)
call assert(size(xhist, 1) == n .and. maxxhist * (maxxhist - maxhist) == 0, &
& 'SIZE(XHIST, 1) == N, SIZE(XHIST, 2) == 0 or MAXHIST', srname)
+ call assert(all(is_finite(xhist(:, 1:min(nf, maxxhist)))), 'XHIST is finite', srname)
+ do k = 1, min(nf, maxxhist)
+ call assert(all(xhist(:, k) >= xl) .and. all(xhist(:, k) <= xu), 'XL <= XHIST <= XU', srname)
+ end do
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
call assert(all(xpt >= spread(sl, dim=2, ncopies=npt)) .and. &
& all(xpt <= spread(su, dim=2, ncopies=npt)), 'SL <= XPT <= SU', srname)
@@ -318,11 +326,7 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
! Originally, it is a WHILE loop, but we change it to a DO loop to avoid infinite cycling.
! N.B.: Overflow will occur in NPT^2 if NPT > 180 and IK = 16. The following is a workaround, which
! is **not needed in Python/MATLAB/Julia/R. In MATLAB, we can just take maxiter = npt^2**.
-if (2.0 * log10(real(npt)) < range(maxiter)) then
- maxiter = npt * npt
-else
- maxiter = huge(maxiter) - 2_IK
-end if
+maxiter = int(min(10**min(range(0), range(0_IK)), int(npt)**2), IK) !!MATLAB: maxiter = npt^2;
do iter = 1, maxiter
! !DO WHILE (ANY(SCORE > 0) .AND. NPROV > 1) ! WHILE version.
! !IF (ALL(SCORE <= 0) .AND. NPROV <= 0) THEN ! Powell's code. May not take any provisional point.
@@ -472,20 +476,34 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
! and IQ entries.
xp = ZERO
xq = ZERO
- xpt(:, kpt) = ZERO
+ xnew = ZERO
if (ip > 0 .and. iq > 0) then
xp = ptsaux(1, ip)
- xpt(ip, kpt) = xp
+ xnew(ip) = xp
xq = ptsaux(1, iq)
- xpt(iq, kpt) = xq
+ xnew(iq) = xq
elseif (ip > 0) then ! IP > 0, IQ == 0
xp = ptsaux(1, ip)
- xpt(ip, kpt) = xp
+ xnew(ip) = xp
elseif (iq > 0) then ! IP == 0, IQ > 0
xq = ptsaux(2, iq)
- xpt(iq, kpt) = xq
+ xnew(iq) = xq
end if
+ ! Zaikun 20240314: Skip the new point if it is too close to XPT(:, KPT), the point to replace.
+ ! Indeed, it may even happen that XNEW == XPT(:, KPT), which did occur when RP = REAL16 (half
+ ! precision) and led to an infinite cycling, because RESCUE did not make any change to XPT,
+ ! and later the algorithm decided to call RESCUE again with the same data. This was fixed by
+ ! the skipping, and by terminating the algorithm if RESCUE is requested for two times
+ ! without any new function evaluations in between, which was the behavior of Powell's code.
+ ! Skipping an XNEW that is close but not identical to XPT(:, KPT) will cause discrepancy
+ ! between [BMAT, ZMAT] and XPT, since the former has been updated, but it is not severe as
+ ! the difference between XNEW and XPT(:, KPT) is tiny.
+ if (sum(abs(xnew - xpt(:, kpt))) <= 1.0E-2 * delta .or. .not. is_finite(sum(abs(xnew)))) then
+ cycle
+ end if
+ xpt(:, kpt) = xnew
+
! Calculate F at the new interpolation point, and set MODERR to the factor that is going to
! multiply the KPT-th Lagrange function when the model is updated to provide interpolation
! to the new function value.
@@ -595,6 +613,11 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
call assert(all(xbase >= xl .and. xbase <= xu), 'XL <= XBASE <= XU', srname)
call assert(size(xhist, 1) == n .and. maxxhist * (maxxhist - maxhist) == 0, &
& 'SIZE(XHIST, 1) == N, SIZE(XHIST, 2) == 0 or MAXHIST', srname)
+ call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
+ ! The last calculated X can be Inf (finite + finite can be Inf numerically).
+ do k = 1, min(nf, maxxhist)
+ call assert(all(xhist(:, k) >= xl) .and. all(xhist(:, k) <= xu), 'XL <= XHIST <= XU', srname)
+ end do
call assert(size(xpt, 1) == n .and. size(xpt, 2) == npt, 'SIZE(XPT) == [N, NPT]', srname)
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
call assert(all(xpt >= spread(sl, dim=2, ncopies=npt)) .and. &
@@ -602,8 +625,12 @@ subroutine rescue(calfun, solver, iprint, maxfun, delta, ftarget, xl, xu, kopt,
call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT) == [N, NPT+N]', srname)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1_IK, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
- call assert(size(xhist, 1) == n .and. maxxhist * (maxxhist - maxhist) == 0, &
- & 'SIZE(XHIST, 1) == N, SIZE(XHIST, 2) == 0 or MAXHIST', srname)
+
+ do j = 1, npt
+ hcol(1:npt) = matprod(zmat, zmat(j, :))
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
end if
end subroutine rescue
@@ -627,6 +654,7 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
use, non_intrinsic :: infnan_mod, only : is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
use, non_intrinsic :: linalg_mod, only : planerot, matprod, outprod, symmetrize, issymmetric
+use, non_intrinsic :: string_mod, only : num2str
implicit none
! Inputs
@@ -655,7 +683,7 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
real(RP) :: v1(size(bmat, 1))
real(RP) :: v2(size(bmat, 1))
real(RP) :: vlag(size(vlag_in))
-real(RP) :: ztest
+real(RP) :: zknew1
! Sizes.
n = int(size(bmat, 1), kind(n))
@@ -671,9 +699,15 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1_IK, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
call assert(size(vlag_in) == npt + n, 'SIZE(VLAG) == NPT + N', srname)
+ do j = 1, npt
+ hcol(1:npt) = matprod(zmat, zmat(j, :))
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
! The following is too expensive to check.
!tol = 1.0E-2_RP
- !call wassert(errh(bmat, zmat, xpt) <= tol .or. RP == kind(0.0), &
+ !call wassert(errh(bmat, zmat, xpt) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! & 'H = W^{-1} in (2.7) of the BOBYQA paper', srname)
end if
@@ -711,10 +745,9 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
vlag(knew) = vlag(knew) - ONE
! Apply Givens rotations to put zeros in the KNEW-th row of ZMAT. After this, ZMAT(KNEW, :) contains
-! only one nonzero at ZMAT(KNEW, 1). Entries of ZMAT are treated as 0 if the moduli are at most ZTEST.
-ztest = 1.0E-20_RP * maxval(abs(zmat))
+! only one nonzero at ZMAT(KNEW, 1). Entries of ZMAT are treated as 0 if the moduli are quite small.
do j = 2, npt - n - 1_IK
- if (abs(zmat(knew, j)) > ztest) then
+ if (abs(zmat(knew, j)) > 1.0E-20 * maxval(abs(zmat))) then ! This threshold is by Powell
grot = planerot(zmat(knew, [1_IK, j]))
zmat(:, [1_IK, j]) = matprod(zmat(:, [1_IK, j]), transpose(grot))
end if
@@ -727,13 +760,16 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
! Complete the updating of ZMAT. See (4.14) of the BOBYQA paper.
sqrtdn = sqrt(denom)
-zmat(:, 1) = (tau / sqrtdn) * zmat(:, 1) - (zmat(knew, 1) / sqrtdn) * vlag(1:npt)
+zknew1 = zmat(knew, 1) / sqrtdn
+zmat(:, 1) = (tau / sqrtdn) * zmat(:, 1) - zknew1 * vlag(1:npt)
+zmat(knew, 1) = zknew1 ! Because TAU = VLAG(KNEW) + 1. Powell's code does not have this.
! Finally, update the matrix BMAT. It implements the last N rows of (4.9) in the BOBYQA paper.
alpha = hcol(knew)
v1 = (alpha * vlag(npt + 1:npt + n) - tau * hcol(npt + 1:npt + n)) / denom
v2 = (-beta * hcol(npt + 1:npt + n) - tau * vlag(npt + 1:npt + n)) / denom
bmat = bmat + outprod(v1, vlag) + outprod(v2, hcol) !call r2update(bmat, ONE, v1, vlag, ONE, v2, hcol)
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
! Numerically, the update above does not guarantee BMAT(:, NPT+1 : NPT+N) to be symmetric.
call symmetrize(bmat(:, npt + 1:npt + n))
@@ -747,11 +783,17 @@ subroutine updateh_rsc(knew, beta, vlag_in, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
+ do j = 1, npt
+ hcol(1:npt) = matprod(zmat, zmat(j, :))
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
! The following is too expensive to check.
! !if (n * npt <= 50) then
! ! xpt_test = xpt
! ! xpt_test(:, knew) = xpt(:, kopt) + d
- ! ! call assert(errh(bmat, zmat, xpt_test) <= tol .or. RP == kind(0.0), &
+ ! ! call assert(errh(bmat, zmat, xpt_test) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! ! & 'H = W^{-1} in (2.7) of the BOBYQA paper', srname)
! !end if
end if
diff --git a/fortran/bobyqa/trustregion.f90 b/fortran/bobyqa/trustregion.f90
index b8baa50792..b5974eb4f1 100644
--- a/fortran/bobyqa/trustregion.f90
+++ b/fortran/bobyqa/trustregion.f90
@@ -8,7 +8,7 @@ module trustregion_bobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Sunday, August 27, 2023 PM09:42:37
+! Last Modified: Thursday, April 04, 2024 PM09:26:23
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -19,7 +19,7 @@ module trustregion_bobyqa_mod
contains
-subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
+subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, tol, xopt, xpt, crvmin, d)
!--------------------------------------------------------------------------------------------------!
! This subroutine approximately solves
! minimize Q(XOPT + D) subject to ||D|| <= DELTA, SL <= XOPT + D <= SU.
@@ -70,6 +70,7 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
real(RP), intent(in) :: pq_in(:) ! PQ_IN(NPT)
real(RP), intent(in) :: sl(:) ! SL(N)
real(RP), intent(in) :: su(:) ! SU(N)
+real(RP), intent(in) :: tol
real(RP), intent(in) :: xopt(:) ! XOPT(N)
real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
@@ -97,6 +98,7 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
real(RP) :: delsq
real(RP) :: dhd
real(RP) :: dhs
+real(RP) :: dold(size(d))
real(RP) :: dredg
real(RP) :: dredsq
real(RP) :: ds
@@ -131,7 +133,6 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
real(RP) :: ssq(size(gopt_in))
real(RP) :: tanbd(size(gopt_in))
real(RP) :: xnew(size(gopt_in))
-real(RP), parameter :: ctest = 0.01_RP ! Convergence test parameter.
! Sizes
n = int(size(gopt_in), kind(n))
@@ -240,7 +241,7 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
stepsq = sum(s**2)
ds = inprod(d(trueloc(xbdi == 0)), s(trueloc(xbdi == 0)))
- if (.not. (stepsq > EPS * delsq .and. gredsq * delsq > (ctest * qred)**2 .and. .not. is_nan(ds))) then
+ if (.not. (stepsq > EPS * delsq .and. gredsq * delsq > (tol * qred)**2 .and. .not. is_nan(ds))) then
exit
end if
@@ -348,7 +349,15 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
ggsav = gredsq
gnew = gnew + stplen * hs
gredsq = sum(gnew(trueloc(xbdi == 0))**2)
+ dold = d
d = d + stplen * s
+
+ ! Exit in case of Inf/NaN in D.
+ if (.not. is_finite(sum(abs(d)))) then
+ d = dold
+ exit
+ end if
+
sdec = max(stplen * (ggsav - HALF * stplen * shs), ZERO)
qred = qred + sdec
end if
@@ -375,7 +384,7 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
elseif (stplen < bstep) then
! Either apply another conjugate gradient iteration or exit.
! N.B. ITERCG > N - NACT is impossible.
- if (itercg >= n - nact .or. sdec <= ctest * qred .or. is_nan(sdec) .or. is_nan(qred)) then
+ if (itercg >= n - nact .or. sdec <= tol * qred .or. is_nan(sdec) .or. is_nan(qred)) then
exit
end if
beta = gredsq / ggsav ! Has GGSAV got the correct value yet?
@@ -437,13 +446,8 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
! Let the search direction S be a linear combination of the reduced D and the reduced G that is
! orthogonal to the reduced D.
- ! Zaikun 20210926:
- ! Should we calculate S as in TRSAPP of NEWUOA in order to make sure that ||S|| = ||D||?? Namely:
- ! S = something, then S = (norm(D)/norm(S))*S
- ! Also, should exit if the orthogonality of S and D is damaged, or S is not finite.
- ! See the corresponding part of TRSAPP.
temp = gredsq * dredsq - dredg * dredg
- if (temp <= ctest**2 * qred * qred .or. is_nan(temp) .or. is_nan(qred)) then
+ if (.not. temp > tol**2 * max(gredsq * dredsq, qred**2)) then ! TEMP is tiny or NaN occurs
exit
end if
temp = sqrt(temp)
@@ -515,7 +519,7 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
if (any(is_nan(args))) then
exit
end if
- ! Defile the grid size of the search for HANGT. Powell defined the size to be 4 if hangt_bd is
+ ! Define the grid size of the search for HANGT. Powell defined the size to be 4 if hangt_bd is
! nearly zero and 20 if it is nearly one, with a linear interpolation in between. We double this
! size, which improves the performance of BOBYQA in general according to a test on 20230827.
!grid_size = nint(17.0_RP * hangt_bd + 4.1_RP, kind(grid_size)) ! Powell's version
@@ -528,17 +532,26 @@ subroutine trsbox(delta, gopt_in, hq_in, pq_in, sl, su, xopt, xpt, crvmin, d)
end if
! Update GNEW, D and HDRED. If the angle of the alternative iteration is restricted by a bound
- ! on a free variable, that variable is fixed at the bound.
- cth = (ONE - hangt * hangt) / (ONE + hangt * hangt)
- sth = (hangt + hangt) / (ONE + hangt * hangt)
+ ! on a free variable, that variable is fixed at the bound. The MIN below is a precaution against
+ ! rounding errors.
+ cth = min((ONE - hangt**2) / (ONE + hangt**2), ONE - hangt**2)
+ sth = min((hangt + hangt) / (ONE + hangt**2), hangt + hangt)
gnew = gnew + (cth - ONE) * hdred + sth * hs
+ dold = d
d(trueloc(xbdi == 0)) = cth * d(trueloc(xbdi == 0)) + sth * s(trueloc(xbdi == 0))
+
+ ! Exit in case of Inf/NaN in D.
+ if (.not. is_finite(sum(abs(d)))) then
+ d = dold
+ exit
+ end if
+
hdred = cth * hdred + sth * hs
qred = qred + sdec
if (iact >= 1 .and. iact <= n .and. hangt >= hangt_bd) then ! D(IACT) reaches lower/upper bound.
xbdi(iact) = nint(sign(ONE, xopt(iact) + d(iact) - HALF * (sl(iact) + su(iact))), kind(xbdi))
- !!MATLAB: xbdi(iact) = sign(xopt(iact)+d(iact) - (sl+su)/2);
- elseif (.not. sdec > ctest * qred) then
+ !!MATLAB: xbdi(iact) = sign(xopt(iact)+d(iact) - 0.5*(sl+su));
+ elseif (.not. sdec > tol * qred) then ! SDEC is small or NaN occurs
exit
end if
end do
diff --git a/fortran/bobyqa/update.f90 b/fortran/bobyqa/update.f90
index 6ae0400cdd..7e49fec3f9 100644
--- a/fortran/bobyqa/update.f90
+++ b/fortran/bobyqa/update.f90
@@ -8,7 +8,7 @@ module update_bobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Monday, August 07, 2023 AM03:57:08
+! Last Modified: Thu 14 Aug 2025 07:34:12 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -37,6 +37,7 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
use, non_intrinsic :: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
use, non_intrinsic :: linalg_mod, only : planerot, matprod, outprod, symmetrize, issymmetric
use, non_intrinsic :: powalg_mod, only : calbeta, calvlag
+use, non_intrinsic :: string_mod, only : num2str
implicit none
@@ -68,7 +69,6 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
real(RP) :: v1(size(bmat, 1))
real(RP) :: v2(size(bmat, 1))
real(RP) :: vlag(size(bmat, 2))
-real(RP) :: ztest
! Sizes.
n = int(size(xpt, 1), kind(n))
@@ -85,11 +85,18 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1_IK, &
& 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
+
+ do j = 1, npt
+ hcol(1:npt) = matprod(zmat, zmat(j, :))
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
! The following is too expensive to check.
!tol = 1.0E-2_RP
- !call wassert(errh(bmat, zmat, xpt) <= tol .or. RP == kind(0.0), &
+ !call wassert(errh(bmat, zmat, xpt) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! & 'H = W^{-1} in (2.7) of the BOBYQA paper', srname)
end if
@@ -139,14 +146,14 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
v1 = (alpha * vlag(npt + 1:npt + n) - tau * hcol(npt + 1:npt + n)) / denom
v2 = (-beta * hcol(npt + 1:npt + n) - tau * vlag(npt + 1:npt + n)) / denom
bmat = bmat + outprod(v1, vlag) + outprod(v2, hcol) !call r2update(bmat, ONE, v1, vlag, ONE, v2, hcol)
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
! Numerically, the update above does not guarantee BMAT(:, NPT+1 : NPT+N) to be symmetric.
call symmetrize(bmat(:, npt + 1:npt + n))
! Apply Givens rotations to put zeros in the KNEW-th row of ZMAT. After this, ZMAT(KNEW, :) contains
-! only one nonzero at ZMAT(KNEW, 1). Entries of ZMAT are treated as 0 if the moduli are at most ZTEST.
-ztest = 1.0E-20_RP * maxval(abs(zmat))
+! only one nonzero at ZMAT(KNEW, 1). Entries of ZMAT are treated as 0 if the moduli are quite small.
do j = 2, npt - n - 1_IK
- if (abs(zmat(knew, j)) > ztest) then
+ if (abs(zmat(knew, j)) > 1.0E-20 * maxval(abs(zmat))) then ! This threshold is by Powell
grot = planerot(zmat(knew, [1_IK, j]))
zmat(:, [1_IK, j]) = matprod(zmat(:, [1_IK, j]), transpose(grot))
end if
@@ -156,6 +163,10 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
! Complete the updating of ZMAT. See (4.14) of the BOBYQA paper.
sqrtdn = sqrt(denom)
zmat(:, 1) = (tau / sqrtdn) * zmat(:, 1) - (zmat(knew, 1) / sqrtdn) * vlag(1:npt)
+! Zaikun 20231012: Either of the following two lines worsens the performance of BOBYQA when the
+! objective function is evaluated with 5 or less correct significance digits. Strange.
+! !zmat(:, 1) = (tau * zmat(:, 1) - zmat(knew, 1) * vlag(1:npt)) / sqrtdn
+! !zmat(knew, 1) = zknew1 / sqrtdn ! ZKNEW1 is the unupdated ZMAT(KNEW, 1)
!====================!
! Calculation ends !
@@ -167,11 +178,17 @@ subroutine updateh(knew, kopt, d, xpt, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
+ do j = 1, npt
+ hcol(1:npt) = matprod(zmat, zmat(j, :))
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
! The following is too expensive to check.
! !if (n * npt <= 50) then
! ! xpt_test = xpt
! ! xpt_test(:, knew) = xpt(:, kopt) + d
- ! ! call assert(errh(bmat, zmat, xpt_test) <= tol .or. RP == kind(0.0), &
+ ! ! call assert(errh(bmat, zmat, xpt_test) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! ! & 'H = W^{-1} in (2.7) of the BOBYQA paper', srname)
! !end if
end if
diff --git a/fortran/classical/README.txt b/fortran/classical/README.txt
index 6fbd537044..896bbaaa84 100644
--- a/fortran/classical/README.txt
+++ b/fortran/classical/README.txt
@@ -4,10 +4,8 @@ For Powell's original code, see prima/fortran/original/.
This version is not maintained and not recommended. Please use the modernized version instead.
-Modified by: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
- and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
- Department of Applied Mathematics,
- The Hong Kong Polytechnic University
+Modified by: Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+ and Zaikun ZHANG (www.zhangzk.net)
Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
diff --git a/fortran/classical/bobyqa/bobyqa.f90 b/fortran/classical/bobyqa/bobyqa.f90
index d95c08c967..b4d5486dc0 100644
--- a/fortran/classical/bobyqa/bobyqa.f90
+++ b/fortran/classical/bobyqa/bobyqa.f90
@@ -84,6 +84,7 @@ subroutine bobyqa(calfun, x, f, &
real(RP) :: xu_loc(size(x))
real(RP), allocatable :: fhist_loc(:)
real(RP), allocatable :: xhist_loc(:, :)
+logical :: honour_x0_loc
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Working variables
@@ -151,7 +152,7 @@ subroutine bobyqa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -218,9 +219,16 @@ subroutine bobyqa(calfun, x, f, &
maxhist_loc = maxval([maxfun_loc, n + 3_IK, MAXFUN_DIM_DFT * n])
end if
+if (present(honour_x0)) then
+ honour_x0_loc = honour_x0
+else
+ honour_x0_loc = .not. present(rhobeg)
+end if
+
! Preprocess the inputs in case some of them are invalid. It does nothing if all inputs are valid.
call preproc(solver, n, iprint_loc, maxfun_loc, maxhist_loc, ftarget_loc, rhobeg_loc, rhoend_loc, &
- & npt=npt_loc, eta1=eta1_loc, eta2=eta2_loc, gamma1=gamma1_loc, gamma2=gamma2_loc, xl=xl_loc, xu=xu_loc)
+ & npt=npt_loc, eta1=eta1_loc, eta2=eta2_loc, gamma1=gamma1_loc, gamma2=gamma2_loc, xl=xl_loc, xu=xu_loc, &
+ & x0=x, honour_x0=honour_x0_loc, has_rhobeg=present(rhobeg))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! !!! Revise X (see below) and RHOBEG, RHOEND
! Zaikun, 2020-05-05
@@ -366,7 +374,7 @@ subroutine bobyqa(calfun, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
end subroutine bobyqa
diff --git a/fortran/classical/bobyqa/bobyqb.f b/fortran/classical/bobyqa/bobyqb.f
index a4ee41af89..91c13c6bab 100644
--- a/fortran/classical/bobyqa/bobyqb.f
+++ b/fortran/classical/bobyqa/bobyqb.f
@@ -438,7 +438,7 @@ subroutine bobyqb(calfun, n, npt, x, xl, xu, rhobeg,rhoend,iprint,
320 FORMAT (/5X,'Return from BOBYQA because of much',
1 ' cancellation in a denominator.')
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
- INFO=4
+ INFO=7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
GOTO 720
END IF
@@ -477,7 +477,7 @@ subroutine bobyqb(calfun, n, npt, x, xl, xu, rhobeg,rhoend,iprint,
IF (NF > NRESC) GOTO 190
IF (IPRINT > 0) PRINT 320
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
- INFO=4
+ INFO=7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
GOTO 720
END IF
diff --git a/fortran/classical/bobyqa/rescue.f b/fortran/classical/bobyqa/rescue.f
index f915927302..5cf5377935 100644
--- a/fortran/classical/bobyqa/rescue.f
+++ b/fortran/classical/bobyqa/rescue.f
@@ -83,7 +83,7 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
ONE=1.0D0
ZERO=0.0D0
NP=N+1
- SFRAC=HALF/DFLOAT(NP)
+ SFRAC=HALF/DBLE(NP)
NPTM=NPT-NP
C
C Shift the interpolation points so that XOPT becomes the origin, and set
@@ -156,9 +156,9 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
DO J=1,N
JP=J+1
JPN=JP+N
- PTSID(JP)=DFLOAT(J)+SFRAC
+ PTSID(JP)=DBLE(J)+SFRAC
IF (JPN <= NPT) THEN
- PTSID(JPN)=DFLOAT(J)/DFLOAT(NP)+SFRAC
+ PTSID(JPN)=DBLE(J)/DBLE(NP)+SFRAC
TEMP=ONE/(PTSAUX(1,J)-PTSAUX(2,J))
BMAT(JP,J)=-TEMP+ONE/PTSAUX(1,J)
BMAT(JPN,J)=TEMP+ONE/PTSAUX(2,J)
@@ -178,13 +178,13 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
IF (NPT >= N+NP) THEN
DO K=2*NP,NPT
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
-C IW=(DFLOAT(K-NP)-HALF)/DFLOAT(N)
- IW=INT((DFLOAT(K-NP)-HALF)/DFLOAT(N))
+C IW=(DBLE(K-NP)-HALF)/DBLE(N)
+ IW=INT((DBLE(K-NP)-HALF)/DBLE(N))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IP=K-NP-IW*N
IQ=IP+IW
IF (IQ > N) IQ=IQ-N
- PTSID(K)=DFLOAT(IP)+DFLOAT(IQ)/DFLOAT(NP)+SFRAC
+ PTSID(K)=DBLE(IP)+DBLE(IQ)/DBLE(NP)+SFRAC
TEMP=ONE/(PTSAUX(1,IP)*PTSAUX(1,IQ))
ZMAT(1,K-NP)=TEMP
ZMAT(IP+1,K-NP)=-TEMP
@@ -265,8 +265,8 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IF (IP > 0) SUM=W(NPT+IP)*PTSAUX(1,IP)
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
-C IQ=DFLOAT(NP)*PTSID(K)-DFLOAT(IP*NP)
- IQ=INT(DFLOAT(NP)*PTSID(K)-DFLOAT(IP*NP))
+C IQ=DBLE(NP)*PTSID(K)-DBLE(IP*NP)
+ IQ=INT(DBLE(NP)*PTSID(K)-DBLE(IP*NP))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IF (IQ > 0) THEN
IW=1
@@ -371,9 +371,9 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
PQ(KPT)=ZERO
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
C IP=PTSID(KPT)
-C IQ=DFLOAT(NP)*PTSID(KPT)-DFLOAT(IP*NP)
+C IQ=DBLE(NP)*PTSID(KPT)-DBLE(IP*NP)
IP=INT(PTSID(KPT))
- IQ=INT(DFLOAT(NP)*PTSID(KPT)-DFLOAT(IP*NP))
+ IQ=INT(DBLE(NP)*PTSID(KPT)-DBLE(IP*NP))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IF (IP > 0) THEN
XP=PTSAUX(1,IP)
@@ -396,7 +396,7 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
IHQ=(IQ+IQ*IQ)/2
VQUAD=VQUAD+XQ*(GOPT(IQ)+HALF*XQ*HQ(IHQ))
IF (IP > 0) THEN
- IW=MAX0(IHP,IHQ)-IABS(IP-IQ)
+ IW=MAX0(IHP,IHQ)-ABS(IP-IQ)
VQUAD=VQUAD+XP*XQ*HQ(IW)
END IF
END IF
@@ -451,9 +451,9 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
ELSE
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
C IP=PTSID(K)
-C IQ=DFLOAT(NP)*PTSID(K)-DFLOAT(IP*NP)
+C IQ=DBLE(NP)*PTSID(K)-DBLE(IP*NP)
IP=INT(PTSID(K))
- IQ=INT(DFLOAT(NP)*PTSID(K)-DFLOAT(IP*NP))
+ IQ=INT(DBLE(NP)*PTSID(K)-DBLE(IP*NP))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IHQ=(IQ*IQ+IQ)/2
IF (IP == 0) THEN
@@ -463,7 +463,7 @@ subroutine rescue(calfun, n, npt, xl, xu, iprint,maxfun,xbase,xpt,
HQ(IHP)=HQ(IHP)+TEMP*PTSAUX(1,IP)**2
IF (IQ > 0) THEN
HQ(IHQ)=HQ(IHQ)+TEMP*PTSAUX(1,IQ)**2
- IW=MAX0(IHP,IHQ)-IABS(IQ-IP)
+ IW=MAX0(IHP,IHQ)-ABS(IQ-IP)
HQ(IW)=HQ(IW)+TEMP*PTSAUX(1,IP)*PTSAUX(1,IQ)
END IF
END IF
diff --git a/fortran/classical/bobyqa/trsbox.f b/fortran/classical/bobyqa/trsbox.f
index a461510378..214763c207 100644
--- a/fortran/classical/bobyqa/trsbox.f
+++ b/fortran/classical/bobyqa/trsbox.f
@@ -105,7 +105,7 @@ SUBROUTINE TRSBOX (N,NPT,XPT,XOPT,GOPT,HQ,PQ,SL,SU,DELTA,
C
C Multiply the search direction by the second derivative matrix of Q and
C calculate some scalars for the choice of steplength. Then set BLEN to
-C the length of the the step to the trust region boundary and STPLEN to
+C the length of the step to the trust region boundary and STPLEN to
C the steplength, ignoring the simple bounds.
C
GOTO 210
@@ -299,7 +299,7 @@ SUBROUTINE TRSBOX (N,NPT,XPT,XOPT,GOPT,HQ,PQ,SL,SU,DELTA,
IU=INT(17.0D0*ANGBD+3.1D0)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
DO I=1,IU
- ANGT=ANGBD*DFLOAT(I)/DFLOAT(IU)
+ ANGT=ANGBD*DBLE(I)/DBLE(IU)
STH=(ANGT+ANGT)/(ONE+ANGT*ANGT)
TEMP=SHS+ANGT*(ANGT*DHD-DHS-DHS)
REDNEW=STH*(ANGT*DREDG-SREDG-HALF*STH*TEMP)
@@ -319,7 +319,7 @@ SUBROUTINE TRSBOX (N,NPT,XPT,XOPT,GOPT,HQ,PQ,SL,SU,DELTA,
IF (ISAV == 0) GOTO 190
IF (ISAV < IU) THEN
TEMP=(RDNEXT-RDPREV)/(REDMAX+REDMAX-RDPREV-RDNEXT)
- ANGT=ANGBD*(DFLOAT(ISAV)+HALF*TEMP)/DFLOAT(IU)
+ ANGT=ANGBD*(DBLE(ISAV)+HALF*TEMP)/DBLE(IU)
END IF
CTH=(ONE-ANGT*ANGT)/(ONE+ANGT*ANGT)
STH=(ANGT+ANGT)/(ONE+ANGT*ANGT)
diff --git a/fortran/classical/cobyla/cobyla.f90 b/fortran/classical/cobyla/cobyla.f90
index f521da9453..47a5127536 100644
--- a/fortran/classical/cobyla/cobyla.f90
+++ b/fortran/classical/cobyla/cobyla.f90
@@ -30,7 +30,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
use, non_intrinsic :: debug_mod, only : assert, errstop, warning
use, non_intrinsic :: evaluate_mod, only : evaluate, moderatex, moderatec
use, non_intrinsic :: history_mod, only : prehist
-use, non_intrinsic :: infnan_mod, only : is_finite
+use, non_intrinsic :: infnan_mod, only : is_finite, is_nan
use, non_intrinsic :: linalg_mod, only : trueloc, matprod
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: pintrf_mod, only : OBJCON
@@ -163,7 +163,12 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
& .or. (size(Aeq, 1) == 0 .and. size(Aeq, 2) == 0 .and. meq == 0), &
& 'SIZE(Aeq) == [Meq, N] unless Aeq and Beq are both empty', srname)
end if
- call assert(present(f0) .eqv. present(nlconstr0), 'F0 and NLCONSTR0 are both present or both absent', srname)
+ if (present(nlconstr0)) then
+ call assert(present(f0), 'If NLCONSTR0 is present, then F0 is present', srname)
+ end if
+ if (present(f0)) then
+ call assert(is_nan(f0) .or. present(nlconstr0), 'If F0 is present and not NaN, then NLCONSTR0 is present', srname)
+ end if
end if
! Exit if the size of NLCONSTR0 is inconsistent with M_NLCON.
@@ -266,7 +271,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -438,7 +443,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist) .or. present(chist) .or. present(nlchist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
@@ -460,7 +465,7 @@ subroutine calcfc_internal(x_internal, f_internal, constr_internal)
& matprod(Aeq_loc, x_internal) - beq_loc, beq_loc - matprod(Aeq_loc, x_internal), &
& bineq_loc - matprod(Aineq_loc, x_internal), -constr_nlc]
-end subroutine
+end subroutine calcfc_internal
end subroutine cobyla
diff --git a/fortran/classical/cobyla/cobylb.f b/fortran/classical/cobyla/cobylb.f
index f92b733419..5713de51d9 100644
--- a/fortran/classical/cobyla/cobylb.f
+++ b/fortran/classical/cobyla/cobylb.f
@@ -22,7 +22,7 @@ subroutine cobylb(calcfc, iprint, maxfun, rhobeg, rhoend, constr,
use, non_intrinsic :: consts_mod, only : RP, IK
use, non_intrinsic :: evaluate_mod, only : evaluate
- use, non_intrinsic :: history_mod, only: savehist, rangehist
+ use, non_intrinsic :: history_mod, only : savehist, rangehist
use, non_intrinsic :: pintrf_mod, only : OBJCON
implicit real(RP) (A-H,O-Z)
@@ -131,7 +131,8 @@ subroutine cobylb(calcfc, iprint, maxfun, rhobeg, rhoend, constr,
if (nfvals > 1) then
call evaluate(calcfc, x, f, constr)
constr = -constr
- cstrv = maxval([ZERO, -constr])
+ cstrv = ZERO
+ if (m > 0) cstrv = maxval([ZERO, -constr])
end if
con(1:m) = constr !!!
call savehist(nfvals, x, xhist, f, fhist, cstrv, chist,
@@ -729,7 +730,8 @@ subroutine cobylb(calcfc, iprint, maxfun, rhobeg, rhoend, constr,
! RETURN
! END
constr = con(1:m)
- cstrv = maxval([0.0_RP, -constr])
+ cstrv = ZERO
+ if (m > 0) cstrv = maxval([ZERO, -constr])
call rangehist(nfvals, xhist, fhist, chist, conhist)
RETURN
end subroutine cobylb
diff --git a/fortran/classical/lincoa/lincoa.f90 b/fortran/classical/lincoa/lincoa.f90
index 8debf38960..612604f15e 100644
--- a/fortran/classical/lincoa/lincoa.f90
+++ b/fortran/classical/lincoa/lincoa.f90
@@ -229,7 +229,7 @@ subroutine lincoa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -349,7 +349,7 @@ subroutine lincoa(calfun, x, f, &
end do
if (temp <= 0) then
if (present(info)) then
- info = 12
+ info = 8
end if
return
end if
@@ -392,7 +392,7 @@ subroutine lincoa(calfun, x, f, &
& int(size(chist_loc), kind=IK))
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-! Deallocate A_LOC, B_LOC, etc. Indeed, automatic allocation will take place at exit.
+! Deallocate A_LOC, B_LOC, etc. We prefer explicit deallocation to the automatic one.
deallocate (A_loc)
deallocate (b_loc)
deallocate (iact)
@@ -457,7 +457,7 @@ subroutine lincoa(calfun, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist) .or. present(chist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
end subroutine lincoa
diff --git a/fortran/classical/lincoa/lincob.f b/fortran/classical/lincoa/lincob.f
index a2b765a6a8..271cebd5c0 100644
--- a/fortran/classical/lincoa/lincob.f
+++ b/fortran/classical/lincoa/lincob.f
@@ -27,7 +27,7 @@ subroutine lincob (calfun, n,npt,m,amat,b,x,rhobeg,rhoend,iprint,
use, non_intrinsic :: consts_mod, only : RP, IK
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist, rangehist
- use, non_intrinsic :: linalg_mod, only: matprod, maximum
+ use, non_intrinsic :: linalg_mod, only : matprod, maximum
use, non_intrinsic :: pintrf_mod, only : OBJ
implicit real(RP) (A-H,O-Z)
@@ -381,12 +381,12 @@ subroutine lincob (calfun, n,npt,m,amat,b,x,rhobeg,rhoend,iprint,
250 FORMAT (/4X,'Return from LINCOA because rounding errors',
1 ' prevent reasonable changes to X.')
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
- INFO=8
+ INFO=7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
GOTO 600
END IF
IF (KSAVE <= 0) IFEAS=1
- F=DFLOAT(IFEAS)
+ F=DBLE(IFEAS)
!----------------------------------------------------------------------!
@@ -394,7 +394,8 @@ subroutine lincob (calfun, n,npt,m,amat,b,x,rhobeg,rhoend,iprint,
!CALL CALFUN (N,X,F)
call evaluate(calfun, x, f)
nf = nf + 1
- cstrv = maximum([ZERO, matprod(x, A_orig) - b_orig])
+ cstrv = ZERO
+ if (m > 0) cstrv = maximum([ZERO, matprod(x, A_orig) - b_orig])
call savehist(nf, x, xhist, f, fhist, cstrv, chist)
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
@@ -468,14 +469,13 @@ subroutine lincob (calfun, n,npt,m,amat,b,x,rhobeg,rhoend,iprint,
C can be moved. If STEP is a trust region step, then KNEW is zero at
C present, but a positive value is picked by subroutine UPDATE.
C
- CALL UPDATE (N,NPT,XPT,BMAT,ZMAT,IDZ,NDIM,SP,STEP,KOPT,
- 1 KNEW,PQW,W)
+ CALL UPDATE (N,NPT,XPT,BMAT,ZMAT,IDZ,NDIM,SP,STEP,KOPT,KNEW,PQW,W)
IF (KNEW == 0) THEN
IF (IPRINT > 0) PRINT 320
320 FORMAT (/4X,'Return from LINCOA because the denominator',
1 ' of the updating formula is zero.')
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
- INFO=9
+ INFO=7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
GOTO 600
END IF
@@ -726,11 +726,12 @@ subroutine lincob (calfun, n,npt,m,amat,b,x,rhobeg,rhoend,iprint,
PRINT 590, F,(X(I),I=1,N)
END IF
W(1)=F
- W(2)=DFLOAT(NF)+HALF
+ W(2)=DBLE(NF)+HALF
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
- cstrv = maximum([ZERO, matprod(x, A_orig) - b_orig])
+ cstrv = ZERO
+ if (m > 0) cstrv = maximum([ZERO, matprod(x, A_orig) - b_orig])
! Arrange CHIST, FHIST, and XHIST so that they are in the chronological order.
call rangehist(nf, xhist, fhist, chist)
!----------------------------------------------------------------------!
diff --git a/fortran/classical/lincoa/prelim.f b/fortran/classical/lincoa/prelim.f
index 60bd2f4893..e43bc03c0e 100644
--- a/fortran/classical/lincoa/prelim.f
+++ b/fortran/classical/lincoa/prelim.f
@@ -24,7 +24,7 @@ subroutine prelim (calfun, n,npt,m,amat,b,x,rhobeg,iprint,xbase,
use, non_intrinsic :: consts_mod, only : RP, IK
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist
- use, non_intrinsic :: linalg_mod, only: matprod, maximum
+ use, non_intrinsic :: linalg_mod, only : matprod, maximum
use, non_intrinsic :: pintrf_mod, only : OBJ
implicit real(RP) (A-H,O-Z)
@@ -174,8 +174,7 @@ subroutine prelim (calfun, n,npt,m,amat,b,x,rhobeg,iprint,xbase,
SP(NPT+K)=SP(NPT+K)+XPT(K,J)*STEP(J)
END DO
END DO
- CALL UPDATE (N,NPT,XPT,BMAT,ZMAT,IDZ,NDIM,SP,STEP,
- 1 KBASE,NF,PQW,W)
+ CALL UPDATE (N,NPT,XPT,BMAT,ZMAT,IDZ,NDIM,SP,STEP,KBASE,NF,PQW,W)
DO I=1,N
XPT(NF,I)=STEP(I)
END DO
diff --git a/fortran/classical/newuoa/bigden.f b/fortran/classical/newuoa/bigden.f
index 236e83c388..00766da2ba 100644
--- a/fortran/classical/newuoa/bigden.f
+++ b/fortran/classical/newuoa/bigden.f
@@ -251,10 +251,10 @@ SUBROUTINE BIGDEN (N,NPT,XOPT,XPT,BMAT,ZMAT,IDZ,NDIM,KOPT,
DENMAX=SUM
ISAVE=0
IU=49
- TEMP=TWOPI/DFLOAT(IU+1)
+ TEMP=TWOPI/DBLE(IU+1)
PAR(1)=ONE
DO I=1,IU
- ANGLE=DFLOAT(I)*TEMP
+ ANGLE=DBLE(I)*TEMP
PAR(2)=COS(ANGLE)
PAR(3)=SIN(ANGLE)
DO J=4,8,2
@@ -282,7 +282,7 @@ SUBROUTINE BIGDEN (N,NPT,XOPT,XPT,BMAT,ZMAT,IDZ,NDIM,KOPT,
TEMPB=TEMPB-DENMAX
STEP=HALF*(TEMPA-TEMPB)/(TEMPA+TEMPB)
END IF
- ANGLE=TEMP*(DFLOAT(ISAVE)+STEP)
+ ANGLE=TEMP*(DBLE(ISAVE)+STEP)
C
C Calculate the new parameters of the denominator, the new VLAG vector
C and the new D. Then test for convergence.
diff --git a/fortran/classical/newuoa/biglag.f b/fortran/classical/newuoa/biglag.f
index 8dd3943982..270bb129b5 100644
--- a/fortran/classical/newuoa/biglag.f
+++ b/fortran/classical/newuoa/biglag.f
@@ -155,9 +155,9 @@ SUBROUTINE BIGLAG (N,NPT,XOPT,XPT,BMAT,ZMAT,IDZ,NDIM,KNEW,
TAUOLD=TAUBEG
ISAVE=0
IU=49
- TEMP=TWOPI/DFLOAT(IU+1)
+ TEMP=TWOPI/DBLE(IU+1)
DO I=1,IU
- ANGLE=DFLOAT(I)*TEMP
+ ANGLE=DBLE(I)*TEMP
CTH=COS(ANGLE)
STH=SIN(ANGLE)
TAU=CF1+(CF2+CF4*CTH)*CTH+(CF3+CF5*CTH)*STH
@@ -178,7 +178,7 @@ SUBROUTINE BIGLAG (N,NPT,XOPT,XPT,BMAT,ZMAT,IDZ,NDIM,KNEW,
TEMPB=TEMPB-TAUMAX
STEP=HALF*(TEMPA-TEMPB)/(TEMPA+TEMPB)
END IF
- ANGLE=TEMP*(DFLOAT(ISAVE)+STEP)
+ ANGLE=TEMP*(DBLE(ISAVE)+STEP)
C
C Calculate the new D and GD. Then test for convergence.
C
diff --git a/fortran/classical/newuoa/newuoa.f90 b/fortran/classical/newuoa/newuoa.f90
index 6106064e63..4e1bf52035 100644
--- a/fortran/classical/newuoa/newuoa.f90
+++ b/fortran/classical/newuoa/newuoa.f90
@@ -109,7 +109,7 @@ subroutine newuoa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -238,7 +238,7 @@ subroutine newuoa(calfun, x, f, &
! If MAXFHIST_IN >= NF_LOC > MAXFHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
end subroutine newuoa
diff --git a/fortran/classical/newuoa/trsapp.f b/fortran/classical/newuoa/trsapp.f
index 81729aab23..ee832b8d97 100644
--- a/fortran/classical/newuoa/trsapp.f
+++ b/fortran/classical/newuoa/trsapp.f
@@ -155,9 +155,9 @@ SUBROUTINE TRSAPP (N,NPT,XOPT,XPT,GQ,HQ,PQ,DELTA,STEP,
QMIN=QBEG
ISAVE=0
IU=49
- TEMP=TWOPI/DFLOAT(IU+1)
+ TEMP=TWOPI/DBLE(IU+1)
DO I=1,IU
- ANGLE=DFLOAT(I)*TEMP
+ ANGLE=DBLE(I)*TEMP
CTH=COS(ANGLE)
STH=SIN(ANGLE)
QNEW=(SG+CF*CTH)*CTH+(DG+DHS*CTH)*STH
@@ -178,7 +178,7 @@ SUBROUTINE TRSAPP (N,NPT,XOPT,XPT,GQ,HQ,PQ,DELTA,STEP,
TEMPB=TEMPB-QMIN
ANGLE=HALF*(TEMPA-TEMPB)/(TEMPA+TEMPB)
END IF
- ANGLE=TEMP*(DFLOAT(ISAVE)+ANGLE)
+ ANGLE=TEMP*(DBLE(ISAVE)+ANGLE)
C
C Calculate the new STEP and HS. Then test for convergence.
C
diff --git a/fortran/classical/uobyqa/uobyqa.f90 b/fortran/classical/uobyqa/uobyqa.f90
index 1752d62bf9..9eda2f8bbf 100644
--- a/fortran/classical/uobyqa/uobyqa.f90
+++ b/fortran/classical/uobyqa/uobyqa.f90
@@ -110,7 +110,7 @@ subroutine uobyqa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -250,7 +250,7 @@ subroutine uobyqa(calfun, x, f, &
! If MAXFHIST_IN >= NF_LOC > MAXFHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
end subroutine uobyqa
diff --git a/fortran/cobyla/cobyla.f90 b/fortran/cobyla/cobyla.f90
index 01bce03cd2..6794cdc1a0 100644
--- a/fortran/cobyla/cobyla.f90
+++ b/fortran/cobyla/cobyla.f90
@@ -42,7 +42,7 @@ module cobyla_mod
!
! Started: July 2021
!
-! Last Modified: Friday, August 11, 2023 PM06:41:04
+! Last Modified: Tue 16 Sep 2025 12:35:02 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -53,27 +53,26 @@ module cobyla_mod
contains
-subroutine cobyla(calcfc, m_nlcon, x, f, &
- & cstrv, nlconstr, &
+subroutine cobyla(calcfc, m_nlcon, x, &
+ & f, cstrv, nlconstr, &
& Aineq, bineq, &
& Aeq, beq, &
& xl, xu, &
& f0, nlconstr0, &
& nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, eta1, eta2, gamma1, gamma2, &
- & xhist, fhist, chist, nlchist, maxhist, maxfilt, info)
+ & xhist, fhist, chist, nlchist, maxhist, maxfilt, callback_fcn, info)
!--------------------------------------------------------------------------------------------------!
-! Among all the arguments, only CALCFC, M_NLCON, X, and F are obligatory. The others are
-! OPTIONAL and you can neglect them unless you are familiar with the algorithm. Any unspecified
-! optional input will take the default value detailed below. For instance, we may invoke the
-! solver as follows.
+! Among all the arguments, only CALCFC, M_NLCON, and X are obligatory. The others are OPTIONAL and
+! you can neglect them unless you are familiar with the algorithm. Any unspecified optional input
+! will take the default value detailed below. For instance, we may invoke the solver as follows.
!
! ! First define CALCFC, M_NLCON, and X, and then do the following.
-! call cobyla(calcfc, m_nlcon, x, f)
+! call cobyla(calcfc, m_nlcon, x, f, cstrv)
!
! or
!
-! ! First define CALCFC, M_NLCON, and X, and then do the following.
-! call cobyla(calcfc, m_nlcon, x, f, rhobeg = 0.5D0, rhoend = 1.0D-3, maxfun = 100)
+! ! First define CALCFC, M_NLCON, X, Aineq, and Bineq, and then do the following.
+! call cobyla(calcfc, m_nlcon, x, f, cstrv, Aineq = Aineq, bineq = bineq, rhobeg = 1.0D0, rhoend = 1.0D-6)
!
! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! ! IMPORTANT NOTICE: The user must set M_NLCON correctly to the number of nonlinear constraints,
@@ -128,8 +127,9 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! MAXVAL([0, XL - X, X - XU, Aineq*X - Bineq, ABS(Aeq*X -Beq), NLCONSTR(X)]).
!
! NLCONSTR
-! Output, ALLOCATABLE REAL(RP) vector.
-! NLCONSTR will be set to the nonlinear constraint value of X at exit.
+! Output, REAL(RP) vector.
+! NLCONSTR should be an M_NLCON-dimensional vector and will be set to the nonlinear constraint
+! value of X at exit.
!
! Aineq, Bineq
! Input, REAL(RP) matrix of size [Mineq, N] and REAL vector of size Mineq unless they are both
@@ -143,7 +143,11 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
!
! XL, XU
! Input, REAL(RP) vectors of size N unless they are both empty, default: [] and [].
-! XL and XU represent the lower and upper bounds of the variables: XL <= X <= XU.
+! XL is the lower bound for X. Its size is either N or 0, the latter signifying that X has no
+! lower bound. Any entry of XL that is NaN or below -BOUNDMAX will be taken as -BOUNDMAX, which
+! effectively means there is no lower bound for the corresponding entry of X. The value of
+! BOUNDMAX is 0.25*HUGE(X), which is about 8.6E37 for single precision and 4.5E307 for double
+! precision. XU is similar.
!
! F0
! Input, REAL(RP) scalar.
@@ -234,6 +238,9 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! the returned solution; default: MAXFILT_DFT (a value lower than MIN_MAXFILT is not recommended);
! see common/consts.F90 for the definitions of MAXFILT_DFT and MIN_MAXFILT.
!
+! CALLBACK_FCN
+! Input, function to report progress and optionally request termination.
+!
! INFO
! Output, INTEGER(IK) scalar.
! INFO is the exit flag. It will be set to one of the following values defined in the module
@@ -256,15 +263,15 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
use, non_intrinsic :: consts_mod, only : DEBUGGING
use, non_intrinsic :: consts_mod, only : MAXFUN_DIM_DFT, MAXFILT_DFT, IPRINT_DFT
use, non_intrinsic :: consts_mod, only : RHOBEG_DFT, RHOEND_DFT, CTOL_DFT, CWEIGHT_DFT, FTARGET_DFT
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, TWO, HALF, TEN, TENTH, EPS, REALMAX
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, TWO, HALF, TEN, TENTH, EPS, BOUNDMAX
use, non_intrinsic :: debug_mod, only : assert, errstop, warning
-use, non_intrinsic :: evaluate_mod, only : evaluate, moderatex, moderatec
+use, non_intrinsic :: evaluate_mod, only : evaluate, moderatex, moderatec, moderatef
use, non_intrinsic :: history_mod, only : prehist
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
use, non_intrinsic :: infos_mod, only : INVALID_INPUT
-use, non_intrinsic :: linalg_mod, only : trueloc, matprod
+use, non_intrinsic :: linalg_mod, only : trueloc, matprod, maximum
use, non_intrinsic :: memory_mod, only : safealloc
-use, non_intrinsic :: pintrf_mod, only : OBJCON
+use, non_intrinsic :: pintrf_mod, only : OBJCON, CALLBACK
use, non_intrinsic :: selectx_mod, only : isbetter
use, non_intrinsic :: preproc_mod, only : preproc
use, non_intrinsic :: string_mod, only : num2str
@@ -277,10 +284,10 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! Compulsory arguments
procedure(OBJCON) :: calcfc ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
real(RP), intent(inout) :: x(:) ! X(N)
-real(RP), intent(out) :: f
-integer(IK), intent(in) :: m_nlcon
+integer(IK), intent(in) :: m_nlcon ! Number of constraints defined in CALCFC
! Optional inputs
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in), optional :: iprint
integer(IK), intent(in), optional :: maxfilt
integer(IK), intent(in), optional :: maxfun
@@ -306,12 +313,13 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! Optional outputs
integer(IK), intent(out), optional :: info
integer(IK), intent(out), optional :: nf
-real(RP), intent(out), optional :: nlconstr(:) ! NLCONSTR(M_NLCON)
-real(RP), intent(out), allocatable, optional :: chist(:) ! CHIST(MAXCHIST)
-real(RP), intent(out), allocatable, optional :: nlchist(:, :) ! NLCHIST(M_NLCON, MAXCONHIST)
-real(RP), intent(out), allocatable, optional :: fhist(:) ! FHIST(MAXFHIST)
-real(RP), intent(out), allocatable, optional :: xhist(:, :) ! XHIST(N, MAXXHIST)
real(RP), intent(out), optional :: cstrv
+real(RP), intent(out), optional :: f
+real(RP), intent(out), optional :: nlconstr(:) ! NLCONSTR(M_NLCON)
+real(RP), intent(out), optional, allocatable :: chist(:) ! CHIST(MAXCHIST)
+real(RP), intent(out), optional, allocatable :: fhist(:) ! FHIST(MAXFHIST)
+real(RP), intent(out), optional, allocatable :: nlchist(:, :) ! NLCHIST(M_NLCON, MAXCONHIST)
+real(RP), intent(out), optional, allocatable :: xhist(:, :) ! XHIST(N, MAXXHIST)
! Local variables
character(len=*), parameter :: solver = 'COBYLA'
@@ -336,11 +344,14 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
real(RP) :: cweight_loc
real(RP) :: eta1_loc
real(RP) :: eta2_loc
+real(RP) :: f_loc
real(RP) :: ftarget_loc
real(RP) :: gamma1_loc
real(RP) :: gamma2_loc
real(RP) :: rhobeg_loc
real(RP) :: rhoend_loc
+real(RP) :: xl_loc(size(x))
+real(RP) :: xu_loc(size(x))
real(RP), allocatable :: Aeq_loc(:, :) ! Aeq_LOC(Meq, N)
real(RP), allocatable :: Aineq_loc(:, :) ! Aineq_LOC(Mineq, N)
real(RP), allocatable :: amat(:, :) ! AMAT(N, M_LCON); each column corresponds to a linear constraint
@@ -348,12 +359,10 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
real(RP), allocatable :: bineq_loc(:) ! Bineq_LOC(Mineq)
real(RP), allocatable :: bvec(:) ! BVEC(M_LCON)
real(RP), allocatable :: chist_loc(:) ! CHIST_LOC(MAXCHIST)
-real(RP), allocatable :: conhist_loc(:, :) ! CONHIST_LOC(M_NLCON, MAXCONHIST)
-real(RP), allocatable :: constr_loc(:) ! CONSTR_LOC(M_NLCON)
+real(RP), allocatable :: conhist_loc(:, :) ! CONHIST_LOC(M, MAXCONHIST)
+real(RP), allocatable :: constr_loc(:) ! CONSTR_LOC(M)
real(RP), allocatable :: fhist_loc(:) ! FHIST_LOC(MAXFHIST)
real(RP), allocatable :: xhist_loc(:, :) ! XHIST_LOC(N, MAXXHIST)
-real(RP), allocatable :: xl_loc(:) ! XL_LOC(N)
-real(RP), allocatable :: xu_loc(:) ! XU_LOC(N)
! Sizes
if (present(bineq)) then
@@ -367,12 +376,12 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
meq = 0
end if
if (present(xl)) then
- mxl = int(count(xl > -REALMAX), kind(mxl))
+ mxl = int(count(xl > -BOUNDMAX), kind(mxl))
else
mxl = 0
end if
if (present(xu)) then
- mxu = int(count(xu < REALMAX), kind(mxu))
+ mxu = int(count(xu < BOUNDMAX), kind(mxu))
else
mxu = 0
end if
@@ -405,7 +414,16 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
if (present(xu)) then
call assert(size(xu) == n .or. size(xu) == 0, 'SIZE(XU) == N unless XU is empty', srname)
end if
- call assert(present(f0) .eqv. present(nlconstr0), 'F0 and NLCONSTR0 are both present or both absent', srname)
+
+ ! N.B.: If NLCONSTR0 is present, then F0 must be present, and we assume that F(X0) = F0 even if
+ ! F0 is NaN; if NLCONSTR0 is absent, then F0 must be either absent or NaN, both of which will
+ ! be interpreted as F(X0) is not provided.
+ if (present(nlconstr0)) then
+ call assert(present(f0), 'If NLCONSTR0 is present, then F0 is present', srname)
+ end if
+ if (present(f0)) then
+ call assert(is_nan(f0) .or. present(nlconstr0), 'If F0 is present and not NaN, then NLCONSTR0 is present', srname)
+ end if
end if
! Exit if the size of NLCONSTR0 is inconsistent with M_NLCON.
@@ -449,23 +467,25 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
beq_loc = beq
end if
-call safealloc(xl_loc, n) ! NOT removable even in F2003, as XL may be absent.
+xl_loc = -BOUNDMAX
if (present(xl)) then
- xl_loc = xl
-else
- xl_loc = -REALMAX
+ if (size(xl) > 0) then
+ xl_loc = xl
+ end if
end if
+xl_loc(trueloc(is_nan(xl_loc) .or. xl_loc < -BOUNDMAX)) = -BOUNDMAX
call safealloc(ixl, mxl)
-ixl = trueloc(xl_loc > -REALMAX)
+ixl = trueloc(xl_loc > -BOUNDMAX)
-call safealloc(xu_loc, n) ! NOT removable even in F2003, as XU may be absent.
+xu_loc = BOUNDMAX
if (present(xu)) then
- xu_loc = xu
-else
- xu_loc = REALMAX
+ if (size(xu) > 0) then
+ xu_loc = xu
+ end if
end if
+xu_loc(trueloc(is_nan(xu_loc) .or. xu_loc > BOUNDMAX)) = BOUNDMAX
call safealloc(ixu, mxu)
-ixu = trueloc(xu_loc < REALMAX)
+ixu = trueloc(xu_loc < BOUNDMAX)
! Wrap the linear and bound constraints into a single constraint: AMAT^T*X <= BVEC.
call get_lincon(Aeq_loc, Aineq_loc, beq_loc, bineq_loc, xl_loc, xu_loc, amat, bvec)
@@ -473,19 +493,25 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! Allocate memory for CONSTR_LOC.
call safealloc(constr_loc, m) ! NOT removable even in F2003!
-! If the user provides the function & nonlinear constraint value at X0, then set up [F, CONSTR_LOC]
-! to them. Otherwise, set [F, CONSTR_LOC] = [F(X0), CONSTR(X0)], so COBYLB only needs one interface.
-constr_loc(1:m - m_nlcon) = moderatec(matprod(x, amat) - bvec)
+! Set [F_LOC, CONSTR_LOC] to [F(X0), CONSTR(X0)] after evaluating the latter if needed. In this way,
+! COBYLB only needs one interface.
+! N.B.: Due to the preconditions above, there are two possibilities for F0 and NLCONSTR0.
+! If NLCONSTR0 is present, then F0 must be present, and we assume that F(X0) = F0 even if F0 is NaN.
+! If NLCONSTR0 is absent, then F0 must be either absent or NaN, both of which will be interpreted as
+! F(X0) is not provided and we have to evaluate F(X0) and NLCONSTR(X0) now.
+constr_loc(1:m - m_nlcon) = moderatec(matprod(x, amat) - bvec) ! Linear and bound constraints
+! Note that EVALUATE moderates the nonlinear constraint values. Thus we also moderate the
+! bound/linear constraint values here to make CSTRV consistent.
if (present(f0) .and. present(nlconstr0) .and. all(is_finite(x))) then
- f = f0
- constr_loc(m - m_nlcon + 1:m) = nlconstr0
+ f_loc = moderatef(f0)
+ constr_loc(m - m_nlcon + 1:m) = moderatec(nlconstr0)
else
x = moderatex(x)
- call evaluate(calcfc, x, f, constr_loc(m - m_nlcon + 1:m))
+ call evaluate(calcfc, x, f_loc, constr_loc(m - m_nlcon + 1:m)) ! Nonlinear constraints
! N.B.: Do NOT call FMSG, SAVEHIST, or SAVEFILT for the function/constraint evaluation at X0.
! They will be called during the initialization, which will read the function/constraint at X0.
end if
-cstrv_loc = maxval([ZERO, constr_loc])
+cstrv_loc = maximum([ZERO, constr_loc])
! If RHOBEG is present, then RHOBEG_LOC is a copy of RHOBEG; otherwise, RHOBEG_LOC takes the default
! value for RHOBEG, taking the value of RHOEND into account. Note that RHOEND is considered only if
@@ -509,7 +535,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -600,28 +626,36 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
!-------------------- Call COBYLB, which performs the real calculations. --------------------------!
-!call cobylb(calcfc_internal, iprint_loc, maxfilt_loc, maxfun_loc, ctol_loc, cweight_loc, eta1_loc, eta2_loc, &
-call cobylb(calcfc, iprint_loc, maxfilt_loc, maxfun_loc, amat, bvec, ctol_loc, cweight_loc, &
+if (present(callback_fcn)) then
+ call cobylb(calcfc, iprint_loc, maxfilt_loc, maxfun_loc, amat, bvec, ctol_loc, cweight_loc, &
+ & eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, gamma2_loc, rhobeg_loc, rhoend_loc, constr_loc, &
+ & f_loc, x, nf_loc, chist_loc, conhist_loc, cstrv_loc, fhist_loc, xhist_loc, info_loc, callback_fcn)
+else
+ call cobylb(calcfc, iprint_loc, maxfilt_loc, maxfun_loc, amat, bvec, ctol_loc, cweight_loc, &
& eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, gamma2_loc, rhobeg_loc, rhoend_loc, constr_loc, &
- & f, x, nf_loc, chist_loc, conhist_loc, cstrv_loc, fhist_loc, xhist_loc, info_loc)
+ & f_loc, x, nf_loc, chist_loc, conhist_loc, cstrv_loc, fhist_loc, xhist_loc, info_loc)
+end if
!--------------------------------------------------------------------------------------------------!
-! Deallocate variables not needed any more. Indeed, automatic allocation will take place at exit.
-deallocate (Aineq_loc, Aeq_loc, amat, bineq_loc, beq_loc, bvec, xl_loc, xu_loc)
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+deallocate (Aineq_loc, Aeq_loc, amat, bineq_loc, beq_loc, bvec)
! Write the outputs.
-! Copy CONSTR_LOC to NLCONSTR if needed.
-if (present(nlconstr)) then
- nlconstr = constr_loc(m - m_nlcon + 1:m)
+if (present(f)) then
+ f = f_loc
end if
-deallocate (constr_loc)
if (present(cstrv)) then
cstrv = cstrv_loc
end if
+if (present(nlconstr)) then
+ nlconstr = constr_loc(m - m_nlcon + 1:m)
+end if
+deallocate (constr_loc)
+
if (present(nf)) then
nf = nf_loc
end if
@@ -691,7 +725,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist) .or. present(chist) .or. present(nlchist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
! Postconditions
@@ -717,7 +751,7 @@ subroutine cobyla(calcfc, m_nlcon, x, f, &
call assert(.not. any(is_nan(nlchist) .or. is_posinf(nlchist)), 'NLCHIST does not contain NaN/+Inf', srname)
end if
if (present(fhist) .and. present(chist)) then
- call assert(.not. any(isbetter(fhist(1:nhist), chist(1:nhist), f, cstrv_loc, ctol_loc)), &
+ call assert(.not. any(isbetter(fhist(1:nhist), chist(1:nhist), f_loc, cstrv_loc, ctol_loc)), &
& 'No point in the history is better than X', srname)
end if
end if
@@ -739,7 +773,7 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, xl, xu, amat, bvec)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, REALMAX, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, BOUNDMAX, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : eye, trueloc
use, non_intrinsic :: memory_mod, only : safealloc
@@ -785,8 +819,8 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, xl, xu, amat, bvec)
!====================!
! Decide the number of nontrivial constraints.
-mxl = int(count(xl > -REALMAX), kind(mxl))
-mxu = int(count(xu < REALMAX), kind(mxu))
+mxl = int(count(xl > -BOUNDMAX), kind(mxl))
+mxu = int(count(xu < BOUNDMAX), kind(mxu))
meq = int(size(beq), kind(meq))
mineq = int(size(bineq), kind(mineq))
m_lcon = mxl + mxu + 2_IK * meq + mineq ! The final number of linear inequality constraints.
@@ -798,8 +832,8 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, xl, xu, amat, bvec)
call safealloc(bvec, m_lcon)
! Define the indices of the nontrivial bound constraints.
-ixl = trueloc(xl > -REALMAX)
-ixu = trueloc(xu < REALMAX)
+ixl = trueloc(xl > -BOUNDMAX)
+ixu = trueloc(xu < BOUNDMAX)
! Wrap the linear constraints.
! The bound constraint XL <= X <= XU is handled as two constraints -X <= -XL, X <= XU.
diff --git a/fortran/cobyla/cobylb.f90 b/fortran/cobyla/cobylb.f90
index a5e5541009..f64abb7db9 100644
--- a/fortran/cobyla/cobylb.f90
+++ b/fortran/cobyla/cobylb.f90
@@ -17,7 +17,7 @@ module cobylb_mod
!
! Started: July 2021
!
-! Last Modified: Monday, August 07, 2023 PM02:53:28
+! Last Modified: Wed 08 Apr 2026 06:38:14 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -29,33 +29,32 @@ module cobylb_mod
subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, eta1, eta2, ftarget, &
- & gamma1, gamma2, rhobeg, rhoend, constr, f, x, nf, chist, conhist, cstrv, fhist, xhist, info)
+ & gamma1, gamma2, rhobeg, rhoend, constr, f, x, nf, chist, conhist, cstrv, fhist, xhist, info, callback_fcn)
!--------------------------------------------------------------------------------------------------!
! This subroutine performs the actual calculations of COBYLA.
!
! IPRINT, MAXFILT, MAXFUN, MAXHIST, CTOL, CWEIGHT, ETA1, ETA2, FTARGET, GAMMA1, GAMMA2, RHOBEG,
-! RHOEND, X, NF, F, XHIST, FHIST, CHIST, CONHIST, CSTRV and INFO are identical to the corresponding
+! RHOEND, X, NF, F, XHIST, FHIST, CHIST, CONHIST, CSTRV, INFO and CALLBACK are identical to the corresponding
! arguments in subroutine COBYLA.
!--------------------------------------------------------------------------------------------------!
! Common modules
use, non_intrinsic :: checkexit_mod, only : checkexit
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, QUART, TENTH, EPS, REALMAX, &
- & DEBUGGING, MIN_MAXFILT
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TENTH, EPS, REALMAX, DEBUGGING, MIN_MAXFILT
use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: evaluate_mod, only : evaluate
+use, non_intrinsic :: evaluate_mod, only : evaluate, moderatec
use, non_intrinsic :: history_mod, only : savehist, rangehist
-use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf
-use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS, DAMAGING_ROUNDING
-use, non_intrinsic :: linalg_mod, only : inprod, matprod, norm
+use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf, is_finite
+use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS, DAMAGING_ROUNDING, CALLBACK_TERMINATE
+use, non_intrinsic :: linalg_mod, only : inprod, matprod, norm, maximum
use, non_intrinsic :: message_mod, only : retmsg, rhomsg, fmsg
-use, non_intrinsic :: pintrf_mod, only : OBJCON
+use, non_intrinsic :: pintrf_mod, only : OBJCON, CALLBACK
use, non_intrinsic :: ratio_mod, only : redrat
use, non_intrinsic :: redrho_mod, only : redrho
use, non_intrinsic :: selectx_mod, only : savefilt, selectx, isbetter
! Solver-specific modules
-use, non_intrinsic :: geometry_cobyla_mod, only : assess_geo, setdrop_geo, setdrop_tr, geostep
+use, non_intrinsic :: geometry_cobyla_mod, only : setdrop_tr, geostep
use, non_intrinsic :: initialize_cobyla_mod, only : initxfc, initfilt
use, non_intrinsic :: trustregion_cobyla_mod, only : trstlp, trrad
use, non_intrinsic :: update_cobyla_mod, only : updatexfc, updatepole
@@ -64,6 +63,7 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! Inputs
procedure(OBJCON) :: calcfc ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfilt
integer(IK), intent(in) :: maxfun
@@ -97,6 +97,7 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! Local variables
character(len=*), parameter :: solver = 'COBYLA'
character(len=*), parameter :: srname = 'COBYLB'
+integer(IK) :: j
integer(IK) :: jdrop_geo
integer(IK) :: jdrop_tr
integer(IK) :: kopt
@@ -119,6 +120,7 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
logical :: improve_geo
logical :: reduce_rho
logical :: shortd
+logical :: terminate
logical :: trfail
logical :: ximproved
real(RP) :: A(size(x), size(constr)) ! A contains the approximate gradient for the constraints
@@ -129,7 +131,9 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
real(RP) :: cpen ! Penalty parameter for constraint in merit function (PARMU in Powell's code)
real(RP) :: cval(size(x) + 1)
real(RP) :: d(size(x))
+real(RP) :: delbar
real(RP) :: delta
+real(RP) :: distsq(size(x) + 1)
real(RP) :: dnorm
real(RP) :: ffilt(size(cfilt))
real(RP) :: fval(size(x) + 1)
@@ -154,16 +158,6 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! 312--314 of Powell's cobylb.f code. Powell's code revises ACTREM to CVAL(N + 1) - CSTRV and PREREM
! to PREREC in this case, which is crucial for feasibility problems.
real(RP), parameter :: cpenmin = EPS
-! FACTOR_ALPHA, FACTOR_BETA, FACTOR_GAMMA, and FACTOR_DELTA are four factors that COBYLB uses
-! when managing the simplex. Note the following.
-! 1. FACTOR_ALPHA < FACTOR_GAMMA < 1 < FACTOR_DELTA <= FACTOR_BETA.
-! 2. FACTOR_DELTA has nothing to do with DELTA, which is the trust-region radius.
-! 3. FACTOR_GAMMA has nothing to do with GAMMA1 and GAMMA2, which are the contracting/expanding
-! factors for updating the trust-region radius DELTA.
-real(RP), parameter :: factor_alpha = QUART ! The factor alpha in the COBYLA paper
-real(RP), parameter :: factor_beta = 2.1_RP ! The factor beta in the COBYLA paper
-real(RP), parameter :: factor_delta = 1.1_RP ! The factor delta in the COBYLA paper
-real(RP), parameter :: factor_gamma = HALF ! The factor gamma in the COBYLA paper
! Sizes
m_lcon = int(size(bvec), kind(m_lcon))
@@ -178,10 +172,11 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! Preconditions
if (DEBUGGING) then
call assert(abs(iprint) <= 3, 'IPRINT is 0, 1, -1, 2, -2, 3, or -3', srname)
- call assert(m >= 0, 'M >= 0', srname)
+ call assert(m >= m_lcon .and. m_lcon >= 0, 'M >= M_LCON >= 0', srname)
call assert(n >= 1, 'N >= 1', srname)
call assert(maxfun >= n + 2, 'MAXFUN >= N + 2', srname)
call assert(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', srname)
+ call assert(all(is_finite(x)), 'X is finite', srname)
call assert(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', srname)
call assert(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', srname)
call assert(ctol >= 0, 'CTOL >= 0', srname)
@@ -196,9 +191,6 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
call assert(size(conhist, 1) == m .and. maxconhist * (maxconhist - maxhist) == 0, &
& 'SIZE(CONHIST, 1) == M, SIZE(CONHIST, 2) == 0 or MAXHIST', srname)
call assert(maxchist * (maxchist - maxhist) == 0, 'SIZE(CHIST) == 0 or MAXHIST', srname)
- call assert(factor_alpha > 0 .and. factor_alpha < factor_gamma .and. factor_gamma < 1, &
- & '0 < FACTOR_ALPHA < FACTOR_GAMMA < 1', srname)
- call assert(factor_beta >= factor_delta .and. factor_delta > 1, 'FACTOR_BETA >= FACTOR_DELTA > 1', srname)
end if
!====================!
@@ -210,8 +202,17 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! function value (regardless of the constraint violation), and SIM(:, 1:N) holds the displacements
! from the other vertices to SIM(:, N+1). FVAL, CONMAT, and CVAL hold the function values,
! constraint values, and constraint violations on the vertices in the order corresponding to SIM.
-call initxfc(calcfc_internal, iprint, maxfun, constr, ctol, f, ftarget, rhobeg, x, nf, chist, conhist, &
- & conmat, cval, fhist, fval, sim, simi, xhist, evaluated, subinfo)
+call initxfc(calcfc, iprint, maxfun, amat, bvec, constr, ctol, f, ftarget, rhobeg, x, nf, chist, &
+ & conhist, conmat, cval, fhist, fval, sim, simi, xhist, evaluated, subinfo)
+
+! Report the current best value, and check if user asks for early termination.
+terminate = .false.
+if (present(callback_fcn)) then
+ call callback_fcn(sim(:, n + 1), fval(n + 1), nf, 0_IK, cval(n + 1), conmat(m_lcon + 1:m, n + 1), terminate)
+ if (terminate) then
+ subinfo = CALLBACK_TERMINATE
+ end if
+end if
! Initialize the filter, including XFILT, FFILT, CONFILT, CFILT, and NFILT.
! N.B.: The filter is used only when selecting which iterate to return. It does not interfere with
@@ -290,10 +291,16 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! better than setting directly DELTA = MAX(NEW_DELTA, RHO).
gamma3 = max(ONE, min(0.75_RP * gamma2, 1.5_RP))
-! MAXTR is the maximal number of trust-region iterations. Each trust-region iteration takes 1 or 2
-! function evaluations unless the trust-region step is short or the trust-region subproblem solver
-! fails but the geometry step is not invoked. Thus the following MAXTR is unlikely to be reached.
-maxtr = max(maxfun, 2_IK * maxfun) ! MAX: precaution against overflow, which will make 2*MAXFUN < 0.
+! MAXTR is the maximal number of trust-region iterations. Here, we set it to HUGE(MAXTR) - 1 so that
+! the algorithm will not terminate due to MAXTR. However, this may not be allowed in other languages
+! such as MATLAB. In that case, we can set MAXTR to 10*MAXFUN, which is unlikely to reach because
+! each trust-region iteration takes 1 or 2 function evaluations unless the trust-region step is short
+! or fails to reduce the trust-region model but the geometry step is not invoked.
+! N.B.: Do NOT set MAXTR to HUGE(MAXTR), as it may cause overflow and infinite cycling in the DO
+! loop. See
+! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+maxtr = huge(maxtr) - 1_IK !!MATLAB: maxtr = 10 * maxfun;
info = MAXTR_REACHED
! Begin the iterative procedure.
@@ -322,11 +329,16 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
exit ! Better action to take? Geometry step, or simply continue?
end if
- ! Does the interpolation set have acceptable geometry? It affects IMPROVE_GEO and REDUCE_RHO.
- adequate_geo = assess_geo(delta, factor_alpha, factor_beta, sim, simi)
+ ! Does the interpolation set have adequate geometry? It affects IMPROVE_GEO and REDUCE_RHO.
+ adequate_geo = all(sum(sim(:, 1:n)**2, dim=1) <= 4.0_RP * delta**2)
! Calculate the linear approximations to the objective and constraint functions.
! N.B.: TRSTLP accesses A mostly by columns, so it is more reasonable to save A instead of A^T.
+ ! Zaikun 2023108: According to a test on 2023108, calculating G and A(:, M_LCON+1:M) by solving
+ ! the linear systems SIM^T*G = FVAL(1:N)-FVAL(N+1) and SIM^T*A = CONMAT(:, 1:N)-CONMAT(:, N+1)
+ ! does not seem to improve or worsen the performance of COBYLA in terms of the number of function
+ ! evaluations. The system was solved by SOLVE in LINALG_MOD based on a QR factorization of SIM
+ ! (not necessarily a good algorithm). No preconditioning or scaling was used.
g = matprod(fval(1:n) - fval(n + 1), simi)
A(:, 1:m_lcon) = amat
A(:, m_lcon + 1:m) = transpose(matprod(conmat(m_lcon + 1:m, 1:n) - spread(conmat(m_lcon + 1:m, n + 1), dim=2, ncopies=n), simi))
@@ -341,7 +353,7 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! TENTH seems to work better than HALF or QUART, especially for linearly constrained problems.
! Note that LINCOA has a slightly more sophisticated way of defining SHORTD, taking into account
! whether D causes a change to the active set. Should we try the same here?
- shortd = (dnorm < TENTH * rho)
+ shortd = (dnorm <= TENTH * rho) ! `<=` works better than `<` in case of underflow.
! Predict the change to F (PREREF) and to the constraint violation (PREREC) due to D.
! We have the following in precise arithmetic. They may fail to hold due to rounding errors.
@@ -351,12 +363,12 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! 2. PREREF may be negative or 0, but it should be positive when PREREC = 0 and SHORTD is FALSE.
! 3. Due to 2, in theory, MAXIMUM([PREREC, PREREF]) > 0 if SHORTD is FALSE.
preref = -inprod(d, g) ! Can be negative.
- prerec = cval(n + 1) - maxval([conmat(:, n + 1) + matprod(d, A), ZERO])
+ prerec = cval(n + 1) - maximum([ZERO, conmat(:, n + 1) + matprod(d, A)])
! Evaluate PREREM, which is the predicted reduction in the merit function.
! In theory, PREREM >= 0 and it is 0 iff CPEN = 0 = PREREF. This may not be true numerically.
prerem = preref + cpen * prerec
- trfail = (.not. prerem > 1.0E-5_RP * min(cpen, ONE) * rho**2) ! PREREM is tiny/negative or NaN.
+ trfail = (.not. prerem > 1.0E-6 * min(cpen, ONE) * rho) ! PREREM is tiny/negative or NaN.
if (shortd .or. trfail) then
! Reduce DELTA if D is short or D fails to render PREREM > 0. The latter can happen due to
@@ -366,19 +378,35 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
delta = rho ! Set DELTA to RHO when it is close to or below.
end if
else
+ ! Calculate the next value of the objective and constraint functions.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective and constraints at X, assuming them to have the values at the closest point.
+ ! N.B.: If this happens, do NOT include X into the filter, as F and CONSTR are inaccurate.
x = sim(:, n + 1) + d
-
- ! Evaluate the objective and constraints at X, taking care of possible Inf/NaN values.
- call evaluate(calcfc_internal, x, f, constr)
- cstrv = maxval([ZERO, constr])
- nf = nf + 1_IK
+ distsq(n + 1) = sum((x - sim(:, n + 1))**2)
+ distsq(1:n) = [(sum((x - (sim(:, n + 1) + sim(:, j)))**2), j=1, n)] ! Implied do-loop
+ !!MATLAB: distsq(1:n) = sum((x - (sim(:,1:n) + sim(:, n+1)))**2, 1) % Implicit expansion
+ j = int(minloc(distsq, dim=1), kind(j))
+ if (distsq(j) <= (1.0E-4 * rhoend)**2) then
+ f = fval(j)
+ constr = conmat(:, j)
+ cstrv = cval(j)
+ else
+ ! Evaluate the objective and constraints at X, taking care of possible Inf/NaN values.
+ constr(1:m_lcon) = moderatec(matprod(x, amat) - bvec) ! Linear constraints
+ call evaluate(calcfc, x, f, constr(m_lcon + 1:m)) ! Nonlinear constraints
+ ! Note that EVALUATE moderates the nonlinear constraint values. Thus we also moderate the
+ ! linear constraint values here to make CSTRV consistent.
+ cstrv = maximum([ZERO, constr])
+ nf = nf + 1_IK
+ ! Save X, F, CONSTR, CSTRV into the history.
+ call savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+ ! Save X, F, CONSTR, CSTRV into the filter.
+ call savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
+ end if
! Print a message about the function/constraint evaluation according to IPRINT.
call fmsg(solver, 'Trust region', iprint, nf, delta, f, x, cstrv, constr)
- ! Save X, F, CONSTR, CSTRV into the history.
- call savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
- ! Save X, F, CONSTR, CSTRV into the filter.
- call savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
! Evaluate ACTREM, which is the actual reduction in the merit function.
actrem = (fval(n + 1) + cpen * cval(n + 1)) - (f + cpen * cstrv)
@@ -437,8 +465,8 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
!----------------------------------------------------------------------------------------------!
- ! Before the next trust-region iteration, we possibly improve the geometry of simplex or
- ! reduces RHO according to IMPROVE_GEO and REDUCE_RHO. Now we decide these indicators.
+ ! Before the next trust-region iteration, we possibly improve the geometry of simplex or reduce
+ ! RHO according to IMPROVE_GEO and REDUCE_RHO. Now we decide these indicators.
! N.B.: We must ensure that the algorithm does not set IMPROVE_GEO = TRUE at infinitely many
! consecutive iterations without moving SIM(:, N+1) or reducing RHO. Otherwise, the algorithm
! will get stuck in repetitive invocations of GEOSTEP. This is ensured by the following facts.
@@ -494,60 +522,86 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! two blocks are exchangeable: IF (IMPROVE_GEO) ... END IF and IF (REDUCE_RHO) ... END IF.
! Improve the geometry of the simplex by removing a point and adding a new one.
- ! If the current interpolation set has acceptable geometry, then we skip the geometry step.
+ ! If the current interpolation set has adequate geometry, then we skip the geometry step.
! The code has a small difference from Powell's original code here: If the current geometry
- ! is acceptable, then we will continue with a new trust-region iteration; however, at the
+ ! is adequate, then we will continue with a new trust-region iteration; however, at the
! beginning of the iteration, CPEN may be updated, which may alter the pole point SIM(:, N+1)
! by UPDATEPOLE; the quality of the interpolation point depends on SIM(:, N + 1), meaning
! that the same interpolation set may have good or bad geometry with respect to different
! "poles"; if the geometry turns out bad with the new pole, the original COBYLA code will
- ! take a geometry step, but our code here will NOT do it but continue to take a trust-region
+ ! take a geometry step, yet our code here will NOT do it but continue to take a trust-region
! step. The argument is this: even if the geometry step is not skipped in the first place, the
! geometry may turn out bad again after the pole is altered due to an update to CPEN; should
! we take another geometry step in that case? If no, why should we do it here? Indeed, this
! distinction makes no practical difference for CUTEst problems with at most 100 variables
! and 5000 constraints, while the algorithm framework is simplified.
- if (improve_geo .and. .not. assess_geo(delta, factor_alpha, factor_beta, sim, simi)) then
+ if (improve_geo .and. .not. all(sum(sim(:, 1:n)**2, dim=1) <= 4.0_RP * delta**2)) then
! Before the geometry step, UPDATEPOLE has been called either implicitly by UPDATEXFC or
! explicitly after CPEN is updated, so that SIM(:, N + 1) is the optimal vertex.
! Decide a vertex to drop from the simplex. It will be replaced with SIM(:, N + 1) + D to
- ! improve acceptability of the simplex. See equations (15) and (16) of the COBYLA paper.
- ! N.B.: COBYLA never sets JDROP_GEO = N + 1.
- jdrop_geo = setdrop_geo(delta, factor_alpha, factor_beta, sim, simi)
-
- ! The following JDROP_GEO comes from UOBYQA/NEWUOA/BOBYQA/LINCOA. It performs poorly!
- !!jdrop_geo = maxloc(sum(sim(:, 1:n)**2, dim=1), dim=1)
-
- ! JDROP_GEO is between 1 and N unless SIM and SIMI contain NaN, which should not happen
- ! at this point unless there is a bug. Nevertheless, for robustness, we include the
- ! following instruction to exit when JDROP_GEO == 0 (if JDROP_GEO does become 0, then
- ! memory error will occur if we continue, as JDROP_GEO will be used as an index of arrays.)
- if (jdrop_geo == 0) then
- info = DAMAGING_ROUNDING
- exit
- end if
+ ! improve the geometry of the simplex.
+ ! N.B.: 1. COBYLA never sets JDROP_GEO = N + 1.
+ ! 2. The following JDROP_GEO comes from UOBYQA/NEWUOA/BOBYQA/LINCOA.
+ ! 3. In Powell's original algorithm, the geometry of the simplex is considered acceptable
+ ! iff the distance between any vertex and the pole is at most 2.1*DELTA, and the distance
+ ! between any vertex and the opposite face of the simplex is at least 0.25*DELTA, as
+ ! specified in (14) of the COBYLA paper. Correspondingly, JDROP_GEO is set to the index of
+ ! the vertex with the largest distance to the pole provided that the distance is larger than
+ ! 2.1*DELTA, or the vertex with the smallest distance to the opposite face of the simplex,
+ ! in which case the distance must be less than 0.25*DELTA, as the current simplex does not
+ ! have acceptable geometry (see (15)--(16) of the COBYLA paper). Once JDROP_GEO is set, the
+ ! algorithm replaces SIM(:, JDROP_GEO) with D specified in (17) of the COBYLA paper, which
+ ! is orthogonal to the face opposite to SIM(:, JDROP_GEO) and has a length of 0.5*DELTA,
+ ! intending to improve the geometry of the simplex as per (14).
+ ! 4. Powell's geometry-improving procedure outlined above has an intrinsic flaw: it may lead
+ ! to infinite cycling, as was observed in a test on 20240320. In this test, the geometry-
+ ! improving point introduced in the previous iteration was replaced with the trust-region
+ ! trial point in the current iteration, which was then replaced with the same geometry-
+ ! improving point in the next iteration, and so on. In this process, the simplex alternated
+ ! between two configurations, neither of which had acceptable geometry. Thus RHO was never
+ ! reduced, leading to infinite cycling. (N.B.: Our implementation uses DELTA as the trust
+ ! region radius, with RHO being its lower bound. When the infinite cycling occurred in this
+ ! test, DELTA = RHO and it could not be reduced due to the requirement that DELTA >= RHO.)
+ jdrop_geo = int(maxloc(sum(sim(:, 1:n)**2, dim=1), dim=1), kind(jdrop_geo))
! Calculate the geometry step D.
- ! In NEWUOA, GEOSTEP takes DELBAR = MAX(MIN(TENTH * SQRT(MAXVAL(DISTSQ)), HALF * DELTA), RHO)
- ! rather than DELTA. This should not be done here, because D should improve the geometry of
- ! the simplex when SIM(:, JDROP) is replaced with D; the quality of the geometry is defined
- ! by DELTA instead of DELBAR as in (14) of the COBYLA paper. See GEOSTEP for more detail.
- d = geostep(jdrop_geo, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamma, simi)
+ delbar = HALF * delta
+ d = geostep(jdrop_geo, amat, bvec, conmat, cpen, cval, delbar, fval, simi)
+
+ ! Calculate the next value of the objective and constraint functions.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective and constraints at X, assuming them to have the values at the closest point.
+ ! N.B.:
+ ! 1. If this happens, do NOT include X into the filter, as F and CONSTR are inaccurate.
+ ! 2. In precise arithmetic, the geometry improving step ensures that the distance between X
+ ! and any interpolation point is at least DELBAR, yet X may be close to them due to
+ ! rounding. In an experiment with single precision on 20240317, X = SIM(:, N+1) occurred.
x = sim(:, n + 1) + d
-
- ! Evaluate the objective and constraints at X, taking care of possible Inf/NaN values.
- call evaluate(calcfc_internal, x, f, constr)
- cstrv = maxval([ZERO, constr])
- nf = nf + 1_IK
+ distsq(n + 1) = sum((x - sim(:, n + 1))**2)
+ distsq(1:n) = [(sum((x - (sim(:, n + 1) + sim(:, j)))**2), j=1, n)] ! Implied do-loop
+ !!MATLAB: distsq(1:n) = sum((x - (sim(:,1:n) + sim(:, n+1)))**2, 1) % Implicit expansion
+ j = int(minloc(distsq, dim=1), kind(j))
+ if (distsq(j) <= (1.0E-4 * rhoend)**2) then
+ f = fval(j)
+ constr = conmat(:, j)
+ cstrv = cval(j)
+ else
+ ! Evaluate the objective and constraints at X, taking care of possible Inf/NaN values.
+ constr(1:m_lcon) = moderatec(matprod(x, amat) - bvec) ! Linear constraints
+ call evaluate(calcfc, x, f, constr(m_lcon + 1:m)) ! Nonlinear constraints
+ ! Note that EVALUATE moderates the nonlinear constraint values. Thus we also moderate the
+ ! linear constraint values here to make CSTRV consistent.
+ cstrv = maximum([ZERO, constr])
+ nf = nf + 1_IK
+ ! Save X, F, CONSTR, CSTRV into the history.
+ call savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+ ! Save X, F, CONSTR, CSTRV into the filter.
+ call savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
+ end if
! Print a message about the function/constraint evaluation according to IPRINT.
call fmsg(solver, 'Geometry', iprint, nf, delta, f, x, cstrv, constr)
- ! Save X, F, CONSTR, CSTRV into the history.
- call savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
- ! Save X, F, CONSTR, CSTRV into the filter.
- call savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
-
! Update SIM, SIMI, FVAL, CONMAT, and CVAL so that SIM(:, JDROP_GEO) is replaced with D.
call updatexfc(jdrop_geo, constr, cpen, cstrv, d, f, conmat, cval, fval, sim, simi, subinfo)
! Check whether to exit due to damaging rounding in UPDATEXFC.
@@ -587,24 +641,34 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
end if
end if ! End of IF (REDUCE_RHO). The procedure of reducing RHO ends.
+ ! Report the current best value, and check if user asks for early termination.
+ if (present(callback_fcn)) then
+ call callback_fcn(sim(:, n + 1), fval(n + 1), nf, tr, cval(n + 1), conmat(m_lcon + 1:m, n + 1), terminate)
+ if (terminate) then
+ info = CALLBACK_TERMINATE
+ exit
+ end if
+ end if
+
end do ! End of DO TR = 1, MAXTR. The iterative procedure ends.
! Return from the calculation, after trying the last trust-region step if it has not been tried yet.
-if (info == SMALL_TR_RADIUS .and. shortd .and. nf < maxfun) then
- ! Zaikun 20230615: UPDATEXFC or UPDATEPOLE is not called since the last trust-region step. Hence
- ! SIM(:, N + 1) remains unchanged. Otherwise, SIM(:, N + 1) + D would not make sense.
- x = sim(:, n + 1) + d
- call evaluate(calcfc_internal, x, f, constr)
- cstrv = maxval([ZERO, constr])
+! Ensure that D has not been updated after SHORTD == TRUE occurred, or the code below is incorrect.
+x = sim(:, n + 1) + d
+if (info == SMALL_TR_RADIUS .and. shortd .and. norm(x - sim(:, n + 1)) > 1.0E-3_RP * rhoend .and. nf < maxfun) then
+ constr(1:m_lcon) = moderatec(matprod(x, amat) - bvec) ! Linear constraints
+ call evaluate(calcfc, x, f, constr(m_lcon + 1:m)) ! Nonlinear constraints
+ ! Note that EVALUATE moderates the nonlinear constraint values. Thus we also moderate the linear
+ ! constraint values here to make CSTRV consistent.
+ cstrv = maximum([ZERO, constr])
nf = nf + 1_IK
- ! Print a message about the function evaluation according to IPRINT.
- ! Zaikun 20230512: DELTA has been updated. RHO is only indicative here. TO BE IMPROVED.
- ! Print a message about the function/constraint evaluation according to IPRINT.
- call fmsg(solver, 'Trust region', iprint, nf, rho, f, x, cstrv, constr)
! Save X, F, CONSTR, CSTRV into the history.
call savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
! Save X, F, CONSTR, CSTRV into the filter.
call savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
+ ! Print a message about the function/constraint evaluation according to IPRINT.
+ ! Zaikun 20230512: DELTA has been updated. RHO is only indicative here. TO BE IMPROVED.
+ call fmsg(solver, 'Trust region', iprint, nf, rho, f, x, cstrv, constr)
end if
! Return the best calculated values of the variables.
@@ -620,7 +684,6 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
! Print a return message according to IPRINT.
call retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
-
!====================!
! Calculation ends !
!====================!
@@ -648,25 +711,6 @@ subroutine cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, et
& 'No point in the history is better than X', srname)
end if
-
-contains
-
-
-subroutine calcfc_internal(x_internal, f_internal, constr_internal)
-!--------------------------------------------------------------------------------------------------!
-! This internal subroutine evaluates the objective function and ALL the constraints.
-! In MATLAB/Python/R/Julia, this can be implemented as a lambda function / anonymous function.
-!--------------------------------------------------------------------------------------------------!b
-implicit none
-! Inputs
-real(RP), intent(in) :: x_internal(:)
-! Outputs
-real(RP), intent(out) :: f_internal
-real(RP), intent(out) :: constr_internal(:)
-constr_internal(1:m_lcon) = matprod(x_internal, amat) - bvec
-call calcfc(x_internal, f_internal, constr_internal(m_lcon + 1:m))
-end subroutine calcfc_internal
-
end subroutine cobylb
@@ -681,7 +725,7 @@ function getcpen(amat, bvec, conmat_in, cpen_in, cval_in, delta, fval_in, rho, s
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite, is_posinf, is_nan
use, non_intrinsic :: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
-use, non_intrinsic :: linalg_mod, only : matprod, inprod, isinv
+use, non_intrinsic :: linalg_mod, only : matprod, inprod, isinv, maximum
! Solver-specific modules
use, non_intrinsic :: trustregion_cobyla_mod, only : trstlp
@@ -742,7 +786,7 @@ function getcpen(amat, bvec, conmat_in, cpen_in, cval_in, delta, fval_in, rho, s
& 'SIZE(FVAL) == N+1 and FVAL does not contain NaN/+Inf', srname)
call assert(size(sim_in, 1) == n .and. size(sim_in, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim_in)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim_in(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim_in(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi_in, 1) == n .and. size(simi_in, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi_in)), 'SIMI is finite', srname)
call assert(isinv(sim_in(:, 1:n), simi_in, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
@@ -796,7 +840,7 @@ function getcpen(amat, bvec, conmat_in, cpen_in, cval_in, delta, fval_in, rho, s
! Predict the change to F (PREREF) and to the constraint violation (PREREC) due to D.
preref = -inprod(d, g) ! Can be negative.
- prerec = cval(n + 1) - maxval([conmat(:, n + 1) + matprod(d, A), ZERO])
+ prerec = cval(n + 1) - maximum([ZERO, conmat(:, n + 1) + matprod(d, A)])
if (.not. (prerec > 0 .and. preref < 0)) then ! PREREC <= 0 or PREREF >= 0 or either is NaN.
exit
@@ -819,10 +863,10 @@ function getcpen(amat, bvec, conmat_in, cpen_in, cval_in, delta, fval_in, rho, s
! Postconditions
if (DEBUGGING) then
- call assert(cpen >= cpen_in .and. cpen > 0, 'CPEN >= CPEN_IN and CPEN > 0', srname)
+ call assert(cpen >= cpen_in .and. cpen > 0 .and. cpen <= REALMAX, 'CPEN >= CPEN_IN, CPEN > 0, and CPEN <= REALMAX', srname)
call assert(preref + cpen * prerec > 0 .or. info == DAMAGING_ROUNDING .or. &
- & .not. (prerec >= 0 .and. max(prerec, preref) > 0) .or. .not. is_finite(preref), &
- & 'PREREF + CPEN*PREREC > 0 unless D is short or the rounding is damaging', srname)
+ & .not. (prerec >= 0 .and. max(prerec, preref) > 0) .or. .not. is_finite(preref) .or. &
+ cpen >= REALMAX, 'PREREF + CPEN*PREREC > 0 unless the rounding is damaging', srname)
end if
end function getcpen
diff --git a/fortran/cobyla/flint b/fortran/cobyla/flint
index 764125c9f1..75b810d749 120000
--- a/fortran/cobyla/flint
+++ b/fortran/cobyla/flint
@@ -1 +1 @@
-../common/flint
\ No newline at end of file
+../tests/tools/flint
\ No newline at end of file
diff --git a/fortran/cobyla/geometry.f90 b/fortran/cobyla/geometry.f90
index e860d72b0a..dcb5810454 100644
--- a/fortran/cobyla/geometry.f90
+++ b/fortran/cobyla/geometry.f90
@@ -8,86 +8,17 @@ module geometry_cobyla_mod
!
! Started: July 2021
!
-! Last Modified: Monday, August 07, 2023 AM03:54:43
+! Last Modified: Sunday, April 21, 2024 PM03:25:55
!--------------------------------------------------------------------------------------------------!
implicit none
private
-public :: assess_geo, setdrop_geo, setdrop_tr, geostep
+public :: setdrop_tr, geostep
contains
-function assess_geo(delta, factor_alpha, factor_beta, sim, simi) result(adequate_geo)
-!--------------------------------------------------------------------------------------------------!
-! This function checks if an interpolation set has acceptable geometry as (14) of the COBYLA paper.
-!--------------------------------------------------------------------------------------------------!
-
-! Common modules
-use, non_intrinsic :: consts_mod, only : IK, RP, ONE, TENTH, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite
-use, non_intrinsic :: linalg_mod, only : isinv
-
-implicit none
-
-! Inputs
-real(RP), intent(in) :: delta
-real(RP), intent(in) :: factor_alpha
-real(RP), intent(in) :: factor_beta
-real(RP), intent(in) :: sim(:, :) ! SIM(N, N+1)
-real(RP), intent(in) :: simi(:, :) ! SIMI(N, N)
-
-! Outputs
-logical :: adequate_geo
-
-! Local variables
-character(len=*), parameter :: srname = 'ASSESS_GEO'
-integer(IK) :: n
-real(RP) :: veta(size(sim, 1))
-real(RP) :: vsig(size(sim, 1))
-real(RP), parameter :: itol = TENTH
-
-! Sizes
-n = int(size(sim, 1), kind(n))
-
-! Preconditions
-if (DEBUGGING) then
- call assert(n >= 1, 'N >= 1', srname)
- call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
- call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
- call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
- call assert(all(is_finite(simi)), 'SIMI is finite', srname)
- call assert(isinv(sim(:, 1:n), simi, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
- call assert(delta > 0, 'DELTA > 0', srname)
- call assert(factor_alpha > 0 .and. factor_alpha < 1, '0 < FACTOR_ALPHA < 1', srname)
- call assert(factor_beta > 1, 'FACTOR_BETA > 1', srname)
-end if
-
-!====================!
-! Calculation starts !
-!====================!
-
-! Calculate the values of sigma and eta.
-! VETA(J) (1 <= J <= N) is the distance between vertices J and 0 (the best vertex) of the simplex.
-! VSIG(J) is the distance from vertex J to its opposite face of the simplex. Thus VSIG <= VETA.
-! N.B.: What about the distance from vertex N+1 to the its opposite face? Consider the simplex
-! {V_{N+1}, V_{N+1} + L*e_1, ..., V_{N+1} + L*e_N}, where V_{N+1} is vertex N+1, namely the current
-! "best" point, [e_1, ..., e_n] is an orthogonal matrix, and L is a constant in the order of DELTA.
-! This simplex is optimal in the sense that the interpolation system has the minimal condition
-! number, i.e., one. For this simplex, the distance from V_{N+1} to its opposite face is L/SQRT{N}.
-vsig = ONE / sqrt(sum(simi**2, dim=2))
-veta = sqrt(sum(sim(:, 1:n)**2, dim=1))
-adequate_geo = all(vsig >= factor_alpha * delta) .and. all(veta <= factor_beta * delta)
-
-!====================!
-! Calculation ends !
-!====================!
-end function assess_geo
-
-
function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
!--------------------------------------------------------------------------------------------------!
! This subroutine finds (the index) of a current interpolation point to be replaced with the
@@ -101,7 +32,7 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
! Common modules
use, non_intrinsic :: consts_mod, only : IK, RP, ZERO, ONE, TENTH, DEBUGGING
-use, non_intrinsic :: linalg_mod, only : matprod, isinv
+use, non_intrinsic :: linalg_mod, only : matprod, isinv, trueloc
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: debug_mod, only : assert
@@ -140,7 +71,7 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
call assert(delta >= rho .and. rho > 0, 'DELTA >= RHO > 0', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
call assert(isinv(sim(:, 1:n), simi, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
@@ -178,7 +109,8 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
!vsig = ONE / sqrt(sum(simi**2, dim=2))
!sigbar = abs(simid) * vsig
!
-!! The following JDROP will overwrite the previous one if its premise holds.
+!! The following JDROP will overwrite the previous one if its premise holds. FACTOR_DELTA = 1.1
+!! and FACTOR_ALPHA = 0.25.
!mask = (veta > factor_delta * delta .and. (sigbar >= factor_alpha * delta .or. sigbar >= vsig))
!if (any(mask)) then
! jdrop = int(maxloc(veta, mask=mask, dim=1), kind(jdrop))
@@ -236,15 +168,19 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
score(n + 1) = -ONE
end if
+! SCORE(J) is NaN implies SIMID(J) is NaN, but we want ABS(SIMID) to be big. So we exclude such J.
+score(trueloc(is_nan(score))) = -ONE
+
+jdrop = 0
! The following IF works a bit better than `IF (ANY(SCORE > 1) .OR. ANY(SCORE > 0) .AND. XIMPROVED)`
! from Powell's UOBYQA and NEWUOA code.
if (any(score > 0)) then ! Powell's BOBYQA and LINCOA code
- jdrop = int(maxloc(score, mask=(.not. is_nan(score)), dim=1), kind(jdrop))
- !!MATLAB: [~, jdrop] = max(score, [], 'omitnan');
-elseif (ximproved) then
+ jdrop = int(maxloc(score, dim=1), kind(jdrop))
+ !!MATLAB: [~, jdrop] = max(score);
+end if
+
+if ((ximproved .and. jdrop == 0) .or. jdrop < 0) then ! JDROP < 0 is impossible in theory.
jdrop = int(maxloc(distsq, dim=1), kind(jdrop))
-else
- jdrop = 0 ! We arrive here when XIMPROVED = FALSE and no entry of SCORE is positive.
end if
!====================!
@@ -254,7 +190,7 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
! Postconditions
if (DEBUGGING) then
call assert(jdrop >= 0 .and. jdrop <= n + 1, '0 <= JDROP <= N+1', srname)
- call assert(jdrop <= n .or. ximproved, 'JDROP >= 1 unless IMPROVEX = TRUE', srname)
+ call assert(jdrop <= n .or. ximproved, 'JDROP <= n unless IMPROVEX = TRUE', srname)
call assert(jdrop >= 1 .or. .not. ximproved, 'JDROP >= 1 unless IMPROVEX = FALSE', srname)
! JDROP >= 1 when XIMPROVED = TRUE unless NaN occurs in DISTSQ, which should not happen if the
! starting point does not contain NaN and the trust-region/geometry steps never contain NaN.
@@ -263,105 +199,17 @@ function setdrop_tr(ximproved, d, delta, rho, sim, simi) result(jdrop)
end function setdrop_tr
-function setdrop_geo(delta, factor_alpha, factor_beta, sim, simi) result(jdrop)
-!--------------------------------------------------------------------------------------------------!
-! This subroutine finds (the index) of a current interpolation point to be replaced with a
-! geometry-improving point. See (15)--(16) of the COBYLA paper.
-! N.B.: COBYLA never sets JDROP = N + 1.
-!--------------------------------------------------------------------------------------------------!
-
-! Common modules
-use, non_intrinsic :: consts_mod, only : IK, RP, ONE, TENTH, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-use, non_intrinsic :: linalg_mod, only : isinv
-
-implicit none
-
-! Inputs
-real(RP), intent(in) :: delta
-real(RP), intent(in) :: factor_alpha
-real(RP), intent(in) :: factor_beta
-real(RP), intent(in) :: sim(:, :) ! SIM(N, N+1)
-real(RP), intent(in) :: simi(:, :) ! SIMI(N, N)
-
-! Outputs
-integer(IK) :: jdrop
-
-! Local variables
-character(len=*), parameter :: srname = 'SETDROP_GEO'
-integer(IK) :: n
-real(RP) :: veta(size(sim, 1))
-real(RP) :: vsig(size(sim, 1))
-real(RP), parameter :: itol = TENTH
-
-! Sizes
-n = int(size(sim, 1), kind(n))
-
-! Preconditions
-if (DEBUGGING) then
- call assert(n >= 1, 'N >= 1', srname)
- call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
- call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
- call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
- call assert(all(is_finite(simi)), 'SIMI is finite', srname)
- call assert(isinv(sim(:, 1:n), simi, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
- call assert(factor_alpha > 0 .and. factor_alpha < 1, '0 < FACTOR_ALPHA < 1', srname)
- call assert(factor_beta > 1, 'FACTOR_BETA > 1', srname)
- call assert(.not. assess_geo(delta, factor_alpha, factor_beta, sim, simi), &
- & 'The geometry is not adequate when '//srname//' is called', srname)
-end if
-
-!====================!
-! Calculation starts !
-!====================!
-
-! Calculate the values of sigma and eta.
-! VSIG(J) (J=1, .., N) is the Euclidean distance from vertex J to the opposite face of the simplex.
-vsig = ONE / sqrt(sum(simi**2, dim=2))
-veta = sqrt(sum(sim(:, 1:n)**2, dim=1))
-
-! Decide which vertex to drop from the simplex. It will be replaced with a new point to improve the
-! acceptability of the simplex. See equations (15) and (16) of the COBYLA paper.
-if (any(veta > factor_beta * delta)) then
- jdrop = int(maxloc(veta, mask=(.not. is_nan(veta)), dim=1), kind(jdrop))
- !!MATLAB: [~, jdrop] = max(veta, [], 'omitnan');
-elseif (any(vsig < factor_alpha * delta)) then
- jdrop = int(minloc(vsig, mask=(.not. is_nan(vsig)), dim=1), kind(jdrop))
- !!MATLAB: [~, jdrop] = min(vsig, [], 'omitnan');
-else
- ! We arrive here if VSIG and VETA are all NaN, which can happen due to NaN in SIM and SIMI,
- ! which should not happen unless there is a bug.
- jdrop = 0
-end if
-
-! Zaikun 20230202: What if we consider VETA and VSIG together? The following attempts do not work well.
-! !jdrop = maxloc(sum(sim(:, 1:n)**2, dim=1) * sum(simi**2, dim=2)) ! Condition number
-! !jdrop = maxloc(sum(sim(:, 1:n)**2, dim=1)**2 * sum(simi**2, dim=2)) ! Condition number times distance
-
-!====================!
-! Calculation ends !
-!====================!
-
-!Postconditions
-if (DEBUGGING) then
- call assert(jdrop >= 1 .and. jdrop <= n, '1 <= JDROP <= N', srname)
-end if
-end function setdrop_geo
-
-
-function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamma, simi) result(d)
+function geostep(jdrop, amat, bvec, conmat, cpen, cval, delbar, fval, simi) result(d)
!--------------------------------------------------------------------------------------------------!
! This function calculates a geometry step so that the geometry of the interpolation set is improved
! when SIM(:, JDRO_GEO) is replaced with SIM(:, N+1) + D. See (15)--(17) of the COBYLA paper.
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : IK, RP, ZERO, ONE, DEBUGGING
+use, non_intrinsic :: consts_mod, only : IK, RP, ZERO, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
-use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm
+use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, maximum
implicit none
@@ -372,8 +220,7 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
real(RP), intent(in) :: conmat(:, :) ! CONMAT(M, N+1)
real(RP), intent(in) :: cpen
real(RP), intent(in) :: cval(:) ! CVAL(N+1)
-real(RP), intent(in) :: delta
-real(RP), intent(in) :: factor_gamma
+real(RP), intent(in) :: delbar
real(RP), intent(in) :: fval(:) ! FVAL(N+1)
real(RP), intent(in) :: simi(:, :) ! SIMI(N, N)
@@ -389,7 +236,6 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
real(RP) :: cvnd
real(RP) :: cvpd
real(RP) :: g(size(simi, 1))
-real(RP) :: vsigj
! Sizes
m_lcon = int(size(bvec), kind(m_lcon))
@@ -398,9 +244,9 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
! Preconditions
if (DEBUGGING) then
- call assert(m >= 0, 'M >= 0', srname)
+ call assert(m >= m_lcon .and. m >= 0, 'M >= 0', srname)
call assert(n >= 1, 'N >= 1', srname)
- call assert(delta > 0, 'DELTA > 0', srname)
+ call assert(delbar > 0, 'DELBAR > 0', srname)
call assert(cpen > 0, 'CPEN > 0', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
@@ -411,7 +257,6 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
call assert(size(cval) == n + 1 .and. .not. any(cval < 0 .or. is_nan(cval) .or. is_posinf(cval)), &
& 'SIZE(CVAL) == NPT and CVAL does not contain negative NaN/+Inf', srname)
call assert(jdrop >= 1 .and. jdrop <= n, '1 <= JDROP <= N', srname)
- call assert(factor_gamma > 0 .and. factor_gamma < 1, '0 < FACTOR_GAMMA < 1', srname)
end if
!====================!
@@ -419,14 +264,9 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
!====================!
! SIMI(JDROP, :) is a vector perpendicular to the face of the simplex to the opposite of vertex
-! JDROP. Thus VSIGJ * SIMI(JDROP, :) is the unit vector in this direction.
-vsigj = ONE / sqrt(sum(simi(jdrop, :)**2))
-
-! Set D to the vector in the above-mentioned direction and with length FACTOR_GAMMA * DELTA. Since
-! FACTOR_ALPHA < FACTOR_GAMMA < FACTOR_BETA, D improves the geometry of the simplex as per (14) of
-! the COBYLA paper. This also explains why this subroutine does not replace DELTA with
-! DELBAR = MAX(MIN(TENTH * SQRT(MAXVAL(DISTSQ)), HALF * DELTA), RHO) as in NEWUOA.
-d = factor_gamma * delta * (vsigj * simi(jdrop, :))
+! JDROP. Set D to the vector in this direction and with length DELBAR.
+d = simi(jdrop, :)
+d = delbar * (d / norm(d))
! The code below chooses the direction of D according to an approximation of the merit function.
! See (17) of the COBYLA paper and line 225 of Powell's cobylb.f.
@@ -439,8 +279,8 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
A(:, m_lcon + 1:m) = transpose(matprod(conmat(m_lcon + 1:m, 1:n) - spread(conmat(m_lcon + 1:m, n + 1), dim=2, ncopies=n), simi))
!!MATLAB: A(:, m_lcon+1:m) = simi'*(conmat(m_lcon+1:m, 1:n) - conmat(m_lcon+1:m, n+1))' % Implicit expansion for subtraction
! CVPD and CVND are the predicted constraint violation of D and -D by the linear models.
-cvpd = maxval([ZERO, conmat(:, n + 1) + matprod(d, A)])
-cvnd = maxval([ZERO, conmat(:, n + 1) - matprod(d, A)])
+cvpd = maximum([ZERO, conmat(:, n + 1) + matprod(d, A)])
+cvnd = maximum([ZERO, conmat(:, n + 1) - matprod(d, A)])
! Take -D if the linear models predict that its merit function value is lower.
if (-inprod(d, g) + cpen * cvnd < inprod(d, g) + cpen * cvpd) then
d = -d
@@ -453,10 +293,10 @@ function geostep(jdrop, amat, bvec, conmat, cpen, cval, delta, fval, factor_gamm
! Postconditions
if (DEBUGGING) then
call assert(size(d) == n .and. all(is_finite(d)), 'SIZE(D) == N, D is finite', srname)
- ! In theory, ||S|| == FACTOR_GAMMA*DELTA, which may be false due to rounding, but not too far.
+ ! In theory, ||S|| == DELBAR, which may be false due to rounding, but not too far.
! It is crucial to ensure that the geometry step is nonzero, which holds in theory.
- call assert(norm(d) > 0.9_RP * factor_gamma * delta .and. norm(d) <= 1.1_RP * factor_gamma * delta, &
- & '||D|| == FACTOR_GAMMA*DELTA', srname)
+ call assert(norm(d) > 0.9_RP * delbar .and. norm(d) <= 1.1_RP * delbar, &
+ & '||D|| == DELBAR', srname)
end if
end function geostep
diff --git a/fortran/cobyla/initialize.f90 b/fortran/cobyla/initialize.f90
index a3779342f3..c1bd9bb19d 100644
--- a/fortran/cobyla/initialize.f90
+++ b/fortran/cobyla/initialize.f90
@@ -8,7 +8,7 @@ module initialize_cobyla_mod
!
! Started: July 2021
!
-! Last Modified: Monday, August 07, 2023 AM03:54:18
+! Last Modified: Tue 16 Sep 2025 12:35:23 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -19,8 +19,8 @@ module initialize_cobyla_mod
contains
-subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x0, nf, chist, &
- & conhist, conmat, cval, fhist, fval, sim, simi, xhist, evaluated, info)
+subroutine initxfc(calcfc, iprint, maxfun, amat, bvec, constr0, ctol, f0, ftarget, rhobeg, x0, nf, &
+ & chist, conhist, conmat, cval, fhist, fval, sim, simi, xhist, evaluated, info)
!--------------------------------------------------------------------------------------------------!
! This subroutine does the initialization concerning X, function values, and constraints.
!--------------------------------------------------------------------------------------------------!
@@ -29,11 +29,11 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
use, non_intrinsic :: checkexit_mod, only : checkexit
use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, TENTH, REALMAX, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: evaluate_mod, only : evaluate, moderatef, moderatec
+use, non_intrinsic :: evaluate_mod, only : evaluate, moderatec
use, non_intrinsic :: history_mod, only : savehist
use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf, is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT
-use, non_intrinsic :: linalg_mod, only : eye, inv, isinv
+use, non_intrinsic :: linalg_mod, only : eye, inv, isinv, maximum, matprod
use, non_intrinsic :: message_mod, only : fmsg
use, non_intrinsic :: pintrf_mod, only : OBJCON
@@ -43,6 +43,8 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
procedure(OBJCON) :: calcfc ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfun
+real(RP), intent(in) :: amat(:, :) ! AMAT(N, M_LCON)
+real(RP), intent(in) :: bvec(:) ! BVEC(M_LCON)
real(RP), intent(in) :: constr0(:) ! CONSTR0(M)
real(RP), intent(in) :: ctol
real(RP), intent(in) :: f0
@@ -70,6 +72,7 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
integer(IK) :: j
integer(IK) :: k
integer(IK) :: m
+integer(IK) :: m_lcon
integer(IK) :: maxchist
integer(IK) :: maxconhist
integer(IK) :: maxfhist
@@ -84,6 +87,7 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
real(RP), parameter :: itol = TENTH
! Sizes
+m_lcon = int(size(bvec), kind(m_lcon))
m = int(size(conmat, 1), kind(m))
n = int(size(sim, 1), kind(n))
maxchist = int(size(chist), kind(maxchist))
@@ -97,6 +101,7 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
call assert(m >= 0, 'M >= 0', srname)
call assert(n >= 1, 'N >= 1', srname)
call assert(abs(iprint) <= 3, 'IPRINT is 0, 1, -1, 2, -2, 3, or -3', srname)
+ call assert(size(amat, 1) == n .and. size(amat, 2) == size(bvec), 'SIZE(AMAT) == [N, SIZE(BVEC)]', srname)
call assert(size(conmat, 1) == m .and. size(conmat, 2) == n + 1, 'SIZE(CONMAT) = [M, N+1]', srname)
call assert(size(cval) == n + 1, 'SIZE(CVAL) == N+1', srname)
call assert(size(fval) == n + 1, 'SIZE(FVAL) == N+1', srname)
@@ -134,28 +139,34 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
! Initialize XHIST, FHIST, CHIST, CONHIST, FVAL, CVAL, and CONMAT. Otherwise, compilers may complain
!that they are not (completely) initialized if the initialization aborts due to abnormality (see
-!CHECKEXIT). Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+!CHECKEXIT).
+! N.B.: 1. Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! 2. Do not initialize the models if the current initialization aborts due to abnormality. Otherwise,
+! errors or exceptions may occur, as FVAL and XPT etc are uninitialized.
xhist = -REALMAX
fhist = REALMAX
chist = REALMAX
-conhist = -REALMAX
+conhist = REALMAX
fval = REALMAX
cval = REALMAX
-conmat = -REALMAX
+conmat = REALMAX
do k = 1, n + 1_IK
x = sim(:, n + 1)
! We will evaluate F corresponding to SIM(:, J).
if (k == 1) then
j = n + 1_IK
- f = moderatef(f0)
- constr = moderatec(constr0)
+ f = f0
+ constr = constr0
else
j = k - 1_IK
x(j) = x(j) + rhobeg
- call evaluate(calcfc, x, f, constr)
+ constr(1:m_lcon) = moderatec(matprod(x, amat) - bvec) ! Linear constraints.
+ call evaluate(calcfc, x, f, constr(m_lcon + 1:m)) ! Nonlinear constraints.
+ ! Note that EVALUATE moderates the nonlinear constraint values. Thus we also moderate the
+ ! linear constraint values here to make CSTRV consistent.
end if
- cstrv = maxval([ZERO, constr])
+ cstrv = maximum([ZERO, constr])
! Print a message about the function/constraint evaluation according to IPRINT.
call fmsg(solver, 'Initialization', iprint, k, rhobeg, f, x, cstrv, constr)
@@ -224,7 +235,7 @@ subroutine initxfc(calcfc, iprint, maxfun, constr0, ctol, f0, ftarget, rhobeg, x
call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
call assert(isinv(sim(:, 1:n), simi, itol) .or. any(.not. evaluated), 'SIMI = SIM(:, 1:N)^{-1}', srname)
@@ -296,7 +307,7 @@ subroutine initfilt(conmat, ctol, cweight, cval, fval, sim, evaluated, nfilt, cf
& 'SIZE(FVAL) == N+1 and FVAL does not contain NaN/+Inf', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(evaluated) == n + 1, 'SIZE(EVALUATED) == N + 1', srname)
end if
diff --git a/fortran/cobyla/mlint b/fortran/cobyla/mlint
index 13013f2f37..9dd46ff833 120000
--- a/fortran/cobyla/mlint
+++ b/fortran/cobyla/mlint
@@ -1 +1 @@
-../common/mlint
\ No newline at end of file
+../tests/tools/mlint
\ No newline at end of file
diff --git a/fortran/cobyla/trustregion.f90 b/fortran/cobyla/trustregion.f90
index 31068c2149..b4f1d7b783 100644
--- a/fortran/cobyla/trustregion.f90
+++ b/fortran/cobyla/trustregion.f90
@@ -8,7 +8,7 @@ module trustregion_cobyla_mod
!
! Started: June 2021
!
-! Last Modified: Sunday, September 03, 2023 PM06:19:23
+! Last Modified: Saturday, March 16, 2024 AM03:37:33
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -163,14 +163,14 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, REALMAX, EPS, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert, validate
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-use, non_intrinsic :: linalg_mod, only : inprod, matprod, eye, isminor, lsqr, norm, linspace, trueloc
+use, non_intrinsic :: linalg_mod, only : inprod, matprod, eye, isminor, lsqr, norm, linspace, trueloc, maximum
use, non_intrinsic :: powalg_mod, only : qradd, qrexc
implicit none
! Inputs
integer(IK), intent(in) :: stage
real(RP), intent(in) :: A(:, :) ! A(N, MCON)
-real(RP), intent(in) :: b(:) ! A(N, MCON)
+real(RP), intent(in) :: b(:) ! B(M)
real(RP), intent(in) :: delta
! In-outputs
@@ -233,7 +233,8 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
& 'D is finite and ||D|| <= 2*DELTA at the beginning of stage 2', srname)
call assert((nact >= 0 .and. nact <= min(mcon, n)), &
& '0 <= NACT <= MIN(MCON, N) at the beginning of stage 2', srname)
- call assert(all(vmultc(1:mcon - 1) >= 0), 'VMULTC >= 0 at the beginning of stage 2', srname)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. all(vmultc(1:mcon - 1) >= 0), &
+ & 'VMULTC >= 0 at the beginning of stage 2', srname)
! N.B.: Stage 1 defines only VMULTC(1:M); VMULTC(M+1) is undefined!
end if
end if
@@ -249,7 +250,7 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
! 2. In MATLAB, linspace(1, mcon, mcon) can also be written as (1:mcon).
nact = 0
d = ZERO
- cviol = maxval([-b, ZERO])
+ cviol = maximum([ZERO, -b])
vmultc = cviol + b
z = eye(n)
if (mcon == 0 .or. cviol <= 0) then
@@ -278,7 +279,7 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
! In Powell's code, stage 2 uses the ZDOTA and CVIOL calculated by stage 1. Here we re-calculate
! them so that they need not be passed from stage 1 to 2, and hence the coupling is reduced.
- cviol = maxval([matprod(d, A(:, 1:m)) - b(1:m), ZERO])
+ cviol = maximum([ZERO, matprod(d, A(:, 1:m)) - b(1:m)])
end if
zdota(1:nact) = [(inprod(z(:, k), A(:, iact(k))), k=1, nact)]
!!MATLAB: zdota(1:nact) = sum(z(:, 1:nact) .* A(:, iact(1:nact)), 1); % Row vector
@@ -301,7 +302,7 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
maxiter = int(min(10**min(4, range(0_IK)), 100 * int(max(m, n))), IK)
do iter = 1, maxiter
if (DEBUGGING) then
- call assert(all(vmultc >= 0), 'VMULTC >= 0', srname)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. all(vmultc >= 0), 'VMULTC >= 0', srname)
end if
if (stage == 1) then
optnew = cviol
@@ -514,7 +515,7 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
dnew = d + step * sdirn
if (stage == 1) then
!cvold = cviol
- cviol = maxval([matprod(dnew, A(:, iact(1:nact))) - b(iact(1:nact)), ZERO])
+ cviol = maximum([ZERO, matprod(dnew, A(:, iact(1:nact))) - b(iact(1:nact))])
! N.B.: CVIOL will be used when calculating VMULTD(NACT+1 : MCON).
end if
@@ -548,18 +549,18 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
! Update D, VMULTC and CVIOL.
dold = d
d = (ONE - frac) * d + frac * dnew
- ! Exit in case of Inf/NaN in D.
- if (.not. is_finite(sum(abs(d)))) then
+ vmultc = max(ZERO, (ONE - frac) * vmultc + frac * vmultd)
+ ! Exit in case of Inf/NaN in D or VMULTC.
+ if (.not. (is_finite(sum(abs(d))) .and. is_finite(sum(abs(vmultc))))) then
d = dold ! Should we restore also IACT, NACT, VMULTC, and Z?
exit
end if
- vmultc = max(ZERO, (ONE - frac) * vmultc + frac * vmultd)
if (stage == 1) then
!cviol = (ONE - frac) * cvold + frac * cviol ! Powell's version
! In theory, CVIOL = MAXVAL([MATPROD(D, A) - B, ZERO]), yet the CVIOL updated as above
! can be quite different from this value if A has huge entries (e.g., > 1E20).
- cviol = maxval([matprod(d, A) - b, ZERO])
+ cviol = maximum([ZERO, matprod(d, A) - b])
end if
if (icon < 1 .or. icon > mcon) then
@@ -577,7 +578,7 @@ subroutine trstlp_sub(iact, nact, stage, A, b, delta, d, vmultc, z)
if (DEBUGGING) then
call assert(size(iact) == mcon, 'SIZE(IACT) == MCON', srname)
call assert(size(vmultc) == mcon, 'SIZE(VMULTC) == MCON', srname)
- call assert(all(vmultc >= 0), 'VMULTC >= 0', srname)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. all(vmultc >= 0), 'VMULTC >= 0', srname)
call assert(size(d) == n, 'SIZE(D) == N', srname)
call assert(all(is_finite(d)), 'D is finite', srname)
call assert(norm(d) <= TWO * delta, '||D|| <= 2*DELTA', srname)
diff --git a/fortran/cobyla/update.f90 b/fortran/cobyla/update.f90
index 027dd69630..642331a5dc 100644
--- a/fortran/cobyla/update.f90
+++ b/fortran/cobyla/update.f90
@@ -8,7 +8,7 @@ module update_cobyla_mod
!
! Started: July 2021
!
-! Last Modified: Monday, August 07, 2023 AM03:53:59
+! Last Modified: Thu 14 Aug 2025 07:34:04 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -86,7 +86,7 @@ subroutine updatexfc(jdrop, constr, cpen, cstrv, d, f, conmat, cval, fval, sim,
& 'SIZE(FVAL) == N+1 and FVAL is not NaN/+Inf', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
call assert(isinv(sim(:, 1:n), simi, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
@@ -104,6 +104,7 @@ subroutine updatexfc(jdrop, constr, cpen, cstrv, d, f, conmat, cval, fval, sim,
sim_old = sim
simi_old = simi
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
if (jdrop <= n) then
sim(:, jdrop) = d
simi_jdrop = simi(jdrop, :) / inprod(simi(jdrop, :), d)
@@ -157,7 +158,7 @@ subroutine updatexfc(jdrop, constr, cpen, cstrv, d, f, conmat, cval, fval, sim,
& 'SIZE(FVAL) == N+1 and FVAL is not NaN/+Inf', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
call assert(isinv(sim(:, 1:n), simi, itol) .or. info == DAMAGING_ROUNDING, &
@@ -245,7 +246,7 @@ subroutine updatepole(cpen, conmat, cval, fval, sim, simi, info)
& 'SIZE(FVAL) == N+1 and FVAL is not NaN/+Inf', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
call assert(isinv(sim(:, 1:n), simi, itol), 'SIMI = SIM(:, 1:N)^{-1}', srname)
@@ -325,7 +326,7 @@ subroutine updatepole(cpen, conmat, cval, fval, sim, simi, info)
& 'SIZE(FVAL) == N+1 and FVAL is not NaN/+Inf', srname)
call assert(size(sim, 1) == n .and. size(sim, 2) == n + 1, 'SIZE(SIM) == [N, N+1]', srname)
call assert(all(is_finite(sim)), 'SIM is finite', srname)
- call assert(all(maxval(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
+ call assert(all(sum(abs(sim(:, 1:n)), dim=1) > 0), 'SIM(:, 1:N) has no zero column', srname)
call assert(size(simi, 1) == n .and. size(simi, 2) == n, 'SIZE(SIMI) == [N, N]', srname)
call assert(all(is_finite(simi)), 'SIMI is finite', srname)
! Do not check SIMI = SIM(:, 1:N)^{-1}, as it may not be true due to damaging rounding.
diff --git a/fortran/common/.gitignore b/fortran/common/.gitignore
new file mode 100644
index 0000000000..dd14be8eef
--- /dev/null
+++ b/fortran/common/.gitignore
@@ -0,0 +1 @@
+intrinsic_linalg.f90
diff --git a/fortran/common/checkexit.f90 b/fortran/common/checkexit.f90
index eaeec5ccce..02b5de11fe 100644
--- a/fortran/common/checkexit.f90
+++ b/fortran/common/checkexit.f90
@@ -7,7 +7,7 @@ module checkexit_mod
!
! Started: September 2021
!
-! Last Modified: Monday, November 14, 2022 PM07:06:00
+! Last Modified: Tuesday, September 26, 2023 AM10:51:16
!--------------------------------------------------------------------------------------------------!
implicit none
diff --git a/fortran/common/consts.F90 b/fortran/common/consts.F90
index b1fe651b0c..c1d31b3ce1 100644
--- a/fortran/common/consts.F90
+++ b/fortran/common/consts.F90
@@ -8,7 +8,7 @@ module consts_mod
!
! Started: July 2020
!
-! Last Modified: Friday, June 23, 2023 PM03:30:01
+! Last Modified: Mon 16 Feb 2026 03:39:56 PM CET
!--------------------------------------------------------------------------------------------------!
!--------------------------------------------------------------------------------------------------!
@@ -45,7 +45,7 @@ module consts_mod
! flang 15.0.3 do not support REAL128).
! - The standard does not specify the range of the default integer. However, if the default real
! occupies 32 bits, which is normally the case, then the default integer occupies also 32 bits,
-! and hence the range is probably [2^32, 2^31-1], approximately [-2*10^9, 2*10^9].
+! and hence the range is probably [-2^32, 2^31-1], approximately [-2*10^9, 2*10^9].
! - The standard does not specify what the range and precision of the default real or the
! double-precision real, except that KIND(0.0D0) should have a greater precision than KIND(0.0)
! --- no requirement about the range.
@@ -60,7 +60,13 @@ module consts_mod
!--------------------------------------------------------------------------------------------------!
! Integer and real kinds. Unsupported kinds are negative.
-use, intrinsic :: iso_fortran_env, only : INT16, INT32, INT64, REAL32, REAL64, REAL128
+use, intrinsic :: iso_fortran_env, only : INT16, INT32, INT64, SP => REAL32, DP => REAL64
+#if PRIMA_HP_AVAILABLE == 1
+use, intrinsic :: iso_fortran_env, only : HP => REAL16
+#endif
+#if PRIMA_QP_AVAILABLE == 1
+use, intrinsic :: iso_fortran_env, only : QP => REAL128
+#endif
! Standard IO units
use, intrinsic :: iso_fortran_env, only : STDIN => INPUT_UNIT, STDOUT => OUTPUT_UNIT, STDERR => ERROR_UNIT
@@ -70,10 +76,16 @@ module consts_mod
private
public :: DEBUGGING
public :: IK, IK_DFT, INT16, INT32, INT64
-public :: RP, RP_DFT, DP, SP, QP
+public :: RP, RP_DFT, DP, SP
+#if PRIMA_HP_AVAILABLE == 1
+public :: HP
+#endif
+#if PRIMA_QP_AVAILABLE == 1
+public :: QP
+#endif
public :: ZERO, ONE, TWO, HALF, QUART, TEN, TENTH, PI
-public :: REALMIN, EPS, TINYCV, REALMAX, FUNCMAX, CONSTRMAX, BOUNDMAX
-public :: SYMTOL_DFT
+public :: REALMIN, EPS, MAXPOW10, TINYCV, REALMAX, FUNCMAX, CONSTRMAX, BOUNDMAX
+public :: SYMTOL_DFT, ORTHTOL_DFT
public :: STDIN, STDOUT, STDERR
public :: RHOBEG_DFT, RHOEND_DFT, FTARGET_DFT, CTOL_DFT, CWEIGHT_DFT
public :: ETA1_DFT, ETA2_DFT, GAMMA1_DFT, GAMMA2_DFT
@@ -83,9 +95,6 @@ module consts_mod
logical, parameter :: DEBUGGING = (PRIMA_DEBUGGING == 1) ! Whether we are in debugging mode
integer, parameter :: IK_DFT = kind(0) ! Default integer kind
integer, parameter :: RP_DFT = kind(0.0) ! Default real kind
-integer, parameter :: SP = REAL32 ! Kind for single precision
-integer, parameter :: DP = REAL64 ! Kind for double precision
-integer, parameter :: QP = REAL128 ! Kind for quadruple precision
! Define the integer kind to be used in the Fortran code.
#if PRIMA_INTEGER_KIND == 0
@@ -99,17 +108,20 @@ module consts_mod
#else
integer, parameter :: IK = IK_DFT
#endif
+
! Define the real kind to be used in the Fortran code.
#if PRIMA_REAL_PRECISION == 0
integer, parameter :: RP = RP_DFT
+#elif (PRIMA_REAL_PRECISION == 16 && PRIMA_HP_AVAILABLE == 1)
+integer, parameter :: RP = HP
#elif PRIMA_REAL_PRECISION == 32
-integer, parameter :: RP = REAL32
+integer, parameter :: RP = SP
#elif PRIMA_REAL_PRECISION == 64
-integer, parameter :: RP = REAL64
-#elif PRIMA_REAL_PRECISION == 128
-integer, parameter :: RP = REAL128
+integer, parameter :: RP = DP
+#elif (PRIMA_REAL_PRECISION == 128 && PRIMA_QP_AVAILABLE == 1)
+integer, parameter :: RP = QP
#else
-integer, parameter :: RP = REAL64 ! double precision
+integer, parameter :: RP = DP ! Double precision
#endif
! Define some frequently used numbers.
@@ -139,15 +151,22 @@ module consts_mod
! R: double.xmax
real(RP), parameter :: REALMAX = huge(ZERO)
-integer, parameter :: MINE = minexponent(ZERO)
-integer, parameter :: MAXE = maxexponent(ZERO)
+integer, parameter :: MAXPOW10 = range(ZERO)
+integer, parameter :: HALF_MAXPOW10 = floor(real(MAXPOW10) / 2.0)
! TINYCV is used in LINCOA. Powell set TINYCV = 1.0D-60. What about setting TINYCV = REALMIN?
-real(RP), parameter :: TINYCV = real(radix(ZERO), RP)**max(-200, MINE) ! Normally, RADIX = 2.
+! N.B.: The `if` is a workaround for the following issues with LLVM flang 19.0.0 and nvfortran 24.3.0:
+! https://fortran-lang.discourse.group/t/flang-new-19-0-warning-overflow-on-power-with-integer-exponent/7801
+! https://forums.developer.nvidia.com/t/bug-of-nvfortran-24-3-0-fort1-terminated-by-signal-11/289026
+#if (defined __flang__ && __flang_major__ <= 19)
+real(RP), parameter :: TINYCV = TEN**max(-60.0, -real(MAXPOW10))
+#else
+real(RP), parameter :: TINYCV = TEN**max(-60, -MAXPOW10)
+#endif
! FUNCMAX is used in the moderated extreme barrier. All function values are projected to the
! interval [-FUNCMAX, FUNCMAX] before passing to the solvers, and NaN is replaced with FUNCMAX.
! CONSTRMAX plays a similar role for constraints.
-real(RP), parameter :: FUNCMAX = real(radix(ZERO), RP)**min(100, MAXE / 2) ! Normally, RADIX = 2.
+real(RP), parameter :: FUNCMAX = TEN**max(4, min(30, HALF_MAXPOW10))
real(RP), parameter :: CONSTRMAX = FUNCMAX
! Any bound with an absolute value at least BOUNDMAX is considered as no bound.
real(RP), parameter :: BOUNDMAX = QUART * REALMAX
@@ -156,48 +175,92 @@ module consts_mod
! IEEE Standard for Floating-Point Arithmetic (IEEE 754) is respected, particularly if addition and
! multiplication are commutative. However, as of 20220408, NAG nagfor does not ensure commutativity
! for REAL128. Indeed, Fortran standards do not enforce IEEE 754, so compilers are not guaranteed to
-! respect it. Hence we set SYMTOL_DFT to a nonzero number when PRIMA_RELEASED is 1, although we do not
-! intend to test symmetry in production. We set SYMTOL_DFT in the same way when PRIMA_DEBUGGING is 0.
+! respect it. Hence we set SYMTOL_DFT to a nonzero number when PRIMA_RELEASED is 1 (although we do not
+! intend to test symmetry in production, it may be tested if PRIMA_DEBUGGING is set to 1).
! Update 20221226: When gfortran 12 is invoked with aggressive optimization options, it is buggy
! with ALL() and ANY(). We set SYMTOL_DFT to REALMAX to signify this case and disable the check.
! Update 20221229: ifx 2023.0.0 20221201 cannot ensure symmetry even up to 100*EPS if invoked
! with aggressive optimization options and if the floating-point numbers are in single precision.
! Update 20230307: ifx 2023.0.0 20221201 cannot ensure symmetry even up to 10*EPS if invoked with
! -O3 and if the floating-point numbers are in single precision.
-! Update 20230316: HUAWEI BiSheng Compiler 2.1.0.B010 (flang) cannot ensure symmetry even up to
-! 10*EPS if invoked with -Ofast and if the floating-point numbers are in single precision.
-#if (defined __GFORTRAN__ || defined __INTEL_COMPILER && PRIMA_REAL_PRECISION < 64) && PRIMA_AGGRESSIVE_OPTIONS == 1
+! Update 20231002: HUAWEI BiSheng Compiler 2.1.0.B010 (flang) cannot ensure symmetry even up to
+! 1.0E2*EPS if invoked with -Ofast and if the floating-point numbers are in single precision.
+! This same is observed for arm-linux-compiler-22.1 on Kunpeng.
+! Update 20260129: AMD AOMP 22.0 cannot ensure symmetry up to TEN*EPS if invoked with -O3 -fast-math
+! and if the floating-point numbers are in single precision.
+!
+#if (defined __INTEL_COMPILER && PRIMA_REAL_PRECISION < 64 || defined __GFORTRAN__) && PRIMA_AGGRESSIVE_OPTIONS == 1
+! ifx with single precision and aggressive optimization options, or gfortran with aggressive
+! optimization options
real(RP), parameter :: SYMTOL_DFT = REALMAX
-#elif (defined __INTEL_COMPILER && PRIMA_REAL_PRECISION < 64)
-real(RP), parameter :: SYMTOL_DFT = max(5.0E1 * EPS, 1.0E-10_RP)
-#elif (defined __FLANG && PRIMA_REAL_PRECISION < 64) && PRIMA_AGGRESSIVE_OPTIONS == 1
-real(RP), parameter :: SYMTOL_DFT = max(1.0E2 * EPS, 1.0E-10_RP)
-#elif (defined __NAG_COMPILER_RELEASE && PRIMA_REAL_PRECISION > 64) || (PRIMA_RELEASED == 1) || (PRIMA_DEBUGGING == 0)
-real(RP), parameter :: SYMTOL_DFT = max(1.0E1 * EPS, 1.0E-10_RP)
+#elif defined __FLANG && PRIMA_REAL_PRECISION < 64 && PRIMA_AGGRESSIVE_OPTIONS == 1
+! HUAWEI BiSheng Compiler with aggressive optimization options and single precision
+real(RP), parameter :: SYMTOL_DFT = max(5.0E3_RP * EPS, TEN**max(-10, -MAXPOW10))
+#elif defined __flang__ && PRIMA_REAL_PRECISION < 64 && PRIMA_AGGRESSIVE_OPTIONS == 1
+! LLVM Flang or ARM ATfL Flang with aggressive optimization options and single precision
+real(RP), parameter :: SYMTOL_DFT = max(5.0E3_RP * EPS, TEN**max(-10, -MAXPOW10))
+#elif defined __INTEL_COMPILER && PRIMA_REAL_PRECISION < 64
+! ifx with single precision
+real(RP), parameter :: SYMTOL_DFT = max(5.0E1_RP * EPS, TEN**max(-10, -MAXPOW10))
+#elif defined __NAG_COMPILER_BUILD && PRIMA_REAL_PRECISION > 64
+! NAG Fortran Compiler with quadruple precision
+real(RP), parameter :: SYMTOL_DFT = max(TEN * EPS, TEN**max(-10, -MAXPOW10))
+#elif PRIMA_RELEASED == 1 && PRIMA_REAL_PRECISION >= 64
+! Double or higher precision in released mode
+real(RP), parameter :: SYMTOL_DFT = max(TEN * EPS, TEN**max(-10, -MAXPOW10))
+#elif PRIMA_RELEASED == 1
+! Single or lower precision in released mode
+real(RP), parameter :: SYMTOL_DFT = max(1.0E2_RP * EPS, TEN**max(-10, -MAXPOW10))
#else
+! Otherwise
real(RP), parameter :: SYMTOL_DFT = ZERO
#endif
+! ORTHTOL_DFT is the default tolerance for testing orthogonality of matrices.
+! In some cases, due to compiler bugs, we need to disable the test. We signify such cases by setting
+! ORTHTOL_DFT to REALMAX. For instance, NAG Fortran Compiler is buggy concerning half-precision
+! floating-point numbers before Release 7.2 Build 7201.
+#if (defined __NAG_COMPILER_BUILD && __NAG_COMPILER_BUILD <= 7200 && PRIMA_REAL_PRECISION <= 16) || (PRIMA_RELEASED == 1) || (PRIMA_DEBUGGING == 0)
+real(RP), parameter :: ORTHTOL_DFT = REALMAX
+#else
+real(RP), parameter :: ORTHTOL_DFT = ZERO
+#endif
+
! Some default values
+! RHOBEG: initial value of the trust region radius. Should be about one tenth of the greatest
+! expected change to a variable.
real(RP), parameter :: RHOBEG_DFT = ONE
-real(RP), parameter :: RHOEND_DFT = 1.0E-6_RP
+! RHOEND: final value of the trust region radius. Should indicate the accuracy required in the final
+! values of the variables.
+real(RP), parameter :: RHOEND_DFT = TEN**max(-6, -MAXPOW10) ! 1.0E-6
+! FTARGET: target value of the objective function. Solvers exit when finding a feasible point with
+! the objective function value no more than FTARGET.
real(RP), parameter :: FTARGET_DFT = -REALMAX
+! CTOL: tolerance for constraint violation. A point with constraint violation <= CTOL is considered feasible.
real(RP), parameter :: CTOL_DFT = sqrt(EPS)
-real(RP), parameter :: CWEIGHT_DFT = 1.0E8_RP
+! CWEIGHT: weight of constraint violation in the merit function used to select the output point.
+real(RP), parameter :: CWEIGHT_DFT = TEN**min(8, MAXPOW10) ! 1.0E8
+! ETA1: threshold of reduction ratio for shrinking the trust region radius.
real(RP), parameter :: ETA1_DFT = TENTH
+! ETA2: threshold of reduction ratio for expanding the trust region radius.
real(RP), parameter :: ETA2_DFT = 0.7_RP
+! GAMMA1: factor for shrinking the trust region radius.
real(RP), parameter :: GAMMA1_DFT = HALF
+! GAMMA2: factor for expanding the trust region radius.
real(RP), parameter :: GAMMA2_DFT = TWO
+! IPRINT: printing level. 0 means no printing.
integer(IK), parameter :: IPRINT_DFT = 0
+! MAXFUN_DIM_DFT*N is the maximal number of function evaluations.
integer(IK), parameter :: MAXFUN_DIM_DFT = 500
! Maximal amount of memory (Byte) allowed for XHIST, FHIST, CONHIST, CHIST, and the filters.
integer, parameter :: MHM = PRIMA_MAX_HIST_MEM_MB * 10**6
! Make sure that MAXHISTMEM does not exceed HUGE(0) to avoid overflow and memory errors.
-integer, parameter :: MAXHISTMEM = min(MHM, huge(0))
+integer, parameter :: MAXHISTMEM = min(MHM, (huge(0) - 1) / 2)
! Maximal length of the filter used in constrained solvers.
integer(IK), parameter :: MIN_MAXFILT = 200 ! Should be positive; < 200 is not recommended.
integer(IK), parameter :: MAXFILT_DFT = 10_IK * MIN_MAXFILT
+
end module consts_mod
diff --git a/fortran/common/debug.F90 b/fortran/common/debug.F90
index 22ae025e80..7453f6ccb4 100644
--- a/fortran/common/debug.F90
+++ b/fortran/common/debug.F90
@@ -8,7 +8,7 @@ module debug_mod
!
! Started: July 2020.
!
-! Last Modified: Saturday, September 16, 2023 AM09:14:37
+! Last Modified: Tue 09 Sep 2025 11:53:28 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
private
@@ -99,12 +99,9 @@ subroutine errstop(srname, msg, code)
character(len=*), intent(in) :: msg
integer, intent(in), optional :: code
-! `backtr` prints a backtrace. As of gfortran 12.1.0, even without calling `backtrace`, a backtrace
-! is printed when the program is stopped by an error stop. Therefore, here, we do not call `backtr`
-! if the compiler is gfortran.
-#if !defined __GFORTRAN__
+! `backtr` prints a backtrace. With gfortran 12, even without calling `backtrace`, a backtrace is
+! printed when the program is stopped by an error stop.
call backtr()
-#endif
write (STDERR, '(/A/)') 'ERROR: '//trim(adjustl(srname))//': '//trim(adjustl(msg))//'.'
if (present(code)) then
@@ -150,8 +147,9 @@ subroutine backtr()
use, non_intrinsic :: ifcore, only : tracebackqq
implicit none
call tracebackqq(user_exit_code=-1)
-! According to "Intel Fortran Compiler 19.1 Developer Guide and Reference", item "TRACEBACKQQ":
-! By specifying a user exit code of -1, control returns to the calling program. Specifying a user
+! According to
+! https://www.intel.com/content/www/us/en/docs/fortran-compiler/developer-guide-reference/2024-1/tracebackqq.html,
+! by specifying a user exit code of -1, control returns to the calling program. Specifying a user
! exit code with a positive value requests that specified value be returned to the operating system.
! The default value is 0, which causes the application to abort execution.
#endif
diff --git a/fortran/common/evaluate.f90 b/fortran/common/evaluate.f90
index f15f768072..0819e1258f 100644
--- a/fortran/common/evaluate.f90
+++ b/fortran/common/evaluate.f90
@@ -6,7 +6,7 @@ module evaluate_mod
!
! Started: August 2021
!
-! Last Modified: Wednesday, August 02, 2023 PM12:26:13
+! Last Modified: Monday, September 25, 2023 PM08:52:04
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -59,7 +59,7 @@ pure elemental function moderatef(f) result(y)
real(RP) :: y
y = f
-if (is_nan(f)) then
+if (is_nan(y)) then
y = FUNCMAX
end if
y = max(-REALMAX, min(FUNCMAX, y))
diff --git a/fortran/common/ffiles.txt b/fortran/common/ffiles.txt
index ac1c9c87b5..580da66789 100644
--- a/fortran/common/ffiles.txt
+++ b/fortran/common/ffiles.txt
@@ -1,6 +1,7 @@
consts.F90
infos.f90
debug.F90
+huge.F90
inf.F90
infnan.F90
memory.F90
diff --git a/fortran/common/get_intrinsic_linalg b/fortran/common/get_intrinsic_linalg
new file mode 100755
index 0000000000..701c37c03e
--- /dev/null
+++ b/fortran/common/get_intrinsic_linalg
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+# This script generates a modified version of linalg.f90 that uses the intrinsic `matmul` function
+# for `matprod` (matrix multiplication) and `dot_product` for `inprod` (inner product).
+# Usage: ./get_intrinsic_linalg
+
+THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+LINALG_ORIG="$THIS_DIR"/linalg.f90
+LINALG_NEW="$THIS_DIR"/intrinsic_linalg.f90
+
+cat >"$LINALG_NEW" << EOF
+!--------------------------------------------------------------------------------------------------!
+! This is a modified version of $LINALG_ORIG that uses the intrinsic MATMUL for MATPROD and
+! DOT_PRODUCT for INPROD.
+!
+! We define MATPROD as an alias for MATMUL, and comment out our implementation of MATPROD.
+! The similar is done for INPROD.
+!
+! For other information, please refer to $LINALG_ORIG.
+!
+! Generated by $(basename "$0").
+!--------------------------------------------------------------------------------------------------!
+
+
+module intrinsic_linalg_mod
+!--------------------------------------------------------------------------------------------------!
+! This module provides the intrinsic MATMUL and DOT_PRODUCT functions.
+implicit none
+private
+public :: matmul, dot_product
+intrinsic :: matmul, dot_product
+end module intrinsic_linalg_mod
+!--------------------------------------------------------------------------------------------------!
+
+
+EOF
+
+cat "$LINALG_ORIG" >> "$LINALG_NEW"
+
+# Replace the line `module linalg_mod` with `module linalg\nuse intrinsic_linalg_mod ...` so that we
+# can use the intrinsic `matmul` function as `matprod` and `dot_product` as `inprod`.
+sed -i 's|\(^\s*\)\(module linalg_mod\)|\1\2\n\n\1! Define MATPROD as an alias for MATMUL and INPROD for DOT_PRODUCT.\n\1use, non_intrinsic :: intrinsic_linalg_mod, only : matprod => matmul, inprod => dot_product\n|' "$LINALG_NEW"
+
+# Comment out the lines between `interface matprod` and `end interface matprod` in "$LINALG_NEW".
+sed -i '/interface matprod/,/end interface matprod/s/^/!/' "$LINALG_NEW"
+
+# Comment out the lines between `function matprodxy` and `end function matprodxy` in
+# "$LINALG_NEW", where xy is one of 12, 21, and 22
+for xy in 12 21 22; do
+ sed -i "/function matprod${xy}/,/end function matprod${xy}/s/^/!/" "$LINALG_NEW"
+done
+
+# Comment out the lines between `function inprod` and `end function inprod` in "$LINALG_NEW".
+sed -i '/function inprod/,/end function inprod/s/^/!/' "$LINALG_NEW"
diff --git a/fortran/common/history.f90 b/fortran/common/history.f90
index 2cde8d6fa2..c9e3a07c76 100644
--- a/fortran/common/history.f90
+++ b/fortran/common/history.f90
@@ -7,7 +7,7 @@ module history_mod
!
! Started: July 2020
!
-! Last Modified: Wednesday, August 02, 2023 AM11:22:57
+! Last Modified: Thursday, April 04, 2024 PM10:15:40
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -52,8 +52,8 @@ subroutine prehist(maxhist, n, output_xhist, xhist, output_fhist, fhist, output_
! Local variables
character(len=*), parameter :: srname = 'PREHIST'
+integer :: unit_memo ! INTEGER(IK) may overflow if IK corresponds to the 16-bit integer.
integer(IK) :: maxhist_in
-integer(IK) :: unit_memo
! Preconditions
if (DEBUGGING) then
@@ -76,14 +76,17 @@ subroutine prehist(maxhist, n, output_xhist, xhist, output_fhist, fhist, output_
maxhist_in = maxhist
! Revise MAXHIST according to MAXHISTMEM, i.e., the maximal memory allowed for the history.
-unit_memo = int(output_xhist) * n + int(output_fhist)
+! N.B.: The `UNIT_MEMO = INT(*)` below converts integers to the default integer kind, which is the
+! kind of UNIT_MEMO. Fortran compilers may complain without the conversion. It is not needed in
+! Python/MATLAB/Julia/R. Meanwhile, INT(OUTPUT_*HIST) converts booleans to integers.
+unit_memo = int(int(output_xhist) * n + int(output_fhist))
if (present(output_chist) .and. present(chist)) then
- unit_memo = unit_memo + int(output_chist)
+ unit_memo = int(unit_memo + int(output_chist))
end if
if (present(m) .and. present(output_conhist) .and. present(conhist)) then
- unit_memo = unit_memo + int(output_conhist) * m
+ unit_memo = int(unit_memo + int(output_conhist) * m)
end if
-unit_memo = unit_memo * cstyle_sizeof(0.0_RP)
+unit_memo = unit_memo * int(cstyle_sizeof(0.0_RP)) ! INT(*) avoids overflow when IK is 16-bit.
if (unit_memo <= 0) then ! No output of history is requested
maxhist = 0
elseif (maxhist > MAXHISTMEM / unit_memo) then
@@ -137,8 +140,9 @@ subroutine savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
! This subroutine saves X, F, CSTRV, and CONSTR into XHIST, FHIST, CHIST, and CONHIST respectively.
!--------------------------------------------------------------------------------------------------!
use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
+use, non_intrinsic :: debug_mod, only : assert, wassert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
+use, non_intrinsic :: string_mod, only : num2str
implicit none
! Inputs
@@ -155,11 +159,14 @@ subroutine savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
real(RP), intent(inout), optional :: conhist(:, :)
! Local variables
+integer(IK) :: i
integer(IK) :: maxchist
integer(IK) :: maxconhist
integer(IK) :: maxfhist
integer(IK) :: maxhist
integer(IK) :: maxxhist
+integer(IK) :: n
+integer(IK) :: nhist
character(len=*), parameter :: srname = 'SAVEHIST'
! Sizes
@@ -198,14 +205,14 @@ subroutine savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
call assert(all(is_finite(xhist(:, 1:min(nf - 1_IK, maxxhist)))), 'XHIST is finite', srname)
call assert(.not. any(is_nan(fhist(1:min(nf - 1_IK, maxfhist))) .or. &
& is_posinf(fhist(1:min(nf - 1_IK, maxfhist)))), 'FHIST does not contain NaN/+Inf', srname)
- !----------------------------------------------------------------------------------------------!
- ! The following test is not applicable to LINCOA.
- ! !if (present(chist)) then
- ! ! call assert(.not. any(chist(1:min(nf - 1_IK, maxchist)) < 0 .or. &
- ! ! & is_nan(chist(1:min(nf - 1_IK, maxchist))) .or. is_posinf(chist(1:min(nf - 1_IK, maxchist)))), &
- ! ! & 'CHIST does not contain nonnegative values or NaN/+Inf', srname)
- ! !end if
- !----------------------------------------------------------------------------------------------!
+ if (present(chist)) then
+ call assert(.not. any(chist(1:min(nf - 1_IK, maxchist)) < 0), 'CHIST does not contain negative values', srname)
+ !------------------------------------------------------------------------------------------!
+ ! The following test is not applicable to LINCOA.
+ ! !call assert(.not. any(is_nan(chist(1:min(nf - 1_IK, maxchist))) .or. &
+ ! ! & is_posinf(chist(1:min(nf - 1_IK, maxchist)))), 'CHIST does not contain NaN/+Inf', srname)
+ !------------------------------------------------------------------------------------------!
+ end if
if (present(conhist)) then
call assert(.not. any(is_nan(conhist(:, 1:min(nf - 1_IK, maxconhist))) .or. &
& is_posinf(conhist(:, 1:min(nf - 1_IK, maxconhist)))), 'CONHIST does not contain NaN/Inf', srname)
@@ -215,14 +222,13 @@ subroutine savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
call assert(.not. any(is_nan(x)), 'X does not contain NaN', srname)
! F cannot be NaN/+Inf due to the moderated extreme barrier.
call assert(.not. (is_nan(f) .or. is_posinf(f)), 'F is not NaN/+Inf', srname)
- !----------------------------------------------------------------------------------------------!
- ! The following test is not applicable to LINCOA.
- ! !if (present(cstrv)) then
- ! ! ! CSTRV cannot be NaN/+Inf due to the moderated extreme barrier.
- ! ! call assert(.not. (cstrv < 0 .or. is_nan(cstrv) .or. is_posinf(cstrv)), &
- ! ! & 'CSTRV is nonnegative and not NaN/+Inf', srname)
- ! !end if
- !----------------------------------------------------------------------------------------------!
+ if (present(cstrv)) then
+ call assert(.not. (cstrv < 0), 'CSTRV is not negative', srname)
+ !------------------------------------------------------------------------------------------!
+ ! The following test is not applicable to LINCOA.
+ ! !call assert(.not. (is_nan(cstrv) .or. is_posinf(cstrv)), 'CSTRV is NaN/+Inf', srname)
+ !------------------------------------------------------------------------------------------!
+ end if
if (present(constr)) then
! CONSTR cannot contain NaN/+Inf due to the moderated extreme barrier.
call assert(.not. any(is_nan(constr) .or. is_posinf(constr)), 'CONSTR does not contain NaN/+Inf', srname)
@@ -263,20 +269,48 @@ subroutine savehist(nf, x, xhist, f, fhist, cstrv, chist, constr, conhist)
call assert(size(fhist) == maxfhist, 'SIZE(FHIST) == MAXFHIST', srname)
call assert(.not. any(is_nan(fhist(1:min(nf, maxfhist))) .or. is_posinf(fhist(1:min(nf, maxfhist)))), &
& 'FHIST does not contain NaN/+Inf', srname)
- !----------------------------------------------------------------------------------------------!
- ! The following test is not applicable to LINCOA.
- ! !if (present(chist)) then
- ! ! call assert(size(chist) == maxchist, 'SIZE(CHIST) == MAXCHIST', srname)
- ! ! call assert(.not. any(chist(1:min(nf, maxchist)) < 0 .or. is_nan(chist(1:min(nf, maxchist))) .or. &
- ! ! & is_posinf(chist(1:min(nf, maxchist)))), 'CHIST does not contain nonnegative values or NaN/+Inf', srname)
- ! !end if
- !----------------------------------------------------------------------------------------------!
+ if (present(chist)) then
+ call assert(size(chist) == maxchist, 'SIZE(CHIST) == MAXCHIST', srname)
+ call assert(.not. any(chist(1:min(nf, maxchist)) < 0), 'CHIST does not contain negative values', srname)
+ !------------------------------------------------------------------------------------------!
+ ! The following test is not applicable to LINCOA.
+ ! !call assert(.not. any(is_nan(chist(1:min(nf, maxchist))) .or. is_posinf(chist(1:min(nf, maxchist)))), &
+ ! ! & 'CHIST does not contain NaN/+Inf', srname)
+ !------------------------------------------------------------------------------------------!
+ end if
if (present(conhist) .and. present(constr)) then
call assert(size(conhist, 1) == size(constr) .and. size(conhist, 2) == maxconhist, &
& 'SIZE(CONHIST) == [SIZE(CONSTR), MAXCONHIST]', srname)
call assert(.not. any(is_nan(conhist(:, 1:min(nf, maxconhist))) .or. &
& is_posinf(conhist(:, 1:min(nf, maxconhist)))), 'CONHIST does not contain NaN/+Inf', srname)
end if
+
+ ! The following code checks that XHIST does not contain a segment that repeats. If such a segment
+ ! is found, we believe that the solver has encountered an infinite cycle, which would be a bug.
+ ! N.B.:
+ ! 1. We check this only if NF > (N+1)*(N+2)/2, when the initialization has surely finished. This
+ ! is because XHIST may contain repeating segments during the initialization if X0 + RHOBEG = X0,
+ ! which can happen if all entries of X0 are excessively large compared with RHOBEG. It is
+ ! possible to revise the initialization subroutine to avoid repetition, but we choose not to,
+ ! a motivation being to keep the initialization parallelizable.
+ ! 2. We skip the test if N = 1, as false positive may occur (also possible when N > 1, but rare).
+ ! 3. For segments of length 1, we check whether it repeats three times. For segments of length
+ ! i > 1, we check whether it repeats twice. Due to rounding errors, it may happen that the same
+ ! point is repeated twice, but the solver is not in an infinite cycle, which was observed in an
+ ! experiment of NEWUOA on 20240404.
+ nhist = min(nf, maxxhist)
+ n = int(size(x), kind(n))
+ if (n > 1 .and. nf > (n + 1) * (n + 2) / 2) then
+ if (nhist >= 3) then
+ call wassert(.not. (all(abs(xhist(:, nhist) - xhist(:, nhist - 1)) <= 0) .and. &
+ & all(abs(xhist(:, nhist - 1) - xhist(:, nhist - 2)) <= 0)), &
+ & 'XHIST does not contain a repeating segment of length 1', srname)
+ end if
+ do i = 2, min(100_IK, nhist / 2_IK)
+ call wassert(.not. all(abs(xhist(:, nhist - i + 1:nhist) - xhist(:, nhist - 2 * i + 1:nhist - i)) <= 0), &
+ & 'XHIST does not contain a repeating segment of length '//num2str(i), srname)
+ end do
+ end if
end if
end subroutine savehist
@@ -342,13 +376,14 @@ subroutine rangehist(nf, xhist, fhist, chist, conhist)
! The last calculated X can be Inf (finite + finite can be Inf numerically).
call assert(.not. any(is_nan(fhist(1:min(nf, maxfhist))) .or. &
& is_posinf(fhist(1:min(nf, maxfhist)))), 'FHIST does not contain NaN/+Inf', srname)
- !----------------------------------------------------------------------------------------------!
- ! The following test is not applicable to LINCOA
- ! !if (present(chist)) then
- ! !call assert(.not. any(chist(1:min(nf, maxchist)) < 0 .or. is_nan(chist(1:min(nf, maxchist))) .or. &
- ! ! & is_posinf(chist(1:min(nf, maxchist)))), 'CHIST does not contain nonnegative values or NaN/+Inf', srname)
- ! !end if
- !----------------------------------------------------------------------------------------------!
+ if (present(chist)) then
+ call assert(.not. any(chist(1:min(nf, maxchist)) < 0), 'CHIST does not contain negative values', srname)
+ !------------------------------------------------------------------------------------------!
+ ! The following test is not applicable to LINCOA.
+ ! !call assert(.not. any(is_nan(chist(1:min(nf, maxchist))) .or. is_posinf(chist(1:min(nf, maxchist)))), &
+ ! ! & 'CHIST does not contain NaN/+Inf', srname)
+ !------------------------------------------------------------------------------------------!
+ end if
if (present(conhist)) then
call assert(.not. any(is_nan(conhist(:, 1:min(nf, maxconhist))) .or. &
& is_posinf(conhist(:, 1:min(nf, maxconhist)))), 'CONHIST does not contain NaN/+Inf', srname)
@@ -402,10 +437,11 @@ subroutine rangehist(nf, xhist, fhist, chist, conhist)
& 'FHIST does not contain NaN/+Inf', srname)
if (present(chist)) then
call assert(size(chist) == maxchist, 'SIZE(CHIST) == MAXCHIST', srname)
+ call assert(.not. any(chist(1:min(nf, maxchist)) < 0), 'CHIST does not contain negative values', srname)
!------------------------------------------------------------------------------------------!
- ! The following test is not applicable to LINCOA
- ! !call assert(.not. any(chist(1:min(nf, maxchist)) < 0 .or. is_nan(chist(1:min(nf, maxchist))) .or. &
- ! ! & is_posinf(chist(1:min(nf, maxchist)))), 'CHIST does not contain nonnegative values or NaN/+Inf', srname)
+ ! The following test is not applicable to LINCOA.
+ ! !call assert(.not. any(is_nan(chist(1:min(nf, maxchist))) .or. is_posinf(chist(1:min(nf, maxchist)))), &
+ ! ! & 'CHIST does not contain NaN/+Inf', srname)
!------------------------------------------------------------------------------------------!
end if
if (present(conhist)) then
diff --git a/fortran/common/huge.F90 b/fortran/common/huge.F90
new file mode 100644
index 0000000000..62389fe0f2
--- /dev/null
+++ b/fortran/common/huge.F90
@@ -0,0 +1,80 @@
+#include "ppf.h"
+
+module huge_mod
+!--------------------------------------------------------------------------------------------------!
+! This module provides a function that returns HUGE(X). See infnan.f90 for more comments.
+!
+! Coded by Zaikun ZHANG (www.zhangzk.net).
+!
+! Started: July 2020.
+!
+! Last Modified: Tuesday, February 27, 2024 PM11:02:29
+!--------------------------------------------------------------------------------------------------!
+
+implicit none
+private
+public :: huge_value
+
+interface huge_value
+ module procedure huge_value_sp, huge_value_dp
+end interface huge_value
+
+
+#if PRIMA_HP_AVAILABLE == 1
+
+interface huge_value
+ module procedure huge_value_hp
+end interface huge_value
+
+#endif
+
+
+#if PRIMA_QP_AVAILABLE == 1
+
+interface huge_value
+ module procedure huge_value_qp
+end interface huge_value
+
+#endif
+
+
+contains
+
+
+pure elemental function huge_value_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+real(SP) :: y
+y = huge(x)
+end function huge_value_sp
+
+pure elemental function huge_value_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+real(DP) :: y
+y = huge(x)
+end function huge_value_dp
+
+#if PRIMA_HP_AVAILABLE == 1
+pure elemental function huge_value_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+real(HP) :: y
+y = huge(x)
+end function huge_value_hp
+#endif
+
+#if PRIMA_QP_AVAILABLE == 1
+pure elemental function huge_value_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+real(QP) :: y
+y = huge(x)
+end function huge_value_qp
+#endif
+
+end module huge_mod
diff --git a/fortran/common/inf.F90 b/fortran/common/inf.F90
index eefcdb69eb..64e21197c6 100644
--- a/fortran/common/inf.F90
+++ b/fortran/common/inf.F90
@@ -9,47 +9,68 @@ module inf_mod
!
! Started: July 2020.
!
-! Last Modified: Tuesday, April 11, 2023 PM08:10:28
+! Last Modified: Tuesday, February 27, 2024 PM10:57:47
!--------------------------------------------------------------------------------------------------!
+use, non_intrinsic :: huge_mod, only : huge_value
implicit none
private
public :: is_finite, is_posinf, is_neginf, is_inf
-#if PRIMA_QP_AVAILABLE == 1
+interface is_finite
+ module procedure is_finite_sp, is_finite_dp
+end interface is_finite
+
+interface is_posinf
+ module procedure is_posinf_sp, is_posinf_dp
+end interface is_posinf
+
+interface is_neginf
+ module procedure is_neginf_sp, is_neginf_dp
+end interface is_neginf
+
+interface is_inf
+ module procedure is_inf_sp, is_inf_dp
+end interface is_inf
+
+
+#if PRIMA_HP_AVAILABLE == 1
interface is_finite
- module procedure is_finite_sp, is_finite_dp, is_finite_qp
+ module procedure is_finite_hp
end interface is_finite
interface is_posinf
- module procedure is_posinf_sp, is_posinf_dp, is_posinf_qp
+ module procedure is_posinf_hp
end interface is_posinf
interface is_neginf
- module procedure is_neginf_sp, is_neginf_dp, is_neginf_qp
+ module procedure is_neginf_hp
end interface is_neginf
interface is_inf
- module procedure is_inf_sp, is_inf_dp, is_inf_qp
+ module procedure is_inf_hp
end interface is_inf
-#else
+#endif
+
+
+#if PRIMA_QP_AVAILABLE == 1
interface is_finite
- module procedure is_finite_sp, is_finite_dp
+ module procedure is_finite_qp
end interface is_finite
interface is_posinf
- module procedure is_posinf_sp, is_posinf_dp
+ module procedure is_posinf_qp
end interface is_posinf
interface is_neginf
- module procedure is_neginf_sp, is_neginf_dp
+ module procedure is_neginf_qp
end interface is_neginf
interface is_inf
- module procedure is_inf_sp, is_inf_dp
+ module procedure is_inf_qp
end interface is_inf
#endif
@@ -63,7 +84,7 @@ pure elemental function is_finite_sp(x) result(y)
implicit none
real(SP), intent(in) :: x
logical :: y
-y = (x <= huge(x) .and. x >= -huge(x))
+y = (x <= huge_value(x) .and. x >= -huge_value(x))
end function is_finite_sp
pure elemental function is_finite_dp(x) result(y)
@@ -71,7 +92,7 @@ pure elemental function is_finite_dp(x) result(y)
implicit none
real(DP), intent(in) :: x
logical :: y
-y = (x <= huge(x) .and. x >= -huge(x))
+y = (x <= huge_value(x) .and. x >= -huge_value(x))
end function is_finite_dp
pure elemental function is_posinf_sp(x) result(y)
@@ -79,7 +100,7 @@ pure elemental function is_posinf_sp(x) result(y)
implicit none
real(SP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x > 0)
+y = (abs(x) > huge_value(x)) .and. (x > 0)
end function is_posinf_sp
pure elemental function is_posinf_dp(x) result(y)
@@ -87,7 +108,7 @@ pure elemental function is_posinf_dp(x) result(y)
implicit none
real(DP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x > 0)
+y = (abs(x) > huge_value(x)) .and. (x > 0)
end function is_posinf_dp
pure elemental function is_neginf_sp(x) result(y)
@@ -95,7 +116,7 @@ pure elemental function is_neginf_sp(x) result(y)
implicit none
real(SP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x < 0)
+y = (abs(x) > huge_value(x)) .and. (x < 0)
end function is_neginf_sp
pure elemental function is_neginf_dp(x) result(y)
@@ -103,7 +124,7 @@ pure elemental function is_neginf_dp(x) result(y)
implicit none
real(DP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x < 0)
+y = (abs(x) > huge_value(x)) .and. (x < 0)
end function is_neginf_dp
pure elemental function is_inf_sp(x) result(y)
@@ -111,7 +132,7 @@ pure elemental function is_inf_sp(x) result(y)
implicit none
real(SP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x))
+y = (abs(x) > huge_value(x))
end function is_inf_sp
pure elemental function is_inf_dp(x) result(y)
@@ -119,10 +140,47 @@ pure elemental function is_inf_dp(x) result(y)
implicit none
real(DP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x))
+y = (abs(x) > huge_value(x))
end function is_inf_dp
+#if PRIMA_HP_AVAILABLE == 1
+
+pure elemental function is_finite_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+logical :: y
+y = (x <= huge_value(x) .and. x >= -huge_value(x))
+end function is_finite_hp
+
+pure elemental function is_posinf_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+logical :: y
+y = (abs(x) > huge_value(x)) .and. (x > 0)
+end function is_posinf_hp
+
+pure elemental function is_neginf_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+logical :: y
+y = (abs(x) > huge_value(x)) .and. (x < 0)
+end function is_neginf_hp
+
+pure elemental function is_inf_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+logical :: y
+y = (abs(x) > huge_value(x))
+end function is_inf_hp
+
+#endif
+
+
#if PRIMA_QP_AVAILABLE == 1
pure elemental function is_finite_qp(x) result(y)
@@ -130,7 +188,7 @@ pure elemental function is_finite_qp(x) result(y)
implicit none
real(QP), intent(in) :: x
logical :: y
-y = (x <= huge(x) .and. x >= -huge(x))
+y = (x <= huge_value(x) .and. x >= -huge_value(x))
end function is_finite_qp
pure elemental function is_posinf_qp(x) result(y)
@@ -138,7 +196,7 @@ pure elemental function is_posinf_qp(x) result(y)
implicit none
real(QP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x > 0)
+y = (abs(x) > huge_value(x)) .and. (x > 0)
end function is_posinf_qp
pure elemental function is_neginf_qp(x) result(y)
@@ -146,7 +204,7 @@ pure elemental function is_neginf_qp(x) result(y)
implicit none
real(QP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x)) .and. (x < 0)
+y = (abs(x) > huge_value(x)) .and. (x < 0)
end function is_neginf_qp
pure elemental function is_inf_qp(x) result(y)
@@ -154,7 +212,7 @@ pure elemental function is_inf_qp(x) result(y)
implicit none
real(QP), intent(in) :: x
logical :: y
-y = (abs(x) > huge(x))
+y = (abs(x) > huge_value(x))
end function is_inf_qp
#endif
diff --git a/fortran/common/infnan.F90 b/fortran/common/infnan.F90
index 522767a559..6191aeced9 100644
--- a/fortran/common/infnan.F90
+++ b/fortran/common/infnan.F90
@@ -47,35 +47,46 @@ module infnan_mod
! (X > HUGE(X) .OR. X < -HUGE(X)) may differ from (ABS(X) > HUGE(X)) , and
! (ABS(X) > HUGE(X) .AND. X > 0) may differ from (X > HUGE(X)) .
!
-! 8. IS_NAN must be implemented in a file separated from is_inf and is_finite (a separated module is
+! 8. IS_NAN must be implemented in a file separated from IS_INF and IS_FINITE (a separated module is
! not enough). Otherwise, IS_NAN may not work with some compilers invoked with aggressive
! optimization flags e.g., ifx -fast with ifx 2022.1.0 or flang -Ofast with flang 15.0.3.
+! Similarly, the intrinsic HUGE must be wrapped by HUGE_VALUE in a file separated from IS_INF and
+! IS_FINITE. Otherwise, IS_INF and IS_FINITE do not work with `gfortran-13 -Ofast`.
!
-! 9. Even though the functions involve invocation of ABS and HUGE, their performance (in terms of
+! 9. The implementation of IS_NAN may seem unnecessarily complicated and redundant. However, it is
+! the only way that I have found to work with all the compilers that I have tested.
+!
+! 10. Even though the functions involve invocation of ABS and HUGE, their performance (in terms of
! CPU time) turns out comparable to or even better than the functions in IEEE_ARITHMETIC.
!
! Coded by Zaikun ZHANG (www.zhangzk.net).
!
! Started: July 2020.
!
-! Last Modified: Tuesday, April 11, 2023 PM08:09:51
+! Last Modified: Tuesday, February 27, 2024 PM10:56:47
!--------------------------------------------------------------------------------------------------!
-use inf_mod, only : is_finite, is_inf, is_posinf, is_neginf
+use, non_intrinsic :: huge_mod, only : huge_value
+use, non_intrinsic :: inf_mod, only : is_finite, is_inf, is_posinf, is_neginf
implicit none
private
public :: is_finite, is_posinf, is_neginf, is_inf, is_nan
-#if PRIMA_QP_AVAILABLE == 1
-
interface is_nan
- module procedure is_nan_sp, is_nan_dp, is_nan_qp
+ module procedure is_nan_sp, is_nan_dp
end interface is_nan
-#else
+
+#if PRIMA_HP_AVAILABLE == 1
interface is_nan
- module procedure is_nan_sp, is_nan_dp
+ module procedure is_nan_hp
+end interface is_nan
+#endif
+
+#if PRIMA_QP_AVAILABLE == 1
+interface is_nan
+ module procedure is_nan_qp
end interface is_nan
#endif
@@ -89,8 +100,10 @@ pure elemental function is_nan_sp(x) result(y)
implicit none
real(SP), intent(in) :: x
logical :: y
-!y = (.not. (x <= huge(x) .and. x >= -huge(x))) .and. (.not. abs(x) > huge(x)) ! Does not always work
-y = (.not. is_finite(x)) .and. (.not. is_inf(x))
+!y = ((.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .and. (.not. abs(x) > huge_value(x))
+!y = (.not. is_finite(x) .and. .not. (abs(x) > huge_value(x))) .or. y
+y = ((.not. is_finite(x)) .and. (.not. is_inf(x)))
+y = ((.not. is_inf(x)) .and. (.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .or. y
end function is_nan_sp
pure elemental function is_nan_dp(x) result(y)
@@ -98,11 +111,29 @@ pure elemental function is_nan_dp(x) result(y)
implicit none
real(DP), intent(in) :: x
logical :: y
-!y = (.not. (x <= huge(x) .and. x >= -huge(x))) .and. (.not. abs(x) > huge(x)) ! Does not always work
-y = (.not. is_finite(x)) .and. (.not. is_inf(x))
+!y = ((.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .and. (.not. abs(x) > huge_value(x))
+!y = (.not. is_finite(x) .and. .not. (abs(x) > huge_value(x))) .or. y
+y = ((.not. is_finite(x)) .and. (.not. is_inf(x)))
+y = ((.not. is_inf(x)) .and. (.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .or. y
end function is_nan_dp
+#if PRIMA_HP_AVAILABLE == 1
+
+pure elemental function is_nan_hp(x) result(y)
+use, non_intrinsic :: consts_mod, only : HP
+implicit none
+real(HP), intent(in) :: x
+logical :: y
+!y = ((.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .and. (.not. abs(x) > huge_value(x))
+!y = (.not. is_finite(x) .and. .not. (abs(x) > huge_value(x))) .or. y
+y = ((.not. is_finite(x)) .and. (.not. is_inf(x)))
+y = ((.not. is_inf(x)) .and. (.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .or. y
+end function is_nan_hp
+
+#endif
+
+
#if PRIMA_QP_AVAILABLE == 1
pure elemental function is_nan_qp(x) result(y)
@@ -110,8 +141,10 @@ pure elemental function is_nan_qp(x) result(y)
implicit none
real(QP), intent(in) :: x
logical :: y
-!y = (.not. (x <= huge(x) .and. x >= -huge(x))) .and. (.not. abs(x) > huge(x)) ! Does not always work
-y = (.not. is_finite(x)) .and. (.not. is_inf(x))
+!y = ((.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .and. (.not. abs(x) > huge_value(x))
+!y = (.not. is_finite(x) .and. .not. (abs(x) > huge_value(x))) .or. y
+y = ((.not. is_finite(x)) .and. (.not. is_inf(x)))
+y = ((.not. is_inf(x)) .and. (.not. (x <= huge_value(x) .and. x >= -huge_value(x)))) .or. y
end function is_nan_qp
#endif
diff --git a/fortran/common/infos.f90 b/fortran/common/infos.f90
index 72698fd48f..e474a5b98b 100644
--- a/fortran/common/infos.f90
+++ b/fortran/common/infos.f90
@@ -25,12 +25,12 @@ module infos_mod
public :: DAMAGING_ROUNDING
public :: NO_SPACE_BETWEEN_BOUNDS
public :: ZERO_LINEAR_CONSTRAINT
+public :: CALLBACK_TERMINATE
public :: INVALID_INPUT
public :: ASSERTION_FAILS
public :: VALIDATION_FAILS
public :: MEMORY_ALLOCATION_FAILS
-
integer(IK), parameter :: INFO_DFT = 0
integer(IK), parameter :: SMALL_TR_RADIUS = 0
integer(IK), parameter :: FTARGET_ACHIEVED = 1
@@ -43,6 +43,7 @@ module infos_mod
integer(IK), parameter :: NO_SPACE_BETWEEN_BOUNDS = 6
integer(IK), parameter :: DAMAGING_ROUNDING = 7
integer(IK), parameter :: ZERO_LINEAR_CONSTRAINT = 8
+integer(IK), parameter :: CALLBACK_TERMINATE = 30
! Stop-codes.
! The following codes are used by ERROR STOP as stop-codes, which should be default integers.
diff --git a/fortran/common/linalg.f90 b/fortran/common/linalg.f90
index fbf15e5896..b77db15f56 100644
--- a/fortran/common/linalg.f90
+++ b/fortran/common/linalg.f90
@@ -39,7 +39,7 @@ module linalg_mod
!
! Started: July 2020
!
-! Last Modified: Monday, August 14, 2023 PM10:42:59
+! Last Modified: Fri 13 Feb 2026 05:11:41 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -222,6 +222,7 @@ subroutine r1(A, alpha, x, y)
! Calculation starts !
!====================!
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
A = A + outprod(alpha * x, y)
!A = A + alpha * outprod(x, y)
@@ -318,6 +319,7 @@ subroutine r2(A, alpha, x, y, beta, u, v)
! Calculation starts !
!====================!
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
A = A + outprod(alpha * x, y) + outprod(beta * u, v)
!A = A + (alpha * outprod(x, y) + beta * outprod(u, v))
@@ -616,7 +618,7 @@ function solve(A, b) result(x)
! and invertible, and B is a vector of length SIZE(A, 1). The implementation is NAIVE.
! TODO: Better to implement it into several subfunctions: triu, tril, and general square.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite
implicit none
@@ -652,10 +654,9 @@ function solve(A, b) result(x)
return
end if
-! Zaikun 20220527: With the following code, the classic flang 15.0.3, Huawei Bisheng flang 1.3.3,
-! NVIDIA nvfortran 23.1, and AOCC 4.0.0 flang, and Arm Fortran Compiler version 22.1 raise a false
-! positive error of out-bound subscripts when invoked with the -Mbounds flag.
-! See https://github.com/flang-compiler/flang/issues/1238
+! Zaikun 20220527: With the following code, Huawei Bisheng flang 2.1.0, Arm Fortran Compiler 23.1,
+! and AOCC 5.1 flang, which raise a false positive error about out-bound subscripts when invoked
+! with the -Mbounds flag. See https://github.com/flang-compiler/flang/issues/1238
if (istril(A)) then
do i = 1, n
x(i) = (b(i) - inprod(A(i, 1:i - 1), x(1:i - 1))) / A(i, i) ! INPROD = 0 if I == 1.
@@ -682,7 +683,7 @@ function solve(A, b) result(x)
if (DEBUGGING) then
call assert(size(x) == size(A, 2), 'SIZE(X) == SIZE(A, 2)', srname)
if (is_finite(sum(abs(A)) + sum(abs(b)))) then
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n + 1_IK, RP)))
call assert(norm(matprod(A, x) - b) <= tol * maxval([ONE, norm(b), norm(x)]), 'A*X == B', srname)
end if
end if
@@ -698,7 +699,7 @@ function inv(A) result(B)
! implement it into several subfunctions: triu with M >= N, tril with M <= N; general with M >= N,
! general with M <= N, etc.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TEN, MAXPOW10, EPS, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -751,6 +752,7 @@ function inv(A) result(B)
! This is NOT the best algorithm for the inverse, but since the QR subroutine is available ...
call qr(A, Q, R, P)
R = transpose(R) ! Take transpose to work on columns.
+ B = ZERO
do i = n, 1, -1
B(:, i) = (Q(:, i) - matprod(B(:, i + 1:n), R(i + 1:n, i))) / R(i, i)
end do
@@ -767,7 +769,7 @@ function inv(A) result(B)
call assert(size(B, 1) == n .and. size(B, 2) == n, 'SIZE(B) == [N, N]', srname)
call assert(istril(B) .or. .not. istril(A), 'If A is lower triangular, then so is B', srname)
call assert(istriu(B) .or. .not. istriu(A), 'If A is upper triangular, then so is B', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E10_RP * EPS * real(n + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(10, MAXPOW10) * EPS * real(n + 1_IK, RP)))
call assert(isinv(A, B, tol), 'B = A^{-1}', srname)
end if
end function inv
@@ -829,7 +831,7 @@ subroutine qr(A, Q, R, P)
! A = Q*R (if no pivoting) or A(:, P) = Q*R (if pivoting), where the columns of Q are orthonormal,
! and R is upper triangular.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -919,7 +921,7 @@ subroutine qr(A, Q, R, P)
! Postconditions
if (DEBUGGING) then
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E4_RP * EPS * real(max(m, n) + 1_IK, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(4, MAXPOW10) * EPS * real(max(m, n) + 1_IK, RP)))
call assert(isorth(Q_loc, tol), 'The columns of Q are orthonormal', srname)
call assert(istril(T, tol), 'R is upper triangular', srname)
if (pivot) then
@@ -950,7 +952,7 @@ function lsqr_Rdiag(A, b, Q, Rdiag) result(x)
! 3. A HAS FULL COLUMN RANK;
! 4. It seems that b (CGRAD and DNEW) is in the column space of A (not sure yet).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -987,7 +989,7 @@ function lsqr_Rdiag(A, b, Q, Rdiag) result(x)
if (present(Q)) then
call assert(size(Q, 1) == m .and. (size(Q, 2) == m .or. size(Q, 2) == min(m, n)), &
& 'SIZE(Q) == [M, N] .or. SIZE(Q) == [M, MIN(M, N)]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E6_RP * EPS * real(max(m, n) + 1_IK, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(6, MAXPOW10) * EPS * real(max(m, n) + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
end if
if (present(Rdiag)) then
@@ -1063,7 +1065,7 @@ function lsqr_Rfull(b, Q, R) result(x)
! 1. The economy-size QR factorization is supplied externally (Q is called QFAC and R is called RFAC);
! 2. R is non-singular.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -1090,7 +1092,7 @@ function lsqr_Rfull(b, Q, R) result(x)
call assert(m >= n .and. n >= 0, 'M >= N >= 0', srname)
call assert(size(b) == m, 'SIZE(B) == M', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) == n, 'SIZE(Q) == [M, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E6_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(6, MAXPOW10) * EPS * real(m + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(size(R, 1) == n .and. size(R, 2) == n, 'SIZE(R) == [N, N]', srname)
call assert(istriu(R), 'R is upper triangular', srname)
@@ -1327,7 +1329,7 @@ function isorth(A, tol) result(is_orth)
!--------------------------------------------------------------------------------------------------!
! This function tests whether the matrix A has orthonormal columns up to the tolerance TOL.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, DEBUGGING, REALMAX, ORTHTOL_DFT
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan
implicit none
@@ -1340,6 +1342,7 @@ function isorth(A, tol) result(is_orth)
! Local variables
character(len=*), parameter :: srname = 'ISORTH'
integer(IK) :: n
+real(RP) :: tol_loc
! Preconditions
if (DEBUGGING) then
@@ -1352,18 +1355,24 @@ function isorth(A, tol) result(is_orth)
! Calculation starts !
!====================!
+tol_loc = ORTHTOL_DFT
+if (present(tol)) then
+ tol_loc = tol
+end if
+
n = int(size(A, 2), kind(n))
+! N.B. (20240304): In some cases, due to compiler bugs, we need to disable the test. We signify such
+! cases by setting ORTHTOL_DFT to REALMAX. For instance, NAG Fortran Compiler Release 7.1(Hanzomon)
+! Build 7143 is buggy concerning half-precision floating-point numbers. See the following:
+! https://fortran-lang.discourse.group/t/nagfor-7-1-supports-half-precision-floating-point-numbers-but-with-many-bugs
+is_orth = .true.
if (n > size(A, 1)) then
is_orth = .false.
-else if (is_nan(sum(abs(A)))) then
+elseif (any(is_nan(A))) then
is_orth = .false.
-else
- if (present(tol)) then
- is_orth = all(abs(matprod(transpose(A), A) - eye(n)) <= max(tol, tol * maxval(abs(A))))
- else
- is_orth = all(abs(matprod(transpose(A), A) - eye(n)) <= 0)
- end if
+elseif (ORTHTOL_DFT < REALMAX) then
+ is_orth = all(abs(matprod(transpose(A), A) - eye(n)) <= max(tol_loc, tol_loc * maxval(abs(A))))
end if
!====================!
@@ -1376,7 +1385,7 @@ function project1(x, v) result(y)
!--------------------------------------------------------------------------------------------------!
! This function returns the projection of X to SPAN(V).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, ONE, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, ONE, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_inf, is_finite, is_nan
implicit none
@@ -1389,7 +1398,6 @@ function project1(x, v) result(y)
! Local variables
character(len=*), parameter :: srname = 'PROJECT1'
real(RP) :: u(size(v))
-real(RP) :: scaling
real(RP) :: tol
! Preconditions
@@ -1410,14 +1418,10 @@ function project1(x, v) result(y)
u(trueloc(is_inf(v))) = sign(ONE, v(trueloc(is_inf(v))))
!!MATLAB: u = 0; u(isinf(v)) = sign(v(isinf(v)))
u = u / norm(u)
- !y = inprod(x, u) * u
- scaling = maxval(abs(x)) ! The scaling seems to reduce the rounding error.
- y = scaling * inprod(x / scaling, u) * u
+ y = inprod(x, u) * u
else
u = v / norm(v)
- !y = inprod(x, u) * u
- scaling = maxval(abs(x)) ! The scaling seems to reduce the rounding error.
- y = scaling * inprod(x / scaling, u) * u
+ y = inprod(x, u) * u
end if
!====================!
@@ -1427,7 +1431,7 @@ function project1(x, v) result(y)
! Postconditions
if (DEBUGGING) then
if (is_finite(norm(x)) .and. is_finite(norm(v))) then
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E6_RP * EPS))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(6, MAXPOW10) * EPS))
call assert(norm(y) <= (ONE + tol) * norm(x), 'NORM(Y) <= NORM(X)', srname)
call assert(norm(x - y) <= (ONE + tol) * norm(x), 'NORM(X - Y) <= NORM(X)', srname)
! The following test may not be passed.
@@ -1442,7 +1446,7 @@ function project2(x, V) result(y)
!--------------------------------------------------------------------------------------------------!
! This function returns the projection of X to RANGE(V).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, ONE, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, ONE, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_inf, is_finite, is_nan
implicit none
@@ -1494,7 +1498,7 @@ function project2(x, V) result(y)
! Postconditions
if (DEBUGGING) then
if (is_finite(norm(x)) .and. is_finite(sum(V**2))) then
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E6_RP * EPS))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(6, MAXPOW10) * EPS))
call assert(norm(y) <= (ONE + tol) * norm(x), 'NORM(Y) <= NORM(X)', srname)
call assert(norm(x - y) <= (ONE + tol) * norm(x), 'NORM(X - Y) <= NORM(X)', srname)
! The following test may not be passed.
@@ -1573,7 +1577,7 @@ function planerot(x) result(G)
! 2. Difference from MATLAB: if X contains NaN or consists of only Inf, MATLAB returns a NaN matrix,
! but we return an identity matrix or a matrix of +/-SQRT(2). We intend to keep G always orthogonal.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, ZERO, ONE, REALMIN, EPS, REALMAX, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, ZERO, ONE, REALMIN, EPS, REALMAX, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite, is_nan, is_inf
implicit none
@@ -1664,7 +1668,7 @@ function planerot(x) result(G)
call assert(all(is_finite(G)), 'G is finite', srname)
call assert(abs(G(1, 1) - G(2, 2)) + abs(G(1, 2) + G(2, 1)) <= 0, &
& 'G(1,1) == G(2,2), G(1,2) = -G(2,1)', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E6_RP * EPS))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, 10.0_RP**min(6, MAXPOW10) * EPS))
call assert(isorth(G, tol), 'G is orthonormal', srname)
if (all(is_finite(x) .and. abs(x) < sqrt(REALMAX / 2.1_RP))) then
r = norm(x)
@@ -1859,9 +1863,9 @@ function p_norm(x, p) result(y)
!--------------------------------------------------------------------------------------------------!
! This function calculates the P-norm of a vector X.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, ONE, TWO, ZERO, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
-use, non_intrinsic :: infnan_mod, only : is_finite, is_posinf
+use, non_intrinsic :: consts_mod, only : RP, ONE, TWO, ZERO, REALMIN, DEBUGGING
+use, non_intrinsic :: debug_mod, only : assert, validate
+use, non_intrinsic :: infnan_mod, only : is_finite, is_posinf, is_nan
implicit none
! Inputs
@@ -1871,13 +1875,15 @@ function p_norm(x, p) result(y)
real(RP) :: y
! Local variables
character(len=*), parameter :: srname = 'P_NORM'
+real(RP) :: maxabs
real(RP) :: p_loc
+real(RP) :: scaling
+real(RP) :: scalmax
+real(RP) :: scalmin
! Preconditions
-if (DEBUGGING) then
- if (present(p)) then
- call assert(p >= 0, 'P >= 0', srname)
- end if
+if (present(p)) then
+ call validate(p >= 0, 'P >= 0', srname)
end if
!====================!
@@ -1890,28 +1896,65 @@ function p_norm(x, p) result(y)
p_loc = TWO
end if
+! If SIZE(X) = 0, then MAXVAL(ABS(X)) = -HUGE(X); since we handle such a case individually,
+! it is OK to write MAXVAL(ABS(X)) below, but we append 0 for robustness.
+maxabs = maxval([abs(x), ZERO])
+
if (size(x) == 0) then
y = ZERO
-elseif (p_loc <= 0) then
+elseif (p_loc <= 0 .and. .not. any(is_nan(x))) then
y = real(count(abs(x) > 0), kind(y))
elseif (.not. all(is_finite(x))) then
- ! If X contains NaN, then Y is NaN. Otherwise, Y is Inf when X contains +/-Inf.
+ ! If X contains NaN, then Y is NaN. Otherwise, Y is Inf when X contains +/-Inf unless P = 0.
y = sum(abs(x))
-elseif (.not. any(abs(x) > 0)) then
- ! The following is incorrect without checking the last case, as X may be all NaN.
+elseif (maxabs <= 0) then
+ ! If MAXABS is zero, then Y is zero. Note that we do this only when X does not contain NaN.
+ ! Otherwise, MAXABS = 0 does not necessarily guarantee that X is all zero.
y = ZERO
-else
+else ! Now P > 0 and X is a finite-valued nonzero vector, as we have handled the other cases above.
if (is_posinf(p_loc)) then
- ! If SIZE(X) = 0, then MAXVAL(ABS(X)) = -HUGE(X); since we have handled such a case in the
- ! above, it is OK to write Y = MAXVAL(ABS(X)) below, but we append 0 for robustness.
- y = maxval([abs(x), ZERO])
- elseif (.not. present(p) .or. abs(p_loc - TWO) <= 0) then
- ! N.B.: We may use the intrinsic NORM2. Here, we use the following naive implementation to
- ! get full control on the computation in a way similar to MATPROD and INPROD. A disadvantage
- ! is the possibility of over/underflow.
+ y = maxabs
+ elseif (abs(p_loc - ONE) <= 0) then
+ y = sum(abs(x))
+ elseif (abs(p_loc - TWO) <= 0) then
+ ! N.B.: We may use the intrinsic NORM2. Here, we use the following naive implementation
+ ! to get full control on the computation in a way similar to MATPROD and INPROD.
+
+ ! To avoid over/underflow, we scale X by SCALING defined as follows if necessary. We make
+ ! sure SCALMIN >= MAX(REALMIN, 1/REALMAX) and SCALMAX <= MIN(REALMAX, 1/REALMIN), or we may
+ ! encounter over/underflow when dividing by SCALING, and even NaN if the compiler evaluates
+ ! 1/SCALING first, which did happen with `flang -ffast-math` on REAL32 with LLVM flang 21.
+ !
+ ! Given a numeric model for floating-point numbers,
+ ! REALMIN = TINY(ZERO) = 2^{emin-1}, REALMAX = HUGE(ZERO) = (1-b^{-d})*b^{emax} >= b^{emax-1},
+ ! where b = RADIX(ZERO) is the base, d = DIGITS(ZERO) > 0 is the number of base-b significant
+ ! digits, emin = MINEXPONENT(ZERO) & emax = MAXEXPONENT(ZERO) are the min & max exponents.
+ ! N.B.: IEEE 754 specifies emax and requires that emin = 1 - emax for "Binary interchange
+ ! floating-point formats" binary32, binary64, and binary128 (Sec. 3.3 of IEEE Std 754-2019).
+ ! However, mathematically, [emin, emax] defined in Fortran standards indeed corresponds to
+ ! [emin+1, emax+1] in IEEE 754. In addition, Fortran compilers may not implement REAL32,
+ ! REAL64, and REAL128 according to binary32, binary64, and binary128. For instance,
+ ! nagfor 7 has d = 106, emin = -968 and emax = 1023 for REAL128, while
+ ! IEEE 754 has d = 113, emin = -16382, and emax = 16383 for binary128. See
+ ! http://fortran-lang.discourse.group/t/ieee-754-binary-interchange-floating-point-formats-versus-iso-fortran-env-real-kinds
+
y = sqrt(sum(x**2))
+ ! The following code handles over/underflow naively.
+ if (is_posinf(y) .or. y <= 0) then
+ scalmin = real(radix(ZERO), RP)**max(minexponent(ZERO) - 1, 1 - maxexponent(ZERO))
+ scalmax = real(radix(ZERO), RP)**min(maxexponent(ZERO) - 1, 1 - minexponent(ZERO))
+ scaling = min(max(maxabs, scalmin), scalmax)
+ y = scaling * sqrt(sum((x / scaling)**2))
+ end if
else
y = sum(abs(x)**p_loc)**(ONE / p_loc)
+ ! The following code handles over/underflow naively.
+ if (is_posinf(y) .or. y <= 0) then
+ scalmin = real(radix(ZERO), RP)**max(minexponent(ZERO) - 1, 1 - maxexponent(ZERO))
+ scalmax = real(radix(ZERO), RP)**min(maxexponent(ZERO) - 1, 1 - minexponent(ZERO))
+ scaling = min(max(maxabs, scalmin), scalmax)
+ y = scaling * sum(abs(x / scaling)**p_loc)**(ONE / p_loc)
+ end if
end if
end if
@@ -1919,6 +1962,14 @@ function p_norm(x, p) result(y)
! Calculation ends !
!====================!
+if (DEBUGGING) then
+ call assert(y >= 0 .or. any(is_nan(x)), 'Y >= 0 unless X contains NaN', srname)
+ call assert(is_nan(y) .eqv. any(is_nan(x)), 'Y is NaN if and only if X contains NaN', srname)
+ ! Even with scaling, Y may still be 0 if all entries of X are zero or subnormal.
+ call assert(y > 0 .or. any(is_nan(x)) .or. all(abs(x) < REALMIN), &
+ & 'Y > 0 unless X contains NaN or all its entries are below REALMIN', srname)
+end if
+
end function p_norm
function named_norm_vec(x, nname) result(y)
@@ -1992,6 +2043,8 @@ function named_norm_mat(x, nname) result(y)
! Calculation starts !
!====================!
+! N.B.: Ideally, we should also do a scaling similar to that in P_NORM to avoid over/underflow.
+
if (size(x, 1) * size(x, 2) == 0) then
y = ZERO
elseif (.not. all(is_finite(x))) then
@@ -2641,7 +2694,7 @@ subroutine hessenberg_full(A, H, Q)
! This subroutine finds a Hessenberg matrix H (all entries below the subdiagonal are 0) such that
! H = Q^T*A*Q, where Q is a orthogonal matrix that may also be returned. A will stay unchanged.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TWO, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TWO, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -2749,7 +2802,7 @@ subroutine hessenberg_full(A, H, Q)
if (DEBUGGING) then
call assert(size(H, 1) == n .and. size(H, 2) == n, 'SIZE(H) == [N, N]', srname)
call assert(isbanded(H, 1_IK, n - 1_IK), 'H is a Hessenberg matrix', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E10_RP * EPS * real(n, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(10, MAXPOW10) * EPS * real(n, RP)))
call assert(issymmetric(H, tol) .or. .not. issymmetric(A), 'H is symmetric if so is A', srname)
if (present(Q)) then
call assert(size(Q, 1) == n .and. size(Q, 2) == n, 'SIZE(Q) == [N, N]', srname)
@@ -2790,7 +2843,7 @@ function eigmin_sym_trid(td, tn, tol) result(eig_min)
! !crvmin = eigs(tridh, 1, 'smallestreal');
! !% It is critical for the efficiency to use `spdiags` to construct `tridh` in the sparse form.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -2830,7 +2883,7 @@ function eigmin_sym_trid(td, tn, tol) result(eig_min)
!====================!
maxiter = 100
-tol_loc = 1.0E-6_RP
+tol_loc = TEN**max(-6, -MAXPOW10)
if (present(tol)) then
tol_loc = tol
end if
diff --git a/fortran/common/memory.F90 b/fortran/common/memory.F90
index 386f95c879..c5fce181d4 100644
--- a/fortran/common/memory.F90
+++ b/fortran/common/memory.F90
@@ -13,7 +13,7 @@ module memory_mod
!
! Started: July 2020
!
-! Last Modified: Tuesday, May 09, 2023 AM11:42:31
+! Last Modified: Wednesday, February 28, 2024 AM12:20:23
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -23,6 +23,9 @@ module memory_mod
interface cstyle_sizeof
module procedure size_of_sp, size_of_dp
+#if PRIMA_HP_AVAILABLE == 1
+ module procedure size_of_hp
+#endif
#if PRIMA_QP_AVAILABLE == 1
module procedure size_of_qp
#endif
@@ -34,6 +37,9 @@ module memory_mod
module procedure alloc_rvector_sp, alloc_rmatrix_sp
module procedure alloc_rvector_dp, alloc_rmatrix_dp
module procedure alloc_character
+#if PRIMA_HP_AVAILABLE == 1
+ module procedure alloc_rvector_hp, alloc_rmatrix_hp
+#endif
#if PRIMA_QP_AVAILABLE == 1
module procedure alloc_rvector_qp, alloc_rmatrix_qp
#endif
@@ -75,6 +81,25 @@ pure function size_of_dp(x) result(y)
end function size_of_dp
+#if PRIMA_HP_AVAILABLE == 1
+
+pure function size_of_hp(x) result(y)
+!--------------------------------------------------------------------------------------------------!
+! Return the storage size of X in Bytes, X being a REAL(HP) scalar.
+!--------------------------------------------------------------------------------------------------!
+use, non_intrinsic :: consts_mod, only : HP, IK
+implicit none
+! Inputs
+real(HP), intent(in) :: x
+! Outputs
+integer(IK) :: y
+
+y = int(storage_size(x) / 8, kind(y))
+end function size_of_hp
+
+#endif
+
+
#if PRIMA_QP_AVAILABLE == 1
pure function size_of_qp(x) result(y)
@@ -130,6 +155,7 @@ subroutine alloc_rvector_sp(x, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
end subroutine alloc_rvector_sp
@@ -163,6 +189,8 @@ subroutine alloc_rmatrix_sp(x, m, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x, 1) == m .and. size(x, 2) == n, 'SIZE(X) == [M, N]', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == m, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == M', srname)
+call validate(lbound(x, 2) == 1 .and. ubound(x, 2) == n, 'LBOUND(X, 2) == 1, UBOUND(X, 2) == N', srname)
end subroutine alloc_rmatrix_sp
@@ -199,6 +227,7 @@ subroutine alloc_rvector_dp(x, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
end subroutine alloc_rvector_dp
@@ -232,9 +261,87 @@ subroutine alloc_rmatrix_dp(x, m, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x, 1) == m .and. size(x, 2) == n, 'SIZE(X) == [M, N]', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == m, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == M', srname)
+call validate(lbound(x, 2) == 1 .and. ubound(x, 2) == n, 'LBOUND(X, 2) == 1, UBOUND(X, 2) == N', srname)
end subroutine alloc_rmatrix_dp
+#if PRIMA_HP_AVAILABLE == 1
+
+subroutine alloc_rvector_hp(x, n)
+!--------------------------------------------------------------------------------------------------!
+! Allocate space for an allocatable REAL(HP) vector X, whose size is N after allocation.
+!--------------------------------------------------------------------------------------------------!
+use, non_intrinsic :: consts_mod, only : HP, IK
+use, non_intrinsic :: debug_mod, only : validate
+implicit none
+
+! Inputs
+integer(IK), intent(in) :: n
+
+! Outputs
+real(HP), allocatable, intent(out) :: x(:)
+
+! Local variables
+integer :: alloc_status
+character(len=*), parameter :: srname = 'ALLOC_RVECTOR_HP'
+
+! Preconditions (checked even not debugging)
+call validate(n >= 0, 'N >= 0', srname)
+
+! According to the Fortran 2003 standard, when a procedure is invoked, any allocated ALLOCATABLE
+! object that is an actual argument associated with an INTENT(OUT) ALLOCATABLE dummy argument is
+! deallocated. So the following line is unnecessary since F2003 as X is INTENT(OUT):
+! !if (allocated(x)) deallocate (x)
+! Allocate memory for X. Initialize X to a compiler-independent strange value.
+allocate (x(1:n), stat=alloc_status) ! Absoft does not support the SOURCE keyword as of 2022.
+x = -huge(x) ! Costly if X is of a large size.
+
+! Postconditions (checked even not debugging)
+call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
+call validate(allocated(x), 'X is allocated', srname)
+call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
+end subroutine alloc_rvector_hp
+
+
+subroutine alloc_rmatrix_hp(x, m, n)
+!--------------------------------------------------------------------------------------------------!
+! Allocate space for an allocatable REAL(HP) matrix X, whose size is (M, N) after allocation.
+!--------------------------------------------------------------------------------------------------!
+use, non_intrinsic :: consts_mod, only : HP, IK
+use, non_intrinsic :: debug_mod, only : validate
+implicit none
+
+! Inputs
+integer(IK), intent(in) :: m, n
+
+! Outputs
+real(HP), allocatable, intent(out) :: x(:, :)
+
+! Local variables
+integer :: alloc_status
+character(len=*), parameter :: srname = 'ALLOC_RMATRIX_HP'
+
+! Preconditions (checked even not debugging)
+call validate(m >= 0 .and. n >= 0, 'M >= 0, N >= 0', srname)
+
+! !if (allocated(x)) deallocate (x) ! Unnecessary in F03 since X is INTENT(OUT)
+! Allocate memory for X. Initialize X to a compiler-independent strange value.
+allocate (x(1:m, 1:n), stat=alloc_status) ! Absoft does not support the SOURCE keyword as of 2022.
+x = -huge(x) ! Costly if X is of a large size.
+
+! Postconditions (checked even not debugging)
+call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
+call validate(allocated(x), 'X is allocated', srname)
+call validate(size(x, 1) == m .and. size(x, 2) == n, 'SIZE(X) == [M, N]', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == m, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == M', srname)
+call validate(lbound(x, 2) == 1 .and. ubound(x, 2) == n, 'LBOUND(X, 2) == 1, UBOUND(X, 2) == N', srname)
+end subroutine alloc_rmatrix_hp
+
+#endif
+
+
#if PRIMA_QP_AVAILABLE == 1
subroutine alloc_rvector_qp(x, n)
@@ -270,6 +377,7 @@ subroutine alloc_rvector_qp(x, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
end subroutine alloc_rvector_qp
@@ -303,6 +411,8 @@ subroutine alloc_rmatrix_qp(x, m, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x, 1) == m .and. size(x, 2) == n, 'SIZE(X) == [M, N]', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == m, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == M', srname)
+call validate(lbound(x, 2) == 1 .and. ubound(x, 2) == n, 'LBOUND(X, 2) == 1, UBOUND(X, 2) == N', srname)
end subroutine alloc_rmatrix_qp
#endif
@@ -338,6 +448,7 @@ subroutine alloc_lvector(x, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
end subroutine alloc_lvector
@@ -371,6 +482,7 @@ subroutine alloc_ivector(x, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x) == n, 'SIZE(X) == N', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == n, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == N', srname)
end subroutine alloc_ivector
@@ -404,6 +516,8 @@ subroutine alloc_imatrix(x, m, n)
call validate(alloc_status == 0, 'Memory allocation succeeds (ALLOC_STATUS == 0)', srname)
call validate(allocated(x), 'X is allocated', srname)
call validate(size(x, 1) == m .and. size(x, 2) == n, 'SIZE(X) == [M, N]', srname)
+call validate(lbound(x, 1) == 1 .and. ubound(x, 1) == m, 'LBOUND(X, 1) == 1, UBOUND(X, 1) == M', srname)
+call validate(lbound(x, 2) == 1 .and. ubound(x, 2) == n, 'LBOUND(X, 2) == 1, UBOUND(X, 2) == N', srname)
end subroutine alloc_imatrix
diff --git a/fortran/common/message.f90 b/fortran/common/message.f90
index 779259e6a6..5199dc6ff2 100644
--- a/fortran/common/message.f90
+++ b/fortran/common/message.f90
@@ -12,7 +12,7 @@ module message_mod
!
! Started: July 2020
!
-! Last Modified: Friday, August 11, 2023 AM12:22:47
+! Last Modified: Sunday, March 31, 2024 PM04:55:58
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -33,7 +33,8 @@ subroutine retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
use, non_intrinsic :: fprint_mod, only : fprint
use, non_intrinsic :: infos_mod, only : FTARGET_ACHIEVED, MAXFUN_REACHED, MAXTR_REACHED, &
& SMALL_TR_RADIUS, TRSUBP_FAILED, NAN_INF_X, NAN_INF_F, NAN_INF_MODEL, DAMAGING_ROUNDING, &
- & NO_SPACE_BETWEEN_BOUNDS, ZERO_LINEAR_CONSTRAINT
+ & NO_SPACE_BETWEEN_BOUNDS, ZERO_LINEAR_CONSTRAINT, CALLBACK_TERMINATE
+use, non_intrinsic :: linalg_mod, only : maximum
use, non_intrinsic :: string_mod, only : strip, num2str
implicit none
@@ -95,7 +96,7 @@ subroutine retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
if (present(cstrv)) then
cstrv_loc = cstrv
elseif (present(constr)) then
- cstrv_loc = maxval([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
+ cstrv_loc = maximum([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
else
cstrv_loc = ZERO
end if
@@ -124,6 +125,8 @@ subroutine retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
reason = 'there is no space between the lower and upper bounds of variable.'
case (ZERO_LINEAR_CONSTRAINT)
reason = 'one of the linear constraints has a zero gradient'
+case (CALLBACK_TERMINATE)
+ reason = 'callback function requested termination of optimization'
case default
reason = 'UNKNOWN EXIT FLAG'
end select
@@ -176,6 +179,7 @@ subroutine rhomsg(solver, iprint, nf, delta, f, rho, x, cstrv, constr, cpen)
!--------------------------------------------------------------------------------------------------!
use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, STDOUT
use, non_intrinsic :: fprint_mod, only : fprint
+use, non_intrinsic :: linalg_mod, only : maximum
use, non_intrinsic :: string_mod, only : strip, num2str
implicit none
@@ -229,7 +233,7 @@ subroutine rhomsg(solver, iprint, nf, delta, f, rho, x, cstrv, constr, cpen)
if (present(cstrv)) then
cstrv_loc = cstrv
elseif (present(constr)) then
- cstrv_loc = maxval([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
+ cstrv_loc = maximum([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
else
cstrv_loc = ZERO
end if
@@ -341,6 +345,7 @@ subroutine fmsg(solver, state, iprint, nf, delta, f, x, cstrv, constr)
!--------------------------------------------------------------------------------------------------!
use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, STDOUT
use, non_intrinsic :: fprint_mod, only : fprint
+use, non_intrinsic :: linalg_mod, only : maximum
use, non_intrinsic :: string_mod, only : strip, num2str
implicit none
@@ -395,7 +400,7 @@ subroutine fmsg(solver, state, iprint, nf, delta, f, x, cstrv, constr)
if (present(cstrv)) then
cstrv_loc = cstrv
elseif (present(constr)) then
- cstrv_loc = maxval([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
+ cstrv_loc = maximum([ZERO, -constr]) ! N.B.: We assume that the constraint is CONSTR >= 0.
else
cstrv_loc = ZERO
end if
diff --git a/fortran/common/pintrf.f90 b/fortran/common/pintrf.f90
index 8a610e0c60..a95b446ada 100644
--- a/fortran/common/pintrf.f90
+++ b/fortran/common/pintrf.f90
@@ -1,21 +1,23 @@
module pintrf_mod
!--------------------------------------------------------------------------------------------------!
-! This is a module specifying the abstract interfaces OBJ and OBJCON. OBJ evaluates the objective
-! function for unconstrained, bound constrained, and linearly constrained problems; OBJCON evaluates
-! the objective and constraint functions for nonlinearly constrained problems.
+! This is a module specifying the abstract interfaces OBJ, OBJCON, and CALLBACK. OBJ evaluates the
+! objective function for unconstrained, bound constrained, and linearly constrained problems; OBJCON
+! evaluates the objective and constraint functions for nonlinearly constrained problems; CALLBACK
+! is a callback function that is called after each iteration of the solvers to report the progress
+! and optionally request termination.
!
! Coded by Zaikun ZHANG (www.zhangzk.net).
!
! Started: July 2020.
!
-! Last Modified: Monday, February 07, 2022 AM12:28:54
+! Last Modified: Friday, December 22, 2023 PM01:23:44
!--------------------------------------------------------------------------------------------------!
!!!!!! Users must provide the implementation of OBJ or OBJCON. !!!!!!
implicit none
private
-public :: OBJ, OBJCON
+public :: OBJ, OBJCON, CALLBACK
abstract interface
@@ -36,7 +38,19 @@ subroutine OBJCON(x, f, constr)
real(RP), intent(out) :: constr(:)
end subroutine OBJCON
-end interface
+ subroutine CALLBACK(x, f, nf, tr, cstrv, nlconstr, terminate)
+ use consts_mod, only : RP, IK
+ implicit none
+ real(RP), intent(in) :: x(:)
+ real(RP), intent(in) :: f
+ integer(IK), intent(in) :: nf
+ integer(IK), intent(in) :: tr
+ real(RP), intent(in), optional :: cstrv
+ real(RP), intent(in), optional :: nlconstr(:)
+ logical, intent(out), optional :: terminate
+ end subroutine CALLBACK
+
+end interface
end module pintrf_mod
diff --git a/fortran/common/powalg.f90 b/fortran/common/powalg.f90
index 9f3af8629d..646b2ffbd7 100644
--- a/fortran/common/powalg.f90
+++ b/fortran/common/powalg.f90
@@ -21,7 +21,7 @@ module powalg_mod
!
! Started: July 2020
!
-! Last Modified: Monday, September 04, 2023 PM01:05:54
+! Last Modified: Thu 14 Aug 2025 07:36:04 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -84,7 +84,7 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
! and R(:, N) (N takes the updated value).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite
use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, planerot, hypotenuse, isorth, isminor, trueloc
@@ -121,7 +121,7 @@ subroutine qradd_Rdiag(c, Q, Rdiag, n) ! Used in COBYLA
call assert(size(c) == m, 'SIZE(C) == M', srname)
call assert(size(Rdiag) >= min(m, n + 1_IK) .and. size(Rdiag) <= m, 'MIN(M, N+1) <= SIZE(Rdiag) <= M', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) == m, 'SIZE(Q) == [M, M]', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E12_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(12, MAXPOW10) * EPS * real(m + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
Qsave = Q(:, 1:n) ! For debugging only
Rdsave = Rdiag(1:n) ! For debugging only
@@ -202,7 +202,7 @@ subroutine qradd_Rfull(c, Q, R, n) ! Used in LINCOA
! 1. At entry, Q is a MxM orthonormal matrix, and R is a MxL upper triangular matrix with N < L <= M.
! 2. The subroutine changes only Q(:, N+1:M) and R(:, N+1) with N taking the original value.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : matprod, planerot, isorth, istriu, diag
implicit none
@@ -237,7 +237,7 @@ subroutine qradd_Rfull(c, Q, R, n) ! Used in LINCOA
call assert(size(Q, 1) == m .and. size(Q, 2) == m, 'SIZE(Q) = [M, M]', srname)
call assert(size(Q, 2) == size(R, 1), 'SIZE(Q, 2) == SIZE(R, 1)', srname)
call assert(size(R, 2) >= n + 1 .and. size(R, 2) <= m, 'N+1 <= SIZE(R, 2) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(m + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(istriu(R), 'R is upper triangular', srname)
call assert(all(diag(R(:, 1:n)) > 0), 'DIAG(R(:, 1:N)) > 0', srname)
@@ -302,7 +302,7 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
! 1. With L = SIZE(Q, 2) = SIZE(R, 1), we have M >= L >= N. Most often, L = M or N.
! 2. The subroutine changes only Q(:, I:N) and RDIAG(I:N).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, planerot, isorth, istriu, diag
implicit none
@@ -340,7 +340,7 @@ subroutine qrexc_Rdiag(A, Q, Rdiag, i) ! Used in COBYLA
call assert(size(Rdiag) == n, 'SIZE(Rdiag) == N', srname)
call assert(size(Q, 1) == m .and. size(Q, 2) >= n .and. size(Q, 2) <= m, &
& 'SIZE(Q, 1) == M, N <= SIZE(Q, 2) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(m + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthonormal', srname) ! Costly!
Qsave = Q ! For debugging only.
Rdsave = Rdiag(1:i) ! For debugging only.
@@ -422,7 +422,7 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
! 1. With L = SIZE(Q, 2) = SIZE(R, 1), we have M >= L >= N. Most often, L = M or N.
! 2. The subroutine changes only Q(:, I:N) and R(:, I:N).
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : matprod, planerot, isorth, istriu, hypotenuse, diag
implicit none
@@ -459,7 +459,7 @@ subroutine qrexc_Rfull(Q, R, i) ! Used in LINCOA
call assert(size(Q, 2) == size(R, 1), 'SIZE(Q, 2) == SIZE(R, 1)', srname)
call assert(size(Q, 2) >= n .and. size(Q, 2) <= m, 'N <= SIZE(Q, 2) <= M', srname)
call assert(size(R, 1) >= n .and. size(R, 1) <= m, 'N <= SIZE(R, 1) <= M', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(m + 1_IK, RP)))
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(m + 1_IK, RP)))
call assert(isorth(Q, tol), 'The columns of Q are orthogonal', srname)
call assert(istriu(R), 'R is upper triangular', srname)
call assert(all(diag(R(:, 1:n)) > 0), 'DIAG(R(:, 1:N)) > 0', srname)
@@ -1110,6 +1110,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
use, non_intrinsic :: infnan_mod, only : is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT, DAMAGING_ROUNDING
use, non_intrinsic :: linalg_mod, only : matprod, planerot, symmetrize, issymmetric, outprod!, r2update
+use, non_intrinsic :: string_mod, only : num2str
implicit none
! Inputs
@@ -1149,7 +1150,6 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
real(RP) :: v1(size(bmat, 1))
real(RP) :: v2(size(bmat, 1))
real(RP) :: vlag(size(bmat, 2))
-real(RP) :: ztest
! Debugging variables
!real(RP) :: beta_test
@@ -1172,6 +1172,13 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
& 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
+
+ do j = 1, npt
+ hcol(1:npt) = omega_col(idz, zmat, j)
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
call assert(all(is_finite(xpt)), 'XPT is finite', srname)
! Theoretically, CALVLAG and CALBETA should be independent of the reference point XPT(:, KREF).
@@ -1183,15 +1190,15 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! call safealloc(vlag_test, npt + n)
! vlag_test = calvlag(knew, bmat, d + (xpt(:, kref) - xpt(:, knew)), xpt, zmat, idz)
! call wassert(all(abs(vlag_test - calvlag(kref, bmat, d, xpt, zmat, idz)) <= &
- ! & tol * maxval([ONE, abs(vlag_test)])) .or. RP == kind(0.0), 'VLAG_TEST == VLAG', srname)
+ ! & tol * maxval([ONE, abs(vlag_test)])) .or. precision(0.0_RP) < precision(0.0D0), 'VLAG_TEST == VLAG', srname)
! deallocate (vlag_test)
! beta_test = calbeta(knew, bmat, d + (xpt(:, kref) - xpt(:, knew)), xpt, zmat, idz)
! call wassert(abs(beta_test - calbeta(kref, bmat, d, xpt, zmat, idz)) <= &
- ! & tol * max(ONE, abs(beta_test)) .or. RP == kind(0.0), 'BETA_TEST == BETA', srname)
+ ! & tol * max(ONE, abs(beta_test)) .or. precision(0.0_RP) < precision(0.0D0), 'BETA_TEST == BETA', srname)
!end if
! The following is too expensive to check.
- !call wassert(errh(idz, bmat, zmat, xpt) <= tol .or. RP == kind(0.0), &
+ !call wassert(errh(idz, bmat, zmat, xpt) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! & 'H = W^{-1} in (3.12) of the NEWUOA paper', srname)
end if
@@ -1219,12 +1226,12 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! N.B.: Powell's original comments mention that VLAG is "the vector THETA*WCHECK + e_b of the
! updating formula (6.11)", which does not match the published version of the NEWUOA paper.
vlag = calvlag(kref, bmat, d, xpt, zmat, idz)
-beta = calbeta(kref, bmat, d, xpt, zmat, idz)
+beta = calbeta(kref, bmat, d, xpt, zmat, idz) ! Nonnegative in precise arithmetic.
! Calculate the parameters of the updating formula (4.18)--(4.20) in the NEWUOA paper.
-alpha = hcol(knew)
-tau = vlag(knew)
-denom = alpha * beta + tau**2
+alpha = hcol(knew) ! Nonnegative in precise arithmetic.
+tau = vlag(knew) ! Nonzero due to the definition of KNEW.
+denom = alpha * beta + tau**2 ! Positive in precise arithmetic.
! After the following line, VLAG = H*w - e_KNEW in the NEWUOA paper (where t = KNEW).
vlag(knew) = vlag(knew) - ONE
@@ -1243,6 +1250,7 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
v1 = (alpha * vlag(npt + 1:npt + n) - tau * hcol(npt + 1:npt + n)) / denom
v2 = (-beta * hcol(npt + 1:npt + n) - tau * vlag(npt + 1:npt + n)) / denom
bmat = bmat + outprod(v1, vlag) + outprod(v2, hcol) !call r2update(bmat, ONE, v1, vlag, ONE, v2, hcol)
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
! Numerically, the update above does not guarantee BMAT(:, NPT+1 : NPT+N) to be symmetric.
call symmetrize(bmat(:, npt + 1:npt + n))
@@ -1255,7 +1263,6 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
! 2. If 2 <= IDZ <= NPT - N -1, then JL = IDZ, and ZMAT(KNEW, 1) is L2-norm of ZMAT(KNEW, 1 : IDZ-1),
! while ZMAT(KNEW, JL) is L2 norm of ZMAT(KNEW, IDZ : NPT-N-1).
! See (4.15)--(4.17) of the NEWUOA paper and the elaboration around them.
-ztest = 1.0E-20_RP * maxval(abs(zmat)) ! Taken from BOBYQA. It is implicitly zero in NEWUOA/LINCOA.
jl = 1 ! In the loop below, if 2 <= J < IDZ, then JL = 1; if IDZ < J <= NPT-N-1, then JL = IDZ.
do j = 2, npt - n - 1_IK
if (j == idz) then
@@ -1264,8 +1271,8 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
end if
! Powell's condition in NEWUOA/LINCOA for the IF ... THEN below: IF (ZMAT(KNEW, J) /= 0) THEN
- ! A possible alternative: IF (ABS(ZMAT(KNEW, J)) > 1.0E-20_RP * ABS(ZMAT(KNEW, JL))) THEN
- if (abs(zmat(knew, j)) > ztest) then
+ ! A possible alternative: IF (ABS(ZMAT(KNEW, J)) > 1.0E-20 * ABS(ZMAT(KNEW, JL))) THEN
+ if (abs(zmat(knew, j)) > 1.0E-20 * maxval(abs(zmat))) then ! Threshold comes from Powell's BOBYQA
! Multiply a Givens rotation to ZMAT from the right so that ZMAT(KNEW, [JL,J]) becomes [*,0].
grot = planerot(zmat(knew, [jl, j])) !!MATLAB: grot = planerot(zmat(knew, [jl, j])')
zmat(:, [jl, j]) = matprod(zmat(:, [jl, j]), transpose(grot))
@@ -1343,8 +1350,10 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
end if
! Now update ZMAT(:, [ja, jb]) according to (4.19)--(4.20) of the NEWUOA paper.
temp = zmat(knew, jb) / denom
- tempa = temp * beta
- tempb = temp * tau
+ !tempa = temp * beta
+ !tempb = temp * tau
+ tempa = (beta / denom) * zmat(knew, jb)
+ tempb = (tau / denom) * zmat(knew, jb)
temp = zmat(knew, ja)
scala = ONE / sqrt(abs(beta) * temp**2 + tau**2) ! 1/SQRT(ZETA) in (4.19)-(4.20) of NEWUOA paper
scalb = scala * sqrtdn
@@ -1408,11 +1417,18 @@ subroutine updateh(knew, kref, d, xpt, idz, bmat, zmat, info)
call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT)==[N, NPT+N]', srname)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, 'SIZE(ZMAT) == [NPT, NPT-N-1]', srname)
+
+ do j = 1, npt
+ hcol(1:npt) = omega_col(idz, zmat, j)
+ hcol(npt + 1:npt + n) = bmat(:, j)
+ call assert(precision(0.0_RP) < precision(0.0D0) .or. sum(abs(hcol)) > 0, 'Column '//num2str(j)//' of H is nonzero', srname)
+ end do
+
! The following is too expensive to check.
!call safealloc(xpt_test, n, npt)
!xpt_test = xpt
!xpt_test(:, knew) = xpt(:, kref) + d
- !call wassert(errh(idz, bmat, zmat, xpt_test) <= tol .or. RP == kind(0.0), &
+ !call wassert(errh(idz, bmat, zmat, xpt_test) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
! & 'H = W^{-1} in (3.12) of the NEWUOA paper', srname)
!deallocate (xpt_test)
end if
@@ -1427,6 +1443,12 @@ end subroutine updateh
! H*w without the (NPT+1)the entry; DEN(t) is SIGMA in (4.12). (4.25)--(4.26) formulate the actual
! calculating scheme of VLAG and BETA.
!
+! Zaikun 20250806: In theory, ALPHA and BETA are both nonnegative (see Lemma 2 of Powell 2002, Least
+! Frobenius norm updating of quadratic models that satisfy interpolation conditions), and DEN is
+! positive. Numerically, however, they can be negative due to rounding errors. Powell's code handles
+! such cases carefully. See the UPDATEH subroutine and Section 4 of the NEWUOA paper, especially the
+! discussions around (4.21)--(4.22).
+!
! In languages like MATLAB/Python/Julia/R, CALVLAG and CALBETA should be implemented into one single
! function, as they share most of the calculation. We separate them in Fortran (at the expense of
! repeating some calculation) because Fortran functions can only have one output.
@@ -1490,7 +1512,7 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, HALF, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, HALF, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert, wassert
use, non_intrinsic :: infnan_mod, only : is_finite
use, non_intrinsic :: linalg_mod, only : matprod, issymmetric
@@ -1566,8 +1588,8 @@ function calvlag_lfqint(kref, bmat, d, xpt, zmat, idz) result(vlag)
! Postconditions
if (DEBUGGING) then
call assert(size(vlag) == npt + n, 'SIZE(VLAG) == NPT + N', srname)
- tol = max(1.0E-8_RP, min(1.0E-1_RP, 1.0E10_RP * EPS * real(npt + n, RP)))
- call wassert(abs(sum(vlag(1:npt)) - ONE) / real(npt, RP) <= tol .or. RP == kind(0.0), &
+ tol = max(TEN**max(-8, -MAXPOW10), min(1.0E-1_RP, TEN**min(12, MAXPOW10) * EPS * real(npt + n, RP)))
+ call wassert(abs(sum(vlag(1:npt)) - ONE) / real(npt, RP) <= tol .or. precision(0.0_RP) < precision(0.0D0), &
& 'SUM(VLAG(1:NPT)) == 1', srname)
end if
diff --git a/fortran/common/ppf.h b/fortran/common/ppf.h
index b67d240f24..e6a93d6da4 100644
--- a/fortran/common/ppf.h
+++ b/fortran/common/ppf.h
@@ -11,8 +11,8 @@
* PRIMA_FORTRAN_STANDARD which Fortran standard to follow: 2008, 2018, 2023
* PRIMA_RELEASED released or not: 1, 0
* PRIMA_DEBUGGING debug or not: 0, 1
- * PRIMA_INTEGER_KIND the integer kind to be used: 0, 32, 64, 16
- * PRIMA_REAL_PRECISION the real precision to be used: 64, 32, 128, 0
+ * PRIMA_INTEGER_KIND the integer kind to be used: 0, 16, 32, 64
+ * PRIMA_REAL_PRECISION the real precision to be used: 64, 16, 32, 128, 0
* PRIMA_QP_AVAILABLE quad precision available or not: 0, 1
* PRIMA_MAX_HIST_MEM_MB maximal MB memory for computation history: 300
* PRIMA_AGGRESSIVE_OPTIONS compile the code with aggressive options: 0, 1
@@ -99,28 +99,51 @@
/******************************************************************************/
/* Which real kind to use?
- * 0 = default REAL (SINGLE PRECISION), 32 = REAL*4, 64 = REAL*8, 128 = REAL*16.
+ * 0 = default REAL (SINGLE PRECISION), 16 = REAL*2, 32 = REAL*4, 64 = REAL*8,
+ * 128 = REAL*16.
* Make sure that your compiler supports the selected kind. Note the following:
* 1. The default REAL (i.e., 0) is the single-precision REAL.
* 2. Fortran standards guarantee that 0, 32, and 64 are supported, but not 128.
- * 3. If you set PRIMA_REAL_PRECISION to 128, then set PRIMA_QP_AVAILABLE to 1.*/
+ * 3. If you set PRIMA_REAL_PRECISION to 16, then set PRIMA_HP_AVAILABLE to 1.
+ * 4. If you set PRIMA_REAL_PRECISION to 128, then set PRIMA_QP_AVAILABLE to 1.*/
#if !defined PRIMA_REAL_PRECISION
#define PRIMA_REAL_PRECISION 64
#endif
+/* Is half precision available on this platform (compiler, hardware ...)? */
+/* Set PRIMA_HP_AVAILABLE to 1 and PRIMA_REAL_PRECISION to 16 if REAL*2 is
+ * available and you REALLY intend to use it. DO NOT DO IT IF NOT SURE. */
+#if !defined PRIMA_HP_AVAILABLE
+#if (defined __NAG_COMPILER_BUILD && __NAG_COMPILER_BUILD > 7200)
+#define PRIMA_HP_AVAILABLE 1
+#else
+#define PRIMA_HP_AVAILABLE 0
+#endif
+#endif
+
+/* Revise PRIMA_REAL_PRECISION according to PRIMA_HP_AVAILABLE . */
+#if PRIMA_HP_AVAILABLE != 1 && PRIMA_REAL_PRECISION < 32
+#undef PRIMA_REAL_PRECISION
+#define PRIMA_REAL_PRECISION 32
+#endif
+
/* Is quad precision available on this platform (compiler, hardware ...)? */
/* Note:
- * 1. Not all platforms support REAL128. For example, pgfortran 19 does not.
- * 2. It is not guaranteed that REAL128 has a wider range than REAL64. For
- * example, REAL128 of nagfor 7.0 has a range of 291, while REAL64
+ * 1. Not all platforms support REAL*16.
+ * 2. It is not guaranteed that REAL*16 has a wider range than REAL*8. For
+ * example, REAL*16 of nagfor 7.0 has a range of 291, while REAL*8
* has a range of 307.
- * 3. It is rarely a good idea to use REAL128 as the working precision,
+ * 3. It is rarely a good idea to use REAL*16 as the working precision,
* which is probably inefficient and unnecessary.
- * 4. Set PRIMA_QP_AVAILABLE to 1 and PRIMA_REAL_PRECISION to 128 if REAL128
+ * 4. Set PRIMA_QP_AVAILABLE to 1 and PRIMA_REAL_PRECISION to 128 if REAL*16
* is available and you REALLY intend to use it. DO NOT DO IT IF NOT SURE. */
#if !defined PRIMA_QP_AVAILABLE
+#if defined __GFORTRAN__ || defined __INTEL_COMPILER || defined __NAG_COMPILER_BUILD
+#define PRIMA_QP_AVAILABLE 1
+#else
#define PRIMA_QP_AVAILABLE 0
#endif
+#endif
/* Revise PRIMA_REAL_PRECISION according to PRIMA_QP_AVAILABLE . */
#if PRIMA_QP_AVAILABLE != 1 && PRIMA_REAL_PRECISION > 64
@@ -135,7 +158,7 @@
* The maximal supported value is 2000, as 2000 M = 2*10^9 = maximum of INT32.
* N.B.: A big value (even < 2000) may lead to SEGFAULTs due to large arrays. */
#if !defined PRIMA_MAX_HIST_MEM_MB
-#define PRIMA_MAX_HIST_MEM_MB 300 /* 1MB > 10^5*REAL64. 100 can be too small.*/
+#define PRIMA_MAX_HIST_MEM_MB 300 /* 1MB > 10^5*DOUBLE. 100 can be too small.*/
#endif
/******************************************************************************/
diff --git a/fortran/common/preproc.f90 b/fortran/common/preproc.f90
index e3f097cf17..7dd0389e34 100644
--- a/fortran/common/preproc.f90
+++ b/fortran/common/preproc.f90
@@ -6,10 +6,13 @@ module preproc_mod
!
! Started: July 2020
!
-! Last Modified: Tuesday, April 11, 2023 PM04:47:58
+! Last Modified: Mon 06 Apr 2026 10:54:37 PM CST
!--------------------------------------------------------------------------------------------------!
-! N.B.: If all the inputs are valid, then PREPROC should do nothing.
+! N.B.:
+! 1. If all the inputs are valid, then PREPROC should do nothing.
+! 2. In PREPROC, we use VALIDATE instead of ASSERT, so that the parameters are validated even if we
+! are not in debug mode.
implicit none
private
@@ -24,11 +27,11 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
!--------------------------------------------------------------------------------------------------!
! This subroutine preprocesses the inputs. It does nothing to the inputs that are valid.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, TWO, TEN, TENTH, HALF, EPS, MAXHISTMEM, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, TWO, TEN, HALF, EPS, MAXHISTMEM, DEBUGGING
use, non_intrinsic :: consts_mod, only : RHOBEG_DFT, RHOEND_DFT, ETA1_DFT, ETA2_DFT, GAMMA1_DFT, GAMMA2_DFT
-use, non_intrinsic :: consts_mod, only : CTOL_DFT, CWEIGHT_DFT, FTARGET_DFT, IPRINT_DFT, MIN_MAXFILT, MAXFILT_DFT
-use, non_intrinsic :: debug_mod, only : assert, warning
-use, non_intrinsic :: infnan_mod, only : is_nan, is_inf, is_finite
+use, non_intrinsic :: consts_mod, only : CTOL_DFT, CWEIGHT_DFT, IPRINT_DFT, MIN_MAXFILT, MAXFILT_DFT
+use, non_intrinsic :: debug_mod, only : validate, warning
+use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: linalg_mod, only : trueloc, falseloc
use, non_intrinsic :: memory_mod, only : cstyle_sizeof
use, non_intrinsic :: string_mod, only : lower, num2str
@@ -50,7 +53,11 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
integer(IK), intent(inout) :: iprint
integer(IK), intent(inout) :: maxfun
integer(IK), intent(inout) :: maxhist
+real(RP), intent(inout) :: eta1
+real(RP), intent(inout) :: eta2
real(RP), intent(inout) :: ftarget
+real(RP), intent(inout) :: gamma1
+real(RP), intent(inout) :: gamma2
real(RP), intent(inout) :: rhobeg
real(RP), intent(inout) :: rhoend
@@ -59,47 +66,60 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
integer(IK), intent(inout), optional :: maxfilt
real(RP), intent(inout), optional :: ctol
real(RP), intent(inout), optional :: cweight
-real(RP), intent(inout), optional :: eta1
-real(RP), intent(inout), optional :: eta2
-real(RP), intent(inout), optional :: gamma1
-real(RP), intent(inout), optional :: gamma2
real(RP), intent(inout), optional :: x0(:)
! Local variables
character(len=*), parameter :: srname = 'PREPROC'
character(len=:), allocatable :: min_maxfun_str
+integer :: min_maxfun ! INTEGER(IK) may overflow if IK corresponds to the 16-bit integer.
+integer :: unit_memo ! INTEGER(IK) may overflow if IK corresponds to the 16-bit integer.
+integer(IK) :: iprint_in
integer(IK) :: m_loc
integer(IK) :: maxfilt_in
-integer(IK) :: min_maxfun
-integer(IK) :: unit_memo
+integer(IK) :: maxfun_in
+integer(IK) :: maxhist_in
+integer(IK) :: npt_in
logical :: is_constrained_loc
logical :: lbx(n)
logical :: ubx(n)
-real(RP) :: eta1_loc
-real(RP) :: eta2_loc
+real(RP) :: ctol_in
+real(RP) :: cweight_in
+real(RP) :: eta1_in
+real(RP) :: eta2_in
+real(RP) :: gamma1_in
+real(RP) :: gamma2_in
real(RP) :: rhobeg_default
-real(RP) :: rhobeg_old
+real(RP) :: rhobeg_in
real(RP) :: rhoend_default
-real(RP) :: x0_old(n)
+real(RP) :: rhoend_in
+real(RP) :: x0_in(n)
! Preconditions
if (DEBUGGING) then
- call assert(n >= 1, 'N >= 1', srname)
+ call validate(n >= 1, 'N >= 1', srname)
+ call validate(present(npt) .eqv. (lower(solver) == 'newuoa' .or. lower(solver) == 'bobyqa' .or. &
+ & lower(solver) == 'lincoa'), 'NPT is present if and only if SOLVER is NEWUOA, BOBYQA, or LINCOA', srname)
if (present(m)) then
- call assert(m >= 0, 'M >= 0', srname)
- call assert(m == 0 .or. lower(solver) == 'cobyla', 'M == 0 unless the solver is COBYLA', srname)
+ call validate(m >= 0, 'M >= 0', srname)
+ call validate(m == 0 .or. lower(solver) == 'cobyla', 'M == 0 unless the solver is COBYLA', srname)
end if
if (lower(solver) == 'cobyla' .and. present(m) .and. present(is_constrained)) then
- call assert(m == 0 .or. is_constrained, 'For COBYLA, M == 0 unless the problem is constrained', srname)
+ call validate(m == 0 .or. is_constrained, 'For COBYLA, M == 0 unless the problem is constrained', srname)
end if
+ call validate(present(maxfilt) .eqv. (lower(solver) == 'lincoa' .or. lower(solver) == 'cobyla'), &
+ & 'MAXFILT is present if and only if the solver is LINCOA or COBYLA', srname)
if (lower(solver) == 'bobyqa') then
- call assert(present(xl) .and. present(xu), 'XL and XU are present if the solver is BOBYQA', srname)
- call assert(all(xu - xl >= TWO * EPS), 'MINVAL(XU-XL) > 2*EPS', srname)
- end if
- if (present(honour_x0)) then
- call assert(lower(solver) == 'bobyqa' .and. present(has_rhobeg) .and. present(xl) .and. present(xu) &
- & .and. present(x0), 'If HONOUR_X0 is present, then so are XL, XU, and X0, and the solver is BOBYQA', srname)
+ call validate(present(xl) .and. present(xu), 'XL and XU are present if the solver is BOBYQA', srname)
+ call validate(all(xu - xl >= TWO * EPS), 'MINVAL(XU-XL) > 2*EPS', srname)
end if
+ call validate((present(honour_x0) .eqv. present(x0)) .and. (present(honour_x0) .eqv. present(has_rhobeg)), &
+ & 'HONOUR_X0, X0, and HAS_RHOBEG are present or absent simultaneously', srname)
+ call validate(present(honour_x0) .eqv. lower(solver) == 'bobyqa', &
+ & 'HONOUR_X0 is present if and only if the solver is BOBYQA', srname)
+ ! N.B.: LINCOA and COBYLA will have HONOUR_X0 as well if we intend to make them respect bounds.
+ ! !call validate(present(honour_x0) .eqv. &
+ ! ! & (lower(solver) == 'bobyqa' .or. lower(solver) == 'lincoa' .or. lower(solver) == 'cobyla'), &
+ ! ! & 'HONOUR_X0 is present if and only if the solver is BOBYQA, LINCOA, or COBYLA', srname)
end if
!====================!
@@ -120,68 +140,84 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
is_constrained_loc = (m_loc > 0)
end if
-
! Validate IPRINT
if (abs(iprint) > 3) then
+ iprint_in = iprint
iprint = IPRINT_DFT
- call warning(solver, 'Invalid IPRINT; it should be 0, 1, -1, 2, -2, 3, or -3; it is set to '//num2str(iprint))
+ call warning(solver, 'Invalid IPRINT: '//num2str(iprint_in)// &
+ & '; it should be 0, 1, -1, 2, -2, 3, or -3; it is set to '// num2str(iprint))
end if
! Validate MAXFUN
+! N.B.: The INT(N), INT(N+1), and INT(N+2) below convert integers to the default integer kind,
+! which is the kind of MIN_MAXFUN. Fortran compilers may complain without the conversion. It is
+! not needed in Python/MATLAB/Julia/R.
select case (lower(solver))
case ('uobyqa')
- min_maxfun = (n + 1_IK) * (n + 2_IK) / 2_IK + 1_IK
+ min_maxfun = (int(n + 1) * int(n + 2)) / 2 + 1 ! INT(*) avoids overflow when IK is 16-bit.
min_maxfun_str = '(N+1)(N+2)/2 + 1'
case ('cobyla')
- min_maxfun = n + 2_IK
+ min_maxfun = int(n) + 2
min_maxfun_str = 'N + 2'
case default ! CASE ('NEWUOA', 'BOBYQA', 'LINCOA')
- min_maxfun = n + 3_IK
+ min_maxfun = int(n) + 3
min_maxfun_str = 'N + 3'
end select
-if (maxfun < min_maxfun) then
- maxfun = min_maxfun
- call warning(solver, 'Invalid MAXFUN; it should be at least '//min_maxfun_str//'; it is set to '//num2str(maxfun))
+if (maxfun <= max(0, min_maxfun - 1)) then
+ maxfun_in = maxfun
+ if (maxfun > 0) then
+ maxfun = int(min_maxfun, kind(maxfun))
+ else ! We assume that non-positive values of MAXFUN are produced by overflow.
+ maxfun = int(max(min_maxfun, 10**min(4, range(maxfun))), kind(maxfun)) !!MATLAB: maxfun = max(min_maxfun, 10^4);
+ ! N.B.: Do NOT set MAXFUN to HUGE(MAXFUN), as it may cause overflow and infinite cycling
+ ! when used as the upper bound of DO loops. This occurred on 20240225 with gfortran 13. See
+ ! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+ ! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+ end if
+ call warning(solver, 'Invalid MAXFUN: '//num2str(maxfun_in)// &
+ & '; it should be at least '//min_maxfun_str//' with N = '//num2str(n)//'; it is set to '//num2str(maxfun))
end if
! Validate MAXHIST
-if (maxhist < 0) then
+if (maxhist <= 0) then
+ maxhist_in = maxhist
maxhist = maxfun
- call warning(solver, 'Invalid MAXHIST; it should be a nonnegative integer; it is set to '//num2str(maxhist))
+ call warning(solver, 'Invalid MAXHIST: '//num2str(maxhist_in)// &
+ & '; it should be a positive integer; it is set to '//num2str(maxhist))
end if
maxhist = min(maxhist, maxfun) ! MAXHIST > MAXFUN is never needed.
! Validate FTARGET
-if (is_nan(ftarget)) then
- ftarget = FTARGET_DFT
- call warning(solver, 'Invalid FTARGET; it should be a real number; it is set to '//num2str(ftarget))
+if (is_nan(ftarget)) then ! No warning if FTARGET is NaN, which is interpreted as no target function value is provided.
+ ftarget = -huge(ftarget)
end if
! Validate NPT
-if ((lower(solver) == 'newuoa' .or. lower(solver) == 'bobyqa' .or. lower(solver) == 'lincoa') &
- & .and. present(npt)) then
- if (npt < n + 2 .or. npt > min(maxfun - 1, ((n + 2) * (n + 1)) / 2)) then
+if (present(npt)) then
+ if (npt < n + 2 .or. npt >= maxfun .or. 2 * int(npt) > int(n + 2) * int(n + 1)) then !INT(*) avoids overflow when IK is 16-bit
+ npt_in = npt
npt = int(min(maxfun - 1, 2 * n + 1), kind(npt))
- call warning(solver, 'Invalid NPT; it should be an integer in the interval [N+2, (N+1)(N+2)/2]'// &
- & ' and less than MAXFUN; it is set to '//num2str(npt))
+ call warning(solver, 'Invalid NPT: '//num2str(npt_in)// &
+ & '; it should be an integer in the interval [N+2, (N+1)(N+2)/2] with N = '//num2str(n)// &
+ & ' and less than MAXFUN = '//num2str(maxfun)//'; it is set to '//num2str(npt))
end if
end if
! Validate MAXFILT
-if (present(maxfilt) .and. (lower(solver) == 'lincoa' .or. lower(solver) == 'cobyla')) then
+if (present(maxfilt)) then
maxfilt_in = maxfilt
- if (maxfilt < 1) then
- maxfilt = MAXFILT_DFT ! The inputted MAXFILT is obviously wrong.
+ if (maxfilt <= 0) then
+ maxfilt = MAXFILT_DFT
else
maxfilt = max(MIN_MAXFILT, maxfilt) ! The inputted MAXFILT is too small.
end if
! Further revise MAXFILT according to MAXHISTMEM.
select case (lower(solver))
case ('lincoa')
- unit_memo = (n + 2_IK) * cstyle_sizeof(0.0_RP)
+ unit_memo = int(n + 2) * int(cstyle_sizeof(0.0_RP)) ! INT(*) avoids overflow when IK is 16-bit.
case ('cobyla')
- unit_memo = (m_loc + n + 2_IK) * cstyle_sizeof(0.0_RP)
- case default
+ unit_memo = int(m_loc + n + 2) * int(cstyle_sizeof(0.0_RP)) ! INT(*) avoids overflow when IK is 16-bit.
+ case default ! The following should not be reached unless there is a bug, but we keep it for safety.
unit_memo = 1
end select
! We cannot simply set MAXFILT = MIN(MAXFILT, MAXHISTMEM/...), as they may not have
@@ -191,104 +227,70 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
end if
maxfilt = min(maxfun, max(MIN_MAXFILT, maxfilt))
if (is_constrained_loc) then
- if (maxfilt_in < 1) then
- call warning(solver, 'Invalid MAXFILT; it should be a positive integer; it is set to ' &
- & //num2str(maxfilt))
+ if (maxfilt_in <= 0) then
+ call warning(solver, 'Invalid MAXFILT: '//num2str(maxfilt_in)// &
+ & '; it should be a positive integer; it is set to '//num2str(maxfilt))
elseif (maxfilt_in < min(maxfun, MIN_MAXFILT)) then
- call warning(solver, 'MAXFILT is too small; it is set to '//num2str(maxfilt))
+ call warning(solver, 'MAXFILT = '//num2str(maxfilt_in)//' is too small; it is set to '//num2str(maxfilt))
elseif (maxfilt < min(maxfilt_in, maxfun)) then
- call warning(solver, 'MAXFILT is set to '//num2str(maxfilt)//' due to memory limit')
+ call warning(solver, 'MAXFILT is reduced from '//num2str(maxfilt_in)//' to '//num2str(maxfilt)//' due to memory limit')
end if
end if
end if
! Validate ETA1 and ETA2
-if (present(eta1)) then
- eta1_loc = eta1
-else
- eta1_loc = ETA1_DFT
-end if
-if (present(eta2)) then
- eta2_loc = eta2
-else
- eta2_loc = ETA2_DFT
-end if
-
-! When the difference between ETA1 and ETA2 is tiny, we force them to equal.
-! See the explanation around RHOBEG and RHOEND for the reason.
-if (present(eta1) .and. present(eta2)) then
- if (abs(eta1 - eta2) < 1.0E2_RP * EPS * max(abs(eta1), ONE)) then
- eta2 = eta1
- end if
-end if
-
-if (present(eta1)) then
- if (is_nan(eta1)) then
- ! In this case, we take the value hard coded in Powell's original code
- ! without any warning. It is useful when interfacing with MATLAB/Python.
+if (.not. (eta1 >= 0 .and. eta1 < 1)) then ! ETA1 = NaN falls into this case.
+ eta1_in = eta1
+ ! Take ETA2 into account if it has a valid value.
+ if (eta2 >= 0 .and. eta2 < 1) then
+ eta1 = eta2 / 7.0_RP
+ else
eta1 = ETA1_DFT
- elseif (eta1 < 0 .or. eta1 >= 1) then
- ! Take ETA2 into account if it has a valid value.
- if (present(eta2) .and. eta2_loc > 0 .and. eta2_loc <= 1) then
- eta1 = max(EPS, eta2 / 7.0_RP)
- else
- eta1 = ETA1_DFT
- end if
- call warning(solver, 'Invalid ETA1; it should be in the interval [0, 1) and not more than ETA2;'// &
- & ' it is set to '//num2str(eta1))
end if
+ call warning(solver, 'Invalid ETA1: '//num2str(eta1_in)// &
+ & '; it should be in the interval [0, 1) and not more than ETA2 = '//num2str(eta2)//'; it is set to '//num2str(eta1))
end if
-if (present(eta2)) then
- if (is_nan(eta2)) then
- ! In this case, we take the value hard coded in Powell's original code
- ! without any warning. It is useful when interfacing with MATLAB/Python.
- eta2 = ETA2_DFT
- elseif (present(eta1) .and. (eta2 < eta1_loc .or. eta2 > 1)) then
+if (.not. (eta2 >= eta1 .and. eta2 < 1)) then ! ETA2 = NaN falls into this case.
+ eta2_in = eta2
+ ! Take ETA1 into account if it has a valid value.
+ if (eta1 >= 0 .and. eta1 < 1) then
eta2 = (eta1 + TWO) / 3.0_RP
- call warning(solver, 'Invalid ETA2; it should be in the interval [0, 1) and not less than ETA1;'// &
- & ' it is set to '//num2str(eta2))
+ else
+ eta2 = ETA2_DFT
end if
+ call warning(solver, 'Invalid ETA2: '//num2str(eta2_in)// &
+ & '; it should be in the interval [0, 1) and not less than ETA1 = '//num2str(eta1)//'; it is set to '//num2str(eta2))
end if
+! The following revision may update ETA1 slightly. It prevents ETA1 > ETA2 due to rounding
+! errors, which would not be accepted by the solvers.
+eta1 = min(eta1, eta2)
+
! Validate GAMMA1 and GAMMA2
-if (present(gamma1)) then
- if (is_nan(gamma1)) then
- ! In this case, we take the value hard coded in Powell's original code
- ! without any warning. It is useful when interfacing with MATLAB/Python.
- gamma1 = GAMMA1_DFT
- elseif (gamma1 <= 0 .or. gamma1 >= 1) then
- gamma1 = GAMMA1_DFT
- call warning(solver, 'Invalid GAMMA1; it should in the interval (0, 1); it is set to '//num2str(gamma1))
- end if
+if (.not. (gamma1 > 0 .and. gamma1 < 1)) then ! GAMMA1 = NaN falls into this case.
+ gamma1_in = gamma1
+ gamma1 = GAMMA1_DFT
+ call warning(solver, 'Invalid GAMMA1: '//num2str(gamma1_in)// &
+ & '; it should in the interval (0, 1); it is set to '//num2str(gamma1))
end if
-if (present(gamma2)) then
- if (is_nan(gamma2)) then
- ! In this case, we take the value hard coded in Powell's original code
- ! without any warning. It is useful when interfacing with MATLAB/Python.
- gamma2 = GAMMA2_DFT
- elseif (gamma2 < 1 .or. is_inf(gamma2)) then
- gamma2 = GAMMA2_DFT
- call warning(solver, 'Invalid GAMMA2; it should be a real number not less than 1; it is set to '//num2str(gamma2))
- end if
+if (.not. (is_finite(gamma2) .and. gamma2 >= 1)) then ! GAMMA2 = NaN falls into this case.
+ gamma2_in = gamma2
+ gamma2 = GAMMA2_DFT
+ call warning(solver, 'Invalid GAMMA2: '//num2str(gamma2_in)// &
+ & '; it should be a real number not less than 1; it is set to '//num2str(gamma2))
end if
! Validate RHOBEG and RHOEND
-if (abs(rhobeg - rhoend) < 1.0E2_RP * EPS * max(abs(rhobeg), ONE)) then
- ! When the data is passed from the interfaces (e.g., MEX) to the Fortran code, RHOBEG, and RHOEND
- ! may change a bit. It was observed in a MATLAB test that MEX passed 1 to Fortran as
- ! 0.99999999999999978. Therefore, if we set RHOEND = RHOBEG in the interfaces, then it may happen
- ! that RHOEND > RHOBEG, which is considered as an invalid input. To avoid this situation, we
- ! force RHOBEG and RHOEND to equal when the difference is tiny.
- rhoend = rhobeg
-end if
+rhobeg_in = rhobeg
+rhoend_in = rhoend
! Revise the default values for RHOBEG/RHOEND according to the solver.
if (lower(solver) == 'bobyqa') then
rhobeg_default = max(EPS, min(RHOBEG_DFT, minval(xu - xl) / 4.0_RP))
- rhoend_default = max(EPS, min(TENTH * rhobeg_default, RHOEND_DFT))
+ rhoend_default = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_default, RHOEND_DFT))
else
rhobeg_default = RHOBEG_DFT
rhoend_default = RHOEND_DFT
@@ -301,11 +303,13 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
! Do NOT make this revision if RHOBEG not positive or not finite, because otherwise RHOBEG
! will get a huge value when XU or XL contains huge values that indicate unbounded variables.
rhobeg = minval(xu - xl) / 4.0_RP ! Here, we do not take RHOBEG_DEFAULT.
- call warning(solver, 'Invalid RHOBEG; '//solver//' requires 0 < RHOBEG <= MINVAL(XU-XL)/2;' &
- & //' it is set to MINVAL(XU-XL)/4')
+ call warning(solver, 'Invalid RHOBEG: '//num2str(rhobeg_in)// &
+ & '; '//solver//' requires 0 < RHOBEG <= MINVAL(XU-XL)/2 = '//num2str(minval(xu - xl) / 2.0_RP)// &
+ & '; it is set to '//num2str(rhobeg))
end if
end if
-if (rhobeg <= 0 .or. is_nan(rhobeg) .or. is_inf(rhobeg)) then
+
+if (.not. (is_finite(rhobeg) .and. rhobeg > 0)) then ! RHOBEG = NaN falls into this case.
! Take RHOEND into account if it has a valid value. We do not do this if the solver is BOBYQA,
! which requires that RHOBEG <= (XU-XL)/2.
if (is_finite(rhoend) .and. rhoend > 0 .and. lower(solver) /= 'bobyqa') then
@@ -313,36 +317,24 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
else
rhobeg = rhobeg_default
end if
- call warning(solver, 'Invalid RHOBEG; it should be a positive number; it is set to '//num2str(rhobeg))
+ call warning(solver, 'Invalid RHOBEG: '//num2str(rhobeg_in)// &
+ & '; it should be a positive number; it is set to '//num2str(rhobeg))
end if
-if (rhoend <= 0 .or. rhobeg < rhoend .or. is_nan(rhoend) .or. is_inf(rhoend)) then
- rhoend = max(EPS, min(TENTH * rhobeg, rhoend_default))
- call warning(solver, 'Invalid RHOEND; it should be a positive number and RHOEND <= RHOBEG; '// &
- & 'it is set to '//num2str(rhoend))
+if (.not. (is_finite(rhoend) .and. rhoend >= 0 .and. rhoend <= rhobeg)) then ! RHOEND = NaN falls into this case.
+ rhoend = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg, rhoend_default))
+ call warning(solver, 'Invalid RHOEND: '//num2str(rhoend_in)// &
+ & '; we should have '//num2str(rhobeg)//' = RHOBEG >= RHOEND >= 0; it is set to '//num2str(rhoend))
end if
! For BOBYQA, revise X0 or RHOBEG so that the distance between X0 and the inactive bounds is at
-! least RHOBEG. If HONOUR_X0 == TRUE, revise RHOBEG if needed; otherwise, revise HONOUR_X0 if needed.
-if (present(honour_x0)) then
- if (honour_x0) then
- rhobeg_old = rhobeg;
- lbx = (is_finite(xl) .and. x0 - xl <= EPS * max(ONE, abs(xl))) ! X0 essentially equals XL
- ubx = (is_finite(xu) .and. x0 - xu >= -EPS * max(ONE, abs(xu))) ! X0 essentially equals XU
- x0(trueloc(lbx)) = xl(trueloc(lbx))
- x0(trueloc(ubx)) = xu(trueloc(ubx))
- rhobeg = max(EPS, minval([rhobeg, x0(falseloc(lbx)) - xl(falseloc(lbx)), xu(falseloc(ubx)) - x0(falseloc(ubx))]))
- if (rhobeg_old - rhobeg > EPS * max(ONE, rhobeg_old)) then
- rhoend = max(EPS, min(TENTH * rhobeg, rhoend)) ! We do not revise RHOEND unless RHOBEG is truly revised.
- if (has_rhobeg) then
- call warning(solver, 'RHOBEG is revised to '//num2str(rhobeg)//' and RHOEND to at most 0.1*RHOBEG'// &
- & ' so that the distance between X0 and the inactive bounds is at least RHOBEG')
- end if
- else
- rhoend = min(rhoend, rhobeg) ! This may update RHOEND slightly.
- end if
- else
- x0_old = x0 ! Recorded to see whether X0 is really revised.
+! least RHOBEG. If HONOUR_X0 == FALSE, revise X0 if needed; then revise RHOBEG if needed.
+! N.B.: We should do the same for LINCOA and COBYLA if we make them respect the bounds in the future.
+! !if (lower(solver) == 'bobyqa' .or. lower(solver) == 'lincoa' .or. lower(solver) == 'cobyla') then
+if (lower(solver) == 'bobyqa') then
+ ! Revise X0 if allowed and needed.
+ if (.not. honour_x0) then
+ x0_in = x0 ! Recorded to see whether X0 is really revised.
! N.B.: The following revision is valid only if XL <= X0 <= XU and RHOBEG <= MINVAL(XU-XL)/2,
! which should hold at this point due to the revision of RHOBEG and moderation of X0.
! The cases below are mutually exclusive in precise arithmetic as MINVAL(XU-XL) >= 2*RHOBEG.
@@ -366,29 +358,55 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
!!x0(ubx) = xu(ubx);
!!x0(ubx_minus) = xu(ubx_minus) - rhobeg;
- if (any(abs(x0_old - x0) > 0)) then
- call warning(solver, 'X0 is revised so that the distance between X0 and the inactive bounds is at least RHOBEG; '// &
- & 'set HONOUR_X0 to .TRUE. if you prefer to keep X0 unchanged')
+ if (any(abs(x0_in - x0) > 0)) then
+ call warning(solver, 'X0 is revised so that the distance between X0 and the inactive bounds is at least RHOBEG = '// &
+ & num2str(rhobeg)//'; revise RHOBEG or set HONOUR_X0 to .TRUE. if you prefer to keep X0 unchanged')
+ end if
+ end if
+
+ ! Revise RHOBEG if needed.
+ ! N.B.: If X0 has been revised above (i.e., HONOUR_X0 is FALSE), then the following revision
+ ! is unnecessary in precise arithmetic. However, it may still be needed due to rounding errors.
+ lbx = (is_finite(xl) .and. x0 - xl <= EPS * max(ONE, abs(xl))) ! X0 essentially equals XL
+ ubx = (is_finite(xu) .and. x0 - xu >= -EPS * max(ONE, abs(xu))) ! X0 essentially equals XU
+ x0(trueloc(lbx)) = xl(trueloc(lbx))
+ x0(trueloc(ubx)) = xu(trueloc(ubx))
+ rhobeg = max(EPS, minval([rhobeg, x0(falseloc(lbx)) - xl(falseloc(lbx)), xu(falseloc(ubx)) - x0(falseloc(ubx))]))
+ if (rhobeg_in - rhobeg > EPS * max(ONE, rhobeg_in)) then
+ rhoend = max(EPS, min((rhoend / rhobeg_in) * rhobeg, rhoend)) ! We do not revise RHOEND unless RHOBEG is truly revised.
+ if (has_rhobeg) then
+ call warning(solver, 'RHOBEG is revised from '//num2str(rhobeg_in)//' to '//num2str(rhobeg)// &
+ & ' and RHOEND from '//num2str(rhoend_in)//' to '//num2str(rhoend)// &
+ & ' so that the distance between X0 and the inactive bounds is at least RHOBEG')
end if
end if
end if
+! The following revision may update RHOBEG and RHOEND slightly. It particularly prevents
+! RHOEND > RHOBEG due to rounding errors, which would not be accepted by the solvers.
+rhobeg = max(rhobeg, EPS)
+rhoend = min(max(rhoend, EPS), rhobeg)
+
! Validate CTOL (it can be 0)
if (present(ctol)) then
- if (is_nan(ctol) .or. ctol < 0) then
+ if (.not. (ctol >= 0)) then ! CTOL = NaN falls into this case.
+ ctol_in = ctol
ctol = CTOL_DFT
if (is_constrained_loc) then
- call warning(solver, 'Invalid CTOL; it should be a nonnegative number; it is set to '//num2str(ctol))
+ call warning(solver, 'Invalid CTOL: '//num2str(ctol_in)// &
+ & '; it should be a nonnegative number; it is set to '//num2str(ctol))
end if
end if
end if
! Validate CWEIGHT (it can be +Inf)
if (present(cweight)) then
- if (is_nan(cweight) .or. cweight < 0) then
+ if (.not. (cweight >= 0)) then ! CWEIGHT = NaN falls into this case.
+ cweight_in = cweight
cweight = CWEIGHT_DFT
if (is_constrained_loc) then
- call warning(solver, 'Invalid CWEIGHT; it should be a nonnegative number; it is set to '//num2str(cweight))
+ call warning(solver, 'Invalid CWEIGHT: '//num2str(cweight_in)// &
+ & '; it should be a nonnegative number; it is set to '//num2str(cweight))
end if
end if
end if
@@ -399,31 +417,28 @@ subroutine preproc(solver, n, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
! Postconditions
if (DEBUGGING) then
- call assert(abs(iprint) <= 3, 'IPRINT is 0, 1, -1, 2, -2, 3, or -3', solver)
- call assert(maxhist >= 0 .and. maxhist <= maxfun, '0 <= MAXHIST <= MAXFUN', solver)
+ call validate(abs(iprint) <= 3, 'IPRINT is 0, 1, -1, 2, -2, 3, or -3', solver)
+ call validate(maxhist >= 0 .and. maxhist <= maxfun, '0 <= MAXHIST <= MAXFUN', solver)
+ call validate(maxfun >= min_maxfun, 'MAXFUN >= MIN_MAXFUN', solver)
if (present(npt)) then
- call assert(maxfun >= npt + 1, 'MAXFUN >= NPT + 1', solver)
- call assert(npt >= 3, 'NPT >= 3', solver)
+ call validate(npt >= n + 2 .and. npt < maxfun .and. 2 * int(npt) <= int(n + 2) * int(n + 1), &
+ & 'N+2 <= NPT < MAXFUN and 2*NPT <= (N+1)(N+2)', solver)
end if
if (present(maxfilt)) then
- call assert(maxfilt >= min(MIN_MAXFILT, maxfun) .and. maxfilt <= maxfun, &
+ call validate(maxfilt >= min(MIN_MAXFILT, maxfun) .and. maxfilt <= maxfun, &
& 'MIN(MIN_MAXFILT, MAXFUN) <= MAXFILT <= MAXFUN', solver)
end if
- if (present(eta1) .and. present(eta2)) then
- call assert(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', solver)
- end if
- if (present(gamma1) .and. present(gamma2)) then
- call assert(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', solver)
- end if
- call assert(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', solver)
+ call validate(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', solver)
+ call validate(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', solver)
+ call validate(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', solver)
if (lower(solver) == 'bobyqa') then
- call assert(all(rhobeg <= (xu - xl) / TWO), 'RHOBEG <= MINVAL(XU-XL)/2', solver)
- call assert(all(is_finite(x0)), 'X0 is finite', solver)
- call assert(all(x0 >= xl .and. (x0 <= xl .or. x0 >= xl + rhobeg)), 'X0 == XL or X0 >= XL + RHOBEG', solver)
- call assert(all(x0 <= xu .and. (x0 >= xu .or. x0 <= xu - rhobeg)), 'X0 == XU or X0 >= XU - RHOBEG', solver)
+ call validate(all(rhobeg <= (xu - xl) / TWO), 'RHOBEG <= MINVAL(XU-XL)/2', solver)
+ call validate(all(is_finite(x0)), 'X0 is finite', solver)
+ call validate(all(x0 >= xl .and. (x0 <= xl .or. x0 - xl >= rhobeg)), 'X0 == XL or X0 - XL >= RHOBEG', solver)
+ call validate(all(x0 <= xu .and. (x0 >= xu .or. xu - x0 >= rhobeg)), 'X0 == XU or XU - X0 >= RHOBEG', solver)
end if
if (present(ctol)) then
- call assert(ctol >= 0, 'CTOL >= 0', solver)
+ call validate(ctol >= 0, 'CTOL >= 0', solver)
end if
end if
diff --git a/fortran/common/redrho.f90 b/fortran/common/redrho.f90
index 2384e6faac..0a89cf29e1 100644
--- a/fortran/common/redrho.f90
+++ b/fortran/common/redrho.f90
@@ -6,7 +6,7 @@ module redrho_mod
!
! Started: September 2021
!
-! Last Modified: Wednesday, March 08, 2023 AM01:10:18
+! Last Modified: Monday, November 06, 2023 PM07:45:58
!--------------------------------------------------------------------------------------------------!
implicit none
diff --git a/fortran/common/selectx.f90 b/fortran/common/selectx.f90
index a87b499758..6a5f7f9bc2 100644
--- a/fortran/common/selectx.f90
+++ b/fortran/common/selectx.f90
@@ -8,7 +8,7 @@ module selectx_mod
!
! Started: September 2021
!
-! Last Modified: Wednesday, August 02, 2023 AM11:24:31
+! Last Modified: Saturday, March 02, 2024 AM12:42:28
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -59,10 +59,10 @@ subroutine savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, cons
! Local variables
character(len=*), parameter :: srname = 'SAVEFILT'
integer(IK) :: index_to_keep(size(ffilt))
+integer(IK) :: kworst
integer(IK) :: m
integer(IK) :: maxfilt
integer(IK) :: n
-integer(IK) :: kworst
logical :: keep(nfilt)
real(RP) :: cfilt_shifted(size(ffilt))
real(RP) :: cref
@@ -120,8 +120,11 @@ subroutine savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, cons
! Calculation starts !
!====================!
-! Return immediately if any column of XFILT is better than X.
-if (any(isbetter(ffilt(1:nfilt), cfilt(1:nfilt), f, cstrv, ctol))) then
+! Return immediately if any column of XFILT is better than X. Note that ISBETTER checks "strictly
+! better", handling NaN/Inf properly, but we need "non-strictly better" here, allowing equality.
+! This is why we need to supplement ISBETTER with (FFILT <= F .AND. CFILT <= CSTRV).
+if (any(isbetter(ffilt(1:nfilt), cfilt(1:nfilt), f, cstrv, ctol)) .or. &
+ & any(ffilt(1:nfilt) <= f .and. cfilt(1:nfilt) <= cstrv)) then
return
end if
@@ -378,10 +381,11 @@ function isbetter00(f1, c1, f2, c2, ctol) result(is_better)
is_better = .false.
! Even though NaN/+Inf should not occur in FC1 or FC2 due to the moderated extreme barrier, for
! security and robustness, the code below does not make this assumption.
-is_better = is_better .or. (any(is_nan([f1, c1])) .and. .not. any(is_nan([f2, c2])))
+is_better = is_better .or. (any(is_nan([f2, c2]) .or. is_posinf([f2, c2])) .and. .not. &
+ & any(is_nan([f1, c1]) .or. is_posinf([f1, c1])))
is_better = is_better .or. (f1 < f2 .and. c1 <= c2)
is_better = is_better .or. (f1 <= f2 .and. c1 < c2)
-! If C1 <= CTOL and C2 is significantly larger/worse than CTOL, i.e., C2 > MAX(CTOL,CREF),
+! If C1 <= CTOL and C2 is significantly larger/worse than CTOL, i.e., C2 > MAX(CTOL, CREF),
! then FC1 is better than FC2 as long as F1 < REALMAX. Normally CREF >= CTOL so MAX(CTOL, CREF)
! is indeed CREF. However, this may not be true if CTOL > 1E-1*CONSTRMAX.
cref = TEN * max(EPS, min(ctol, 1.0E-2_RP * CONSTRMAX)) ! The MIN avoids overflow.
@@ -393,6 +397,13 @@ function isbetter00(f1, c1, f2, c2, ctol) result(is_better)
! Postconditions
if (DEBUGGING) then
+ ! Even though NaN/+Inf should not occur in FC1 due to moderated extreme barrier, for security
+ ! and robustness, the code below does not make this assumption.
+ call assert(.not. (is_better .and. any(is_nan([f1, c1]) .or. is_posinf([f1, c1]))), &
+ & 'IS_BETTER cannot be true if [F1, C1] contains NaN/+Inf', srname)
+ call assert(is_better .or. any(is_nan([f1, c1]) .or. is_posinf([f1, c1])) .or. &
+ & .not. any(is_nan([f2, c2]) .or. is_posinf([f2, c2])), &
+ & 'if [F2, C2] contains NaN/+Inf, then either IS_BETTER is true or [F1, C1] contains NaN/+Inf', srname)
call assert(.not. (is_better .and. f1 >= f2 .and. c1 >= c2), &
& '[F1, C1] >= [F2, C2] and IS_BETTER cannot be both true', srname)
call assert(is_better .or. .not. (f1 <= f2 .and. c1 < c2), &
diff --git a/fortran/common/shiftbase.f90 b/fortran/common/shiftbase.f90
index cf75873c68..dfc052f773 100644
--- a/fortran/common/shiftbase.f90
+++ b/fortran/common/shiftbase.f90
@@ -9,7 +9,7 @@ module shiftbase_mod
!
! Started: July 2020
!
-! Last Modified: Monday, August 07, 2023 AM03:53:29
+! Last Modified: Thu 14 Aug 2025 07:35:47 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -104,7 +104,7 @@ subroutine shiftbase_lfqint(kopt, xbase, xpt, zmat, bmat, pq, hq, idz)
call assert(size(pq) == npt, 'SIZE(PQ) = NPT', srname)
call assert(size(hq, 1) == n .and. issymmetric(hq), 'HQ is an NxN symmetric matrix', srname)
! The following test cannot be passed.
- !htol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E10_RP * EPS)) ! Tolerance for error in H
+ !htol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(10, MAXPOW10) * EPS)) ! Tolerance for error in H
!call assert(errh(idz_loc, bmat, zmat, xpt) <= htol, 'H = W^{-1} in (3.12) of the NEWUOA paper', srname)
end if
diff --git a/fortran/common/string.f90 b/fortran/common/string.f90
index 7131d775bd..1ee89be358 100644
--- a/fortran/common/string.f90
+++ b/fortran/common/string.f90
@@ -6,7 +6,7 @@ module string_mod
!
! Started: September 2021
!
-! Last Modified: Thursday, September 07, 2023 PM10:47:46
+! Last Modified: Sunday, March 31, 2024 PM09:44:38
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -15,8 +15,8 @@ module string_mod
! MAX_NUM_STR_LEN is the maximum length of a string that is needed to represent a real or integer
! number. Assuming that such a number is represented by at most 128 bits, it is safe to set this
-! maximum length to 128. We set this number to 256 to be on the safe side.
-integer, parameter :: MAX_NUM_STR_LEN = 256
+! maximum length to 128. We set this number to 1024 to be on the safe side.
+integer, parameter :: MAX_NUM_STR_LEN = 1024
! MAX_WIDTH is the maximum number of characters printed in each row when printing arrays.
integer, parameter :: MAX_WIDTH = 100
@@ -121,9 +121,9 @@ function real2str_scalar(x, ndgt, nexp) result(s)
character(len=*), parameter :: srname = 'REAL2STR_SCALAR'
character(len=:), allocatable :: sformat
character(len=MAX_NUM_STR_LEN) :: str
-integer(IK) :: ndgt_loc ! The number of decimal digits to print
-integer(IK) :: nexp_loc ! The number of digits in the exponent
-integer(IK) :: wx ! The width of the printed X
+integer :: ndgt_loc ! The number of decimal digits to print
+integer :: nexp_loc ! The number of digits in the exponent
+integer :: wx ! The width of the printed X
! Preconditions
if (DEBUGGING) then
@@ -141,27 +141,28 @@ function real2str_scalar(x, ndgt, nexp) result(s)
! Calculation starts !
!====================!
+if (present(ndgt)) then
+ ndgt_loc = ndgt
+else
+ ! By default, we print at most the same number of decimal digits as the double precision.
+ ndgt_loc = min(precision(x), precision(0.0_DP)) + 1
+end if
+ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0)) ! Safeguard
+if (present(nexp)) then
+ nexp_loc = nexp
+else
+ nexp_loc = ceiling(log10(real(range(x) + 0.1))) ! Use + 0.1 in case RANGE(X) = 10^k.
+end if
+nexp_loc = min(nexp_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0))
+
if (.not. is_finite(x)) then
write (str, *) x
s = strip(str) ! Remove the leading and trailing spaces, if any.
else
- if (present(ndgt)) then
- ndgt_loc = int(ndgt, IK)
- else
- ! By default, we print at most the same number of decimal digits as the double precision.
- ndgt_loc = int(min(precision(x), precision(0.0_DP)), IK)
- end if
- ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0, IK))
- if (present(nexp)) then
- nexp_loc = int(nexp, IK)
- else
- nexp_loc = ceiling(log10(real(range(x) + 0.1)), IK) ! Use + 0.1 in case RANGE(X) = 10^k.
- end if
- nexp_loc = min(nexp_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0, IK))
- wx = ndgt_loc + nexp_loc + 5_IK
+ wx = ndgt_loc + nexp_loc + 5
call validate(wx <= MAX_NUM_STR_LEN, 'The width of the printed number is at most ' &
& //int2str(int(MAX_NUM_STR_LEN, IK)), srname)
- sformat = '(1PE'//int2str(wx)//'.'//int2str(ndgt_loc)//'E'//int2str(nexp_loc)//')'
+ sformat = '(1PE'//int2str(int(wx, IK))//'.'//int2str(int(ndgt_loc, IK))//'E'//int2str(int(nexp_loc, IK))//')'
write (str, sformat) x
s = trim(str) ! Remove the trailing spaces, but keep the leading ones, if any.
end if
@@ -246,9 +247,9 @@ function real2str_vector(x, ndgt, nexp, nx) result(s)
ndgt_loc = ndgt
else
! By default, we print at most the same number of decimal digits as the double precision.
- ndgt_loc = min(precision(x), precision(0.0_DP))
+ ndgt_loc = min(precision(x), precision(0.0_DP)) + 1
end if
-ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0))
+ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) / 2.0)) ! Safeguard
if (present(nexp)) then
nexp_loc = nexp
@@ -321,7 +322,10 @@ function int2str(x) result(s)
character(len=*), parameter :: srname = 'INT2STR'
character(len=:), allocatable :: s
character(len=MAX_NUM_STR_LEN) :: str
-write (str, *) x
+! In the following, 'I0' means to use the minimum number of digits needed to print.
+! It should work also if we use * instead of I0. However, this sometimes lead to a segmentation
+! fault on Windows Server 2022 with gcc/gfortran 13.
+write (str, '(I0)') x
s = strip(str)
if (DEBUGGING) then
call assert(len(s) > 0 .and. len(s) <= MAX_NUM_STR_LEN, '0 < LEN(S) <= MAX_NUM_STR_LEN', srname)
diff --git a/fortran/examples/.gitignore b/fortran/examples/.gitignore
index fdfb4b4993..ab877a39f7 100644
--- a/fortran/examples/.gitignore
+++ b/fortran/examples/.gitignore
@@ -1,3 +1,5 @@
+*.mod
+*.mod0
9test
atest
dtest
diff --git a/fortran/examples/Makefile.common b/fortran/examples/Makefile.common
new file mode 100644
index 0000000000..39bef78809
--- /dev/null
+++ b/fortran/examples/Makefile.common
@@ -0,0 +1,68 @@
+# This Makefile is intended to be included in the Makefiles of the solvers. It defines the compilers
+# to test and their options.
+#
+# N.B.: Do not miss the quotes around the command substitution for the compilers. This is to ensure
+# that the path is resolved correctly if it contains spaces (very often on Windows).
+
+
+# AMD AOMP Flang
+MFORT := "$(shell command -v amdflang)"
+mtest: FC := $(MFORT) -std=f$(FSTD)
+
+# AMD AOCC Flang
+DFORT := "$(shell find -L /opt/AMD -type f -name flang -exec test -x {} \; -print 2> /dev/null | sort -V | tail -n 1)"
+dtest: FC := $(DFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+
+# LLVM Flang
+# Note that AOMP and AOCC also provide "flang". We define FFORT as follows to make sure the correct
+# one is being called. Otherwise, flang may be resolved to the one provided by AOMP or AOCC, which
+# did happen in our tests.
+FFORT := "$(shell find -L /usr/local/llvm* /usr/lib/llvm* /opt/homebrew /usr/local/opt /usr/local/Cellar -type f -name flang -exec test -x {} \; -print 2>/dev/null | sort -V | tail -n 1)"
+# If the above command fail to find flang (it happens on Windows), we try setting FFORT manually.
+ifeq ($(FFORT),"") # The quotation marks are necessary
+ FFORT := "$(shell command -v flang)"
+endif
+# On Windows, we need to specify the clang_rt.
+ifeq ($(OS),Windows_NT)
+ # Architecture part of the target triple, e.g., "x86_64" or "aarch64".
+ ARCH := $(shell $(FFORT) -dumpmachine | cut -d- -f1)
+ # Note the quotation marks to handle spaces in the path.
+ CLANGRT := "$(shell find "/c/Program Files/LLVM/lib" -name clang_rt.builtins*$(ARCH)* 2>/dev/null | sort -V | tail -n 1)"
+ # In case nothing is found above, we try clang_rt without the architecture part
+ ifeq ($(CLANGRT),"")
+ CLANGRT := "$(shell find "/c/Program Files/LLVM/lib" -name "clang_rt.builtins*" 2>/dev/null | sort -V | tail -n 1)"
+ endif
+ FFORT := $(FFORT) $(CLANGRT)
+endif
+# On macOS, we need to link against the system libraries.
+ifeq ($(shell uname -s),Darwin)
+ SDKROOT := $(shell /usr/bin/xcrun --show-sdk-path -sdk macosx)
+ FFORT := $(FFORT) -lSystem -L$(SDKROOT)/usr/lib
+endif
+ftest: FC := $(FFORT) -std=f$(FSTD)
+
+# GNU gfortran: due to `error stop` and `backtrace`, we must either compile with no `-std` or
+# use `-std=f20xy -fall-intrinsics` with xy >= 18.
+GFORT := "$(shell command -v gfortran)"
+gtest: FC := $(GFORT) -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
+
+# Intel ifort
+IFORT := "$(shell command -v ifort)"
+itest: FC := $(IFORT) -warn all -stand f$(FS) -diag-disable=10448 # 10448: suppress the warning about deprecation of ifort.
+
+# NAG nagfor
+NFORT := "$(shell command -v nagfor)"
+#ntest: FC := $(NFORT) -C -f$(FSTD) -fpp # As of nagfor 7.1, -fpp is needed on macOS and Windows, but not on Linux.
+ntest: FC := $(NFORT) -C -f$(FSTD) -fpp -nan -ieee=full # `-nan -ieee=full` is to accommodate tests with REAL16.
+
+# NVIDIA nvfortran
+VFORT := "$(shell command -v nvfortran)"
+vtest: FC := $(VFORT) -Wall -Wextra -Mstandard
+
+# Arm Flang
+RFORT := "$(shell command -v armflang)"
+rtest: FC := $(RFORT) -Wall -std=f$(FSTD)
+
+# Intel ifx
+XFORT := "$(shell command -v ifx)"
+xtest: FC := $(XFORT) -warn all -stand f$(FS)
diff --git a/fortran/examples/README.txt b/fortran/examples/README.txt
index 6626d736ae..e7f7ee297d 100644
--- a/fortran/examples/README.txt
+++ b/fortran/examples/README.txt
@@ -2,20 +2,55 @@ This directory contains simple examples that illustrate how to use the modernize
implementation of Powell's derivative-free optimization solvers.
N.B.:
+
+0. In production, if the dimension of the problem is big (e.g., more than 100), then compilers
+should be instructed to compile PRIMA with the automatic arrays allocated on the heap. Otherwise,
+those arrays may be allocated on the stack, which may lead to stack overflow. Since PRIMA is
+designed to solve problems with expensive function evaluations, we do not worry about the
+performance of heap arrays.
+
+The compiler flags for heap arrays are as follows:
+- AMD AOCC Flang: -fno-stack-arrays
+- AMD AOMP Flang: -fno-stack-arrays -mmlir -fdynamic-heap-array
+- Arm Fortran Compiler: -fno-stack-arrays -mmlir -fdynamic-heap-array
+- LLVM Flang: -fno-stack-arrays -mmlir -fdynamic-heap-array
+- GNU gfortran: -fno-stack-arrays
+- Intel ifx: -heap-arrays
+- Intel ifort: -heap-arrays
+- NVIDIA nvfortran: -Mnostack_arrays
+- NAG Fortran Compiler: Not needed. According to the NAG support, "the behaviour of nagfor is for
+ small fixed-size arrays to go on the stack, and for variable-size arrays and fixed-size arrays to
+ go on the heap".
+
+If ever a segmentation fault occurs, check whether the above flags are used in the compilation.
+
+N.B.: For LLVM Flang and relatives, `-mmlir -fdynamic-heap-array` is needed as of LLVM 21.1.8. See
+https://github.com/llvm/llvm-project/issues/88344
+https://github.com/zequipe/flang_heap_arrays
+
1. See the Makefiles for how to compile the examples.
-2. The examples assume that the macros in ../common/ppf.h are set to their default values. In
+
+2. The first example in every folder, example_1, uses the same objective function:
+f(x1, x2) = (x1 - 5)**2 + (x2 - 4)**2.
+The unconstrained minimizer is obviously (5, 4) with an optimal value of 0. This example uses a
+trivial objective function in order to let the user focus on understanding the PRIMA API and usage.
+
+3. The examples assume that the macros in ../common/ppf.h are set to their default values. In
particular, PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0 (default integer).
-3. In the Makefiles, we impose Fortran 2018 standard in the compilation. It is our intention to be
+
+4. In the Makefiles, we impose Fortran 2018 standard in the compilation. It is our intention to be
compliant with Fortran 2008 and above.
-4. As of June 2023, the examples run successfully with the following compilers.
-- AMD AOCC Flang 4.0.0
-- Arm Fortran Compiler 22.1
-- Classic Flang 15.0.3
-- GNU gfortran 12.1.0
-- Intel ifort 2021.9.0
-- Intel ifx 2023.1.0
-- NAG Fortran Compiler Release 7.1
-- NVIDIA nvfortran 23.5
+
+5. As of January 2026, the examples run successfully with the following compilers on Ubuntu 24.04.
+- AMD AOCC Flang 5.1
+- AMD AOMP Flang 22.0
+- Arm Fortran Compiler 23.10
+- LLVM Flang 21.1
+- GNU gfortran 14.2
+- Intel ifx 2025.3
+- Intel ifort 2021.11.1
+- NVIDIA nvfortran 26.1
+- NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7231
The following discontinued compilers are not supported: Absoft af95, g95, Oracle sunf95.
Coded by Zaikun ZHANG (www.zhangzk.net).
diff --git a/fortran/examples/bobyqa/CMakeLists.txt b/fortran/examples/bobyqa/CMakeLists.txt
index c2b01f5cb8..64319985dc 100644
--- a/fortran/examples/bobyqa/CMakeLists.txt
+++ b/fortran/examples/bobyqa/CMakeLists.txt
@@ -2,6 +2,9 @@
cmake_minimum_required(VERSION 3.13)
project(bobyqa_example Fortran)
find_package(PRIMA CONFIG REQUIRED)
-add_executable(bobyqa_example bobyqa_example.f90)
-target_link_libraries(bobyqa_example prima::primaf)
-install(TARGETS bobyqa_example DESTINATION bin)
+add_executable(bobyqa_example_1 bobyqa_example_1.f90)
+target_link_libraries(bobyqa_example_1 prima::primaf)
+install(TARGETS bobyqa_example_1 DESTINATION bin)
+add_executable(bobyqa_example_2 bobyqa_example_2.f90)
+target_link_libraries(bobyqa_example_2 prima::primaf)
+install(TARGETS bobyqa_example_2 DESTINATION bin)
diff --git a/fortran/examples/bobyqa/Makefile b/fortran/examples/bobyqa/Makefile
index 999128e34c..b5ca0d51dd 100644
--- a/fortran/examples/bobyqa/Makefile
+++ b/fortran/examples/bobyqa/Makefile
@@ -5,82 +5,57 @@
#
# Started: July 2020
#
-# Last Modified: September 13, 2021
+# Last Modified: February 13, 2026
#
# N.B.:
# The .F90 and .f90 files will be compiled in the enumeration order of the .o files. The order
# matters, because the compilation of each .o file depends on the .o files (and the corresponding
# .mod files) preceding it.
+
.PHONY: test clean
-####################################################################################################
+# All the tests
+test:
+ $(MAKE) dtest
+ $(MAKE) ftest
+ $(MAKE) gtest
+ $(MAKE) itest
+ $(MAKE) mtest
+ $(MAKE) ntest
+ $(MAKE) vtest
+ $(MAKE) xtest
+ $(MAKE) rtest
+
# Variables
SOLVER := $(shell basename $(CURDIR))
# Define the Fortran standard to follow. We aim to make the code compatible with F2008 and above.
-# Make sure that your compiler supports the selected standard. For example, gfortran does not
-# recognize -std=f2018 until gfortran 8.1 released in May 2018.
+# Make sure that your compiler supports the selected standard.
FS ?= 18 # Set FS if it does not have a value.
FSTD := 20$(FS)
# Default options for all the compilers.
-FFLAGS := -O3 -g
+FFLAGS := -O -g
# Common directories.
COMMON := ../../common
# Headers.
HEADERS := $(COMMON)/*.h
# Solver source files.
SOLVER_SRC := ../../$(SOLVER)
+# Overridable selection for the program to compile.
+EXAMPLE_NUM ?= 1
+EXAMPLE_SRC := $(SOLVER)_example_$(EXAMPLE_NUM).f90
-####################################################################################################
-# All the tests
-test:
- $(MAKE) dtest
- $(MAKE) ftest
- $(MAKE) gtest
- $(MAKE) itest
- $(MAKE) ntest
- $(MAKE) vtest
- $(MAKE) xtest
- $(MAKE) rtest
-
-####################################################################################################
-# Here are the compilers to test.
-
-# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2> /dev/null | sort | tail -n 1)
-dtest: FC := $(AFLANG) -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# Flang
-ftest: FC := flang -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# GNU gfortran: due to `error stop` and `backtrace`, we must either compile wit no `-std` or
-# use `-std=f20xy -fall-intrinsics` with xy >= 18.
-gtest: FC := gfortran -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
-
-# Intel ifort
-itest: FC := ifort -warn all -stand f$(FS)
-
-# NAG nagfor
-ntest: FC := nagfor -C -f$(FSTD)
-
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2> /dev/null | sort | tail -n 1)
-vtest: FC := $(VFORT) -C -Wall -Wextra -Mstandard
-
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2> /dev/null | sort | tail -n 1)
-rtest: FC := $(RFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+# Include the common Makefile defining the compilers and their options.
+include ../Makefile.common
-# Intel ifx
-xtest: FC := ifx -warn all -stand f$(FS)
####################################################################################################
# Compile the binary needed for a compiler-specific test
-%test: $(SOLVER)_example.f90 \
- consts.o infos.o debug.o inf.o infnan.o memory.o string.o linalg.o powalg.o univar.o ratio.o \
+%test: $(EXAMPLE_SRC) \
+ consts.o infos.o debug.o huge.o inf.o infnan.o memory.o string.o linalg.o powalg.o univar.o ratio.o \
redrho.o history.o checkexit.o fprint.o message.o preproc.o pintrf.o evaluate.o shiftbase.o \
xinbd.o update.o initialize.o rescue.o trustregion.o geometry.o bobyqb.o bobyqa.o
- $(FC) $(FFLAGS) -o $@ $(SOLVER)_example.f90 *.o
+ $(FC) $(FFLAGS) -o $@ $(EXAMPLE_SRC) *.o
./$@
@$(MAKE) clean
diff --git a/fortran/examples/bobyqa/bobyqa_example_1.f90 b/fortran/examples/bobyqa/bobyqa_example_1.f90
new file mode 100644
index 0000000000..a1d1cafe8b
--- /dev/null
+++ b/fortran/examples/bobyqa/bobyqa_example_1.f90
@@ -0,0 +1,92 @@
+!--------------------------------------------------------------------------------------------------!
+! This is an example to illustrate the usage of the solver.
+!
+! The objective function is trivial. This is intentional, as the focus is how to use the API.
+!--------------------------------------------------------------------------------------------------!
+
+
+!------------------------- THE MODULE THAT IMPLEMENTS CALFUN, CALLBACK_FCN ------------------------!
+module calfun_mod
+
+implicit none
+private
+public :: IK, RP, calfun, callback_fcn
+integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
+
+contains
+
+! Objective function
+subroutine calfun(x, f)
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x(:)
+
+! Outputs
+real(RP), intent(out) :: f
+
+f = (x(1) - 5.0_RP)**2 + (x(2) - 4.0_RP)**2
+
+end subroutine calfun
+
+! Callback function
+subroutine callback_fcn(x, f, nf, tr, cstrv, nlconstr, terminate)
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(in) :: f
+integer(IK), intent(in) :: nf
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv
+real(RP), intent(in), optional :: nlconstr(:)
+logical, intent(out), optional :: terminate
+
+if (.false.) print *, cstrv ! Suppress compiler warning about unused variable
+if (.false.) print *, nlconstr ! Suppress compiler warning about unused variable
+
+write (*, '("Best point so far: x = [", F6.4, ", ", F6.4, "], f = ", F6.3, ", nf = ", I0, ", tr = ", I0, "")') &
+ & x(1), x(2), f, nf, tr
+
+terminate = .false.
+
+end subroutine callback_fcn
+
+end module calfun_mod
+
+
+!---------------------------------------- THE MAIN PROGRAM ----------------------------------------!
+program bobyqa_exmp
+
+! The following line makes the solver available.
+use bobyqa_mod, only : bobyqa
+
+! The following line specifies which module provides CALFUN and CALLBACK_FCN.
+use calfun_mod, only : RP, IK, calfun, callback_fcn
+
+implicit none
+
+integer, parameter :: n = 2
+integer :: nf, info
+real(RP) :: f, x(n), x0(n), lb(n), ub(n)
+
+! Define the starting point.
+x0 = 0.0_RP
+
+! Define the lower and upper bounds. We define an upper bound that will be active
+! in order to demonstrate the usage of bounds.
+lb = -1.0_RP
+ub = 4.5_RP
+
+! The following lines illustrates how to call the solver.
+x = x0
+call bobyqa(calfun, x, f, lb, ub) ! This call will not print anything.
+
+! In addition to the compulsory arguments, the following illustration specifies also RHOBEG and
+! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+! take their default values coded in the solver.
+x = x0
+call bobyqa(calfun, x, f, lb, ub, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info, callback_fcn=callback_fcn)
+
+end program bobyqa_exmp
diff --git a/fortran/examples/bobyqa/bobyqa_example.f90 b/fortran/examples/bobyqa/bobyqa_example_2.f90
similarity index 82%
rename from fortran/examples/bobyqa/bobyqa_example.f90
rename to fortran/examples/bobyqa/bobyqa_example_2.f90
index ae89fd79f0..5923420e45 100644
--- a/fortran/examples/bobyqa/bobyqa_example.f90
+++ b/fortran/examples/bobyqa/bobyqa_example_2.f90
@@ -5,7 +5,7 @@
!
! Started: July 2020
!
-! Last Modified: Tuesday, May 30, 2023 PM06:26:48
+! Last Modified: Friday, March 15, 2024 PM02:04:34
!--------------------------------------------------------------------------------------------------!
@@ -14,8 +14,11 @@ module calfun_mod
implicit none
private
-public :: RP, calfun
+public :: RP, IK, calfun
integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
contains
@@ -40,7 +43,7 @@ subroutine calfun(x, f)
do i = 4, n, 2
do j = 2, i - 2, 2
temp = (x(i - 1) - x(j - 1))**2 + (x(i) - x(j))**2
- temp = max(temp, 1.0D-6)
+ temp = max(temp, 1.0E-4_RP)
f = f + 1.0_RP / sqrt(temp)
end do
end do
@@ -57,7 +60,7 @@ program bobyqa_exmp
use bobyqa_mod, only : bobyqa
! The following line specifies which module provides CALFUN.
-use calfun_mod, only : RP, calfun
+use calfun_mod, only : RP, IK, calfun
implicit none
@@ -82,7 +85,6 @@ program bobyqa_exmp
! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
! take their default values coded in the solver.
x = x0
-call bobyqa(calfun, x, f, lb, ub, rhobeg=1.0D-1, iprint=1, nf=nf, info=info)
-
+call bobyqa(calfun, x, f, lb, ub, rhobeg=0.1_RP, iprint=1_IK, nf=nf, info=info)
end program bobyqa_exmp
diff --git a/fortran/examples/cobyla/CMakeLists.txt b/fortran/examples/cobyla/CMakeLists.txt
index f98297e822..40f1dee7da 100644
--- a/fortran/examples/cobyla/CMakeLists.txt
+++ b/fortran/examples/cobyla/CMakeLists.txt
@@ -2,6 +2,9 @@
cmake_minimum_required(VERSION 3.13)
project(cobyla_example Fortran)
find_package(PRIMA CONFIG REQUIRED)
-add_executable(cobyla_example cobyla_example.f90)
-target_link_libraries(cobyla_example prima::primaf)
-install(TARGETS cobyla_example DESTINATION bin)
+add_executable(cobyla_example_1 cobyla_example_1.f90)
+target_link_libraries(cobyla_example_1 prima::primaf)
+install(TARGETS cobyla_example_1 DESTINATION bin)
+add_executable(cobyla_example_2 cobyla_example_2.f90)
+target_link_libraries(cobyla_example_2 prima::primaf)
+install(TARGETS cobyla_example_2 DESTINATION bin)
diff --git a/fortran/examples/cobyla/Makefile b/fortran/examples/cobyla/Makefile
index 7d8cd14f71..c2cef5b4b5 100644
--- a/fortran/examples/cobyla/Makefile
+++ b/fortran/examples/cobyla/Makefile
@@ -5,82 +5,57 @@
#
# Started: July 2020
#
-# Last Modified: September 13, 2021
+# Last Modified: February 13, 2026
#
# N.B.:
# The .F90 and .f90 files will be compiled in the enumeration order of the .o files. The order
# matters, because the compilation of each .o file depends on the .o files (and the corresponding
# .mod files) preceding it.
+
.PHONY: test clean
-####################################################################################################
+# All the tests
+test:
+ $(MAKE) dtest
+ $(MAKE) ftest
+ $(MAKE) gtest
+ $(MAKE) itest
+ $(MAKE) mtest
+ $(MAKE) ntest
+ $(MAKE) vtest
+ $(MAKE) xtest
+ $(MAKE) rtest
+
# Variables
SOLVER := $(shell basename $(CURDIR))
# Define the Fortran standard to follow. We aim to make the code compatible with F2008 and above.
-# Make sure that your compiler supports the selected standard. For example, gfortran does not
-# recognize -std=f2018 until gfortran 8.1 released in May 2018.
+# Make sure that your compiler supports the selected standard.
FS ?= 18 # Set FS if it does not have a value.
FSTD := 20$(FS)
# Default options for all the compilers.
-FFLAGS := -O3 -g
+FFLAGS := -O -g
# Common directories.
COMMON := ../../common
# Headers.
HEADERS := $(COMMON)/*.h
# Solver source files.
SOLVER_SRC := ../../$(SOLVER)
+# Overridable selection for the program to compile.
+EXAMPLE_NUM ?= 1
+EXAMPLE_SRC := $(SOLVER)_example_$(EXAMPLE_NUM).f90
-####################################################################################################
-# All the tests
-test:
- $(MAKE) dtest
- $(MAKE) ftest
- $(MAKE) gtest
- $(MAKE) itest
- $(MAKE) ntest
- $(MAKE) vtest
- $(MAKE) xtest
- $(MAKE) rtest
-
-####################################################################################################
-# Here are the compilers to test.
-
-# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2> /dev/null | sort | tail -n 1)
-dtest: FC := $(AFLANG) -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# Flang
-ftest: FC := flang -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# GNU gfortran: due to `error stop` and `backtrace`, we must either compile wit no `-std` or
-# use `-std=f20xy -fall-intrinsics` with xy >= 18.
-gtest: FC := gfortran -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
-
-# Intel ifort
-itest: FC := ifort -warn all -stand f$(FS)
-
-# NAG nagfor
-ntest: FC := nagfor -C -f$(FSTD)
-
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2> /dev/null | sort | tail -n 1)
-vtest: FC := $(VFORT) -C -Wall -Wextra -Mstandard
-
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2> /dev/null | sort | tail -n 1)
-rtest: FC := $(RFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+# Include the common Makefile defining the compilers and their options.
+include ../Makefile.common
-# Intel ifx
-xtest: FC := ifx -warn all -stand f$(FS)
####################################################################################################
# Compile the binary needed for a compiler-specific test
-%test: $(SOLVER)_example.f90 \
- consts.o infos.o debug.o inf.o infnan.o memory.o string.o linalg.o powalg.o \
+%test: $(EXAMPLE_SRC) \
+ consts.o infos.o debug.o huge.o inf.o infnan.o memory.o string.o linalg.o powalg.o \
ratio.o redrho.o history.o selectx.o checkexit.o fprint.o message.o preproc.o pintrf.o evaluate.o \
update.o initialize.o trustregion.o geometry.o cobylb.o cobyla.o
- $(FC) $(FFLAGS) -o $@ $(SOLVER)_example.f90 *.o
+ $(FC) $(FFLAGS) -o $@ $(EXAMPLE_SRC) *.o
./$@
@$(MAKE) clean
diff --git a/fortran/examples/cobyla/cobyla_example_1.f90 b/fortran/examples/cobyla/cobyla_example_1.f90
new file mode 100644
index 0000000000..408691183b
--- /dev/null
+++ b/fortran/examples/cobyla/cobyla_example_1.f90
@@ -0,0 +1,91 @@
+!--------------------------------------------------------------------------------------------------!
+! This is an example to illustrate the usage of the solver.
+!
+! The objective function is trivial. This is intentional, as the focus is how to use the API.
+!--------------------------------------------------------------------------------------------------!
+
+
+!------------------------- THE MODULE THAT IMPLEMENTS CALCFC, CALLBACK_FCN ------------------------!
+module calcfc_mod
+
+implicit none
+private
+public :: IK, RP, calcfc, callback_fcn
+integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
+
+contains
+
+! Objective function
+! This one is slightly different from the other example_1 files since COBYLA
+! requires a different signature for the objective function.
+subroutine calcfc(x, f, constr)
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x(:)
+
+! Outputs
+real(RP), intent(out) :: f
+real(RP), intent(out) :: constr(:)
+
+f = (x(1) - 5.0_RP)**2 + (x(2) - 4.0_RP)**2
+
+! We add a constraint we know will be active in order to demonstrate usage
+! The constraint is x(1)**2 - 9 <= 0, meaning |x1| <= 3.
+constr(1) = x(1)**2 - 9.0_RP
+
+end subroutine calcfc
+
+! Callback function
+subroutine callback_fcn(x, f, nf, tr, cstrv, nlconstr, terminate)
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(in) :: f
+integer(IK), intent(in) :: nf
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv
+real(RP), intent(in), optional :: nlconstr(:)
+logical, intent(out), optional :: terminate
+
+write (*, '("Best point so far: x = [", F6.4, ", ", F6.4, "], f = ", F6.3, ", cstrv = ", F6.3, &
+ & ", nlconstr = [", F6.3, "], nf = ", I0, ", tr = ", I0, "")') x(1), x(2), f, cstrv, nlconstr(1), nf, tr
+
+terminate = .false.
+
+end subroutine callback_fcn
+
+end module calcfc_mod
+
+
+!---------------------------------------- THE MAIN PROGRAM ----------------------------------------!
+program cobyla_exmp
+
+! The following line makes the solver available.
+use cobyla_mod, only : cobyla
+
+! The following line specifies which module provides CALCFC and CALLBACK_FCN.
+use calcfc_mod, only : RP, IK, calcfc, callback_fcn
+
+implicit none
+
+integer, parameter :: n = 2
+integer :: nf, info
+real(RP) :: f, x(n), x0(n), cstrv
+
+! Define the starting point.
+x0 = 0.0_RP
+
+! The following lines illustrates how to call the solver.
+x = x0
+call cobyla(calcfc, 1_IK, x, f, cstrv) ! This call will not print anything.
+
+! In addition to the compulsory arguments, the following illustration specifies also RHOBEG and
+! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+! take their default values coded in the solver.
+x = x0
+call cobyla(calcfc, 1_IK, x, f, cstrv, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info, callback_fcn=callback_fcn)
+
+end program cobyla_exmp
diff --git a/fortran/examples/cobyla/cobyla_example.f90 b/fortran/examples/cobyla/cobyla_example_2.f90
similarity index 84%
rename from fortran/examples/cobyla/cobyla_example.f90
rename to fortran/examples/cobyla/cobyla_example_2.f90
index 6f0c8a0705..723cda6fa3 100644
--- a/fortran/examples/cobyla/cobyla_example.f90
+++ b/fortran/examples/cobyla/cobyla_example_2.f90
@@ -5,7 +5,7 @@
!
! Started: July 2020
!
-! Last Modified: Tuesday, September 12, 2023 AM10:52:56
+! Last Modified: Friday, March 15, 2024 PM02:05:11
!--------------------------------------------------------------------------------------------------!
@@ -14,8 +14,11 @@ module calcfc_mod
implicit none
private
-public :: RP, calcfc_chebyquad, calcfc_hexagon
+public :: RP, IK, calcfc_chebyquad, calcfc_hexagon
integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
contains
@@ -65,12 +68,12 @@ subroutine calcfc_hexagon(x, f, constr)
constr(1) = -1.0_RP + x(3)**2 + x(4)**2
constr(2) = -1.0_RP + x(9)**2
constr(3) = -1.0_RP + x(5)**2 + x(6)**2
-constr(4) = -1.0_RP + x(1)**2 + (x(2) + x(9))**2
-constr(5) = -1.0_RP + (x(1) + x(5))**2 + (x(2) + x(6))**2
-constr(6) = -1.0_RP + (x(1) + x(7))**2 + (x(2) + x(8))**2
-constr(7) = -1.0_RP + (x(3) + x(5))**2 + (x(4) + x(6))**2
-constr(8) = -1.0_RP + (x(3) + x(7))**2 + (x(4) + x(8))**2
-constr(9) = -1.0_RP + x(7)**2 + (x(8) + x(9))**2
+constr(4) = -1.0_RP + x(1)**2 + (x(2) - x(9))**2
+constr(5) = -1.0_RP + (x(1) - x(5))**2 + (x(2) - x(6))**2
+constr(6) = -1.0_RP + (x(1) - x(7))**2 + (x(2) - x(8))**2
+constr(7) = -1.0_RP + (x(3) - x(5))**2 + (x(4) - x(6))**2
+constr(8) = -1.0_RP + (x(3) - x(7))**2 + (x(4) - x(8))**2
+constr(9) = -1.0_RP + x(7)**2 + (x(8) - x(9))**2
constr(10) = -x(1) * x(4) + x(2) * x(3)
constr(11) = -x(3) * x(9)
constr(12) = -x(5) * x(9)
@@ -88,7 +91,7 @@ program cobyla_exmp
use cobyla_mod, only : cobyla
! The following line specifies which module provides CALCFC.
-use calcfc_mod, only : RP, calcfc_chebyquad, calcfc_hexagon
+use calcfc_mod, only : RP, IK, calcfc_chebyquad, calcfc_hexagon
implicit none
@@ -109,7 +112,7 @@ program cobyla_exmp
! and IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
! take their default values coded in the solver.
x_chebyquad = [(real(i, RP) / real(n_chebyquad + 1, RP), i=1, n_chebyquad)] ! Starting point
-call cobyla(calcfc_chebyquad, m, x_chebyquad, f, cstrv, rhobeg=0.2_RP * x_chebyquad(1), iprint=1, nf=nf, info=info)
+call cobyla(calcfc_chebyquad, m, x_chebyquad, f, cstrv, rhobeg=0.2_RP * x_chebyquad(1), iprint=1_IK, nf=nf, info=info)
! The following lines illustrates how to call the solver to solve the Hexagon problem.
x_hexagon = 2.0_RP ! Starting point.
@@ -122,7 +125,7 @@ program cobyla_exmp
! the value of CONSTR(X_HEXAGON) when the solver returns.
x_hexagon = 2.0_RP ! Starting point.
allocate (constr(m))
-call cobyla(calcfc_hexagon, m, x_hexagon, f, cstrv, nlconstr=constr, rhobeg=1.0_RP, rhoend=1.0E-3_RP, iprint=1, nf=nf, info=info)
+call cobyla(calcfc_hexagon, m, x_hexagon, f, cstrv, nlconstr=constr, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info)
deallocate (constr) ! Deallocate the array CONSTR, which is allocated by the solver. Otherwise, memory leaks.
end program cobyla_exmp
diff --git a/fortran/examples/lincoa/CMakeLists.txt b/fortran/examples/lincoa/CMakeLists.txt
index eb555b07f8..1cb340d9bf 100644
--- a/fortran/examples/lincoa/CMakeLists.txt
+++ b/fortran/examples/lincoa/CMakeLists.txt
@@ -2,6 +2,9 @@
cmake_minimum_required(VERSION 3.13)
project(lincoa_example Fortran)
find_package(PRIMA CONFIG REQUIRED)
-add_executable(lincoa_example lincoa_example.f90)
-target_link_libraries(lincoa_example prima::primaf)
-install(TARGETS lincoa_example DESTINATION bin)
+add_executable(lincoa_example_1 lincoa_example_1.f90)
+target_link_libraries(lincoa_example_1 prima::primaf)
+install(TARGETS lincoa_example_1 DESTINATION bin)
+add_executable(lincoa_example_2 lincoa_example_2.f90)
+target_link_libraries(lincoa_example_2 prima::primaf)
+install(TARGETS lincoa_example_2 DESTINATION bin)
diff --git a/fortran/examples/lincoa/Makefile b/fortran/examples/lincoa/Makefile
index 1510d5f96d..987badb24c 100644
--- a/fortran/examples/lincoa/Makefile
+++ b/fortran/examples/lincoa/Makefile
@@ -5,83 +5,57 @@
#
# Started: July 2020
#
-# Last Modified: September 13, 2021
+# Last Modified: February 13, 2026
#
# N.B.:
# The .F90 and .f90 files will be compiled in the enumeration order of the .o files. The order
# matters, because the compilation of each .o file depends on the .o files (and the corresponding
# .mod files) preceding it.
+
.PHONY: test clean
-####################################################################################################
+# All the tests
+test:
+ $(MAKE) dtest
+ $(MAKE) ftest
+ $(MAKE) gtest
+ $(MAKE) itest
+ $(MAKE) mtest
+ $(MAKE) ntest
+ $(MAKE) vtest
+ $(MAKE) xtest
+ $(MAKE) rtest
+
# Variables
SOLVER := $(shell basename $(CURDIR))
# Define the Fortran standard to follow. We aim to make the code compatible with F2008 and above.
-# Make sure that your compiler supports the selected standard. For example, gfortran does not
-# recognize -std=f2018 until gfortran 8.1 released in May 2018.
+# Make sure that your compiler supports the selected standard.
FS ?= 18 # Set FS if it does not have a value.
FSTD := 20$(FS)
# Default options for all the compilers.
-FFLAGS := -O3 -g
+FFLAGS := -O -g
# Common directories.
COMMON := ../../common
# Headers.
HEADERS := $(COMMON)/*.h
# Solver source files.
SOLVER_SRC := ../../$(SOLVER)
+# Overridable selection for the program to compile.
+EXAMPLE_NUM ?= 1
+EXAMPLE_SRC := $(SOLVER)_example_$(EXAMPLE_NUM).f90
-####################################################################################################
-# All the tests
-test:
- $(MAKE) dtest
- $(MAKE) ftest
- $(MAKE) gtest
- $(MAKE) itest
- $(MAKE) ntest
- $(MAKE) vtest
- $(MAKE) xtest
- $(MAKE) rtest
-
-####################################################################################################
-# Here are the compilers to test.
-
-# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2> /dev/null | sort | tail -n 1)
-dtest: FC := $(AFLANG) -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# Flang
-ftest: FC := flang -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# GNU gfortran: due to `error stop` and `backtrace`, we must either compile wit no `-std` or
-# use `-std=f20xy -fall-intrinsics` with xy >= 18.
-gtest: FC := gfortran -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
-
-# Intel ifort
-itest: FC := ifort -warn all -stand f$(FS)
-
-# NAG nagfor
-ntest: FC := nagfor -C -f$(FSTD)
-
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2> /dev/null | sort | tail -n 1)
-#vtest: FC := $(VFORT) -C -Wall -Wextra -Mstandard # As of nvfortran 22.2-0, `-C` leads to a false positive of out of boundary subscript.
-vtest: FC := $(VFORT) -Wall -Wextra -Mstandard
-
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2> /dev/null | sort | tail -n 1)
-rtest: FC := $(RFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+# Include the common Makefile defining the compilers and their options.
+include ../Makefile.common
-# Intel ifx
-xtest: FC := ifx -warn all -stand f$(FS)
####################################################################################################
# Compile the binary needed for a compiler-specific test
-%test: $(SOLVER)_example.f90 \
- consts.o infos.o debug.o inf.o infnan.o memory.o string.o linalg.o powalg.o ratio.o redrho.o \
+%test: $(EXAMPLE_SRC) \
+ consts.o infos.o debug.o huge.o inf.o infnan.o memory.o string.o linalg.o powalg.o ratio.o redrho.o \
history.o selectx.o checkexit.o fprint.o message.o preproc.o pintrf.o evaluate.o shiftbase.o \
update.o initialize.o getact.o trustregion.o geometry.o lincob.o lincoa.o
- $(FC) $(FFLAGS) -o $@ $(SOLVER)_example.f90 *.o
+ $(FC) $(FFLAGS) -o $@ $(EXAMPLE_SRC) *.o
./$@
@$(MAKE) clean
diff --git a/fortran/examples/lincoa/lincoa_example_1.f90 b/fortran/examples/lincoa/lincoa_example_1.f90
new file mode 100644
index 0000000000..432e7ee8a9
--- /dev/null
+++ b/fortran/examples/lincoa/lincoa_example_1.f90
@@ -0,0 +1,91 @@
+!--------------------------------------------------------------------------------------------------!
+! This is an example to illustrate the usage of the solver.
+!
+! The objective function is trivial. This is intentional, as the focus is how to use the API.
+!--------------------------------------------------------------------------------------------------!
+
+
+!------------------------- THE MODULE THAT IMPLEMENTS CALFUN, CALLBACK_FCN ------------------------!
+module calfun_mod
+
+implicit none
+private
+public :: IK, RP, calfun, callback_fcn
+integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
+
+contains
+
+! Objective function
+subroutine calfun(x, f)
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x(:)
+
+! Outputs
+real(RP), intent(out) :: f
+
+f = (x(1) - 5.0_RP)**2 + (x(2) - 4.0_RP)**2
+
+end subroutine calfun
+
+! Callback function
+subroutine callback_fcn(x, f, nf, tr, cstrv, nlconstr, terminate)
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(in) :: f
+integer(IK), intent(in) :: nf
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv
+real(RP), intent(in), optional :: nlconstr(:)
+logical, intent(out), optional :: terminate
+
+if (.false.) print *, nlconstr ! Suppress compiler warning about unused variable
+
+write (*, '("Best point so far: x = [", F6.4, ", ", F6.4, "], f = ", F6.3, ", cstrv = ", F6.3, &
+& ", nf = ", I0, ", tr = ", I0, "")') x(1), x(2), f, cstrv, nf, tr
+
+terminate = .false.
+
+end subroutine callback_fcn
+
+end module calfun_mod
+
+
+!---------------------------------------- THE MAIN PROGRAM ----------------------------------------!
+program lincoa_exmp
+
+! The following line makes the solver available.
+use lincoa_mod, only : lincoa
+
+! The following line specifies which module provides CALFUN and CALLBACK_FCN.
+use calfun_mod, only : RP, IK, calfun, callback_fcn
+
+implicit none
+
+integer, parameter :: n = 2
+integer :: nf, info
+real(RP) :: f, cstrv, x(n), x0(n), Aineq(1, n), bineq(1)
+
+! Define the starting point.
+x0 = 0.0_RP
+
+! Define the constraints. We define constraints that will be active
+! in order to demonstrate their usage. The constraint is x1 + x2 < 8
+Aineq = 1.0_RP
+bineq = 8.0_RP
+
+! The following lines illustrates how to call the solver.
+x = x0
+call lincoa(calfun, x, f, cstrv, Aineq, bineq) ! This call will not print anything.
+
+! In addition to the compulsory arguments, the following illustration specifies also RHOBEG and
+! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+! take their default values coded in the solver.
+x = x0
+call lincoa(calfun, x, f, cstrv, Aineq, bineq, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info, callback_fcn=callback_fcn)
+
+end program lincoa_exmp
diff --git a/fortran/examples/lincoa/lincoa_example.f90 b/fortran/examples/lincoa/lincoa_example_2.f90
similarity index 91%
rename from fortran/examples/lincoa/lincoa_example.f90
rename to fortran/examples/lincoa/lincoa_example_2.f90
index 936b18235b..32da6ed323 100644
--- a/fortran/examples/lincoa/lincoa_example.f90
+++ b/fortran/examples/lincoa/lincoa_example_2.f90
@@ -5,7 +5,7 @@
!
! Started: July 2020
!
-! Last Modified: Monday, July 03, 2023 PM02:47:06
+! Last Modified: Friday, March 15, 2024 PM02:06:02
!--------------------------------------------------------------------------------------------------!
@@ -14,8 +14,11 @@ module tetrahedron_mod
implicit none
private
-public :: RP, calfun, setup
+public :: RP, IK, calfun, setup
integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
contains
@@ -117,7 +120,7 @@ program lincoa_exmp
use lincoa_mod, only : lincoa
! The following line specifies which module provides CALFUN.
-use tetrahedron_mod, only : RP, calfun, setup
+use tetrahedron_mod, only : RP, IK, calfun, setup
implicit none
@@ -136,6 +139,6 @@ program lincoa_exmp
! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
! take their default values coded in the solver.
x = x0
-call lincoa(calfun, x, f, cstrv, Aineq, bineq, rhobeg=1.0_RP, iprint=1, nf=nf, info=info)
+call lincoa(calfun, x, f, cstrv, Aineq, bineq, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info)
end program lincoa_exmp
diff --git a/fortran/examples/newuoa/CMakeLists.txt b/fortran/examples/newuoa/CMakeLists.txt
index 313da33939..23929bde3d 100644
--- a/fortran/examples/newuoa/CMakeLists.txt
+++ b/fortran/examples/newuoa/CMakeLists.txt
@@ -2,6 +2,9 @@
cmake_minimum_required(VERSION 3.13)
project(newuoa_example Fortran)
find_package(PRIMA CONFIG REQUIRED)
-add_executable(newuoa_example newuoa_example.f90)
-target_link_libraries(newuoa_example prima::primaf)
-install(TARGETS newuoa_example DESTINATION bin)
+add_executable(newuoa_example_1 newuoa_example_1.f90)
+target_link_libraries(newuoa_example_1 prima::primaf)
+install(TARGETS newuoa_example_1 DESTINATION bin)
+add_executable(newuoa_example_2 newuoa_example_2.f90)
+target_link_libraries(newuoa_example_2 prima::primaf)
+install(TARGETS newuoa_example_2 DESTINATION bin)
diff --git a/fortran/examples/newuoa/Makefile b/fortran/examples/newuoa/Makefile
index df9f49153b..e4e19463e3 100644
--- a/fortran/examples/newuoa/Makefile
+++ b/fortran/examples/newuoa/Makefile
@@ -5,82 +5,57 @@
#
# Started: July 2020
#
-# Last Modified: September 13, 2021
+# Last Modified: February 13, 2026
#
# N.B.:
# The .F90 and .f90 files will be compiled in the enumeration order of the .o files. The order
# matters, because the compilation of each .o file depends on the .o files (and the corresponding
# .mod files) preceding it.
+
.PHONY: test clean
-####################################################################################################
+# All the tests
+test:
+ $(MAKE) dtest
+ $(MAKE) ftest
+ $(MAKE) gtest
+ $(MAKE) itest
+ $(MAKE) mtest
+ $(MAKE) ntest
+ $(MAKE) vtest
+ $(MAKE) xtest
+ $(MAKE) rtest
+
# Variables
SOLVER := $(shell basename $(CURDIR))
# Define the Fortran standard to follow. We aim to make the code compatible with F2008 and above.
-# Make sure that your compiler supports the selected standard. For example, gfortran does not
-# recognize -std=f2018 until gfortran 8.1 released in May 2018.
+# Make sure that your compiler supports the selected standard.
FS ?= 18 # Set FS if it does not have a value.
FSTD := 20$(FS)
# Default options for all the compilers.
-FFLAGS := -O3 -g
+FFLAGS := -O -g
# Common directories.
COMMON := ../../common
# Headers.
HEADERS := $(COMMON)/*.h
# Solver source files.
SOLVER_SRC := ../../$(SOLVER)
+# Overridable selection for the program to compile.
+EXAMPLE_NUM ?= 1
+EXAMPLE_SRC := $(SOLVER)_example_$(EXAMPLE_NUM).f90
-####################################################################################################
-# All the tests
-test:
- $(MAKE) dtest
- $(MAKE) ftest
- $(MAKE) gtest
- $(MAKE) itest
- $(MAKE) ntest
- $(MAKE) vtest
- $(MAKE) xtest
- $(MAKE) rtest
-
-####################################################################################################
-# Here are the compilers to test.
-
-# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2> /dev/null | sort | tail -n 1)
-dtest: FC := $(AFLANG) -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# Flang
-ftest: FC := flang -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# GNU gfortran: due to `error stop` and `backtrace`, we must either compile wit no `-std` or
-# use `-std=f20xy -fall-intrinsics` with xy >= 18.
-gtest: FC := gfortran -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
-
-# Intel ifort
-itest: FC := ifort -warn all -stand f$(FS)
-
-# NAG nagfor
-ntest: FC := nagfor -C -f$(FSTD)
-
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2> /dev/null | sort | tail -n 1)
-vtest: FC := $(VFORT) -C -Wall -Wextra -Mstandard
-
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2> /dev/null | sort | tail -n 1)
-rtest: FC := $(RFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+# Include the common Makefile defining the compilers and their options.
+include ../Makefile.common
-# Intel ifx
-xtest: FC := ifx -warn all -stand f$(FS)
####################################################################################################
# Compile the binary needed for a compiler-specific test
-%test: $(SOLVER)_example.f90 \
- consts.o infos.o debug.o inf.o infnan.o memory.o string.o linalg.o powalg.o univar.o ratio.o \
+%test: $(EXAMPLE_SRC) \
+ consts.o infos.o debug.o huge.o inf.o infnan.o memory.o string.o linalg.o powalg.o univar.o ratio.o \
redrho.o history.o checkexit.o fprint.o message.o preproc.o pintrf.o evaluate.o shiftbase.o \
initialize.o trustregion.o geometry.o update.o newuob.o newuoa.o
- $(FC) $(FFLAGS) -o $@ $(SOLVER)_example.f90 *.o
+ $(FC) $(FFLAGS) -o $@ $(EXAMPLE_SRC) *.o
./$@
@$(MAKE) clean
diff --git a/fortran/examples/newuoa/newuoa_example_1.f90 b/fortran/examples/newuoa/newuoa_example_1.f90
new file mode 100644
index 0000000000..40571d0895
--- /dev/null
+++ b/fortran/examples/newuoa/newuoa_example_1.f90
@@ -0,0 +1,87 @@
+!--------------------------------------------------------------------------------------------------!
+! This is an example to illustrate the usage of the solver.
+!
+! The objective function is trivial. This is intentional, as the focus is how to use the API.
+!--------------------------------------------------------------------------------------------------!
+
+
+!------------------------- THE MODULE THAT IMPLEMENTS CALFUN, CALLBACK_FCN ------------------------!
+module calfun_mod
+
+implicit none
+private
+public :: IK, RP, calfun, callback_fcn
+integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
+
+contains
+
+! Objective function
+subroutine calfun(x, f)
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x(:)
+
+! Outputs
+real(RP), intent(out) :: f
+
+f = (x(1) - 5.0_RP)**2 + (x(2) - 4.0_RP)**2
+
+end subroutine calfun
+
+! Callback function
+subroutine callback_fcn(x, f, nf, tr, cstrv, nlconstr, terminate)
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(in) :: f
+integer(IK), intent(in) :: nf
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv
+real(RP), intent(in), optional :: nlconstr(:)
+logical, intent(out), optional :: terminate
+
+if (.false.) print *, cstrv ! Suppress compiler warning about unused variable
+if (.false.) print *, nlconstr ! Suppress compiler warning about unused variable
+
+write (*, '("Best point so far: x = [", F6.4, ", ", F6.4, "], f = ", F6.3, ", nf = ", I0, ", tr = ", I0, "")') &
+ & x(1), x(2), f, nf, tr
+
+terminate = .false.
+
+end subroutine callback_fcn
+
+end module calfun_mod
+
+
+!---------------------------------------- THE MAIN PROGRAM ----------------------------------------!
+program newuoa_exmp
+
+! The following line makes the solver available.
+use newuoa_mod, only : newuoa
+
+! The following line specifies which module provides CALFUN.
+use calfun_mod, only : RP, IK, calfun, callback_fcn
+
+implicit none
+
+integer, parameter :: n = 2
+integer :: nf, info
+real(RP) :: f, x(n), x0(n)
+
+! Define the starting point.
+x0 = 0.0_RP
+
+! The following lines illustrates how to call the solver.
+x = x0
+call newuoa(calfun, x, f) ! This call will not print anything.
+
+! In addition to the compulsory arguments, the following illustration specifies also RHOBEG and
+! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+! take their default values coded in the solver.
+x = x0
+call newuoa(calfun, x, f, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info, callback_fcn=callback_fcn)
+
+end program newuoa_exmp
diff --git a/fortran/examples/newuoa/newuoa_example.f90 b/fortran/examples/newuoa/newuoa_example_2.f90
similarity index 84%
rename from fortran/examples/newuoa/newuoa_example.f90
rename to fortran/examples/newuoa/newuoa_example_2.f90
index c5246fe584..f8b3d43d7e 100644
--- a/fortran/examples/newuoa/newuoa_example.f90
+++ b/fortran/examples/newuoa/newuoa_example_2.f90
@@ -5,7 +5,7 @@
!
! Started: July 2020
!
-! Last Modified: Tuesday, May 30, 2023 PM06:30:04
+! Last Modified: Friday, March 15, 2024 PM02:06:18
!--------------------------------------------------------------------------------------------------!
@@ -14,8 +14,11 @@ module calfun_mod
implicit none
private
-public :: RP, calfun
+public :: RP, IK, calfun
integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
contains
@@ -57,7 +60,7 @@ program newuoa_exmp
use newuoa_mod, only : newuoa
! The following line specifies which module provides CALFUN.
-use calfun_mod, only : RP, calfun
+use calfun_mod, only : RP, IK, calfun
implicit none
@@ -73,6 +76,6 @@ program newuoa_exmp
! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
! take their default values coded in the solver.
x = [(real(i, RP) / real(n + 1, RP), i=1, n)] ! Define the starting point.
-call newuoa(calfun, x, f, rhobeg=0.2_RP * x(1), iprint=1, nf=nf, info=info)
+call newuoa(calfun, x, f, rhobeg=0.2_RP * x(1), iprint=1_IK, nf=nf, info=info)
end program newuoa_exmp
diff --git a/fortran/examples/uobyqa/CMakeLists.txt b/fortran/examples/uobyqa/CMakeLists.txt
index 0a564e8d18..5034f37a60 100644
--- a/fortran/examples/uobyqa/CMakeLists.txt
+++ b/fortran/examples/uobyqa/CMakeLists.txt
@@ -2,6 +2,9 @@
cmake_minimum_required(VERSION 3.13)
project(uobyqa_example Fortran)
find_package(PRIMA CONFIG REQUIRED)
-add_executable(uobyqa_example uobyqa_example.f90)
-target_link_libraries(uobyqa_example prima::primaf)
-install(TARGETS uobyqa_example DESTINATION bin)
+add_executable(uobyqa_example_1 uobyqa_example_1.f90)
+target_link_libraries(uobyqa_example_1 prima::primaf)
+install(TARGETS uobyqa_example_1 DESTINATION bin)
+add_executable(uobyqa_example_2 uobyqa_example_2.f90)
+target_link_libraries(uobyqa_example_2 prima::primaf)
+install(TARGETS uobyqa_example_2 DESTINATION bin)
diff --git a/fortran/examples/uobyqa/Makefile b/fortran/examples/uobyqa/Makefile
index 0c228d5bd6..02b6d7afb9 100644
--- a/fortran/examples/uobyqa/Makefile
+++ b/fortran/examples/uobyqa/Makefile
@@ -5,82 +5,57 @@
#
# Started: July 2020
#
-# Last Modified: September 13, 2021
+# Last Modified: February 13, 2026
#
# N.B.:
# The .F90 and .f90 files will be compiled in the enumeration order of the .o files. The order
# matters, because the compilation of each .o file depends on the .o files (and the corresponding
# .mod files) preceding it.
+
.PHONY: test clean
-####################################################################################################
+# All the tests
+test:
+ $(MAKE) dtest
+ $(MAKE) ftest
+ $(MAKE) gtest
+ $(MAKE) itest
+ $(MAKE) mtest
+ $(MAKE) ntest
+ $(MAKE) vtest
+ $(MAKE) xtest
+ $(MAKE) rtest
+
# Variables
SOLVER := $(shell basename $(CURDIR))
# Define the Fortran standard to follow. We aim to make the code compatible with F2008 and above.
-# Make sure that your compiler supports the selected standard. For example, gfortran does not
-# recognize -std=f2018 until gfortran 8.1 released in May 2018.
+# Make sure that your compiler supports the selected standard.
FS ?= 18 # Set FS if it does not have a value.
FSTD := 20$(FS)
# Default options for all the compilers.
-FFLAGS := -O3 -g
+FFLAGS := -O -g
# Common directories.
COMMON := ../../common
# Headers.
HEADERS := $(COMMON)/*.h
# Solver source files.
SOLVER_SRC := ../../$(SOLVER)
+# Overridable selection for the program to compile.
+EXAMPLE_NUM ?= 1
+EXAMPLE_SRC := $(SOLVER)_example_$(EXAMPLE_NUM).f90
-####################################################################################################
-# All the tests
-test:
- $(MAKE) dtest
- $(MAKE) ftest
- $(MAKE) gtest
- $(MAKE) itest
- $(MAKE) ntest
- $(MAKE) vtest
- $(MAKE) xtest
- $(MAKE) rtest
-
-####################################################################################################
-# Here are the compilers to test.
-
-# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2> /dev/null | sort | tail -n 1)
-dtest: FC := $(AFLANG) -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# Flang
-ftest: FC := flang -Wall -Wextra -std=f$(FSTD) -Mstandard
-
-# GNU gfortran: due to `error stop` and `backtrace`, we must either compile wit no `-std` or
-# use `-std=f20xy -fall-intrinsics` with xy >= 18.
-gtest: FC := gfortran -Wall -Wextra -Wno-function-elimination -std=f$(FSTD) -fall-intrinsics
-
-# Intel ifort
-itest: FC := ifort -warn all -stand f$(FS)
-
-# NAG nagfor
-ntest: FC := nagfor -C -f$(FSTD)
-
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2> /dev/null | sort | tail -n 1)
-vtest: FC := $(VFORT) -C -Wall -Wextra -Mstandard
-
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2> /dev/null | sort | tail -n 1)
-rtest: FC := $(RFORT) -Wall -Wextra -std=f$(FSTD) -Mstandard
+# Include the common Makefile defining the compilers and their options.
+include ../Makefile.common
-# Intel ifx
-xtest: FC := ifx -warn all -stand f$(FS)
####################################################################################################
# Compile the binary needed for a compiler-specific test
-%test: $(SOLVER)_example.f90 \
- consts.o infos.o debug.o inf.o infnan.o memory.o string.o linalg.o powalg.o ratio.o redrho.o \
+%test: $(EXAMPLE_SRC) \
+ consts.o infos.o debug.o huge.o inf.o infnan.o memory.o string.o linalg.o powalg.o ratio.o redrho.o \
history.o checkexit.o fprint.o message.o preproc.o pintrf.o evaluate.o shiftbase.o \
initialize.o update.o trustregion.o geometry.o uobyqb.o uobyqa.o
- $(FC) $(FFLAGS) -o $@ $(SOLVER)_example.f90 *.o
+ $(FC) $(FFLAGS) -o $@ $(EXAMPLE_SRC) *.o
./$@
@$(MAKE) clean
diff --git a/fortran/examples/uobyqa/uobyqa_example_1.f90 b/fortran/examples/uobyqa/uobyqa_example_1.f90
new file mode 100644
index 0000000000..b9c7cfb600
--- /dev/null
+++ b/fortran/examples/uobyqa/uobyqa_example_1.f90
@@ -0,0 +1,87 @@
+!--------------------------------------------------------------------------------------------------!
+! This is an example to illustrate the usage of the solver.
+!
+! The objective function is trivial. This is intentional, as the focus is how to use the API.
+!--------------------------------------------------------------------------------------------------!
+
+
+!------------------------- THE MODULE THAT IMPLEMENTS CALFUN, CALLBACK_FCN ------------------------!
+module calfun_mod
+
+implicit none
+private
+public :: IK, RP, calfun, callback_fcn
+integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
+
+contains
+
+! Objective function
+subroutine calfun(x, f)
+implicit none
+
+! Inputs
+real(RP), intent(in) :: x(:)
+
+! Outputs
+real(RP), intent(out) :: f
+
+f = (x(1) - 5.0_RP)**2 + (x(2) - 4.0_RP)**2
+
+end subroutine calfun
+
+! Callback function
+subroutine callback_fcn(x, f, nf, tr, cstrv, nlconstr, terminate)
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(in) :: f
+integer(IK), intent(in) :: nf
+integer(IK), intent(in) :: tr
+real(RP), intent(in), optional :: cstrv
+real(RP), intent(in), optional :: nlconstr(:)
+logical, intent(out), optional :: terminate
+
+if (.false.) print *, cstrv ! Suppress compiler warning about unused variable
+if (.false.) print *, nlconstr ! Suppress compiler warning about unused variable
+
+write (*, '("Best point so far: x = [", F6.4, ", ", F6.4, "], f = ", F6.3, ", nf = ", I0, ", tr = ", I0, "")') &
+ & x(1), x(2), f, nf, tr
+
+terminate = .false.
+
+end subroutine callback_fcn
+
+end module calfun_mod
+
+
+!---------------------------------------- THE MAIN PROGRAM ----------------------------------------!
+program uobyqa_exmp
+
+! The following line makes the solver available.
+use uobyqa_mod, only : uobyqa
+
+! The following line specifies which module provides CALFUN and CALLBACK_FCN.
+use calfun_mod, only : RP, IK, calfun, callback_fcn
+
+implicit none
+
+integer, parameter :: n = 2
+integer :: nf, info
+real(RP) :: f, x(n), x0(n)
+
+! Define the starting point.
+x0 = 0.0_RP
+
+! The following lines illustrates how to call the solver.
+x = x0
+call uobyqa(calfun, x, f) ! This call will not print anything.
+
+! In addition to the compulsory arguments, the following illustration specifies also RHOBEG and
+! IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+! take their default values coded in the solver.
+x = x0
+call uobyqa(calfun, x, f, rhobeg=1.0_RP, iprint=1_IK, nf=nf, info=info, callback_fcn=callback_fcn)
+
+end program uobyqa_exmp
diff --git a/fortran/examples/uobyqa/uobyqa_example.f90 b/fortran/examples/uobyqa/uobyqa_example_2.f90
similarity index 84%
rename from fortran/examples/uobyqa/uobyqa_example.f90
rename to fortran/examples/uobyqa/uobyqa_example_2.f90
index 5fbbd59da5..54e053c89a 100644
--- a/fortran/examples/uobyqa/uobyqa_example.f90
+++ b/fortran/examples/uobyqa/uobyqa_example_2.f90
@@ -5,7 +5,7 @@
!
! Started: July 2020
!
-! Last Modified: Tuesday, May 30, 2023 PM06:33:27
+! Last Modified: Friday, March 15, 2024 PM02:06:51
!--------------------------------------------------------------------------------------------------!
@@ -14,8 +14,11 @@ module calfun_mod
implicit none
private
-public :: RP, calfun
+public :: RP, IK, calfun
integer, parameter :: RP = kind(0.0D0)
+integer, parameter :: IK = kind(0)
+! N.B.: We assume that PRIMA_REAL_PRECISION = 64 (double precision) and PRIMA_INTEGER_KIND = 0
+! (default kind). Revise RP and IK if this is not the case.
contains
@@ -57,7 +60,7 @@ program uobyqa_exmp
use uobyqa_mod, only : uobyqa
! The following line specifies which module provides CALFUN.
-use calfun_mod, only : RP, calfun
+use calfun_mod, only : RP, IK, calfun
implicit none
@@ -73,6 +76,6 @@ program uobyqa_exmp
! which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will take their
! default values coded in the solver.
x = [(real(i, RP) / real(n + 1, RP), i=1, n)] ! Starting point
-call uobyqa(calfun, x, f, rhobeg=0.2_RP * x(1), iprint=1, nf=nf, info=info)
+call uobyqa(calfun, x, f, rhobeg=0.2_RP * x(1), iprint=1_IK, nf=nf, info=info)
end program uobyqa_exmp
diff --git a/fortran/lincoa/flint b/fortran/lincoa/flint
index 764125c9f1..75b810d749 120000
--- a/fortran/lincoa/flint
+++ b/fortran/lincoa/flint
@@ -1 +1 @@
-../common/flint
\ No newline at end of file
+../tests/tools/flint
\ No newline at end of file
diff --git a/fortran/lincoa/geometry.f90 b/fortran/lincoa/geometry.f90
index 4ab6560fbb..b01613afb9 100644
--- a/fortran/lincoa/geometry.f90
+++ b/fortran/lincoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_lincoa_mod
!
! Started: February 2022
!
-! Last Modified: Friday, August 25, 2023 AM08:26:17
+! Last Modified: Sunday, April 21, 2024 PM03:15:36
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -38,7 +38,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
use, non_intrinsic :: consts_mod, only : RP, IK, ONE, TENTH, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-use, non_intrinsic :: linalg_mod, only : issymmetric
+use, non_intrinsic :: linalg_mod, only : issymmetric, trueloc
use, non_intrinsic :: powalg_mod, only : calden
implicit none
@@ -98,10 +98,10 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
-! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
-! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
-! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
+! the trust-region trial point has not been included into XPT yet --- it cannot be included without
+! knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code picks KNEW
+! based on the distance to the un-updated "optimal point", which is unreasonable. This has been
+! corrected in our implementation of LINCOA, yet it does not boost the performance.
if (ximproved) then
distsq = sum((xpt - spread(xpt(:, kopt) + d, dim=2, ncopies=npt))**2, dim=1)
!!MATLAB: distsq = sum((xpt - (xpt(:, kopt) + d)).^2) % d should be a column!! Implicit expansion
@@ -160,21 +160,24 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
score(kopt) = -ONE
end if
+! SCORE(K) is NaN implies ABS(DEN(K)) is NaN, but we want ABS(DEN) to be big. So we exclude such K.
+score(trueloc(is_nan(score))) = -ONE
+
+knew = 0
! The following IF works a bit better than `IF (ANY(SCORE > 1) .OR. ANY(SCORE > 0) .AND. XIMPROVED)`
! from Powell's UOBYQA and NEWUOA code.
if (any(score > 0)) then ! Powell's BOBYQA and LINCOA code
- ! SCORE(K) is NaN implies ABS(DEN(K)) is NaN, but we want ABS(DEN) to be big. So we exclude such K.
- knew = int(maxloc(score, mask=(.not. is_nan(score)), dim=1), kind(knew))
- !!MATLAB: [~, knew] = max(score, [], 'omitnan');
-else if (ximproved) then
- ! Powell's code does not include the following instructions. With Powell's code, if DEN consists
- ! of only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the
- ! following value, to make sure that the new trial point is included in the interpolation set.
- ! However, the updating subroutine will likely need to skip the update of the Lagrange
- ! polynomials (i.e., H), or they would be destroyed by the NaNs.
+ knew = int(maxloc(score, dim=1), kind(knew))
+ !!MATLAB: [~, knew] = max(score);
+end if
+
+! Powell's code does not include the following instructions. With Powell's code, if DEN consists of
+! only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the following value,
+! to make sure that the new trial point is included in the interpolation set. However, the updating
+! subroutine will likely need to skip the update of the Lagrange polynomials (i.e., H), or they
+! would be destroyed by the NaNs.
+if ((ximproved .and. knew == 0) .or. knew < 0) then ! KNEW < 0 is impossible in theory.
knew = int(maxloc(distsq, dim=1), kind(knew))
-else
- knew = 0
end if
!====================!
@@ -254,7 +257,7 @@ subroutine geostep(iact, idz, knew, kopt, nact, amat, bmat, delbar, qfac, rescon
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, HALF, TEN, TENTH, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, HALF, TEN, TENTH, MAXPOW10, EPS, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: linalg_mod, only : matprod, inprod, isorth, maximum, trueloc, norm
@@ -327,7 +330,7 @@ subroutine geostep(iact, idz, knew, kopt, nact, amat, bmat, delbar, qfac, rescon
call assert(size(bmat, 1) == n .and. size(bmat, 2) == npt + n, 'SIZE(BMAT) == [N, NPT+N]', srname)
call assert(delbar > 0, 'DELBAR> 0', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n, RP)))
call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
call assert(size(rescon) == m, 'SIZE(RESCON) == M', srname)
@@ -444,12 +447,12 @@ subroutine geostep(iact, idz, knew, kopt, nact, amat, bmat, delbar, qfac, rescon
end if
end if
-! In case S contains NaN, replace it with a displacement from XPT(:, KNEW) to XOPT. Powell's code
-! does not have this.
-if (is_nan(sum(abs(s)))) then
+! In case S is zero or contains Inf/NaN, replace it with a displacement from XPT(:, KNEW) to
+! XOPT. Powell's code does not have this.
+if (sum(abs(s)) <= 0 .or. .not. is_finite(sum(abs(s)))) then
s = xpt(:, knew) - xopt
scaling = delbar / norm(s)
- s = max(0.6_RP * scaling, min(HALF, scaling)) * s
+ s = max(0.6_RP * scaling, min(HALF, scaling)) * s ! 0.6: ensure |D| > DELBAR/2
cstrv = maximum([ZERO, matprod(s, amat(:, trueloc(rstat >= 0))) - rescon(trueloc(rstat >= 0))])
feasible = (cstrv <= 0)
end if
diff --git a/fortran/lincoa/getact.f90 b/fortran/lincoa/getact.f90
index 4d92360821..3876f067d9 100644
--- a/fortran/lincoa/getact.f90
+++ b/fortran/lincoa/getact.f90
@@ -12,7 +12,7 @@ module getact_mod
!
! Started: February 2022
!
-! Last Modified: Friday, September 08, 2023 PM07:25:41
+! Last Modified: Saturday, March 09, 2024 PM12:12:21
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -77,7 +77,7 @@ subroutine getact(amat, delta, g, iact, nact, qfac, resact, resnew, rfac, psd)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, TEN, EPS, REALMAX, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, TEN, MAXPOW10, EPS, REALMAX, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite, is_nan
use, non_intrinsic :: linalg_mod, only : matprod, inprod, eye, istriu, isorth, norm, lsqr, solve, trueloc
@@ -140,7 +140,7 @@ subroutine getact(amat, delta, g, iact, nact, qfac, resact, resnew, rfac, psd)
call assert(size(resact) == m, 'SIZE(RESACT) == M', srname)
call assert(size(resnew) == m, 'SIZE(RESNEW) == M', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n, RP)))
call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
call assert(size(rfac, 1) == n .and. size(rfac, 2) == n, 'SIZE(RFAC) == [N, N]', srname)
call assert(istriu(rfac), 'RFAC is upper triangular', srname)
@@ -202,11 +202,11 @@ subroutine getact(amat, delta, g, iact, nact, qfac, resact, resnew, rfac, psd)
! What is the theoretical maximal number of iterations in the following procedure? Powell's code for
! this part is essentially a `DO WHILE (NACT < N) ... END DO` loop. We enforce the following maximal
! number of iterations, which is never reached in our tests (indeed, even 2*N cannot be reached).
-! N.B.: 1. The MAX below is a precaution against overflow, which will make 2*(m+n) < 0, which can
-! happen if the integer being used is 2-byte. This precaution is UNNEEDED in MATLAB/Python/Julia/R.
+! N.B.: 1. The formulation of MAXITER below contains a precaution against overflow. In
+! MATLAB/Python/Julia/R, we can write maxiter = min(10000, 2*(m + n))
! 2. The iteration counter ITER never appears in the code of the iterations, as its purpose is
! merely to impose an upper bound on the number of iterations.
-maxiter = max(2_IK * (m + n), m)
+maxiter = int(min(10**min(4, range(0_IK)), 2 * int(m + n)), IK)
do iter = 1, maxiter
! When NACT == N, exit with PSD = 0. Indeed, with a correctly implemented matrix product, the
! lines below this IF should render DD = 0 and trigger an exit. We make it explicit for clarity.
@@ -404,7 +404,7 @@ subroutine addact(l, c, iact, nact, qfac, resact, resnew, rfac, vlam)
! gradient of the new active constraint.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, EPS, TEN, MAXPOW10, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : istriu, isorth
use, non_intrinsic :: powalg_mod, only : qradd
@@ -443,7 +443,7 @@ subroutine addact(l, c, iact, nact, qfac, resact, resnew, rfac, vlam)
call assert(all(iact(1:nact) >= 1 .and. iact(1:nact) <= m), '1 <= IACT <= M', srname)
call assert(.not. any(iact(1:nact) == l), 'L is not in IACT(1:NACT)', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n, RP)))
call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
call assert(size(rfac, 1) == n .and. size(rfac, 2) == n, 'SIZE(RFAC) == [N, N]', srname)
call assert(istriu(rfac), 'RFAC is upper triangular', srname)
@@ -497,7 +497,7 @@ subroutine delact(icon, iact, nact, qfac, resact, resnew, rfac, vlam)
! QFAC, etc accordingly, and reduces NACT to NACT-1.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : RP, IK, EPS, TINYCV, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, EPS, TEN, MAXPOW10, TINYCV, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: linalg_mod, only : isorth, istriu
use, non_intrinsic :: powalg_mod, only : qrexc
@@ -536,7 +536,7 @@ subroutine delact(icon, iact, nact, qfac, resact, resnew, rfac, vlam)
call assert(icon >= 1 .and. icon <= nact, '1 <= ICON <= NACT', srname)
call assert(all(iact(1:nact) >= 1 .and. iact(1:nact) <= m), '1 <= IACT <= M', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n, RP)))
+ tol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n, RP)))
call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
call assert(size(rfac, 1) == n .and. size(rfac, 2) == n, 'SIZE(RFAC) == [N, N]', srname)
call assert(istriu(rfac), 'RFAC is upper triangular', srname)
diff --git a/fortran/lincoa/initialize.f90 b/fortran/lincoa/initialize.f90
index a156e00a12..ab3a89a233 100644
--- a/fortran/lincoa/initialize.f90
+++ b/fortran/lincoa/initialize.f90
@@ -1,3 +1,5 @@
+! FIXME: The definitions of CVAL, FEASIBLE, and KOPT are questionable.
+
module initialize_lincoa_mod
!--------------------------------------------------------------------------------------------------!
! This module performs the initialization of LINCOA.
@@ -8,7 +10,7 @@ module initialize_lincoa_mod
!
! Started: February 2022
!
-! Last Modified: Friday, August 04, 2023 PM09:28:30
+! Last Modified: Tue 10 Feb 2026 02:41:49 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -41,7 +43,7 @@ subroutine initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ft
! Common modules
use, non_intrinsic :: checkexit_mod, only : checkexit
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, ZERO, EPS, REALMAX, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, ZERO, EPS, TEN, MAXPOW10, REALMAX, BOUNDMAX, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist
@@ -103,7 +105,7 @@ subroutine initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ft
integer(IK), allocatable :: ixl(:)
integer(IK), allocatable :: ixu(:)
logical :: feasible(size(xpt, 2))
-real(RP) :: constr(count(xl > -REALMAX) + count(xu < REALMAX) + 2 * size(beq) + size(bineq))
+real(RP) :: constr(count(xl > -BOUNDMAX) + count(xu < BOUNDMAX) + 2 * size(beq) + size(bineq))
real(RP) :: constr_leq(size(beq))
real(RP) :: cstrv
real(RP) :: f
@@ -159,7 +161,9 @@ subroutine initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ft
! Initialize XHIST, FHIST, CHIST, FVAL, and CVAL. Otherwise, compilers may complain that they are
! not (completely) initialized if the initialization aborts due to abnormality (see CHECKEXIT).
-! Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! N.B.: 1. Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! 2. Do not initialize the models if the current initialization aborts due to abnormality. Otherwise,
+! errors or exceptions may occur, as FVAL and XPT etc are uninitialized.
xhist = -REALMAX
fhist = REALMAX
chist = REALMAX
@@ -211,10 +215,10 @@ subroutine initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ft
! Set FVAL by evaluating F. Totally parallelizable except for FMSG.
! IXL and IXU are the indices of the nontrivial lower and upper bounds, respectively.
-call safealloc(ixl, int(count(xl > -REALMAX), IK)) ! Removable in F2003.
-call safealloc(ixu, int(count(xu < REALMAX), IK)) ! Removable in F2003.
-ixl = trueloc(xl > -REALMAX)
-ixu = trueloc(xu < REALMAX)
+call safealloc(ixl, int(count(xl > -BOUNDMAX), IK)) ! Removable in F2003.
+call safealloc(ixu, int(count(xu < BOUNDMAX), IK)) ! Removable in F2003.
+ixl = trueloc(xl > -BOUNDMAX)
+ixu = trueloc(xu < BOUNDMAX)
do k = 1, npt
x = xbase + xpt(:, k)
call evaluate(calfun, x, f)
@@ -278,7 +282,7 @@ subroutine initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ft
call assert(size(xhist, 1) == n .and. size(xhist, 2) == maxxhist, 'SIZE(XHIST) == [N, MAXXHIST]', srname)
! LINCOA always starts with a feasible point.
if (m > 0) then
- call assert(all(matprod(xpt(:, 1), amat) - b <= max(1.0E-12_RP, 1.0E2 * EPS) * &
+ call assert(all(matprod(xpt(:, 1), amat) - b <= max(TEN**max(-12, -MAXPOW10), 1.0E2_RP * EPS) * &
& (ONE + sum(abs(xpt(:, 1))) + sum(abs(b)))), 'The starting point is feasible', srname)
end if
end if
@@ -292,12 +296,12 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
! NEWUOA paper (see also (2.7) of the BOBYQA paper).
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : issymmetric, eye
-use, non_intrinsic :: powalg_mod, only : errh
+!use, non_intrinsic :: powalg_mod, only : errh
implicit none
@@ -389,7 +393,7 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
idz = 1
if (present(info)) then
- if (is_nan(sum(abs(bmat)) + sum(abs(zmat)))) then
+ if (any(is_nan(bmat)) .or. any(is_nan(zmat))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -407,8 +411,8 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
& 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
- call assert(errh(idz, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS), &
- & '[IDZ, BMA, ZMAT] represents H = W^{-1}', srname)
+ !call assert(errh(idz, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS), &
+ ! & '[IDZ, BMA, ZMAT] represents H = W^{-1}', srname)
end if
end subroutine inith
diff --git a/fortran/lincoa/lincoa.f90 b/fortran/lincoa/lincoa.f90
index 04198b4a8f..82a2db4c06 100644
--- a/fortran/lincoa/lincoa.f90
+++ b/fortran/lincoa/lincoa.f90
@@ -36,7 +36,7 @@ module lincoa_mod
!
! Started: February 2022
!
-! Last Modified: Friday, September 15, 2023 PM09:40:18
+! Last Modified: Sunday, April 07, 2024 PM04:11:56
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -47,15 +47,15 @@ module lincoa_mod
contains
-subroutine lincoa(calfun, x, f, &
- & cstrv, &
+subroutine lincoa(calfun, x, &
+ & f, cstrv, &
& Aineq, bineq, &
& Aeq, beq, &
& xl, xu, &
& nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, npt, iprint, eta1, eta2, gamma1, gamma2, &
- & xhist, fhist, chist, maxhist, maxfilt, info)
+ & xhist, fhist, chist, maxhist, maxfilt, callback_fcn, info)
!--------------------------------------------------------------------------------------------------!
-! Among all the arguments, only CALFUN, X, and F are obligatory. The others are OPTIONAL and you can
+! Among all the arguments, only CALFUN, and X are obligatory. The others are OPTIONAL and you can
! neglect them unless you are familiar with the algorithm. Any unspecified optional input will take
! the default value detailed below. For instance, we may invoke the solver as follows.
!
@@ -65,7 +65,7 @@ subroutine lincoa(calfun, x, f, &
! or
!
! ! First define CALFUN, X, Aineq, and Bineq, and then do the following.
-! call lincoa(calfun, x, f, Aineq = Aineq, bineq = bineq, rhobeg = 0.5D0, rhoend = 1.0D-3, maxfun = 100)
+! call lincoa(calfun, x, f, cstrv, Aineq = Aineq, bineq = bineq, rhobeg = 1.0D0, rhoend = 1.0D-6)
!
! See examples/lincoa_exmp.f90 for a concrete example.
!
@@ -115,7 +115,11 @@ subroutine lincoa(calfun, x, f, &
!
! XL, XU
! Input, REAL(RP) vectors of size N unless they are both empty, default: [] and [].
-! XL and XU represent the lower and upper bounds of the variables: XL <= X <= XU.
+! XL is the lower bound for X. Its size is either N or 0, the latter signifying that X has no
+! lower bound. Any entry of XL that is NaN or below -BOUNDMAX will be taken as -BOUNDMAX, which
+! effectively means there is no lower bound for the corresponding entry of X. The value of
+! BOUNDMAX is 0.25*HUGE(X), which is about 8.6E37 for single precision and 4.5E307 for double
+! precision. XU is similar.
!
! NF
! Output, INTEGER(IK) scalar.
@@ -135,8 +139,8 @@ subroutine lincoa(calfun, x, f, &
! CTOL
! Input, REAL(RP) scalar, default: machine epsilon.
! CTOL is the tolerance of constraint violation. X is considered feasible if CSTRV(X) <= CTOL.
-! N.B.: 1. CTOL is absolute, not relative. 2. CTOL is used only when selecting the returned X.
-! It does not affect the iterations of the algorithm.
+! N.B.: 1. CTOL is absolute, not relative.
+! 2. CTOL is used for choosing the returned X. It does not affect the iterations of the algorithm.
!
! CWEIGHT
! Input, REAL(RP) scalar, default: CWEIGHT_DFT defined in the module CONSTS_MOD in common/consts.F90.
@@ -195,6 +199,9 @@ subroutine lincoa(calfun, x, f, &
! CONSTS_MOD (see consts.F90 under the directory named "common").
! Use *HIST with caution! (N.B.: the algorithm is NOT designed for large problems).
!
+! CALLBACK_FCN
+! Input, function to report progress and optionally request termination.
+!
! INFO
! Output, INTEGER(IK) scalar.
! INFO is the exit flag. It will be set to one of the following values defined in the module
@@ -206,7 +213,7 @@ subroutine lincoa(calfun, x, f, &
! NAN_INF_MODEL: NaN or Inf occurs in the model;
! NAN_INF_X: NaN or Inf occurs in X;
! DAMAGING_ROUNDING: the rounding error becomes damaging;
-! ZERO_LINEAR_CONSTRAINT: one of the linear constraint has a zero gradient
+! ZERO_LINEAR_CONSTRAINT: one of the linear constraints has a zero gradient
! !--------------------------------------------------------------------------!
! The following case(s) should NEVER occur unless there is a bug.
! NAN_INF_F: the objective function returns NaN or +Inf;
@@ -218,13 +225,14 @@ subroutine lincoa(calfun, x, f, &
use, non_intrinsic :: consts_mod, only : DEBUGGING
use, non_intrinsic :: consts_mod, only : MAXFUN_DIM_DFT, MAXFILT_DFT, IPRINT_DFT
use, non_intrinsic :: consts_mod, only : RHOBEG_DFT, RHOEND_DFT, CTOL_DFT, CWEIGHT_DFT, FTARGET_DFT
-use, non_intrinsic :: consts_mod, only : RP, IK, TWO, HALF, TEN, TENTH, EPS, REALMAX
+use, non_intrinsic :: consts_mod, only : RP, IK, TWO, HALF, TEN, TENTH, EPS, BOUNDMAX
use, non_intrinsic :: debug_mod, only : assert, warning
use, non_intrinsic :: evaluate_mod, only : moderatex
use, non_intrinsic :: history_mod, only : prehist
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
+use, non_intrinsic :: linalg_mod, only : trueloc
use, non_intrinsic :: memory_mod, only : safealloc
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: preproc_mod, only : preproc
use, non_intrinsic :: selectx_mod, only : isbetter
use, non_intrinsic :: string_mod, only : num2str
@@ -237,9 +245,9 @@ subroutine lincoa(calfun, x, f, &
! Compulsory arguments
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
real(RP), intent(inout) :: x(:) ! X(N)
-real(RP), intent(out) :: f
! Optional inputs
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in), optional :: iprint
integer(IK), intent(in), optional :: maxfilt
integer(IK), intent(in), optional :: maxfun
@@ -264,10 +272,11 @@ subroutine lincoa(calfun, x, f, &
! Optional outputs
integer(IK), intent(out), optional :: info
integer(IK), intent(out), optional :: nf
-real(RP), intent(out), allocatable, optional :: chist(:) ! CHIST(MAXCHIST)
-real(RP), intent(out), allocatable, optional :: fhist(:) ! FHIST(MAXFHIST)
-real(RP), intent(out), allocatable, optional :: xhist(:, :) ! XHIST(N, MAXXHIST)
real(RP), intent(out), optional :: cstrv
+real(RP), intent(out), optional :: f
+real(RP), intent(out), optional, allocatable :: chist(:) ! CHIST(MAXCHIST)
+real(RP), intent(out), optional, allocatable :: fhist(:) ! FHIST(MAXFHIST)
+real(RP), intent(out), optional, allocatable :: xhist(:, :) ! XHIST(N, MAXXHIST)
! Local variables
character(len=*), parameter :: solver = 'LINCOA'
@@ -288,11 +297,14 @@ subroutine lincoa(calfun, x, f, &
real(RP) :: cweight_loc
real(RP) :: eta1_loc
real(RP) :: eta2_loc
+real(RP) :: f_loc
real(RP) :: ftarget_loc
real(RP) :: gamma1_loc
real(RP) :: gamma2_loc
real(RP) :: rhobeg_loc
real(RP) :: rhoend_loc
+real(RP) :: xl_loc(size(x))
+real(RP) :: xu_loc(size(x))
real(RP), allocatable :: Aeq_loc(:, :) ! Aeq_LOC(Meq, N)
real(RP), allocatable :: Aineq_loc(:, :) ! Aineq_LOC(Mineq, N)
real(RP), allocatable :: amat(:, :) ! AMAT(N, M); each column corresponds to a constraint
@@ -302,8 +314,6 @@ subroutine lincoa(calfun, x, f, &
real(RP), allocatable :: chist_loc(:) ! CHIST_LOC(MAXCHIST)
real(RP), allocatable :: fhist_loc(:) ! FHIST_LOC(MAXFHIST)
real(RP), allocatable :: xhist_loc(:, :) ! XHIST_LOC(N, MAXXHIST)
-real(RP), allocatable :: xl_loc(:) ! XL_LOC(N)
-real(RP), allocatable :: xu_loc(:) ! XU_LOC(N)
! Sizes
if (present(bineq)) then
@@ -372,19 +382,21 @@ subroutine lincoa(calfun, x, f, &
beq_loc = beq
end if
-call safealloc(xl_loc, n) ! NOT removable even in F2003, as XL may be absent.
+xl_loc = -BOUNDMAX
if (present(xl)) then
- xl_loc = xl
-else
- xl_loc = -REALMAX
+ if (size(xl) > 0) then
+ xl_loc = xl
+ end if
end if
+xl_loc(trueloc(is_nan(xl_loc) .or. xl_loc < -BOUNDMAX)) = -BOUNDMAX
-call safealloc(xu_loc, n) ! NOT removable even in F2003, as XU may be absent.
+xu_loc = BOUNDMAX
if (present(xu)) then
- xu_loc = xu
-else
- xu_loc = REALMAX
+ if (size(xu) > 0) then
+ xu_loc = xu
+ end if
end if
+xu_loc(trueloc(is_nan(xu_loc) .or. xu_loc > BOUNDMAX)) = BOUNDMAX
! If RHOBEG is present, then RHOBEG_LOC is a copy of RHOBEG; otherwise, RHOBEG_LOC takes the default
! value for RHOBEG, taking the value of RHOEND into account. Note that RHOEND is considered only if
@@ -408,7 +420,7 @@ subroutine lincoa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -439,8 +451,8 @@ subroutine lincoa(calfun, x, f, &
if (present(npt)) then
npt_loc = npt
-elseif (maxfun_loc >= 1) then
- npt_loc = max(n + 2_IK, min(maxfun_loc - 1_IK, 2_IK * n + 1_IK))
+elseif (maxfun_loc >= n + 3_IK) then ! Take MAXFUN into account if it is valid.
+ npt_loc = min(maxfun_loc - 1_IK, 2_IK * n + 1_IK)
else
npt_loc = 2_IK * n + 1_IK
end if
@@ -508,18 +520,29 @@ subroutine lincoa(calfun, x, f, &
call get_lincon(Aeq_loc, Aineq_loc, beq_loc, bineq_loc, rhoend_loc, xl_loc, xu_loc, x, amat, bvec)
!-------------------- Call LINCOB, which performs the real calculations. --------------------------!
-call lincob(calfun, iprint_loc, maxfilt_loc, maxfun_loc, npt_loc, Aeq_loc, Aineq_loc, amat, &
- & beq_loc, bineq_loc, bvec, ctol_loc, cweight_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
- & gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, chist_loc, cstrv_loc, &
- & f, fhist_loc, xhist_loc, info_loc)
+if (present(callback_fcn)) then
+ call lincob(calfun, iprint_loc, maxfilt_loc, maxfun_loc, npt_loc, Aeq_loc, Aineq_loc, amat, &
+ & beq_loc, bineq_loc, bvec, ctol_loc, cweight_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, chist_loc, cstrv_loc, &
+ & f_loc, fhist_loc, xhist_loc, info_loc, callback_fcn)
+else
+ call lincob(calfun, iprint_loc, maxfilt_loc, maxfun_loc, npt_loc, Aeq_loc, Aineq_loc, amat, &
+ & beq_loc, bineq_loc, bvec, ctol_loc, cweight_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, xl_loc, xu_loc, x, nf_loc, chist_loc, cstrv_loc, &
+ & f_loc, fhist_loc, xhist_loc, info_loc)
+end if
!--------------------------------------------------------------------------------------------------!
-! Deallocate variables not needed any more. Indeed, automatic allocation will take place at exit.
-deallocate (Aineq_loc, Aeq_loc, amat, bineq_loc, beq_loc, bvec, xl_loc, xu_loc)
+! Deallocate variables not needed any more. We prefer explicit deallocation to the automatic one.
+deallocate (Aineq_loc, Aeq_loc, amat, bineq_loc, beq_loc, bvec)
! Write the outputs.
+if (present(f)) then
+ f = f_loc
+end if
+
if (present(cstrv)) then
cstrv = cstrv_loc
end if
@@ -577,7 +600,7 @@ subroutine lincoa(calfun, x, f, &
! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist) .or. present(chist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
! Postconditions
@@ -598,7 +621,7 @@ subroutine lincoa(calfun, x, f, &
call assert(.not. any(is_nan(chist) .or. is_posinf(chist)), 'CHIST does not contain NaN/+Inf', srname)
end if
if (present(fhist) .and. present(chist)) then
- call assert(.not. any(isbetter(fhist(1:nhist), chist(1:nhist), f, cstrv_loc, ctol_loc)),&
+ call assert(.not. any(isbetter(fhist(1:nhist), chist(1:nhist), f_loc, cstrv_loc, ctol_loc)),&
& 'No point in the history is better than X', srname)
end if
end if
@@ -624,7 +647,7 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, rhoend, xl, xu, x0, amat, bvec)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, EPS, REALMAX, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, EPS, TEN, MAXPOW10, BOUNDMAX, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert, warning
use, non_intrinsic :: linalg_mod, only : matprod, eye, trueloc
use, non_intrinsic :: memory_mod, only : safealloc
@@ -682,8 +705,8 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, rhoend, xl, xu, x0, amat, bvec)
!====================!
! Decide the number of nontrivial and valid (gradient is nonzero) constraints.
-mxl = int(count(xl > -REALMAX), kind(mxl))
-mxu = int(count(xu < REALMAX), kind(mxu))
+mxl = int(count(xl > -BOUNDMAX), kind(mxl))
+mxu = int(count(xu < BOUNDMAX), kind(mxu))
Aeq_norm = sqrt(sum(Aeq**2, dim=2))
meq = int(count(Aeq_norm > 0), kind(meq))
Aineq_norm = sqrt(sum(Aineq**2, dim=2))
@@ -705,8 +728,8 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, rhoend, xl, xu, x0, amat, bvec)
call safealloc(Anorm, 2_IK * meq + mineq)
! Define the indices of the valid and nontrivial constraints.
-ixl = trueloc(xl > -REALMAX)
-ixu = trueloc(xu < REALMAX)
+ixl = trueloc(xl > -BOUNDMAX)
+ixu = trueloc(xu < BOUNDMAX)
ieq = trueloc(Aeq_norm > 0)
iineq = trueloc(Aineq_norm > 0)
@@ -738,7 +761,7 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, rhoend, xl, xu, x0, amat, bvec)
deallocate (ixl, ixu, ieq, iineq, Anorm)
! Print a warning if the starting point is sufficiently infeasible and the constraints are modified.
-smallx = 1.0E-6_RP * rhoend
+smallx = TEN**max(-6, -MAXPOW10) * rhoend
constr_modified = (any(x0 + smallx < xl) .or. any(x0 - smallx > xu) .or. &
& any(abs(Aeqx0 - beq) > smallx * Aeq_norm) .or. any(Aineqx0 - bineq > smallx * Aineq_norm))
if (constr_modified) then
@@ -754,7 +777,7 @@ subroutine get_lincon(Aeq, Aineq, beq, bineq, rhoend, xl, xu, x0, amat, bvec)
if (DEBUGGING) then
call assert(size(amat, 1) == size(x0) .and. size(amat, 2) == size(bvec), &
& 'SIZE(AMAT) == [SIZE(X), SIZE(BVEC)]', srname)
- call assert(all(matprod(x0, amat) - bvec <= max(1.0E-12_RP, 1.0E2 * EPS) * &
+ call assert(all(matprod(x0, amat) - bvec <= max(TEN**max(-12, -MAXPOW10), 1.0E2_RP * EPS) * &
& (ONE + sum(abs(x0)) + sum(abs(bvec)))), 'The starting point is feasible', srname)
end if
end subroutine get_lincon
diff --git a/fortran/lincoa/lincob.f90 b/fortran/lincoa/lincob.f90
index 61cbe245eb..a148ce94f3 100644
--- a/fortran/lincoa/lincob.f90
+++ b/fortran/lincoa/lincob.f90
@@ -15,7 +15,7 @@ module lincob_mod
!
! Started: February 2022
!
-! Last Modified: Friday, August 04, 2023 PM09:53:21
+! Last Modified: Wed 08 Apr 2026 06:38:26 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -28,7 +28,7 @@ module lincob_mod
subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, bineq, bvec, &
& ctol, cweight, eta1, eta2, ftarget, gamma1, gamma2, rhobeg, rhoend, xl, xu, x, nf, chist, &
- & cstrv, f, fhist, xhist, info)
+ & cstrv, f, fhist, xhist, info, callback_fcn)
!--------------------------------------------------------------------------------------------------!
! This subroutine performs the actual calculations of LINCOA.
!
@@ -73,16 +73,16 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! Generic models
use, non_intrinsic :: checkexit_mod, only : checkexit
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TENTH, REALMAX, MIN_MAXFILT, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TENTH, REALMAX, BOUNDMAX, MIN_MAXFILT, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist, rangehist
-use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf
-use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS
+use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf, is_finite
+use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS, CALLBACK_TERMINATE, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : matprod, maximum, eye, trueloc, linspace, norm, trueloc
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: message_mod, only : fmsg, rhomsg, retmsg
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: powalg_mod, only : quadinc, omega_mul, hess_mul, updateh
use, non_intrinsic :: ratio_mod, only : redrat
use, non_intrinsic :: redrho_mod, only : redrho
@@ -99,6 +99,7 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! Inputs
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfilt
integer(IK), intent(in) :: maxfun
@@ -169,21 +170,23 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
logical :: reduce_rho
logical :: shortd
logical :: small_trrad
+logical :: terminate
logical :: trfail
logical :: ximproved
real(RP) :: b(size(bvec))
real(RP) :: bmat(size(x), npt + size(x))
real(RP) :: cfilt(maxfilt)
-real(RP) :: constr(count(xl > -REALMAX) + count(xu < REALMAX) + 2 * size(beq) + size(bineq))
+real(RP) :: constr(count(xl > -BOUNDMAX) + count(xu < BOUNDMAX) + 2 * size(beq) + size(bineq))
real(RP) :: constr_leq(size(beq))
+real(RP) :: cval(npt)
real(RP) :: d(size(x))
real(RP) :: delbar
real(RP) :: delta
real(RP) :: distsq(npt)
real(RP) :: dnorm
-real(RP) :: dnorm_rec(4) ! Powell's implementation uses 5
+real(RP) :: dnorm_rec(3) ! Powell's implementation: DNORM_REC(5)
real(RP) :: ffilt(maxfilt)
-real(RP) :: fval(npt), cval(npt)
+real(RP) :: fval(npt)
real(RP) :: galt(size(x))
real(RP) :: gamma3
real(RP) :: gopt(size(x))
@@ -204,6 +207,7 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
real(RP) :: xosav(size(x))
real(RP) :: xpt(size(x), npt)
real(RP) :: zmat(npt, npt - size(x) - 1)
+real(RP), parameter :: trtol = 1.0E-2_RP ! Convergence tolerance of trust-region subproblem solver
! Sizes.
m = int(size(bvec), kind(m))
@@ -226,6 +230,7 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
call assert(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', srname)
call assert(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', srname)
call assert(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', srname)
+ call assert(all(is_finite(x)), 'X is finite', srname)
call assert(size(xl) == n .and. size(xu) == n, 'SIZE(XL) == N == SIZE(XU)', srname)
call assert(maxfilt >= min(MIN_MAXFILT, maxfun) .and. maxfilt <= maxfun, &
& 'MIN(MIN_MAXFILT, MAXFUN) <= MAXFILT <= MAXFUN', srname)
@@ -241,16 +246,25 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
!====================!
! IXL and IXU are the indices of the nontrivial lower and upper bounds, respectively.
-call safealloc(ixl, int(count(xl > -REALMAX), IK)) ! Removable in F2003.
-call safealloc(ixu, int(count(xu < REALMAX), IK)) ! Removable in F2003.
-ixl = trueloc(xl > -REALMAX)
-ixu = trueloc(xu < REALMAX)
+call safealloc(ixl, int(count(xl > -BOUNDMAX), IK)) ! Removable in F2003.
+call safealloc(ixu, int(count(xu < BOUNDMAX), IK)) ! Removable in F2003.
+ixl = trueloc(xl > -BOUNDMAX)
+ixu = trueloc(xu < BOUNDMAX)
! Initialize B, XBASE, XPT, FVAL, CVAL, and KOPT, together with the history, NF, IJ, and EVALUATED.
b = bvec
call initxf(calfun, iprint, maxfun, Aeq, Aineq, amat, beq, bineq, ctol, ftarget, rhobeg, xl, xu, &
& x, b, ij, kopt, nf, chist, cval, fhist, fval, xbase, xhist, xpt, evaluated, subinfo)
+! Report the current best value, and check if user asks for early termination.
+terminate = .false.
+if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, 0_IK, cval(kopt), terminate=terminate)
+ if (terminate) then
+ subinfo = CALLBACK_TERMINATE
+ end if
+end if
+
! Initialize X, F, CONSTR, and CSTRV according to KOPT.
! N.B.: We must set CONSTR and CSTRV. Otherwise, if REDUCE_RHO is TRUE after the very first
! iteration due to SHORTD, then RHOMSG will be called with CONSTR and CSTRV uninitialized.
@@ -273,6 +287,24 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
end if
end do
+! Finish the initialization if INITXF completed normally and CALLBACK did not request termination;
+! otherwise, do not proceed, as XPT etc may be uninitialized, leading to errors or exceptions.
+if (subinfo == INFO_DFT) then
+ ! Initialize [BMAT, ZMAT, IDZ], representing inverse of KKT matrix of the interpolation system.
+ call inith(ij, xpt, idz, bmat, zmat)
+
+ ! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
+ ! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
+ hq = ZERO
+ pq = omega_mul(idz, zmat, fval)
+ gopt = matprod(bmat(:, 1:npt), fval) + hess_mul(xpt(:, kopt), xpt, pq)
+ pqalt = pq
+ galt = gopt
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ subinfo = NAN_INF_MODEL
+ end if
+end if
+
! Check whether to return due to abnormal cases that may occur during the initialization.
if (subinfo /= INFO_DFT) then
info = subinfo
@@ -287,20 +319,27 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
call retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
! Arrange CHIST, FHIST, and XHIST so that they are in the chronological order.
call rangehist(nf, xhist, fhist, chist)
+ ! Postconditions
+ if (DEBUGGING) then
+ call assert(nf <= maxfun, 'NF <= MAXFUN', srname)
+ call assert(size(x) == n .and. .not. any(is_nan(x)), 'SIZE(X) == N, X does not contain NaN', srname)
+ call assert(.not. (is_nan(f) .or. is_posinf(f)), 'F is not NaN/+Inf', srname)
+ call assert(size(xhist, 1) == n .and. size(xhist, 2) == maxxhist, 'SIZE(XHIST) == [N, MAXXHIST]', srname)
+ call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
+ ! The last calculated X can be Inf (finite + finite can be Inf numerically).
+ call assert(size(fhist) == maxfhist, 'SIZE(FHIST) == MAXFHIST', srname)
+ call assert(.not. any(is_nan(fhist(1:min(nf, maxfhist))) .or. is_posinf(fhist(1:min(nf, maxfhist)))), &
+ & 'FHIST does not contain NaN/+Inf', srname)
+ call assert(size(chist) == maxchist, 'SIZE(CHIST) == MAXCHIST', srname)
+ call assert(.not. any(chist(1:min(nf, maxchist)) < 0 .or. is_nan(chist(1:min(nf, maxchist))) &
+ & .or. is_posinf(chist(1:min(nf, maxchist)))), 'CHIST does not contain negative values or NaN/+Inf', srname)
+ nhist = minval([nf, maxfhist, maxchist])
+ call assert(.not. any(isbetter(fhist(1:nhist), chist(1:nhist), f, cstrv, ctol)),&
+ & 'No point in the history is better than X', srname)
+ end if
return
end if
-! Initialize [BMAT, ZMAT, IDZ], representing the inverse of the KKT matrix of the interpolation system.
-call inith(ij, xpt, idz, bmat, zmat)
-
-! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
-! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
-hq = ZERO
-pq = omega_mul(idz, zmat, fval)
-gopt = matprod(bmat(:, 1:npt), fval) + hess_mul(xpt(:, kopt), xpt, pq)
-pqalt = pq
-galt = gopt
-
! Initialize RESCON.
rescon = max(b - matprod(xpt(:, kopt), amat), ZERO)
rescon(trueloc(rescon >= rhobeg)) = -rescon(trueloc(rescon >= rhobeg))
@@ -335,10 +374,16 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! better than setting directly DELTA = MAX(NEW_DELTA, RHO).
gamma3 = max(ONE, min(0.75_RP * gamma2, 1.5_RP))
-! MAXTR is the maximal number of trust-region iterations. Each trust-region iteration takes 1 or 2
-! function evaluations unless the trust-region step is short or fails to reduce the trust-region
-! model but the geometry step is not invoked. Thus the following MAXTR is unlikely to be reached.
-maxtr = max(maxfun, 2_IK * maxfun) ! MAX: precaution against overflow, which will make 2*MAXFUN < 0.
+! MAXTR is the maximal number of trust-region iterations. Here, we set it to HUGE(MAXTR) - 1 so that
+! the algorithm will not terminate due to MAXTR. However, this may not be allowed in other languages
+! such as MATLAB. In that case, we can set MAXTR to 10*MAXFUN, which is unlikely to reach because
+! each trust-region iteration takes 1 or 2 function evaluations unless the trust-region step is short
+! or fails to reduce the trust-region model but the geometry step is not invoked.
+! N.B.: Do NOT set MAXTR to HUGE(MAXTR), as it may cause overflow and infinite cycling in the DO
+! loop. See
+! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+maxtr = huge(maxtr) - 1_IK !!MATLAB: maxtr = 10 * maxfun;
info = MAXTR_REACHED
! Begin the iterative procedure.
@@ -349,7 +394,7 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! LINCOA never sets IMPROVE_GEO and REDUCE_RHO to TRUE simultaneously.
do tr = 1, maxtr
! Generate the next trust region step D by calling TRSTEP. Note that D is feasible.
- call trstep(amat, delta, gopt, hq, pq, rescon, xpt, iact, nact, qfac, rfac, d, ngetact)
+ call trstep(amat, delta, gopt, hq, pq, rescon, trtol, xpt, iact, nact, qfac, rfac, d, ngetact)
dnorm = min(delta, norm(d))
! A trust region step is applied whenever its length is at least 0.5*DELTA. It is also
@@ -359,7 +404,8 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! N.B. The magic number 0.1999 seems to be related to the fact that a linear constraint is
! considered nearly active if the point under consideration is within 0.2*DELTA to the boundary
! of the constraint. See the subroutine GETACT and Section 3 of Powell (2015) for more details.
- shortd = ((dnorm < HALF * delta .and. ngetact < 2) .or. dnorm < 0.1999_RP * delta)
+ ! `<=` works better than `<` in case of underflow.
+ shortd = ((dnorm <= HALF * delta .and. ngetact < 2) .or. dnorm <= 0.1999_RP * delta)
!------------------------------------------------------------------------------------------!
! The SHORTD defined above needs NGETACT, which relies on Powell's trust region subproblem
! solver. If a different subproblem solver is used, we can take the following SHORTD adopted
@@ -367,9 +413,9 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! !SHORTD = (DNORM < HALF * RHO)
!------------------------------------------------------------------------------------------!
- ! DNORM_REC records the DNORM of last few (five) trust-region iterations. It will be used to
- ! decide whether we should improve the geometry of the interpolation set or reduce RHO when
- ! SHORTD is TRUE. Note that it does not record the geometry steps.
+ ! DNORM_REC records the DNORM of recent trust-region iterations. It will be used to decide
+ ! whether we should improve the geometry of the interpolation set or reduce RHO when SHORTD
+ ! is TRUE. Note that it does not record the geometry steps.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! In some cases, we reset DNORM_REC to REALMAX. This indicates a preference of improving the
@@ -381,9 +427,9 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
end if
! Set QRED to the reduction of the quadratic model when the move D is made from XOPT. QRED
- ! should be positive If it is nonpositive due to rounding errors, we will not take this step.
+ ! should be positive. If it is nonpositive due to rounding errors, we will not take this step.
qred = -quadinc(d, xpt, gopt, pq, hq) ! QRED = Q(XOPT) - Q(XOPT + D)
- trfail = (.not. qred > 1.0E-5 * rho**2) ! QRED is tiny/negative or NaN.
+ trfail = (.not. qred > 1.0E-6 * rho**2) ! QRED is tiny/negative or NaN.
if (shortd .or. trfail) then
! In this case, do nothing but reducing DELTA. Afterward, DELTA < DNORM may occur.
@@ -459,6 +505,10 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! the current model with the alternative model if the recent few (three) alternative
! models are more accurate in predicting the function value of XOPT + D.
call tryqalt(idz, bmat, fval - fval(kopt), xpt(:, kopt), xpt, zmat, qalt_better, gopt, pq, hq, galt, pqalt)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
! Update RESCON if XOPT is changed.
! Zaikun 20221115: Shouldn't we do it after DELTA is updated?
@@ -484,7 +534,7 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! verify a curvature condition that really indicates that recent models are sufficiently
! accurate. Here, however, we are not really sure whether they are accurate or not. Therefore,
! ACCURATE_MOD is not the best name, but we keep it to align with the other solvers.
- accurate_mod = all(dnorm_rec <= HALF * rho) .or. all(dnorm_rec(3:size(dnorm_rec)) <= 0.2 * rho)
+ accurate_mod = all(dnorm_rec <= rho) .or. all(dnorm_rec(2:size(dnorm_rec)) <= 0.2 * rho)
! Powell's version (note that size(dnorm_rec) = 5 in his implementation):
!accurate_mod = all(dnorm_rec <= HALF * rho) .or. all(dnorm_rec(3:size(dnorm_rec)) <= TENTH * rho)
! CLOSE_ITPSET: Are the interpolation points close to XOPT?
@@ -600,6 +650,10 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
! more accurate in predicting the function value of XOPT + D.
! N.B.: Powell's code does this only if XOPT + D is feasible.
call tryqalt(idz, bmat, fval - fval(kopt), xpt(:, kopt), xpt, zmat, qalt_better, gopt, pq, hq, galt, pqalt)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
! Update RESCON. Zaikun 20221115: Currently, UPDATERES does not update RESCON if XIMPROVED
! is FALSE. Shouldn't we do it whenever DELTA is updated? Have we MISUNDERSTOOD RESCON?
@@ -633,10 +687,21 @@ subroutine lincob(calfun, iprint, maxfilt, maxfun, npt, Aeq, Aineq, amat, beq, b
pqalt = omega_mul(idz, zmat, fval - fval(kopt))
galt = matprod(bmat(:, 1:npt), fval - fval(kopt)) + hess_mul(xpt(:, kopt), xpt, pqalt)
end if
+
+ ! Report the current best value, and check if user asks for early termination.
+ if (present(callback_fcn)) then
+ ! FIXME: CVAL(KOP) is WRONG! CVAL is not updated.
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, tr, cval(kopt), terminate=terminate)
+ if (terminate) then
+ info = CALLBACK_TERMINATE
+ exit
+ end if
+ end if
+
end do ! End of DO TR = 1, MAXTR. The iterative procedure ends.
! Return from the calculation, after trying the Newton-Raphson step if it has not been tried yet.
-if (info == SMALL_TR_RADIUS .and. shortd .and. nf < maxfun) then
+if (info == SMALL_TR_RADIUS .and. shortd .and. dnorm > TENTH * rhoend .and. nf < maxfun) then
x = xbase + (xpt(:, kopt) + d)
call evaluate(calfun, x, f)
nf = nf + 1_IK
diff --git a/fortran/lincoa/mlint b/fortran/lincoa/mlint
index 13013f2f37..9dd46ff833 120000
--- a/fortran/lincoa/mlint
+++ b/fortran/lincoa/mlint
@@ -1 +1 @@
-../common/mlint
\ No newline at end of file
+../tests/tools/mlint
\ No newline at end of file
diff --git a/fortran/lincoa/trustregion.f90 b/fortran/lincoa/trustregion.f90
index 0e9f87c136..3039fcb3fc 100644
--- a/fortran/lincoa/trustregion.f90
+++ b/fortran/lincoa/trustregion.f90
@@ -11,7 +11,7 @@ module trustregion_lincoa_mod
!
! Started: February 2022
!
-! Last Modified: Saturday, June 03, 2023 PM01:36:44
+! Last Modified: Saturday, March 09, 2024 PM12:09:39
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -22,7 +22,7 @@ module trustregion_lincoa_mod
contains
-subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, qfac, rfac, s, ngetact)
+subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, tol, xpt, iact, nact, qfac, rfac, s, ngetact)
!--------------------------------------------------------------------------------------------------!
! This subroutine solves
! minimize Q(XOPT + D) s.t. ||D|| <= DELTA, AMAT^T*D <= B.
@@ -46,7 +46,7 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ONE, ZERO, TWO, HALF, EPS, REALMIN, TINYCV, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ONE, ZERO, TWO, HALF, TEN, MAXPOW10, EPS, REALMIN, TINYCV, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_finite, is_nan
use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, solve, isorth, istriu, &
@@ -65,6 +65,7 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
real(RP), intent(in) :: hq_in(:, :) ! HQ_IN(N, N)
real(RP), intent(in) :: pq_in(:) ! PQ_IN(NPT)
real(RP), intent(in) :: rescon(:) ! RESCON(M)
+real(RP), intent(in) :: tol
real(RP), intent(in) :: xpt(:, :) ! XPT(N, NPT)
! In-outputs
@@ -107,6 +108,7 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
real(RP) :: hd(size(gopt_in))
real(RP) :: hq(size(hq_in, 1), size(hq_in, 2))
real(RP) :: modscal
+real(RP) :: orthtol
real(RP) :: pg(size(gopt_in))
real(RP) :: pq(size(pq_in))
real(RP) :: psd(size(gopt_in))
@@ -118,8 +120,6 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
real(RP) :: sold(size(s))
real(RP) :: sqrtd
real(RP) :: ss
-real(RP) :: tol
-real(RP), parameter :: ctest = 0.01_RP ! Convergence test parameter.
! Sizes.
m = int(size(amat, 2), kind(m))
@@ -140,8 +140,8 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
call assert(size(iact) == m, 'SIZE(IACT) == M', srname)
call assert(all(iact(1:nact) >= 1 .and. iact(1:nact) <= m), '1 <= IACT <= M', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- tol = max(1.0E-10_RP, min(1.0E-1_RP, 1.0E8_RP * EPS * real(n, RP)))
- call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
+ orthtol = max(TEN**max(-10, -MAXPOW10), min(1.0E-1_RP, TEN**min(8, MAXPOW10) * EPS * real(n, RP)))
+ call assert(isorth(qfac, orthtol), 'QFAC is orthogonal', srname)
call assert(size(rfac, 1) == n .and. size(rfac, 2) == n, 'SIZE(RFAC) == [N, N]', srname)
call assert(istriu(rfac), 'RFAC is upper triangular', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
@@ -333,9 +333,9 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
exit
end if
- ! Powell's condition for the following IF: -ALPHA * DG <= CTEST * REDUCT. Note that the EXIT
+ ! Powell's condition for the following IF: -ALPHA * DG <= TOL * REDUCT. Note that the EXIT
! will be triggered if DG >= 0, as ALPHA >= 0.
- if (-alpha * dg <= ctest * reduct .or. is_nan(alpha * dg)) then
+ if (-alpha * dg <= tol * reduct .or. is_nan(alpha * dg)) then
exit
end if
@@ -434,7 +434,7 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
end if
! Test for termination.
- if (alpha >= alpht .or. -alphm * (dg + HALF * alphm * dhd) <= ctest * reduct) then
+ if (alpha >= alpht .or. -alphm * (dg + HALF * alphm * dhd) <= tol * reduct) then
exit
end if
@@ -494,7 +494,7 @@ subroutine trstep(amat, delta, gopt_in, hq_in, pq_in, rescon, xpt, iact, nact, q
call assert(norm(s) <= TWO * delta, '||S|| <= 2*DELTA', srname)
call assert(nact >= 0 .and. nact <= min(m, n), '0 <= NACT <= MIN(M, N)', srname)
call assert(size(qfac, 1) == n .and. size(qfac, 2) == n, 'SIZE(QFAC) == [N, N]', srname)
- call assert(isorth(qfac, tol), 'QFAC is orthogonal', srname)
+ call assert(isorth(qfac, orthtol), 'QFAC is orthogonal', srname)
call assert(size(rfac, 1) == n .and. size(rfac, 2) == n, 'SIZE(RFAC) == [N, N]', srname)
call assert(istriu(rfac), 'RFAC is upper triangular', srname)
if (present(ngetact)) then
diff --git a/fortran/lincoa/update.f90 b/fortran/lincoa/update.f90
index 21c7dc3c58..978e190d13 100644
--- a/fortran/lincoa/update.f90
+++ b/fortran/lincoa/update.f90
@@ -8,7 +8,7 @@ module update_lincoa_mod
!
! Started: February 2022
!
-! Last Modified: Monday, August 07, 2023 AM03:56:25
+! Last Modified: Friday, March 15, 2024 PM03:35:14
!--------------------------------------------------------------------------------------------------!
implicit none
diff --git a/fortran/newuoa/flint b/fortran/newuoa/flint
index 764125c9f1..75b810d749 120000
--- a/fortran/newuoa/flint
+++ b/fortran/newuoa/flint
@@ -1 +1 @@
-../common/flint
\ No newline at end of file
+../tests/tools/flint
\ No newline at end of file
diff --git a/fortran/newuoa/geometry.f90 b/fortran/newuoa/geometry.f90
index 02f42c7009..de89c353e5 100644
--- a/fortran/newuoa/geometry.f90
+++ b/fortran/newuoa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_newuoa_mod
!
! Started: July 2020
!
-! Last Modified: Monday, August 07, 2023 AM03:56:10
+! Last Modified: Sunday, April 21, 2024 PM03:20:57
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -40,7 +40,7 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
use, non_intrinsic :: consts_mod, only : RP, IK, ONE, TENTH, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
-use, non_intrinsic :: linalg_mod, only : issymmetric
+use, non_intrinsic :: linalg_mod, only : issymmetric, trueloc
use, non_intrinsic :: powalg_mod, only : calden
implicit none
@@ -100,10 +100,10 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
-! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
-! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
-! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
+! the trust-region trial point has not been included into XPT yet --- it cannot be included without
+! knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code picks KNEW
+! based on the distance to the un-updated "optimal point", which is unreasonable. This has been
+! corrected in our implementation of LINCOA, yet it does not boost the performance.
if (ximproved) then
distsq = sum((xpt - spread(xpt(:, kopt) + d, dim=2, ncopies=npt))**2, dim=1)
!!MATLAB: distsq = sum((xpt - (xpt(:, kopt) + d)).^2) % d should be a column! Implicit expansion
@@ -128,21 +128,24 @@ function setdrop_tr(idz, kopt, ximproved, bmat, d, delta, rho, xpt, zmat) result
score(kopt) = -ONE
end if
-! The following IF works a bit better than `IF (ANY(SCORE > 0))` from Powell's BOBYQA and LINCOA code.
+! SCORE(K) is NaN implies ABS(DEN(K)) is NaN, but we want ABS(DEN) to be big. So we exclude such K.
+score(trueloc(is_nan(score))) = -ONE
+
+knew = 0
+! The following IF works a bit better than `IF (ANY(SCORE > 0))` from Powell's BOBYQA/LINCOA code.
if (any(score > 1) .or. (ximproved .and. any(score > 0))) then ! Powell's UOBYQA and NEWUOA code
! See (7.5) of the NEWUOA paper for the definition of KNEW in this case.
- ! SCORE(K) is NaN implies ABS(DEN(K)) is NaN, but we want ABS(DEN) to be big. So we exclude such K.
- knew = int(maxloc(score, mask=(.not. is_nan(score)), dim=1), kind(knew))
- !!MATLAB: [~, knew] = max(score, [], 'omitnan');
-elseif (ximproved) then
- ! Powell's code does not include the following instructions. With Powell's code, if DEN consists
- ! of only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the
- ! following value, to make sure that the new trial point is included in the interpolation set.
- ! However, the updating subroutine will likely need to skip the update of the Lagrange
- ! polynomials (i.e., H), or they would be destroyed by the NaNs.
+ knew = int(maxloc(score, dim=1), kind(knew))
+ !!MATLAB: [~, knew] = max(score);
+end if
+
+! Powell's code does not include the following instructions. With Powell's code, if DEN consists of
+! only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the following value,
+! to make sure that the new trial point is included in the interpolation set. However, the updating
+! subroutine will likely need to skip the update of the Lagrange polynomials (i.e., H), or they
+! would be destroyed by the NaNs.
+if ((ximproved .and. knew == 0) .or. knew < 0) then ! KNEW < 0 is impossible in theory.
knew = int(maxloc(distsq, dim=1), kind(knew))
-else
- knew = 0
end if
!====================!
@@ -204,6 +207,7 @@ function geostep(idz, knew, kopt, bmat, delbar, xpt, zmat) result(d)
real(RP) :: denom
real(RP) :: denrat
real(RP) :: pqlag(size(xpt, 2))
+real(RP) :: scaling
real(RP) :: vlag(size(xpt, 1) + size(xpt, 2))
! Sizes
@@ -262,6 +266,14 @@ function geostep(idz, knew, kopt, bmat, delbar, xpt, zmat) result(d)
end if
end if
+! In case D is zero or contains Inf/NaN, replace it with a displacement from XPT(:, KNEW) to
+! XOPT. Powell's code does not have this.
+if (sum(abs(d)) <= 0 .or. .not. is_finite(sum(abs(d)))) then
+ d = xpt(:, knew) - xpt(:, kopt)
+ scaling = delbar / norm(d)
+ d = max(0.6_RP * scaling, min(HALF, scaling)) * d ! 0.6: ensure |D| > DELBAR/2
+end if
+
!====================!
! Calculation ends !
!====================!
diff --git a/fortran/newuoa/initialize.f90 b/fortran/newuoa/initialize.f90
index c56ec1d382..3d1a2015b2 100644
--- a/fortran/newuoa/initialize.f90
+++ b/fortran/newuoa/initialize.f90
@@ -8,7 +8,7 @@ module initialize_newuoa_mod
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
!
-! Last Modified: Monday, August 07, 2023 AM03:55:41
+! Last Modified: Tue 10 Feb 2026 01:56:05 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -133,7 +133,9 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, x0, ij, kopt, nf, fhi
! Initialize XHIST, FHIST, and FVAL. Otherwise, compilers may complain that they are not
! (completely) initialized if the initialization aborts due to abnormality (see CHECKEXIT).
-! Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! N.B.: 1. Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! 2. Do not initialize the models if the current initialization aborts due to abnormality. Otherwise,
+! errors or exceptions may occur, as FVAL and XPT etc are uninitialized.
xhist = -REALMAX
fhist = REALMAX
fval = REALMAX
@@ -364,7 +366,7 @@ subroutine initq(ij, fval, xpt, gopt, hq, pq, info)
pq = ZERO
if (present(info)) then
- if (is_nan(sum(abs(gopt)) + sum(abs(hq)))) then
+ if (any(is_nan(gopt)) .or. any(is_nan(hq))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -392,12 +394,12 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
!--------------------------------------------------------------------------------------------------!
! Common modules
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, EPS, DEBUGGING
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: infos_mod, only : INFO_DFT, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : issymmetric, eye
-use, non_intrinsic :: powalg_mod, only : errh
+!use, non_intrinsic :: powalg_mod, only : errh
implicit none
@@ -489,7 +491,7 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
idz = 1
if (present(info)) then
- if (is_nan(sum(abs(bmat)) + sum(abs(zmat)))) then
+ if (any(is_nan(bmat)) .or. any(is_nan(zmat))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -507,8 +509,8 @@ subroutine inith(ij, xpt, idz, bmat, zmat, info)
call assert(issymmetric(bmat(:, npt + 1:npt + n)), 'BMAT(:, NPT+1:NPT+N) is symmetric', srname)
call assert(size(zmat, 1) == npt .and. size(zmat, 2) == npt - n - 1, &
& 'SIZE(ZMAT) == [NPT, NPT - N - 1]', srname)
- call assert(errh(idz, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS), &
- & '[IDZ, BMA, ZMAT] represents H = W^{-1}', srname)
+ !call assert(errh(idz, bmat, zmat, xpt) <= max(1.0E-3_RP, 1.0E2_RP * real(npt, RP) * EPS), &
+ ! & '[IDZ, BMA, ZMAT] represents H = W^{-1}', srname)
end if
end subroutine inith
diff --git a/fortran/newuoa/mlint b/fortran/newuoa/mlint
index 13013f2f37..9dd46ff833 120000
--- a/fortran/newuoa/mlint
+++ b/fortran/newuoa/mlint
@@ -1 +1 @@
-../common/mlint
\ No newline at end of file
+../tests/tools/mlint
\ No newline at end of file
diff --git a/fortran/newuoa/newuoa.f90 b/fortran/newuoa/newuoa.f90
index 0c1556fe70..7046c32793 100644
--- a/fortran/newuoa/newuoa.f90
+++ b/fortran/newuoa/newuoa.f90
@@ -22,7 +22,7 @@ module newuoa_mod
!
! Started: July 2020
!
-! Last Modified: Monday, May 08, 2023 PM03:15:27
+! Last Modified: Thursday, February 22, 2024 PM03:30:12
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -33,11 +33,11 @@ module newuoa_mod
contains
-subroutine newuoa(calfun, x, f, &
- & nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &
- & eta1, eta2, gamma1, gamma2, xhist, fhist, maxhist, info)
+subroutine newuoa(calfun, x, &
+ & f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, eta1, eta2, gamma1, gamma2, &
+ & xhist, fhist, maxhist, callback_fcn, info)
!--------------------------------------------------------------------------------------------------!
-! Among all the arguments, only CALFUN, X, and F are obligatory. The others are OPTIONAL and you can
+! Among all the arguments, only CALFUN and X are obligatory. The others are OPTIONAL and you can
! neglect them unless you are familiar with the algorithm. Any unspecified optional input will take
! the default value detailed below. For instance, we may invoke the solver as follows.
!
@@ -47,7 +47,7 @@ subroutine newuoa(calfun, x, f, &
! or
!
! ! First define CALFUN and X, and then do the following.
-! call newuoa(calfun, x, f, rhobeg = 0.5D0, rhoend = 1.0D-3, maxfun = 100)
+! call newuoa(calfun, x, f, rhobeg = 1.0D0, rhoend = 1.0D-6)
!
! See examples/newuoa_exmp.f90 for a concrete example.
!
@@ -142,6 +142,9 @@ subroutine newuoa(calfun, x, f, &
! CONSTS_MOD (see consts.F90 under the directory named "common").
! Use *HIST with caution! (N.B.: the algorithm is NOT designed for large problems).
!
+! CALLBACK_FCN
+! Input, function to report progress and optionally request termination.
+!
! INFO
! Output, INTEGER(IK) scalar.
! INFO is the exit flag. It will be set to one of the following values defined in the module
@@ -169,7 +172,7 @@ subroutine newuoa(calfun, x, f, &
use, non_intrinsic :: history_mod, only : prehist
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
use, non_intrinsic :: memory_mod, only : safealloc
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: preproc_mod, only : preproc
use, non_intrinsic :: string_mod, only : num2str
@@ -178,25 +181,30 @@ subroutine newuoa(calfun, x, f, &
implicit none
-! Arguments
+! Compulsory arguments
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
real(RP), intent(inout) :: x(:)
-real(RP), intent(out) :: f
-integer(IK), intent(out), optional :: nf
-real(RP), intent(in), optional :: rhobeg
-real(RP), intent(in), optional :: rhoend
-real(RP), intent(in), optional :: ftarget
+
+! Optional inputs
+procedure(CALLBACK), optional :: callback_fcn
+integer(IK), intent(in), optional :: iprint
integer(IK), intent(in), optional :: maxfun
+integer(IK), intent(in), optional :: maxhist
integer(IK), intent(in), optional :: npt
-integer(IK), intent(in), optional :: iprint
real(RP), intent(in), optional :: eta1
real(RP), intent(in), optional :: eta2
+real(RP), intent(in), optional :: ftarget
real(RP), intent(in), optional :: gamma1
real(RP), intent(in), optional :: gamma2
+real(RP), intent(in), optional :: rhobeg
+real(RP), intent(in), optional :: rhoend
+
+! Optional outputs
+integer(IK), intent(out), optional :: info
+integer(IK), intent(out), optional :: nf
+real(RP), intent(out), optional :: f
real(RP), intent(out), optional, allocatable :: fhist(:)
real(RP), intent(out), optional, allocatable :: xhist(:, :)
-integer(IK), intent(in), optional :: maxhist
-integer(IK), intent(out), optional :: info
! Local variables
character(len=*), parameter :: solver = 'NEWUOA'
@@ -211,6 +219,7 @@ subroutine newuoa(calfun, x, f, &
integer(IK) :: npt_loc
real(RP) :: eta1_loc
real(RP) :: eta2_loc
+real(RP) :: f_loc
real(RP) :: ftarget_loc
real(RP) :: gamma1_loc
real(RP) :: gamma2_loc
@@ -249,7 +258,7 @@ subroutine newuoa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -268,8 +277,8 @@ subroutine newuoa(calfun, x, f, &
if (present(npt)) then
npt_loc = npt
-elseif (maxfun_loc >= 1) then
- npt_loc = max(n + 2_IK, min(maxfun_loc - 1_IK, 2_IK * n + 1_IK))
+elseif (maxfun_loc >= n + 3_IK) then ! Take MAXFUN into account if it is valid.
+ npt_loc = min(maxfun_loc - 1_IK, 2_IK * n + 1_IK)
else
npt_loc = 2_IK * n + 1_IK
end if
@@ -328,13 +337,22 @@ subroutine newuoa(calfun, x, f, &
!-------------------- Call NEWUOB, which performs the real calculations. --------------------------!
-call newuob(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
- & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f, fhist_loc, xhist_loc, info_loc)
+if (present(callback_fcn)) then
+ call newuob(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f_loc, fhist_loc, xhist_loc, info_loc, callback_fcn)
+else
+ call newuob(calfun, iprint_loc, maxfun_loc, npt_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f_loc, fhist_loc, xhist_loc, info_loc)
+end if
!--------------------------------------------------------------------------------------------------!
! Write the outputs.
+if (present(f)) then
+ f = f_loc
+end if
+
if (present(nf)) then
nf = nf_loc
end if
@@ -378,7 +396,7 @@ subroutine newuoa(calfun, x, f, &
! If MAXFHIST_IN >= NF_LOC > MAXFHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
! Postconditions
@@ -393,7 +411,7 @@ subroutine newuoa(calfun, x, f, &
if (present(fhist)) then
call assert(size(fhist) == nhist, 'SIZE(FHIST) == NHIST', srname)
call assert(.not. any(is_nan(fhist) .or. is_posinf(fhist)), 'FHIST does not contain NaN/+Inf', srname)
- call assert(.not. any(fhist < f), 'F is the smallest in FHIST', srname)
+ call assert(.not. any(fhist < f_loc), 'F is the smallest in FHIST', srname)
end if
end if
diff --git a/fortran/newuoa/newuob.f90 b/fortran/newuoa/newuob.f90
index 01482865d8..1f96ac12d1 100644
--- a/fortran/newuoa/newuob.f90
+++ b/fortran/newuoa/newuob.f90
@@ -8,7 +8,7 @@ module newuob_mod
!
! Started: July 2020
!
-! Last Modified: Friday, August 04, 2023 PM09:55:30
+! Last Modified: Wed 08 Apr 2026 06:38:51 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -20,7 +20,7 @@ module newuob_mod
subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamma2, rhobeg, &
- & rhoend, x, nf, f, fhist, xhist, info)
+ & rhoend, x, nf, f, fhist, xhist, info, callback_fcn)
!--------------------------------------------------------------------------------------------------!
! This subroutine performs the actual calculations of NEWUOA.
!
@@ -56,11 +56,11 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist, rangehist
-use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf
-use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS
+use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf, is_finite
+use, non_intrinsic :: infos_mod, only : INFO_DFT, MAXTR_REACHED, SMALL_TR_RADIUS, CALLBACK_TERMINATE, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : norm
use, non_intrinsic :: message_mod, only : retmsg, rhomsg, fmsg
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: powalg_mod, only : quadinc, updateh
use, non_intrinsic :: ratio_mod, only : redrat
use, non_intrinsic :: redrho_mod, only : redrho
@@ -76,6 +76,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Inputs
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfun
integer(IK), intent(in) :: npt
@@ -103,6 +104,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
integer(IK) :: idz
integer(IK) :: ij(2, max(0_IK, int(npt - 2 * size(x) - 1, IK)))
integer(IK) :: itest
+integer(IK) :: k
integer(IK) :: knew_geo
integer(IK) :: knew_tr
integer(IK) :: kopt
@@ -121,6 +123,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
logical :: reduce_rho
logical :: shortd
logical :: small_trrad
+logical :: terminate
logical :: trfail
logical :: ximproved
real(RP) :: bmat(size(x), npt + size(x))
@@ -146,7 +149,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
real(RP) :: xosav(size(x))
real(RP) :: xpt(size(x), npt)
real(RP) :: zmat(npt, npt - size(x) - 1)
-real(RP), parameter :: trtol = 1.0E-2_RP ! Tolerance used in TRSAPP.
+real(RP), parameter :: trtol = 1.0E-2_RP ! Convergence tolerance of trust-region subproblem solver
! Sizes
n = int(size(x), kind(n))
@@ -160,6 +163,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call assert(n >= 1 .and. npt >= n + 2, 'N >= 1, NPT >= N + 2', srname)
call assert(maxfun >= npt + 1, 'MAXFUN >= NPT + 1', srname)
call assert(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', srname)
+ call assert(all(is_finite(x)), 'X is finite', srname)
call assert(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', srname)
call assert(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', srname)
call assert(maxhist >= 0 .and. maxhist <= maxfun, '0 <= MAXHIST <= MAXFUN', srname)
@@ -175,10 +179,34 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Initialize XBASE, XPT, FVAL, and KOPT, together with the history, NF, and IJ.
call initxf(calfun, iprint, maxfun, ftarget, rhobeg, x, ij, kopt, nf, fhist, fval, xbase, xhist, xpt, subinfo)
+! Report the current best value, and check if user asks for early termination.
+terminate = .false.
+if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, 0_IK, terminate=terminate)
+ if (terminate) then
+ subinfo = CALLBACK_TERMINATE
+ end if
+end if
+
! Initialize X and F according to KOPT.
x = xbase + xpt(:, kopt)
f = fval(kopt)
+! Finish the initialization if INITXF completed normally and CALLBACK did not request termination;
+! otherwise, do not proceed, as XPT etc may be uninitialized, leading to errors or exceptions.
+if (subinfo == INFO_DFT) then
+ ! Initialize [BMAT, ZMAT, IDZ], representing inverse of KKT matrix of the interpolation
+ ! system.
+ call inith(ij, xpt, idz, bmat, zmat)
+
+ ! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
+ ! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
+ call initq(ij, fval, xpt, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ subinfo = NAN_INF_MODEL
+ end if
+end if
+
! Check whether to return due to abnormal cases that may occur during the initialization.
if (subinfo /= INFO_DFT) then
info = subinfo
@@ -202,16 +230,6 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
return
end if
-! Initialize [BMAT, ZMAT, IDZ], representing the inverse of the KKT matrix of the interpolation system.
-call inith(ij, xpt, idz, bmat, zmat)
-
-! Initialize the quadratic represented by [GOPT, HQ, PQ], so that its gradient at XBASE+XOPT is
-! GOPT; its Hessian is HQ + sum_{K=1}^NPT PQ(K)*XPT(:, K)*XPT(:, K)'.
-call initq(ij, fval, xpt, gopt, hq, pq)
-
-! After initializing GOPT, HQ, PQ, BMAT, ZMAT, one can also choose to return if these arrays contain
-! NaN. We do not do it here. The code will continue to run and possibly recovers by geometry steps.
-
! Set some more initial values.
! We must initialize RATIO. Otherwise, when SHORTD = TRUE, compilers may raise a run-time error that
! RATIO is undefined. But its value will not be used: when SHORTD = FALSE, its value will be
@@ -238,10 +256,16 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! better than setting directly DELTA = MAX(NEW_DELTA, RHO).
gamma3 = max(ONE, min(0.75_RP * gamma2, 1.5_RP))
-! MAXTR is the maximal number of trust-region iterations. Each trust-region iteration takes 1 or 2
-! function evaluations unless the trust-region step is short or fails to reduce the trust-region
-! model but the geometry step is not invoked. Thus the following MAXTR is unlikely to be reached.
-maxtr = max(maxfun, 2_IK * maxfun) ! MAX: precaution against overflow, which will make 2*MAXFUN < 0.
+! MAXTR is the maximal number of trust-region iterations. Here, we set it to HUGE(MAXTR) - 1 so that
+! the algorithm will not terminate due to MAXTR. However, this may not be allowed in other languages
+! such as MATLAB. In that case, we can set MAXTR to 10*MAXFUN, which is unlikely to reach because
+! each trust-region iteration takes 1 or 2 function evaluations unless the trust-region step is short
+! or fails to reduce the trust-region model but the geometry step is not invoked.
+! N.B.: Do NOT set MAXTR to HUGE(MAXTR), as it may cause overflow and infinite cycling in the DO
+! loop. See
+! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+maxtr = huge(maxtr) - 1_IK !!MATLAB: maxtr = 10 * maxfun;
info = MAXTR_REACHED
! Begin the iterative procedure.
@@ -254,13 +278,16 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Generate the next trust region step D.
call trsapp(delta, gopt, hq, pq, trtol, xpt, crvmin, d)
dnorm = min(delta, norm(d))
+
+ ! Check whether D is too short to invoke a function evaluation.
! SHORTD corresponds to Box 3 of the NEWUOA paper. N.B.: we compare DNORM with RHO, not DELTA.
- shortd = (dnorm < HALF * rho) ! HALF seems to work better than TENTH or QUART.
+ ! HALF seems to work better than TENTH or QUART.
+ shortd = (dnorm <= HALF * rho) ! `<=` works better than `<` in case of underflow.
! Set QRED to the reduction of the quadratic model when the move D is made from XOPT. QRED
! should be positive. If it is nonpositive due to rounding errors, we will not take this step.
qred = -quadinc(d, xpt, gopt, pq, hq)
- trfail = (.not. qred > 1.0E-5 * rho**2) ! QRED is tiny/negative, or NaN.
+ trfail = (.not. qred > 1.0E-6 * rho**2) ! QRED is tiny/negative, or NaN.
if (shortd .or. trfail) then
! In this case, do nothing but reducing DELTA. Afterward, DELTA < DNORM may occur.
@@ -273,27 +300,30 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
end if
else
! Calculate the next value of the objective function.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective function X, assuming it to have the value at the closest point.
x = xbase + (xpt(:, kopt) + d)
- call evaluate(calfun, x, f)
- nf = nf + 1_IK
+ distsq = [(sum((x - (xbase + xpt(:, k)))**2, dim=1), k=1, npt)] ! Implied do-loop
+ !!MATLAB: distsq = sum((x - (xbase + xpt))**2, 1) % Implicit expansion
+ k = int(minloc(distsq, dim=1), kind(k))
+ if (distsq(k) <= (1.0E-3 * rhoend)**2) then
+ f = fval(k)
+ else
+ ! Evaluate the objective function at X, taking care of possible Inf/NaN values.
+ call evaluate(calfun, x, f)
+ nf = nf + 1_IK
+ ! Save X and F into the history.
+ call savehist(nf, x, xhist, f, fhist)
+ end if
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Trust region', iprint, nf, delta, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
-
- ! Check whether to exit
- subinfo = checkexit(maxfun, nf, f, ftarget, x)
- if (subinfo /= INFO_DFT) then
- info = subinfo
- exit
- end if
! Update DNORM_REC and MODERR_REC.
- ! DNORM_REC records the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC records the DNORM of the recent function evaluations with the current RHO.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC records the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC records the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) + qred
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
@@ -347,6 +377,17 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! has been revised after the last function evaluation.
! 5. Powell's code tries Q_alt only when DELTA == RHO.
call tryqalt(idz, bmat, fval - fval(kopt), ratio, xpt(:, kopt), xpt, zmat, itest, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
+ end if
+
+ ! Check whether to exit
+ subinfo = checkexit(maxfun, nf, f, ftarget, x)
+ if (subinfo /= INFO_DFT) then
+ info = subinfo
+ exit
end if
end if ! End of IF (SHORTD .OR. TRFAIL). The normal trust-region calculation ends.
@@ -363,7 +404,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! ACCURATE_MOD: Are the recent models sufficiently accurate? Used only if SHORTD is TRUE.
accurate_mod = all(abs(moderr_rec) <= 0.125_RP * crvmin * rho**2) .and. all(dnorm_rec <= rho)
- ! CLOSE_ITPSET: Are the interpolation points close to XOPT?
+ ! CLOSE_ITPSET: Are the interpolation points close to XOPT? It affects IMPROVE_GEO, REDUCE_RHO.
distsq = sum((xpt - spread(xpt(:, kopt), dim=2, ncopies=npt))**2, dim=1)
!!MATLAB: distsq = sum((xpt - xpt(:, kopt)).^2) % Implicit expansion
close_itpset = all(distsq <= 4.0_RP * delta**2) ! Powell's code.
@@ -506,29 +547,32 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
d = geostep(idz, knew_geo, kopt, bmat, delbar, xpt, zmat)
! Calculate the next value of the objective function.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective function X, assuming it to have the value at the closest point.
x = xbase + (xpt(:, kopt) + d)
- call evaluate(calfun, x, f)
- nf = nf + 1_IK
+ distsq = [(sum((x - (xbase + xpt(:, k)))**2, dim=1), k=1, npt)] ! Implied do-loop
+ !!MATLAB: distsq = sum((x - (xbase + xpt))**2, 1) % Implicit expansion
+ k = int(minloc(distsq, dim=1), kind(k))
+ if (distsq(k) <= (1.0E-3 * rhoend)**2) then
+ f = fval(k)
+ else
+ ! Evaluate the objective function at X, taking care of possible Inf/NaN values.
+ call evaluate(calfun, x, f)
+ nf = nf + 1_IK
+ ! Save X and F into the history.
+ call savehist(nf, x, xhist, f, fhist)
+ end if
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Geometry', iprint, nf, delbar, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
-
- ! Check whether to exit
- subinfo = checkexit(maxfun, nf, f, ftarget, x)
- if (subinfo /= INFO_DFT) then
- info = subinfo
- exit
- end if
! Update DNORM_REC and MODERR_REC. (Should we?)
- ! DNORM_REC contains the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC contains the DNORM of the recent function evaluations with the current RHO.
dnorm = min(delbar, norm(d)) ! In theory, DNORM = DELBAR in this case.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC is the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC is the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) - quadinc(d, xpt, gopt, pq, hq)
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
!------------------------------------------------------------------------------------------!
@@ -548,6 +592,17 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
call updateh(knew_geo, kopt, d, xpt, idz, bmat, zmat)
call updatexf(knew_geo, ximproved, f, xosav + d, kopt, fval, xpt)
call updateq(idz, knew_geo, ximproved, bmat, d, moderr, xdrop, xosav, xpt, zmat, gopt, hq, pq)
+ if (.not. (all(is_finite(gopt)) .and. all(is_finite(hq)) .and. all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
+
+ ! Check whether to exit
+ subinfo = checkexit(maxfun, nf, f, ftarget, x)
+ if (subinfo /= INFO_DFT) then
+ info = subinfo
+ exit
+ end if
end if ! End of IF (IMPROVE_GEO). The procedure of improving geometry ends.
! The calculations with the current RHO are complete. Enhance the resolution of the algorithm
@@ -561,7 +616,7 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
rho = redrho(rho, rhoend)
! Print a message about the reduction of RHO according to IPRINT.
call rhomsg(solver, iprint, nf, delta, fval(kopt), rho, xbase + xpt(:, kopt))
- ! DNORM_REC and MODERR_REC are corresponding to the latest 3 function evaluations with
+ ! DNORM_REC and MODERR_REC are corresponding to the recent function evaluations with
! the current RHO. Update them after reducing RHO.
dnorm_rec = REALMAX
moderr_rec = REALMAX
@@ -571,28 +626,42 @@ subroutine newuob(calfun, iprint, maxfun, npt, eta1, eta2, ftarget, gamma1, gamm
! Powell's original criteria for shifting XBASE is as follows.
! 1. After a trust region step that is not short, shift XBASE if SUM(XOPT**2) >= 1.0E3*DNORM**2.
! 2. Before a geometry step, shift XBASE if SUM(XOPT**2) >= 1.0E3*DELBAR**2.
- if (sum(xpt(:, kopt)**2) >= 1.0E2_RP * delta**2) then ! 1.0E2 works better than 1.0E3 on 20230227.
+ ! 3. 1.0E2 works better than 1.0E3 on 20230227. In addition, 1.0E2 works better than 2.0E2,
+ ! 5.0E2, and 1.0E3 on 20240406, especially if RP = REAL32.
+ if (sum(xpt(:, kopt)**2) >= 1.0E2_RP * delta**2) then
call shiftbase(kopt, xbase, xpt, zmat, bmat, pq, hq, idz)
end if
+
+ ! Report the current best value, and check if user asks for early termination.
+ if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, tr, terminate=terminate)
+ if (terminate) then
+ info = CALLBACK_TERMINATE
+ exit
+ end if
+ end if
+
end do ! End of DO TR = 1, MAXTR. The iterative procedure ends.
! Return from the calculation, after trying the Newton-Raphson step if it has not been tried yet.
-if (info == SMALL_TR_RADIUS .and. shortd .and. nf < maxfun) then
- x = xbase + (xpt(:, kopt) + d)
+x = xbase + (xpt(:, kopt) + d)
+if (info == SMALL_TR_RADIUS .and. shortd .and. norm(x - (xbase + xpt(:, kopt))) > TENTH * rhoend .and. nf < maxfun) then
call evaluate(calfun, x, f)
nf = nf + 1_IK
+ ! Save X, F into the history.
+ call savehist(nf, x, xhist, f, fhist)
! Print a message about the function evaluation according to IPRINT.
! Zaikun 20230512: DELTA has been updated. RHO is only indicative here. TO BE IMPROVED.
call fmsg(solver, 'Trust region', iprint, nf, rho, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
+ if (f < fval(kopt)) then
+ xpt(:, kopt) = xpt(:, kopt) + d
+ fval(kopt) = f
+ end if
end if
-! Choose the [X, F] to return: either the current [X, F] or [XBASE + XOPT, FOPT].
-if (fval(kopt) < f .or. is_nan(f)) then
- x = xbase + xpt(:, kopt)
- f = fval(kopt)
-end if
+! Choose the [X, F] to return.
+x = xbase + xpt(:, kopt)
+f = fval(kopt)
! Arrange FHIST and XHIST so that they are in the chronological order.
call rangehist(nf, xhist, fhist)
diff --git a/fortran/newuoa/trustregion.f90 b/fortran/newuoa/trustregion.f90
index 18018e06d7..d7623287cd 100644
--- a/fortran/newuoa/trustregion.f90
+++ b/fortran/newuoa/trustregion.f90
@@ -8,7 +8,7 @@ module trustregion_newuoa_mod
!
! Started: July 2020
!
-! Last Modified: Monday, August 07, 2023 AM03:55:07
+! Last Modified: Saturday, April 06, 2024 PM11:03:07
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -351,8 +351,8 @@ subroutine trsapp(delta, gopt_in, hq_in, pq_in, tol, xpt, crvmin, s, info)
! PROJECT(X, V) returns the projection of X to SPAN(V): X'*(V/||V||)*(V/||V||).
d = (g + hs) - project(g + hs, s)
! N.B.:
- ! 1. The condition ||D||<=SQRT(TOL*GG) below is equivalent to |INPROD(G+HS,S)|<=SQRT((1-TOL)*GG*SS)
- ! in theory. As given above, Powell's code triggers an exit if INPROD(G+HS,S)=SGK<=(TOL-1)*GG*SS.
+ ! 1. The condition ||D||<=SQRT(TOL*GG) below is equivalent to |INPROD(G+HS,S)|<=SQRT((1-TOL)*GG*SS).
+ ! As given above, Powell's code triggers an exit if INPROD(G+HS,S)=SGK<=(TOL-1)*SQRT(GG*SS).
! Since |SQRT(1-TOL) - (1-TOL)| <= TOL/2, our condition is close to |SGK| <= (TOL-1)*GG*SS.
! When |SGK| is tiny, S and G+HS are nearly parallel and hence the 2-dimensional search cannot
! continue. Note that SGK is unlikely positive if everything goes well.
diff --git a/fortran/newuoa/update.f90 b/fortran/newuoa/update.f90
index bc68139d8b..01bac03aad 100644
--- a/fortran/newuoa/update.f90
+++ b/fortran/newuoa/update.f90
@@ -8,7 +8,7 @@ module update_newuoa_mod
!
! Started: July 2020
!
-! Last Modified: Monday, August 07, 2023 AM03:55:23
+! Last Modified: Friday, March 15, 2024 PM03:38:22
!--------------------------------------------------------------------------------------------------!
implicit none
diff --git a/fortran/primaf-GNU.def b/fortran/primaf-GNU.def
index 9b3eb465fc..d2bb592f78 100644
--- a/fortran/primaf-GNU.def
+++ b/fortran/primaf-GNU.def
@@ -1,7 +1,11 @@
EXPORTS
+ __memory_mod_MOD_alloc_rvector_sp
+ __memory_mod_MOD_alloc_rvector_dp
__bobyqa_mod_MOD_bobyqa
__cobyla_mod_MOD_cobyla
__lincoa_mod_MOD_lincoa
__newuoa_mod_MOD_newuoa
__uobyqa_mod_MOD_uobyqa
__debug_mod_MOD_assert
+ __infnan_mod_MOD_is_nan_dp
+ __infnan_mod_MOD_is_nan_sp
diff --git a/fortran/primaf-Intel.def b/fortran/primaf-Intel.def
index 936abb5af4..006f81c645 100644
--- a/fortran/primaf-Intel.def
+++ b/fortran/primaf-Intel.def
@@ -1,7 +1,11 @@
EXPORTS
+ MEMORY_MOD_mp_ALLOC_RVECTOR_SP
+ MEMORY_MOD_mp_ALLOC_RVECTOR_DP
BOBYQA_MOD_mp_BOBYQA
COBYLA_MOD_mp_COBYLA
LINCOA_MOD_mp_LINCOA
NEWUOA_MOD_mp_NEWUOA
UOBYQA_MOD_mp_UOBYQA
DEBUG_MOD_mp_ASSERT
+ INFNAN_MOD_mp_IS_NAN_DP
+ INFNAN_MOD_mp_IS_NAN_SP
diff --git a/fortran/tests/9src b/fortran/tests/9src
deleted file mode 100755
index b19df0a83a..0000000000
--- a/fortran/tests/9src
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-# This script pre-processes the Fortran source code for g95
-# It does not support REAL32, REAL64, REAL128 in ISO_FORTRAN_ENV.
-
-DIR="$(realpath "$1")"
-CONSTSF90="consts.F90"
-CONSTS="$DIR/common/$CONSTSF90"
-MEMORYF90="memory.F90"
-MEMORY="$DIR/common/$MEMORYF90"
-STRINGF90="string.f90"
-STRING="$DIR/common/$STRINGF90"
-CMN_FPRINTF90="fprint.f90"
-CMN_FPRINT="$DIR/common/$CMN_FPRINTF90"
-MEX_FPRINTF90="fprint.F90"
-MEX_FPRINT="$DIR/common/$MEX_FPRINTF90"
-COBYLB="$DIR/cobyla/cobylb.f90"
-
-if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
- printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
- exit 1
-fi
-
-if [[ -f "$CONSTS" ]] ; then
- sed -i "/end module consts_mod/d" "$CONSTS"
-
- STR="use, intrinsic :: iso_fortran_env, only : INT16, INT32, INT64, REAL32, REAL64, REAL128"
- sed -i "/$STR/d" "$CONSTS"
- OLD_STR="integer, parameter :: SP = REAL32"
- NEW_STR="integer, parameter :: SP = kind(0.0)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: DP = REAL64"
- NEW_STR="integer, parameter :: DP = kind(0.0D0)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: QP = REAL128"
- NEW_STR="integer, parameter :: QP = selected_real_kind(p=30)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: RP = REAL32"
- NEW_STR="integer, parameter :: RP = kind(0.0)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: RP = REAL64"
- NEW_STR="integer, parameter :: RP = kind(0.0D0)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: RP = REAL128"
- NEW_STR="integer, parameter :: RP = selected_real_kind(p=30)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- OLD_STR="integer, parameter :: IK = INT16"
- NEW_STR="integer, parameter :: IK = selected_int_kind(4)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- echo "integer, parameter :: INT16 = selected_int_kind(4)" >> "$CONSTS"
- OLD_STR="integer, parameter :: IK = INT32"
- NEW_STR="integer, parameter :: IK = selected_int_kind(7)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- echo "integer, parameter :: INT32 = selected_int_kind(7)" >> "$CONSTS"
- OLD_STR="integer, parameter :: IK = INT64"
- NEW_STR="integer, parameter :: IK = selected_int_kind(14)"
- sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
- echo "integer, parameter :: INT64 = selected_int_kind(14)" >> "$CONSTS"
-
- echo "end module consts_mod" >> "$CONSTS"
-fi
-
-if [[ -f "$MEMORY" ]] ; then
- OLD_STR="y = int(storage_size(x) \/ 8, kind(y))"
- NEW_STR="y = int(8, kind(y))"
- sed -i "s/$OLD_STR/$NEW_STR/" "$MEMORY"
-fi
-
-if [[ -f "$STRING" ]] ; then
- sed -i "/ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) \/ 2.0))/d" "$STRING"
- sed -i "/nexp_loc = min(nexp_loc, floor(real(MAX_NUM_STR_LEN - 5) \/ 2.0))/d" "$STRING"
-fi
-
-for FILE in "$CMN_FPRINT" "$MEX_FPRINT" ; do
- if [[ -f "$FILE" ]] ; then
- sed -i "s|funit_loc = -1|funit_loc = 42|g" "$FILE"
- sed -i "s|newunit|unit|g" "$FILE"
- fi
-done
-
-# g95 does not support internal subroutines as arguments.
-if [[ -f "$COBYLB" ]] ; then
- sed -ni '1,/\s*!\s*Calculation ends\s*!/p;/end subroutine cobylb/,$p' "$COBYLB"
- sed -i "s|calcfc_internal|calcfc|" "$COBYLB"
-fi
-
-exit 0
diff --git a/fortran/tests/Makefile b/fortran/tests/Makefile
index 26becbec7f..6b948fb5b2 100644
--- a/fortran/tests/Makefile
+++ b/fortran/tests/Makefile
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest.S: test solver S with compiler X
# Ctest.S_c: test solver S with compiler X; compilation only (do not run the binary)
# Ctest_iN: test solver S with compiler X and integer kind INT(N*8)
diff --git a/fortran/tests/a9src b/fortran/tests/a9src
deleted file mode 100755
index 53a3cea458..0000000000
--- a/fortran/tests/a9src
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/bash
-# This script pre-processes the Fortran source code for g95 and Absoft af95.
-# 1. They do not recognize the `back` keyword in `min/maxloc`, which is available since F2008.
-# 2. They do not support allocatable characters (variable length strings), which is F2003.
-# 3. af95 does not support `error stop`, which is F2003.
-
-DIR="$(realpath "$1")"
-GETACTF90="getact.f90"
-GETACT="$DIR/lincoa/$GETACTF90"
-PREPROCF90="preproc.f90"
-PREPROC="$DIR/common/$PREPROCF90"
-LINALGF90="linalg.f90"
-LINALG="$DIR/common/$LINALGF90"
-CMN_FPRINTF90="fprint.f90"
-CMN_FPRINT="$DIR/common/$CMN_FPRINTF90"
-MEX_FPRINTF90="fprint.F90"
-MEX_FPRINT="$DIR/common/$MEX_FPRINTF90"
-DEBUGF90="debug.F90"
-DEBUG="$DIR/common/$DEBUGF90"
-NOISEF90="noise.f90"
-NOISE="$DIR/testsuite/$NOISEF90"
-FMXAPIF90="fmxapi.F90"
-FMXAPI="$DIR/$FMXAPIF90"
-MEMORYF90="memory.F90"
-MEMORY="$DIR/common/$MEMORYF90"
-STRINGF90="string.f90"
-STRING="$DIR/common/$STRINGF90"
-MESSAGEF90="message.f90"
-MESSAGE="$DIR/common/$MESSAGEF90"
-
-if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
- printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
- exit 1
-fi
-
-if [[ -f "$GETACT" ]] ; then
- sed -i "s/,\s*back\s*=\s*\.true\.//" "$GETACT"
-fi
-
-for FILE in "$PREPROC" "$LINALG" "$CMN_FPRINT" "$MEX_FPRINT" "$DEBUG" "$NOISE" "$FMXAPI" ; do
- if [[ -f "$FILE" ]] ; then
- sed -i "s/character(len=:), allocatable/character(len=1024)/" "$FILE"
- sed -i "/character(len=\*), parameter :: newline/d" "$FILE"
- sed -i "s|newline|achar(10)|g" "$FILE"
- fi
-done
-
-if [[ -f "$STRING" ]] ; then
- sed -i "s/character(len=:), allocatable/character(len=1024)/" "$STRING"
- sed -i "/LEN(S) <= MAX_NUM_STR_LEN/d" "$STRING"
- sed -i "s/write (str, sformat) x/write(*,*) sformat; write (str, *) x/" "$STRING"
- sed -i "/character(len=\*), parameter :: newline/d" "$STRING"
- sed -i "s|newline|achar(10)|g" "$STRING"
-fi
-
-if [[ -f "$MEMORY" ]] ; then
- sed -i "s/character(len=:), allocatable/character(len=1024)/" "$MEMORY"
- sed -i "/allocate (character/d" "$MEMORY"
- sed -i "/call validate(allocated(x), 'X is allocated', srname)/d" "$MEMORY"
- sed -i "s/integer :: alloc_status/integer :: alloc_status = 0/" "$MEMORY"
-fi
-
-# message.f90 does not work due to the changes to string.f90, especially to real2str_vector.
-(cd "$DIR" && grep -R 'call .*msg' | sed 's|:.*$||' | grep -v 'debug.F90\|fmxapi.F90' | xargs sed -i "s|call .*msg.*$|write(*,*) solver|" && cd - || exit 1) > /dev/null
-(cd "$DIR" && grep -R 'use.*message_mod' | sed 's|:.*$||' | xargs sed -i "/use.*message_mod/d" && cd - || exit 1) > /dev/null
-printf "module message_mod \n end module message_mod" > "$MESSAGE"
-
-if [[ -f "$DEBUG" ]] ; then
- sed -i "s|^\s*error stop.*$|stop|" "$DEBUG"
-fi
-
-for SOL in cobyla uobyqa newuoa bobyqa lincoa ; do
- FILE="$DIR/test_$SOL.f90"
- if [[ -f $FILE ]] ; then
- sed -i "s|character(len=:), allocatable|character(len=1024)|g" "$FILE"
- sed -i "/safealloc(testdim.*$/d" "$FILE"
- fi
-done
-
-if [[ -d "$DIR"/lincoa ]] ; then
- for FILE in "$DIR"/lincoa/* ; do
- if [[ -f "$FILE" ]] ; then
- sed -i "s|count(xl > -REALMAX)|size(xl)|" "$FILE"
- sed -i "s|count(xu < REALMAX)|size(xu)|" "$FILE"
- fi
- done
- sed -i -e '/amat = reshape(shape=shape(amat)/,+3d' "$DIR"/lincoa/lincoa.f90
- sed -i '/idmat/d' "$DIR"/lincoa/lincoa.f90
-fi
-
-
-exit 0
diff --git a/fortran/tests/makefiles/Makefile.bobyqa b/fortran/tests/makefiles/Makefile.bobyqa
index bc118f2535..b356aa4105 100644
--- a/fortran/tests/makefiles/Makefile.bobyqa
+++ b/fortran/tests/makefiles/Makefile.bobyqa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/fortran/tests/makefiles/Makefile.cobyla b/fortran/tests/makefiles/Makefile.cobyla
index 859c259b0c..7998ba6132 100644
--- a/fortran/tests/makefiles/Makefile.cobyla
+++ b/fortran/tests/makefiles/Makefile.cobyla
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/fortran/tests/makefiles/Makefile.common b/fortran/tests/makefiles/Makefile.common
index 2951e1acab..b461e13518 100644
--- a/fortran/tests/makefiles/Makefile.common
+++ b/fortran/tests/makefiles/Makefile.common
@@ -2,18 +2,18 @@
#
# 1. The following compilers are tested:
# 9: G95 (compilation only; no compilation of test_*.f90 and other testing code, which contains F03 constructs unsupported by G95)
-# a: Absoft af95 (compilation only; the compiler is buggy and not maintained anymore)
-# d: AOCC flang
-# f: Classic flang
+# d: AOCC flang (based on Classic Flang)
+# f: LLVM flang
# g: GNU gfortran
+# m: AOMP flang (based on LLVM Flang)
# n: NAG nagfor
# i: Intel ifort
-# r: Arm flang
+# r: Arm flang (based on LLVM Flang)
# s: Oracle sunf95
-# v: NVIDIA nvfortran
+# v: NVIDIA nvfortran (based on Classic Flang)
# x: Intel ifx
#
-# 2. The following tests are available, where C = 9, a, d, f, g, l, n, i, r, s, v, x, and N = 2, 4, 8:
+# 2. The following tests are available, where C = 9, d, f, g, l, m, n, i, r, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
@@ -24,16 +24,28 @@
# empty, then we will test both an optimized version (compiled with -O3) and a debugging version
# (compiled with -g -O0); otherwise, a version indicated by FFLAGS will be tested.
#
+# 4. `-Wl,-z,noexecstack` passes the `-z noexecstack` option to the linker and tells the linker to
+# set the "stack is not executable" attribute in the output file. If executable stacks are actually
+# needed, then it will lead to a runtime error. This is to test that our code does not require executable
+# stacks, which is a security risk. COBYLA used to use an internal subroutine passed as an actual
+# argument, which necessitates an executable stack with some compilers, but such code has been
+# removed on 20250812. See git commit 5e5e871 and
+# https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+#
+# 5. Do not miss the quotes around the command substitution for the compilers. This is to ensure
+# that the path is resolved correctly if it contains spaces (very often on Windows).
+#
+#
# Coded by Zaikun ZHANG (www.zhangzk.net).
#
# Started: September 2021
#
-# Last Modified: June 2023
+# Last Modified: February 2026
####################################################################################################
#$(VERBOSE).SILENT: # Order make to work quietly. Not desirable in this case.
-SHELL = /bin/bash
+SHELL = /usr/bin/env bash # Without this, the Makefile will not work as it contains bash-specific commands.
####################################################################################################
# Variables
@@ -84,14 +96,16 @@ endif
# Define the compiler-dependent "fast flags", which will be used when FFLAGS is -fast.
AFF := -O4
-DFF := -O4 -Ofast -ffast-math # As of AOCC 4.1.0, -Ofast implies -O3, but it is not clear whether it implies -ffast-math
-FFF := -O3 -Ofast -ffast-math # No documentation found about the relation between -Ofast, -O3, and -ffast-math
+DFF := -O3 -Ofast -ffast-math # As of AOCC 5.1, -Ofast implies -O3, but it is not clear whether it implies -ffast-math; -O4 and -O3 are equivalent for AOCC 5.1
+FFF := -O3 -ffast-math # Suggested by flang 20.1.7
GFF := -Ofast # -Ofast implies -O3 and -ffast-math
+MFF := -O3 -ffast-math # Suggested by AOMP 22.0-2 when running `amdflang --help | grep fast`, -Ofast is deprecated
NFF := -O4
-RFF := -Ofast -ffast-math # -Ofast implies -O3, but no documentation found about the relation between -Ofast and -ffast-math
+RFF := -O3 -ffast-math # '-Ofast' is deprecated; use '-O3 -ffast-math -fstack-arrays' for the same behavior
SFF := -fast # -fast implies -O5 and ftrap=common
SFF16 := -O5 # -fast leads to a mistakenly failure of assertion in BOBYQA: RHOBEG <= MINVAL(XU-XL)/2
VFF := -O4 -fast # nvfortran -fast implies -O2, but the highest optimization level is -O4
+9FF := -O4
# Define fast flags for Intel compilers according to the CPU brand.
# As of ifx (IFORT) 2022.2.1, -fast will lead to the following error on AMD CPUs:
@@ -100,14 +114,19 @@ VFF := -O4 -fast # nvfortran -fast implies -O2, but the highest optimization le
# catastrophic error: Function return parameter requires SSE register while SSE is disabled.
# In the following, we assume that there is only one type of CPU on the current platform.
ifneq (,$(findstring intel,$(shell lscpu 2> /dev/null | tr '[:upper:]' '[:lower:]'))) # Intel CPU.
- IFF := -fast # -fast is "A combination of -Ofast, -ipo, -static (for static linking), and -xHost."
- #XFF := -fast # As of ifx (IFORT) 2022.2.1, IS_NAN does not work with -fast and leads to exceptions
- XFF := -Ofast -xHost
+ # According to
+ # https://www.intel.com/content/www/us/en/docs/fortran-compiler/developer-guide-reference/2024-0/fast.html,
+ # -fast sets the following options:
+ # -ipo, -O3, -no-prec-div,-static, -fp-model fast=2, and -xHost
+ # ifort -fast with ifort (IFORT) 2021.11.1 does not work due to an error with -ipo.
+ IFF := -O3 -no-prec-div -static -fp-model fast=2 -xHost
+ XFF := -fast # -fast seems to work starting from ifx (IFX) 2024.0.0
else # Non-Intel CPU, especially AMD.
IFF := -Ofast # -Ofast implies -O3
XFF := -Ofast -xHost
endif
+TESTSEED ?=
TESTDIM ?= small
# Log file stamp.
@@ -134,6 +153,8 @@ TESTSUITE_DIR := $(TEST_DIR)/$(TESTSUITE)
SRC_DIRS := $(COMMON_DIR) $(SOLVER_DIR) $(TESTSUITE_DIR)
# Driver directory.
DRIVER_DIR := $(TEST_DIR)
+# Tools directory
+TOOLS_DIR := $(TEST_DIR)/tools
# Sources.
COMMON_SRC := $(shell sed "s/^/\.\/common\//" $(COMMON_DIR)/ffiles.txt)
@@ -149,14 +170,15 @@ SRC_NO_TEST := $(COMMON_SRC) $(SOLVER_SRC)
HEADERS := $(TEST_SOLVER_DIR)/$(COMMON)/*.h
# The checktest script.
-CHCKTST := $(TEST_DIR)/checktest
+CHCKTST := $(TOOLS_DIR)/checktest
# Define the tests.
######################################################################################
# Decide whether to test QP, i.e., REAL128.
-# Flang and nvfortran do not support REAL128;
-# AOCC Flang complains about a symbol lookup error: undefined symbol: "fort_rnumq_i8";
-TESTS_QP = atest gtest ntest itest stest 9test xtest
+# AOCC 5.1 Flang is buggy concerning `nint` with REAL128 (https://github.com/zequipe/test_compiler/blob/master/test_nint.f90)
+# LLVM Flang 21.1.0 does not support REAL128.
+# nvfortran 26.1 do not support REAL128;
+TESTS_QP = gtest mtest ntest itest stest 9test xtest
TESTS_NO_QP = dtest ftest rtest vtest
TESTS = $(TESTS_QP) $(TESTS_NO_QP)
# When listing the tests, we first put the ones that are more likely to raise errors.
@@ -180,7 +202,7 @@ $(foreach TST, $(TESTS), $(eval $(TST): TST_C := $(TST)_c))
# The following tests compile the code but do not run the binary. This is useful if we want to
# check whether the code can compile.
-TESTS_C_QP = atest_c gtest_c ntest_c itest_c stest_c 9test_c xtest_c
+TESTS_C_QP = gtest_c mtest_c ntest_c itest_c stest_c 9test_c xtest_c
TESTS_C_NO_QP = dtest_c ftest_c rtest_c vtest_c
TESTS_C = $(TESTS_C_QP) $(TESTS_C_NO_QP)
$(foreach TST, $(TESTS_C_QP), $(eval $(TST): SUBTESTS_C := \
@@ -203,8 +225,8 @@ $(foreach TST, $(TESTS_C_NO_QP), $(eval $(TST): SUBTESTS_C := \
# The tests with ifort, ifx, etc are slow due to the extensive runtime checks. To save time, we
# define the following tests. If itest_i2 succeeds, then itest_i4 and itest_i8 are likely (but not
# surely) to be OK, because the only difference in the code is the integer kind. Similar for others.
-TESTS_INT_QP = atest_i2 atest_i4 atest_i8 \
- gtest_i2 gtest_i4 gtest_i8 \
+TESTS_INT_QP = gtest_i2 gtest_i4 gtest_i8 \
+ mtest_i2 mtest_i4 mtest_i8 \
ntest_i2 ntest_i4 ntest_i8 \
itest_i2 itest_i4 itest_i8 \
stest_i2 stest_i4 stest_i8 \
@@ -227,8 +249,8 @@ $(foreach TST, $(TESTS_INT), $(eval $(TST): TST_C := $(TST)_c))
# The following tests compile the code but do not run the binary. This is useful if we want to
# check whether the code can compile.
-TESTS_INT_C_QP = atest_i2_c atest_i4_c atest_i8_c \
- gtest_i2_c gtest_i4_c gtest_i8_c \
+TESTS_INT_C_QP = gtest_i2_c gtest_i4_c gtest_i8_c \
+ mtest_i2_c mtest_i4_c mtest_i8_c \
ntest_i2_c ntest_i4_c ntest_i8_c \
itest_i2_c itest_i4_c itest_i8_c \
stest_i2_c stest_i4_c stest_i8_c \
@@ -252,47 +274,55 @@ $(foreach TST, $(TESTS_INT_C_NO_QP), $(eval $(TST): SUBTESTS_C := \
.PHONY: all test test_c clean $(TESTS) $(TESTS_C) $(TESTS_INT) $(TESTS_INT_C) source_* clean*
# Operating system.
-OSTYPE :=
+# We assume the OS is LINUX by default.
+OSTYPE := LINUX
ifeq ($(OS),Windows_NT)
OSTYPE := WINDOWS
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
OSTYPE := MAC
- else # We assume the OS is LINUX by default.
- OSTYPE := LINUX
+ endif
+ ifeq ($(UNAME_S),FreeBSD)
+ OSTYPE := FREEBSD
endif
endif
-# Invoke the binary by gdb in the batch mode, so that we get necessary information if it crashes.
-# N.B.: -return-child-return asks gdb to return with the exit code of the binary that it invokes,
-# unless gdb itself terminates with an error. In our situation, it is better not to use this flag,
-# Otherwise, when the binary crashes, `checktest` will not be called due to the error returned by gdb.
-# On GitHub Actions, gdb does not work out of the box due to security restrictions.
+# Invoke the binary by gdb using $(TOOLS_DIR)/run_gdb, which is a wrapper script. It does two
+# things: 1) It runs the binary under gdb in the batch mode and the -return-child-result mode, so
+# that the exit code of the binary is returned as the exit code of gdb. 2) If gdb fails (e.g., the
+# binary crashes), it records the exit code of gdb in a specified file and exits successfully.
+# In this way, the Makefile receipt can continue to call `checktest` to check the log file regardless
+# of the failure. The exit code will be checked after the test by reading the aforementioned file.
+# For more details, see $(TOOLS_DIR)/run_gdb.
ifeq ($(OSTYPE), MAC)
+ # On GitHub Actions, gdb does not work out of the box due to security restrictions.
GDB :=
else
- #GDB := gdb -return-child-result -batch -ex run -ex where -ex quit
- GDB := gdb -batch -ex run -ex where -ex quit
+ GDB := bash $(TOOLS_DIR)/run_gdb
endif
-# Define SEDI.
-# When calling "sed -i" on macOS, it is obligatory to specify a string (e.g., .bak) after -i as the
-# extension for saving a backup. If the string is "", then no backup will be saved. If no string is
-# specified, then an error will be raised, saying "invalid command code".
-SEDI :=
-ifeq ($(OSTYPE), MAC)
- SEDI := sed -i ""
-else
- SEDI := sed -i
-endif
+## Define SEDI.
+## When calling "sed -i" on macOS and FreeBSD, it is obligatory to specify a string (e.g., .bak)
+## after -i as the extension for saving a backup. If the string is "", then no backup will be saved.
+## If no string is specified, then an error will be raised, saying "invalid command code".
+#SEDI :=
+#ifeq ($(OSTYPE), MAC)
+# SEDI := sed -i ""
+#else ifeq ($(OSTYPE), FREEBSD)
+# SEDI := sed -i ""
+#else
+# SEDI := sed -i
+#endif
# Define NPROCS to be the number of processors available to make tests in parallel.
NPROCS :=
ifeq ($(OSTYPE), LINUX)
NPROCS := $(shell grep -c 'processor' /proc/cpuinfo 2>/dev/null || echo 1)
else ifeq ($(OSTYPE), MAC)
- NPROCS := $(shell sysctl hw.ncpu | grep -o '[0-9]\+' || echo 1)
+ NPROCS := $(shell sysctl -n hw.ncpu || echo 1)
+else ifeq ($(OSTYPE), FREEBSD)
+ NPROCS := $(shell sysctl -n hw.ncpu || echo 1)
else
NPROCS := $(shell echo $(NUMBER_OF_PROCESSORS) || echo 1)
endif
@@ -314,46 +344,22 @@ endif
# e.g., over/underflow.
####################################################################################################
-# Absoft af95
-# N.B.: According to Absoft support: With the Absoft compiler, automatic arrays are always allocated
-# on the stack. There is no option to move the allocation to the heap.
-AFORT := af95 -m1 -en -et -Rb -Rc -Rs -Rp -OPT:Olimit=6204
-
-atest_i2_r4_d1_tst_c atest_i4_r4_d1_tst_c atest_i8_r4_d1_tst_c atest_i2_r4_d0_tst_c atest_i4_r4_d0_tst_c atest_i8_r4_d0_tst_c: \
- FCO := $(AFORT) $(FFLAGSO) -TENV:simd_zmask=off
-atest_i2_r4_d1_tst_c atest_i4_r4_d1_tst_c atest_i8_r4_d1_tst_c atest_i2_r4_d0_tst_c atest_i4_r4_d0_tst_c atest_i8_r4_d0_tst_c: \
- FCG := $(AFORT) $(FFLAGSG) -TENV:simd_zmask=off
-atest_i2_r4_d1_tst_c atest_i4_r4_d1_tst_c atest_i8_r4_d1_tst_c atest_i2_r4_d0_tst_c atest_i4_r4_d0_tst_c atest_i8_r4_d0_tst_c: \
- FCF := $(AFORT) -TENV:simd_zmask=off $(AFF)
-
-atest_i2_r8_d1_tst_c atest_i4_r8_d1_tst_c atest_i8_r8_d1_tst_c atest_i2_r8_d0_tst_c atest_i4_r8_d0_tst_c atest_i8_r8_d0_tst_c: \
- FCO := $(AFORT) $(FFLAGSO) \
- -TENV:simd_zmask=off -TENV:simd_omask=off -TENV:simd_imask=off #-TENV:simd_dmask=off -TENV:simd_umask=off
-atest_i2_r8_d1_tst_c atest_i4_r8_d1_tst_c atest_i8_r8_d1_tst_c atest_i2_r8_d0_tst_c atest_i4_r8_d0_tst_c atest_i8_r8_d0_tst_c: \
- FCG := $(AFORT) $(FFLAGSG) \
- -TENV:simd_zmask=off -TENV:simd_omask=off -TENV:simd_imask=off #-TENV:simd_dmask=off -TENV:simd_umask=off
-atest_i2_r8_d1_tst_c atest_i4_r8_d1_tst_c atest_i8_r8_d1_tst_c atest_i2_r8_d0_tst_c atest_i4_r8_d0_tst_c atest_i8_r8_d0_tst_c: \
- FCF := $(AFORT) -TENV:simd_zmask=off -TENV:simd_omask=off -TENV:simd_imask=off $(AFF)
- #-TENV:simd_dmask=off -TENV:simd_umask=off
-
-atest_i2_r16_d1_tst_c atest_i4_r16_d1_tst_c atest_i8_r16_d1_tst_c atest_i2_r16_d0_tst_c atest_i4_r16_d0_tst_c atest_i8_r16_d0_tst_c: \
- FCO := $(AFORT) $(FFLAGSO) -TENV:simd_zmask=off
-atest_i2_r16_d1_tst_c atest_i4_r16_d1_tst_c atest_i8_r16_d1_tst_c atest_i2_r16_d0_tst_c atest_i4_r16_d0_tst_c atest_i8_r16_d0_tst_c: \
- FCG := $(AFORT) $(FFLAGSG) -TENV:simd_zmask=off
-atest_i2_r16_d1_tst_c atest_i4_r16_d1_tst_c atest_i8_r16_d1_tst_c atest_i2_r16_d0_tst_c atest_i4_r16_d0_tst_c atest_i8_r16_d0_tst_c: \
- FCF := $(AFORT) -TENV:simd_zmask=off $(AFF)
-
# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2>/dev/null | sort | tail -n 1)
-AFINC := $(shell dirname $$(dirname $(AFLANG)) 2>/dev/null)/include
-AFLIB := $(shell dirname $$(dirname $(AFLANG)) 2>/dev/null)/lib
-AFLANG := $(AFLANG) -I$(AFINC) -L$(AFLIB)
-AFLANG := $(AFLANG) -pedantic-errors -Werror
+AFLANG := "$(shell find -L /opt/AMD -type f -name flang -exec test -x {} \; -print 2>/dev/null | sort | tail -n 1)"
+#AFINC := $(shell dirname $$(dirname $(AFLANG) 2>/dev/null) 2>/dev/null)/include
+#AFLIB := $(shell dirname $$(dirname $(AFLANG) 2>/dev/null) 2>/dev/null)/lib
+#AFLANG := $(AFLANG) -I$(AFINC) -L$(AFLIB)
+DFORT := $(AFLANG) -pedantic-errors -Werror
ifeq ($(TESTDIM),small)
- AFLANG := $(AFLANG) -Mstack_arrays
+ DFORT := $(DFORT) -Mstack_arrays
+else
+ DFORT := $(DFORT) -fno-stack-arrays # -Mnostack_arrays is not recognized by AOCC 5.1
endif
# Strangely, with `-Mchkptr`, the compiler may not print the error message of `-Mbounds` anymore.
-DFORT := $(AFLANG) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mrecursive #-Kieee #-Mchkptr
+DFORT := $(DFORT) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mrecursive #-Kieee #-Mchkptr
+ifeq ($(OSTYPE),LINUX)
+ DFORT := $(DFORT) -Wl,-z,noexecstack
+endif
dtest_i2_r4_d1_tst_c dtest_i4_r4_d1_tst_c dtest_i8_r4_d1_tst_c dtest_i2_r4_d0_tst_c dtest_i4_r4_d0_tst_c dtest_i8_r4_d0_tst_c: \
FCO := $(DFORT) $(FFLAGSO)
@@ -376,15 +382,66 @@ dtest_i2_r16_d1_tst_c dtest_i4_r16_d1_tst_c dtest_i8_r16_d1_tst_c dtest_i2_r16_d
dtest_i2_r16_d1_tst_c dtest_i4_r16_d1_tst_c dtest_i8_r16_d1_tst_c dtest_i2_r16_d0_tst_c dtest_i4_r16_d0_tst_c dtest_i8_r16_d0_tst_c: \
FCF := $(DFORT) -ffp-exception-behavior=strict $(DFF)
-# Classic Flang
-# We must define FFORT as follows, because AOCC will also provide "flang", except that it is
-# located under /opt/AMD. Similar for ARM Flang.
-FFORT := $(shell find -L /usr/bin /usr/local/bin ${HOME}/local ${HOME}/.local /tmp -type f -executable -name flang -print 2>/dev/null | sort | tail -n 1)
-FFORT := $(FFORT) -pedantic-errors -Werror
+# AMD AOMP Flang
+MFORT := "$(shell command -v amdflang)"
+MFORT := $(MFORT) -fimplicit-none -Werror
ifeq ($(TESTDIM),small)
- FFORT := $(FFORT) -Mstack_arrays
+ MFORT := $(MFORT) -fstack-arrays
+else
+ MFORT := $(MFORT) -fno-stack-arrays -mmlir -fdynamic-heap-array
+ # See https://github.com/llvm/llvm-project/issues/88344
+endif
+MFORT := $(MFORT) -std=f2018 -pedantic # As of flang 20.0, only -std=f2018 is supported
+ifeq ($(OSTYPE),LINUX)
+ MFORT := $(MFORT) -Wl,-z,noexecstack
+endif
+
+mtest_i2_r4_d1_tst_c mtest_i4_r4_d1_tst_c mtest_i8_r4_d1_tst_c mtest_i2_r4_d0_tst_c mtest_i4_r4_d0_tst_c mtest_i8_r4_d0_tst_c: \
+ FCO := $(MFORT) $(FFLAGSO)
+mtest_i2_r4_d1_tst_c mtest_i4_r4_d1_tst_c mtest_i8_r4_d1_tst_c mtest_i2_r4_d0_tst_c mtest_i4_r4_d0_tst_c mtest_i8_r4_d0_tst_c: \
+ FCG := $(MFORT) $(FFLAGSG)
+mtest_i2_r4_d1_tst_c mtest_i4_r4_d1_tst_c mtest_i8_r4_d1_tst_c mtest_i2_r4_d0_tst_c mtest_i4_r4_d0_tst_c mtest_i8_r4_d0_tst_c: \
+ FCF := $(MFORT) $(MFF)
+
+mtest_i2_r8_d1_tst_c mtest_i4_r8_d1_tst_c mtest_i8_r8_d1_tst_c mtest_i2_r8_d0_tst_c mtest_i4_r8_d0_tst_c mtest_i8_r8_d0_tst_c: \
+ FCO := $(MFORT) $(FFLAGSO)
+mtest_i2_r8_d1_tst_c mtest_i4_r8_d1_tst_c mtest_i8_r8_d1_tst_c mtest_i2_r8_d0_tst_c mtest_i4_r8_d0_tst_c mtest_i8_r8_d0_tst_c: \
+ FCG := $(MFORT) $(FFLAGSG)
+mtest_i2_r8_d1_tst_c mtest_i4_r8_d1_tst_c mtest_i8_r8_d1_tst_c mtest_i2_r8_d0_tst_c mtest_i4_r8_d0_tst_c mtest_i8_r8_d0_tst_c: \
+ FCF := $(MFORT) $(MFF)
+
+mtest_i2_r16_d1_tst_c mtest_i4_r16_d1_tst_c mtest_i8_r16_d1_tst_c mtest_i2_r16_d0_tst_c mtest_i4_r16_d0_tst_c mtest_i8_r16_d0_tst_c: \
+ FCO := $(MFORT) $(FFLAGSO)
+mtest_i2_r16_d1_tst_c mtest_i4_r16_d1_tst_c mtest_i8_r16_d1_tst_c mtest_i2_r16_d0_tst_c mtest_i4_r16_d0_tst_c mtest_i8_r16_d0_tst_c: \
+ FCG := $(MFORT) $(FFLAGSG)
+mtest_i2_r16_d1_tst_c mtest_i4_r16_d1_tst_c mtest_i8_r16_d1_tst_c mtest_i2_r16_d0_tst_c mtest_i4_r16_d0_tst_c mtest_i8_r16_d0_tst_c: \
+ FCF := $(MFORT) $(MFF)
+
+# LLVM Flang
+# Note that AOMP and AOCC also provide "flang". We define FFORT as follows to make sure the correct
+# one is being called. Otherwise, flang may be resolved to the one provided by AOMP or AOCC, which
+# did happen in our tests.
+FFORT := "$(shell find -L /usr/local/llvm* /usr/lib/llvm* /opt/homebrew /usr/local/opt /usr/local/Cellar -type f -name flang -exec test -x {} \; -print 2>/dev/null | sort | tail -n 1)"
+# If the above command fail to find flang (it happens on Windows), we try setting FFORT manually.
+ifeq ($(FFORT),"") # The quotation marks are necessary
+ FFORT := "$(shell command -v flang)"
+endif
+FFORT := $(FFORT) -fimplicit-none -Werror
+
+ifeq ($(TESTDIM),small)
+ FFORT := $(FFORT) -fstack-arrays
+else
+ FFORT := $(FFORT) -fno-stack-arrays -mmlir -fdynamic-heap-array
+ # See https://github.com/llvm/llvm-project/issues/88344
+endif
+FFORT := $(FFORT) -std=f2018 -pedantic # As of flang 20.0, only -std=f2018 is supported
+ifeq ($(OSTYPE),LINUX)
+ FFORT := $(FFORT) -Wl,-z,noexecstack
+endif
+ifeq ($(OSTYPE), MAC)
+ SDKROOT := $(shell /usr/bin/xcrun --show-sdk-path -sdk macosx)
+ FFORT := $(FFORT) -lSystem -L$(SDKROOT)/usr/lib
endif
-FFORT := $(FFORT) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mrecursive -Mchkptr #-Kieee
ftest_i2_r4_d1_tst_c ftest_i4_r4_d1_tst_c ftest_i8_r4_d1_tst_c ftest_i2_r4_d0_tst_c ftest_i4_r4_d0_tst_c ftest_i8_r4_d0_tst_c: \
FCO := $(FFORT) $(FFLAGSO)
@@ -418,7 +475,7 @@ ftest_i2_r16_d1_tst_c ftest_i4_r16_d1_tst_c ftest_i8_r16_d1_tst_c ftest_i2_r16_d
# with a leading 0 are interpreted as octal (base-8) numbers within double brackets.
# see https://stackoverflow.com/questions/24777597/value-too-great-for-base-error-token-is-08
#GFORT := gfortran -pg # Compile for profiling with the gprof profiler. Does not work with macOS on GitHub Actions due to missing gcrt1.o
-GFORT := gfortran
+GFORT := "$(shell command -v gfortran)"
GFSTD := $(shell [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 8 ]] && ([ $(FS) -lt 18 ] && echo -std=f2018 || echo -std=f20$(FS)) || echo "")
# 1. gfortran 7 raises a false positive warning regarding "maybe-uninitialized" of allocatable arrays.
# See https://stackoverflow.com/questions/56261880/fortran-re-allocation-on-assignment-and-gfortran-warnings
@@ -426,7 +483,15 @@ GFSTD := $(shell [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\
# allocatable characters. See
# https://fortran-lang.discourse.group/t/warning-str-may-be-used-uninitialized
WE := $(shell if [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 11 || $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 8 && -z '$(FFLAGSG)' ]] ; then echo "-Werror"; else echo ""; fi)
-GFORT := $(GFORT) $(WE) -pedantic-errors -fmax-errors=1
+# gfortran 14 has a bug when it is called with `gfortran -Wmaybe-uninitialized -fsanitize=undefined -Ok`,
+# where k is 2, 3, 4, or fast. See
+# https://fortran-lang.discourse.group/t/gfortran-14-mem-real-kind-4-0-real-kind-4-0-32-0-may-be-used-uninitialized
+WU := $(shell if [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 13 && "$(FFLAGS)" != "-g" && "$(FFLAGS)" != "-O" && "$(FFLAGS)" != "-O1" ]] ; then echo "-Wno-uninitialized"; else echo "-Wuninitialized -Wmaybe-uninitialized"; fi)
+# gfortran 14+ supports the `-ftrampoline-impl=heap` option, which is needed to avoid the
+# warning about executable stacks caused by internal procedures passed as arguments. See
+# https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+TRAMPOLINE := $(shell [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 13 ]] && (echo "-ftrampoline-impl=heap") || echo "")
+GFORT := $(GFORT) $(WE) $(TRAMPOLINE) -pedantic-errors -fmax-errors=1
# `-fautomatic` (the default) tells `gfortran` to use the stack for local variables smaller than the
# value given by -fmax-stack-var-size. `-fstack-arrays` forces all automatic arrays to be on the stack.
# We enable them for small problems to verify that we will not have stack overflows.
@@ -437,16 +502,20 @@ else
GFORT := $(GFORT) -fno-stack-arrays -fstack-check
endif
# -fsanitize=leak,address does not work with gdb.
-FSAN := $(shell if [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 9 ]] ; then echo "-fsanitize=undefined"; else echo ""; fi)
+FSAN := $(shell if [[ $$(($(GFORT) -dumpversion 2>/dev/null || echo 0) | sed 's|\..*||') -gt 9 && $(OSTYPE) == 'LINUX' ]] && ! grep -iq "alpine" /etc/os-release 2>/dev/null ; then echo "-fsanitize=undefined"; else echo ""; fi)
GFORT := $(GFORT) $(GFSTD) -fall-intrinsics -frecursive \
- -Wall -Wextra -Wpedantic -pedantic -Wampersand -Wconversion -Wuninitialized -Wmaybe-uninitialized \
+ -Wall -Wextra -Wpedantic -pedantic -Wampersand -Wconversion \
-Wsurprising -Waliasing -Wimplicit-interface -Wimplicit-procedure -Wintrinsics-std -Wunderflow \
-Wuse-without-only -Wunused-parameter -Wreturn-type \
+ $(WU) \
$(FSAN) \
-fPIC -fimplicit-none -fbacktrace -fcheck=all \
-ftrapv
- #-finit-real=nan -finit-integer=-9999999 \ # This will hide some warnings on uninitialized variables.
+ #-finit-real=nan -finit-integer=-9999999 # This will hide some warnings on uninitialized variables.
#-Wrealloc-lhs -Wrealloc-lhs-all
+ifeq ($(OSTYPE),LINUX)
+ GFORT := $(GFORT) -Wl,-z,noexecstack
+endif
gtest_i2_r4_d1_tst_c gtest_i4_r4_d1_tst_c gtest_i8_r4_d1_tst_c gtest_i2_r4_d0_tst_c gtest_i4_r4_d0_tst_c gtest_i8_r4_d0_tst_c: \
FCO := $(GFORT) $(FFLAGSO) -Wno-function-elimination -ffpe-trap=zero
@@ -456,16 +525,16 @@ gtest_i2_r4_d1_tst_c gtest_i4_r4_d1_tst_c gtest_i8_r4_d1_tst_c gtest_i2_r4_d0_ts
FCF := $(GFORT) -Wno-function-elimination -ffpe-trap=zero $(GFF)
gtest_i2_r8_d1_tst_c gtest_i4_r8_d1_tst_c gtest_i8_r8_d1_tst_c gtest_i2_r8_d0_tst_c gtest_i4_r8_d0_tst_c gtest_i8_r8_d0_tst_c: \
- FCO := $(GFORT) $(FFLAGSO) -Wno-function-elimination -ffpe-trap=zero,overflow#,invalid,underflow,denorm
+ FCO := $(GFORT) $(FFLAGSO) -Wno-function-elimination -ffpe-trap=zero,overflow,invalid#,underflow,denorm
gtest_i2_r8_d1_tst_c gtest_i4_r8_d1_tst_c gtest_i8_r8_d1_tst_c gtest_i2_r8_d0_tst_c gtest_i4_r8_d0_tst_c gtest_i8_r8_d0_tst_c: \
- FCG := $(GFORT) $(FFLAGSG) -ffpe-trap=zero,overflow#,invalid,underflow,denorm
+ FCG := $(GFORT) $(FFLAGSG) -ffpe-trap=zero,overflow,invalid#,underflow,denorm
gtest_i2_r8_d1_tst_c gtest_i4_r8_d1_tst_c gtest_i8_r8_d1_tst_c gtest_i2_r8_d0_tst_c gtest_i4_r8_d0_tst_c gtest_i8_r8_d0_tst_c: \
- FCF := $(GFORT) -Wno-function-elimination -ffpe-trap=zero $(GFF)
+ FCF := $(GFORT) -Wno-function-elimination -ffpe-trap=zero,invalid $(GFF)
gtest_i2_r16_d1_tst_c gtest_i4_r16_d1_tst_c gtest_i8_r16_d1_tst_c gtest_i2_r16_d0_tst_c gtest_i4_r16_d0_tst_c gtest_i8_r16_d0_tst_c: \
- FCO := $(GFORT) $(FFLAGSO) -Wno-function-elimination -ffpe-trap=zero,invalid,overflow,#,underflow,denorm
+ FCO := $(GFORT) $(FFLAGSO) -Wno-function-elimination -ffpe-trap=zero,overflow,invalid#,underflow,denorm
gtest_i2_r16_d1_tst_c gtest_i4_r16_d1_tst_c gtest_i8_r16_d1_tst_c gtest_i2_r16_d0_tst_c gtest_i4_r16_d0_tst_c gtest_i8_r16_d0_tst_c: \
- FCG := $(GFORT) $(FFLAGSG) -ffpe-trap=zero,invalid,overflow#,underflow,denorm
+ FCG := $(GFORT) $(FFLAGSG) -ffpe-trap=zero,overflow,invalid#,underflow,denorm
gtest_i2_r16_d1_tst_c gtest_i4_r16_d1_tst_c gtest_i8_r16_d1_tst_c gtest_i2_r16_d0_tst_c gtest_i4_r16_d0_tst_c gtest_i8_r16_d0_tst_c: \
FCF := $(GFORT) -Wno-function-elimination -ffpe-trap=zero,invalid $(GFF)
@@ -478,7 +547,8 @@ gtest_i2_r16_d1_tst_c gtest_i4_r16_d1_tst_c gtest_i8_r16_d1_tst_c gtest_i2_r16_d
# noold_ldout_format, noold_ldout_zero, noold_maxminloc, noold_unit_star, noold_xor,
# protect_parens, realloc_lhs1, recursion, std_intent_in, std_minus0_rounding1, std_mod_proc_name,
# and std_value1.
-IFORT := ifort #-stand f$(FS)
+IFORT := "$(shell command -v ifort)"
+IFORT := $(IFORT) -diag-disable=10448 # Ignore the warning about the deprecation of ifort
IFORT := $(IFORT) -warn all -debug extended -warn errors -traceback -debug-parameters all #-diag-error-limit 1
# `-auto` causes all local, non-SAVEd variables to be allocated to the run-time stack.
# `-no-heap-arrays`, which is the default for `ifort`, causes the compiler puts automatic arrays
@@ -491,41 +561,45 @@ ifeq ($(TESTDIM),small)
else
IFORT := $(IFORT) -auto -heap-arrays
endif
-IFORT := $(IFORT) -standard-semantics -assume recursion -fimplicit-none
+IFORT := $(IFORT) -standard-semantics -assume recursion
+ifeq ($(OSTYPE),LINUX)
+ IFORT := $(IFORT) -Wl,-z,noexecstack
+endif
itest_i2_r4_d1_tst_c itest_i4_r4_d1_tst_c itest_i8_r4_d1_tst_c itest_i2_r4_d0_tst_c itest_i4_r4_d0_tst_c itest_i8_r4_d0_tst_c: \
- FCO := $(IFORT) $(FFLAGSO) -fp-trap=divzero
+ FCO := $(IFORT) $(FFLAGSO)
itest_i2_r4_d1_tst_c itest_i4_r4_d1_tst_c itest_i8_r4_d1_tst_c itest_i2_r4_d0_tst_c itest_i4_r4_d0_tst_c itest_i8_r4_d0_tst_c: \
- FCG := $(IFORT) $(FFLAGSG) -check all -fp-trap=divzero
+ FCG := $(IFORT) $(FFLAGSG) -check all
itest_i2_r4_d1_tst_c itest_i4_r4_d1_tst_c itest_i8_r4_d1_tst_c itest_i2_r4_d0_tst_c itest_i4_r4_d0_tst_c itest_i8_r4_d0_tst_c: \
- FCF := $(IFORT) -fp-trap=divzero $(IFF)
+ FCF := $(IFORT) $(IFF)
itest_i2_r8_d1_tst_c itest_i4_r8_d1_tst_c itest_i8_r8_d1_tst_c itest_i2_r8_d0_tst_c itest_i4_r8_d0_tst_c itest_i8_r8_d0_tst_c: \
- FCO := $(IFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#,invalid,underflow,denormal
+ FCO := $(IFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r8_d1_tst_c itest_i4_r8_d1_tst_c itest_i8_r8_d1_tst_c itest_i2_r8_d0_tst_c itest_i4_r8_d0_tst_c itest_i8_r8_d0_tst_c: \
- FCG := $(IFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#,invalid,underflow,denormal
+ FCG := $(IFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r8_d1_tst_c itest_i4_r8_d1_tst_c itest_i8_r8_d1_tst_c itest_i2_r8_d0_tst_c itest_i4_r8_d0_tst_c itest_i8_r8_d0_tst_c: \
- FCF := $(IFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags -fp-trap=divzero,overflow $(IFF)
+ FCF := $(IFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags $(IFF)
itest_i2_r16_d1_tst_c itest_i4_r16_d1_tst_c itest_i8_r16_d1_tst_c itest_i2_r16_d0_tst_c itest_i4_r16_d0_tst_c itest_i8_r16_d0_tst_c: \
- FCO := $(IFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#invalid,underflow,denormal
+ FCO := $(IFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r16_d1_tst_c itest_i4_r16_d1_tst_c itest_i8_r16_d1_tst_c itest_i2_r16_d0_tst_c itest_i4_r16_d0_tst_c itest_i8_r16_d0_tst_c: \
- FCG := $(IFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#,invalid,underflow,denormal
+ FCG := $(IFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r16_d1_tst_c itest_i4_r16_d1_tst_c itest_i8_r16_d1_tst_c itest_i2_r16_d0_tst_c itest_i4_r16_d0_tst_c itest_i8_r16_d0_tst_c: \
- FCF := $(IFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags -fp-trap=divzero,overflow $(IFF)
+ FCF := $(IFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags $(IFF)
+
# NAG nagfor
# In massive tests, we skip the useful -mtrace option (print memory allocation trace), as its output is enormous.
-# It is necessary to use "-I $(TESTSUITE_DIR)"; otherwise, the INCLUDE lines in the test suite will not work.
-NFORT := nagfor
-NFORT := $(NFORT) -I $(TESTSUITE_DIR)
+# It is necessary to use "-I$(TESTSUITE_DIR)"; otherwise, the INCLUDE lines in the test suite will not work.
+NFORT := "$(shell command -v nagfor)"
+NFORT := $(NFORT) -fpp # As of nagfor 7.1, -fpp is needed on macOS and Windows, but not on Linux.
+NFORT := $(NFORT) -I$(TESTSUITE_DIR)
NFORT := $(NFORT) -colour=error:red,warn:magenta,info:cyan
NFORT := $(NFORT) -f$(FSTD) -recursive -info -gline -u -C -C=alias -C=dangling -C=intovf -C=undefined -kind=unique \
-Warn=constant_coindexing -Warn=subnormal #-Warn=allocation
+ifeq ($(OSTYPE),LINUX)
+ NFORT := $(NFORT) -Wl,-z,noexecstack
+endif
ntest_i2_r4_d1_tst_c ntest_i4_r4_d1_tst_c ntest_i8_r4_d1_tst_c ntest_i2_r4_d0_tst_c ntest_i4_r4_d0_tst_c ntest_i8_r4_d0_tst_c: \
FCO := $(NFORT) $(FFLAGSO) -nan -ieee=full
@@ -549,7 +623,7 @@ ntest_i2_r16_d1_tst_c ntest_i4_r16_d1_tst_c ntest_i8_r16_d1_tst_c ntest_i2_r16_d
FCF := $(NFORT) -nan -ieee=stop $(NFF)
# NVIDIA nvfortran
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2>/dev/null | sort | tail -n 1)
+VFORT := "$(shell command -v nvfortran)"
VFORT := $(VFORT) -Werror
# For nvfortran, -Mstack_arrays places automatic arrays on the stack. -Mnostack_arrays allocates
# automatic arrays on the heap. -Mnostack_arrays is the default.
@@ -558,7 +632,11 @@ ifeq ($(TESTDIM),small)
else
VFORT := $(VFORT) -Mnostack_arrays
endif
-VFORT := $(VFORT) -C -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mbounds -Mchkstk -Mchkptr -Mchkstk -traceback
+VFORT := $(VFORT) -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mchkstk -Mchkptr -traceback
+#VFORT := $(VFORT) -C -Mbounds # As of nvfortran 26.3, this will cause false-positive errors about array bounds when handling empty arrays.
+ifeq ($(OSTYPE),LINUX)
+ VFORT := $(VFORT) -Wl,-z,noexecstack
+endif
vtest_i2_r4_d1_tst_c vtest_i4_r4_d1_tst_c vtest_i8_r4_d1_tst_c vtest_i2_r4_d0_tst_c vtest_i4_r4_d0_tst_c vtest_i8_r4_d0_tst_c: \
FCO := $(VFORT) $(FFLAGSO) #-Ktrap=divz #-Kieee
@@ -568,11 +646,11 @@ vtest_i2_r4_d1_tst_c vtest_i4_r4_d1_tst_c vtest_i8_r4_d1_tst_c vtest_i2_r4_d0_ts
FCF := $(VFORT) $(VFF)
vtest_i2_r8_d1_tst_c vtest_i4_r8_d1_tst_c vtest_i8_r8_d1_tst_c vtest_i2_r8_d0_tst_c vtest_i4_r8_d0_tst_c vtest_i8_r8_d0_tst_c: \
- FCO := $(VFORT) $(FFLAGSO) -Ktrap=divz,inv#,ovf,unf,denorm -Kieee
+ FCO := $(VFORT) $(FFLAGSO) -Ktrap=divz#,inv#,ovf,unf,denorm -Kieee
vtest_i2_r8_d1_tst_c vtest_i4_r8_d1_tst_c vtest_i8_r8_d1_tst_c vtest_i2_r8_d0_tst_c vtest_i4_r8_d0_tst_c vtest_i8_r8_d0_tst_c: \
- FCG := $(VFORT) $(FFLAGSG) -Ktrap=divz,inv#,ovf,unf,denorm -Kieee
+ FCG := $(VFORT) $(FFLAGSG) -Ktrap=divz#,inv#,ovf,unf,denorm -Kieee
vtest_i2_r8_d1_tst_c vtest_i4_r8_d1_tst_c vtest_i8_r8_d1_tst_c vtest_i2_r8_d0_tst_c vtest_i4_r8_d0_tst_c vtest_i8_r8_d0_tst_c: \
- FCF := $(VFORT) -Ktrap=inv $(VFF)
+ FCF := $(VFORT) $(VFF) #-Ktrap=inv $(VFF)
vtest_i2_r16_d1_tst_c vtest_i4_r16_d1_tst_c vtest_i8_r16_d1_tst_c vtest_i2_r16_d0_tst_c vtest_i4_r16_d0_tst_c vtest_i8_r16_d0_tst_c: \
FCO := $(VFORT) $(FFLAGSO) -Ktrap=divz,ovf,inv#,unf,denorm -Kieee
@@ -582,15 +660,17 @@ vtest_i2_r16_d1_tst_c vtest_i4_r16_d1_tst_c vtest_i8_r16_d1_tst_c vtest_i2_r16_d
FCF := $(VFORT) -Ktrap=divz,ovf,inv $(VFF)
-# ARM Flang
-RFORT := $(shell find -L /opt/arm -type f -executable -name armflang -print 2>/dev/null | sort | tail -n 1)
-RFORT := $(RFORT) -pedantic-errors -Werror
+# Arm Flang
+RFORT := "$(shell command -v armflang)"
ifeq ($(TESTDIM),small)
RFORT := $(RFORT) -fstack-arrays
else
- RFORT := $(RFORT) -fno-stack-arrays
+ RFORT := $(RFORT) -fno-stack-arrays -mmlir -fdynamic-heap-array
+endif
+RFORT := $(RFORT) -std=f2018 -pedantic -Wall # As of 21.1, only f2018 is supported.
+ifeq ($(OSTYPE),LINUX)
+ RFORT := $(RFORT) -Wl,-z,noexecstack
endif
-RFORT := $(RFORT) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mrecursive -Mbounds -Mchkptr #-Kieee
rtest_i2_r4_d1_tst_c rtest_i4_r4_d1_tst_c rtest_i8_r4_d1_tst_c rtest_i2_r4_d0_tst_c rtest_i4_r4_d0_tst_c rtest_i8_r4_d0_tst_c: \
FCO := $(RFORT) $(FFLAGSO)
@@ -615,11 +695,14 @@ rtest_i2_r16_d1_tst_c rtest_i4_r16_d1_tst_c rtest_i8_r16_d1_tst_c rtest_i2_r16_d
# Oracle sunf95
-SFORT := sunf95
+SFORT := "$(shell command -v sunf95)"
ifeq ($(TESTDIM),small)
SFORT := $(SFORT) -stackvar # Force all automatic arrays to be on the stack to test whether stack overflow can occur
endif
SFORT := $(SFORT) -w3 -u -U -ansi -xcheck=%all -C
+ifeq ($(OSTYPE),LINUX)
+ SFORT := $(SFORT) -Wl,-z,noexecstack
+endif
stest_i2_r4_d1_tst_c stest_i4_r4_d1_tst_c stest_i8_r4_d1_tst_c stest_i2_r4_d0_tst_c stest_i4_r4_d0_tst_c stest_i8_r4_d0_tst_c: \
FCO := $(SFORT) $(FFLAGSO) -fnonstd -ftrap=division
@@ -643,8 +726,8 @@ stest_i2_r16_d1_tst_c stest_i4_r16_d1_tst_c stest_i8_r16_d1_tst_c stest_i2_r16_d
FCF := $(SFORT) -fnonstd $(SFF16) -ftrap=overflow,division
# Intel ifx
-XFORT := ifx #-stand f$(FS)
-XFORT := $(XFORT) -warn all -debug extended -warn errors -traceback -debug-parameters all #-diag-error-limit 1
+XFORT := "$(shell command -v ifx)" #-stand f$(FS)
+XFORT := $(XFORT) -warn all -debug full -warn errors -traceback -debug-parameters all #-diag-error-limit 1
# `-auto` causes all local, non-SAVEd variables to be allocated to the run-time stack.
# `-no-heap-arrays`, which is the default for `ifort`, causes the compiler puts automatic arrays
# and temporary arrays in the stack storage area. We enable them for small problems to verify that
@@ -653,66 +736,72 @@ XFORT := $(XFORT) -warn all -debug extended -warn errors -traceback -debug-param
# involve many automatic arrays.
# About -fp-stack-check: According to Steve Lionel (Dr. Fortran), "drop -fp-stack-check - that does
# nothing useful for you (it is for pre-SSE x87 32-bit code.)".
+# Zaikun 20250819: Even comparing with quiet NaN may trigger FPE with `ifx -fpe-all=0`.
ifeq ($(TESTDIM),small)
XFORT := $(XFORT) -auto -no-heap-arrays
else
XFORT := $(XFORT) -auto -heap-arrays
endif
-XFORT := $(XFORT) -standard-semantics -assume recursion -fimplicit-none
+XFORT := $(XFORT) -standard-semantics -assume recursion
+ifeq ($(OSTYPE),LINUX)
+ XFORT := $(XFORT) -Wl,-z,noexecstack
+endif
+# In the following, `-check all,nouninit` can be changed to `-check all` later, probably with OneAPI 2024.1. See
+# https://community.intel.com/t5/Intel-Fortran-Compiler/ifx-missing-clang-rt-msan-to-link-Fortran-program-in-debug-build/m-p/1533430
xtest_i2_r4_d1_tst_c xtest_i4_r4_d1_tst_c xtest_i8_r4_d1_tst_c xtest_i2_r4_d0_tst_c xtest_i4_r4_d0_tst_c xtest_i8_r4_d0_tst_c: \
- FCO := $(XFORT) $(FFLAGSO) -fp-trap=divzero
+ FCO := $(XFORT) $(FFLAGSO)
xtest_i2_r4_d1_tst_c xtest_i4_r4_d1_tst_c xtest_i8_r4_d1_tst_c xtest_i2_r4_d0_tst_c xtest_i4_r4_d0_tst_c xtest_i8_r4_d0_tst_c: \
- FCG := $(XFORT) $(FFLAGSG) -check all -fp-trap=divzero
+ FCG := $(XFORT) $(FFLAGSG) -check all,nouninit
xtest_i2_r4_d1_tst_c xtest_i4_r4_d1_tst_c xtest_i8_r4_d1_tst_c xtest_i2_r4_d0_tst_c xtest_i4_r4_d0_tst_c xtest_i8_r4_d0_tst_c: \
- FCF := $(XFORT) -fp-trap=divzero $(XFF)
+ FCF := $(XFORT) $(XFF)
xtest_i2_r8_d1_tst_c xtest_i4_r8_d1_tst_c xtest_i8_r8_d1_tst_c xtest_i2_r8_d0_tst_c xtest_i4_r8_d0_tst_c xtest_i8_r8_d0_tst_c: \
- FCO := $(XFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#invalid,underflow,denormal
- #-no-ftz
+ FCO := $(XFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r8_d1_tst_c xtest_i4_r8_d1_tst_c xtest_i8_r8_d1_tst_c xtest_i2_r8_d0_tst_c xtest_i4_r8_d0_tst_c xtest_i8_r8_d0_tst_c: \
- FCG := $(XFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#invalid,underflow,denormal
- #-no-ftz
+ FCG := $(XFORT) $(FFLAGSG) -check all,nouninit -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r8_d1_tst_c xtest_i4_r8_d1_tst_c xtest_i8_r8_d1_tst_c xtest_i2_r8_d0_tst_c xtest_i4_r8_d0_tst_c xtest_i8_r8_d0_tst_c: \
- FCF := $(XFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow $(XFF)
+ FCF := $(XFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags $(XFF)
xtest_i2_r16_d1_tst_c xtest_i4_r16_d1_tst_c xtest_i8_r16_d1_tst_c xtest_i2_r16_d0_tst_c xtest_i4_r16_d0_tst_c xtest_i8_r16_d0_tst_c: \
- FCO := $(XFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#invalid,underflow,denormal
- #-no-ftz
+ FCO := $(XFORT) $(FFLAGSO) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r16_d1_tst_c xtest_i4_r16_d1_tst_c xtest_i8_r16_d1_tst_c xtest_i2_r16_d0_tst_c xtest_i4_r16_d0_tst_c xtest_i8_r16_d0_tst_c: \
- FCG := $(XFORT) $(FFLAGSG) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow#invalid,underflow,denormal
- #-no-ftz
+ FCG := $(XFORT) $(FFLAGSG) -check all,nouninit -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r16_d1_tst_c xtest_i4_r16_d1_tst_c xtest_i8_r16_d1_tst_c xtest_i2_r16_d0_tst_c xtest_i4_r16_d0_tst_c xtest_i8_r16_d0_tst_c: \
- FCF := $(XFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,overflow $(XFF)
+ FCF := $(XFORT) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags $(XFF)
+
# G95
+9FORT := "$(shell command -v g95)"
# G95 supports f2003, but not higher.
-9FORT := g95
9FORT := $(9FORT) -std=f2003 -pedantic -Wall -Wextra \
-Werror=100,113,115,137,146,147,159,163 \
-Wimplicit-none -Wline-truncation -Wprecision-loss -Wunused-module-vars -Wunused-vars -Wunset-vars \
-fimplicit-none -fbounds-check -ftrace=full -freal=nan -fmodule-private
+ifeq ($(OSTYPE),LINUX)
+ 9FORT := $(9FORT) -Wl,-z,noexecstack
+endif
9test_i2_r4_d1_tst_c 9test_i4_r4_d1_tst_c 9test_i8_r4_d1_tst_c 9test_i2_r4_d0_tst_c 9test_i4_r4_d0_tst_c 9test_i8_r4_d0_tst_c: \
FCO := $(9FORT) $(FFLAGSO)
9test_i2_r4_d1_tst_c 9test_i4_r4_d1_tst_c 9test_i8_r4_d1_tst_c 9test_i2_r4_d0_tst_c 9test_i4_r4_d0_tst_c 9test_i8_r4_d0_tst_c: \
FCG := $(9FORT) $(FFLAGSG)
+9test_i2_r4_d1_tst_c 9test_i4_r4_d1_tst_c 9test_i8_r4_d1_tst_c 9test_i2_r4_d0_tst_c 9test_i4_r4_d0_tst_c 9test_i8_r4_d0_tst_c: \
+ FCF := $(9FORT) $(9FF)
9test_i2_r8_d1_tst_c 9test_i4_r8_d1_tst_c 9test_i8_r8_d1_tst_c 9test_i2_r8_d0_tst_c 9test_i4_r8_d0_tst_c 9test_i8_r8_d0_tst_c: \
FCO := $(9FORT) $(FFLAGSO)
9test_i2_r8_d1_tst_c 9test_i4_r8_d1_tst_c 9test_i8_r8_d1_tst_c 9test_i2_r8_d0_tst_c 9test_i4_r8_d0_tst_c 9test_i8_r8_d0_tst_c: \
FCG := $(9FORT) $(FFLAGSG)
+9test_i2_r8_d1_tst_c 9test_i4_r8_d1_tst_c 9test_i8_r8_d1_tst_c 9test_i2_r8_d0_tst_c 9test_i4_r8_d0_tst_c 9test_i8_r8_d0_tst_c: \
+ FCF := $(9FORT) $(9FF)
9test_i2_r16_d1_tst_c 9test_i4_r16_d1_tst_c 9test_i8_r16_d1_tst_c 9test_i2_r16_d0_tst_c 9test_i4_r16_d0_tst_c 9test_i8_r16_d0_tst_c: \
FCO := $(9FORT) $(FFLAGSO)
9test_i2_r16_d1_tst_c 9test_i4_r16_d1_tst_c 9test_i8_r16_d1_tst_c 9test_i2_r16_d0_tst_c 9test_i4_r16_d0_tst_c 9test_i8_r16_d0_tst_c: \
FCG := $(9FORT) $(FFLAGSG)
+9test_i2_r16_d1_tst_c 9test_i4_r16_d1_tst_c 9test_i8_r16_d1_tst_c 9test_i2_r16_d0_tst_c 9test_i4_r16_d0_tst_c 9test_i8_r16_d0_tst_c: \
+ FCF := $(9FORT) $(9FF)
####################################################################################################
@@ -771,6 +860,8 @@ $(TESTS_C) $(TESTS_INT_C):
# Make will ignore the rule if such files do not exist, resulting in "No rule to make ..." mistake.
# 2. `set -eu -o pipefail` instructs Bash to fail immediately and loudly when anything goes wrong
# in the piped commands.
+# 3. The calls to `execstack` are for debugging, to check whether executable stack exists when
+# `Wl,-z,noexecstack` is removed from the compiler options.
#%_tst: %_tst_c
%_tst:
@if [[ ! -f $@_c ]] ; then \
@@ -779,16 +870,23 @@ $(TESTS_C) $(TESTS_INT_C):
printf "\n$@_c exists!\n\n" ; \
fi
@mkdir -p $(LOG_DIR)
- @if echo $@ | grep -q "atest\|9test" ; then \
+ @echo 0 > "$(LOG_DIR)/$@.GDB_EXIT_CODE" # Initialize the GDB exit code file, because GDB may not be executed (e.g., test is skipped or on Mac)
+ @if echo $@ | grep -q "9test" ; then \
printf "\n$@ is skipped!\n\n" | tee -a "$(LOG_DIR)/$@.log" ; \
elif echo $@ | grep -q "_d0" && [[ $(TESTDIM) = "big" ]] ; then \
printf "\n$@ is skipped since we are testing a BIG problem!\n\n" | tee -a "$(LOG_DIR)/$@.log" ; \
else \
if [[ -n "$(FFLAGSO)" ]] ; then \
if [[ -x $@_c_$(SUFFIXO) ]] ; then \
+ if command -v execstack >/dev/null 2>&1 ; then \
+ printf "\n===> Check executable stack: " && execstack -q $@_c_$(SUFFIXO) && \
+ printf "===> Clear executable stack: " && execstack -c $@_c_$(SUFFIXO) && \
+ printf "\n===> Check executable stack again: " && execstack -q $@_c_$(SUFFIXO) \
+ | tee -a "$(LOG_DIR)/$@.log" ; \
+ fi ; \
export NVCOMPILER_TERM=trace && set -eu -o pipefail && \
printf "\n$@_$(SUFFIXO) starts.\n\n" | tee -a "$(LOG_DIR)/$@.log" && \
- $(GDB) ./$@_c_$(SUFFIXO) 2>&1 \
+ $(GDB) ./$@_c_$(SUFFIXO) $(LOG_DIR)/$@.GDB_EXIT_CODE 2>&1 \
| grep -v "Processor does not support trapping of floating-point exceptions" \
| grep -v "libthread_db" \
| tee -a "$(LOG_DIR)/$@.log" ; \
@@ -798,20 +896,33 @@ $(TESTS_C) $(TESTS_INT_C):
fi ; \
fi ; \
if [[ -n "$(FFLAGSG)" ]] ; then \
- export NVCOMPILER_TERM=trace && set -eu -o pipefail && \
- printf "\n$@_$(SUFFIXG) starts.\n\n" | tee -a "$(LOG_DIR)/$@.log" && \
- $(GDB) ./$@_c_$(SUFFIXG) 2>&1 \
- | grep -v "Processor does not support trapping of floating-point exceptions" \
- | grep -v "libthread_db" \
- | tee -a "$(LOG_DIR)/$@.log" ; \
+ if [[ -x $@_c_$(SUFFIXG) ]] ; then \
+ if command -v execstack >/dev/null 2>&1 ; then \
+ printf "\n===> Check executable stack: " && execstack -q $@_c_$(SUFFIXG) && \
+ printf "===> Clear executable stack: " && execstack -c $@_c_$(SUFFIXG) && \
+ printf "\n===> Check executable stack again: " && execstack -q $@_c_$(SUFFIXG) \
+ | tee -a "$(LOG_DIR)/$@.log" ; \
+ fi ; \
+ export NVCOMPILER_TERM=trace && set -eu -o pipefail && \
+ printf "\n$@_$(SUFFIXG) starts.\n\n" | tee -a "$(LOG_DIR)/$@.log" && \
+ $(GDB) ./$@_c_$(SUFFIXG) $(LOG_DIR)/$@.GDB_EXIT_CODE 2>&1 \
+ | grep -v "Processor does not support trapping of floating-point exceptions" \
+ | grep -v "libthread_db" \
+ | tee -a "$(LOG_DIR)/$@.log" ; \
+ else \
+ printf "\n$@_c_$(SUFFIXG) is not executable!\n\n" | tee -a "$(LOG_DIR)/$@.log" ; \
+ exit 126 ; \
+ fi ; \
fi ; \
fi
@set +o pipefail # Restore the original pipe setting
@bash $(CHCKTST) --error "$(LOG_DIR)/$@.log"
@bash $(CHCKTST) --warning "$(LOG_DIR)/$@.log"
+ @echo "gdb exit code was: " && cat "$(LOG_DIR)/$@.GDB_EXIT_CODE" && exit $$(cat "$(LOG_DIR)/$@.GDB_EXIT_CODE")
@printf "$@ ends at $(shell date +%Y.%m.%d_%H.%M.%S).\n" | tee -a "$(LOG_DIR)/$@.log"
@cat "$(LOG_DIR)/$@.log" >> "$(LOG_DIR)/$(shell echo $@ | sed "s/_.*//").log"
@rm "$(LOG_DIR)/$@.log" || :
+ @rm "$(LOG_DIR)/$@.GDB_EXIT_CODE" || :
@$(MAKE) cleanmisc
# Make the binary corresponding to %_tst_c, but do not execute it.
@@ -824,11 +935,14 @@ $(TESTS_C) $(TESTS_INT_C):
$(eval RP := $(shell expr 8 \* $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//")))
$(eval QPAVLB := $(shell expr $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//") / 16))
$(eval EXTRA_FLAGS := -g -DPRIMA_DEBUGGING=$(DBG) -DPRIMA_AGGRESSIVE_OPTIONS=$(AGGRESSIVE) \
- -DPRIMA_INTEGER_KIND=$(IK) -DPRIMA_REAL_PRECISION=$(RP) -DPRIMA_QP_AVAILABLE=$(QPAVLB))
- $(eval EXTRA_FLAGS := $(shell if ! echo $@ | grep -q 'atest'; then \
- echo "$(EXTRA_FLAGS) -DPRIMA_TESTDIM=\"'$(TESTDIM)'\""; \
- else echo "$(EXTRA_FLAGS)"; fi))
+ -DPRIMA_INTEGER_KIND=$(IK) -DPRIMA_REAL_PRECISION=$(RP) -DPRIMA_QP_AVAILABLE=$(QPAVLB) -DPRIMA_TESTDIM=\"'$(TESTDIM)'\")
+ $(eval EXTRA_FLAGS := $(shell if [[ -z "$(TESTSEED)" ]] ; then echo "$(EXTRA_FLAGS)"; else echo "$(EXTRA_FLAGS) -DPRIMA_TESTSEED=$(TESTSEED)"; fi))
$(eval SRC := $(shell if echo $@ | grep -q "9test"; then echo $(SRC_NO_TEST) ; else echo $(SRC) ; fi))
+ $(eval FC := $(shell cut -d' ' -f1 <<< "$(FCO)"))
+ @echo "======> OSTYPE = " $(OSTYPE)
+ @echo "Compiler: $(FC)"
+ @$(FC) --version 2>&1 | grep -v "\-\-version\|usage" || true # Print the compiler version, but do not fail if the compiler does not support `--version`
+ @echo ""
(if echo $@ | grep -q '9test'; then \
if [[ -n '$(FFLAGSO)' ]] ; then \
if [[ '$(FFLAGSO)' = '-fast' ]] ; then \
@@ -857,9 +971,12 @@ $(TESTS_C) $(TESTS_INT_C):
fi ; \
fi) 2>&1 \
| grep -v "^[[:blank:]]*$$" \
- | grep -v "/usr/bin/ld: warning: libpgmath.so.0d, needed by /home/zaikunzhang/local/flang/lib/libflang.so, not found" \
+ | grep -v "Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking" \
+ | grep -v "ld: warning: no platform load command found in.*assuming: macOS" \
+ | grep -v "/usr/bin/ld: warning: libpgmath.so.0d, needed by .*libflang.so, not found" \
| grep -v "Warning (165): Implicit interface 'calfun' called at (1)" \
| grep -v "Warning (165): Implicit interface 'calcfc' called at (1)" \
+ | grep -v "Warning (165): Implicit interface 'callback_fcn' called at (1)" \
| grep -v "Warning (102): MODULE PROCEDURE 'inv' USEd at (1) is not referenced" \
| grep -v "Warning (102): MODULE PROCEDURE 'calfun' USEd at (1) is not referenced" \
| grep -v "Warning (102): MODULE PROCEDURE 'calcfc' USEd at (1) is not referenced" \
@@ -867,17 +984,17 @@ $(TESTS_C) $(TESTS_INT_C):
| grep -v "Warning (102): MODULE PROCEDURE 'redrho' USEd at (1) is not referenced" \
| grep -v "common/linalg.f90:[0-9]*:.*pivot.*may be used uninitialized" \
| grep -v "common/linalg.f90:[0-9]*:.*rank.*may be used uninitialized" \
- | grep -v "common/preproc.f90:22:.*min_maxfun.*may be used uninitialized" \
- | grep -v "common/preproc.f90:22:.*unit_memo.*may be used uninitialized" \
+ | grep -v "common/preproc.f90:[0-9]*:.*min_maxfun.*may be used uninitialized" \
+ | grep -v "common/preproc.f90:[0-9]*:.*unit_memo.*may be used uninitialized" \
| grep -v "Compiling file\|Module subprogram name" \
| grep -v "An allocatable array function '.*' is an extended feature" \
- | grep -v "An allocatable dummy array\ '.*' is an extended feature" \
+ | grep -v "An allocatable dummy array '.*' is an extended feature" \
| grep -v "WARNING -- When --chk x is specified, it must be used to compile all source files of a program" \
| grep -v "0 Errors, 0 Warnings" \
| grep -v "Encountered 0 errors, 0 warnings, [0-9]* information.* in file" \
- | grep -v "Absoft Pro Fortran.*: 0 Errors, 0 Warnings, 0 Other messages," \
- | grep -v "Absoft ANSI 1610:" \
| grep -v "after the END INTERFACE keywords is only legal in Fortran 95 and beyond" \
+ | grep -v "Extension.*Stop-code is not constant" \
+ | grep -v "Option warning: Due to MacOS/Silicon limitations, this may not work reliably" \
| grep -v "NAG Fortran Compiler Release [0-9]*.* Build [0-9]*" \
| grep -v "Questionable: ./lincoa/geometry.f90, line [0-9]*: Variable RSTAT set but never referenced" \
| grep -v "Expression in IF construct is constant" \
@@ -896,8 +1013,6 @@ $(TESTS_C) $(TESTS_INT_C):
| grep -v "Fortran requires all intrinsic procedure actual arguments to be type integer or character for restricted expressions" \
| grep -v "Fortran 90 requires all intrinsic procedure actual arguments to be type integer or character for restricted expressions" \
| grep -v "Intrinsics which return a data type other than integer are not allowed in specification expressions" \
- | grep -v "Absoft ANSI 274:" \
- | grep -v "Absoft ANSI 1392:" \
| grep -v "remark #5415: Feature not yet implemented" \
| grep -v "interpolation error" \
| grep -v "In file ./common/evaluate.f90:134" \
@@ -934,7 +1049,14 @@ $(TESTS_C) $(TESTS_INT_C):
| grep -v "\.position.* may be used uninitialized" \
| grep -v "\.sformat.* may be used uninitialized" \
| grep -v "\.noise_type_loc.* may be used uninitialized" \
+ | grep -v "ld: warning: -keep_dwarf_unwind is obsolete" \
+ | grep -v "ld: warning: ignoring duplicate libraries:.*libsvml.a" \
| grep -v "ipo: warning #11021: unresolved __ehdr_start" \
+ | grep -v "\[-Wgcc-install-dir-libstdcxx\]" \
+ | grep -v "ld: warning: ignoring duplicate libraries: '-lSystem'" \
+ | grep -v "ld: warning: reexported library with install name" \
+ | grep -v "warning: future releases of the clang compiler will prefer GCC installations containing libstdc" \
+ | grep -v "warning: argument unused during compilation" \
| tee -a "$(LOG_DIR)/$@.log"
@bash $(CHCKTST) --error "$(LOG_DIR)/$@.log"
@bash $(CHCKTST) --warning "$(LOG_DIR)/$@.log"
@@ -953,7 +1075,7 @@ source_%: $(SRC_DIRS)
@printf "\nMaking a copy of the source code for the test.\n"
@for DIR in $(SRC_DIRS); do cp -r "$$DIR" ./; done
@for SRC in $(DRIVER_SRC); do cp $(DRIVER_DIR)/"$$SRC" ./; done
- @find ./ -type f \( \
+ @find ./ \( \
-name "*.mod" \
-o -name "*.o" \
-o -name "*.dbg" \
@@ -963,47 +1085,35 @@ source_%: $(SRC_DIRS)
-o -name "*.stb" \
-o -name "*.out" \
-o -name "*__genmod.f90" \
+ -o -name "flint" \
+ -o -name "mlint" \
\) -exec rm {} \; # Cleaning up; important!!!
- # Flang 15.0.3, Arm Fortran Compiler version 22.1, and AOCC 4.1.0 are buggy
- # concerning 0-dimensional arrays, and they report false positive out-bound indices. Thus
- # preprocessing by frdsrc is needed. See
+ # Huawei Bisheng Flang 2.1.0 and AOCC 5.1 Flang are buggy concerning 0-dimensional arrays, and
+ # they report false positive out-bound indices. Thus preprocessing by dsrc is needed. See
# https://github.com/flang-compiler/flang/issues/1238
# Check it again when new versions are available. Make a test using
# https://github.com/zequipe/test_compiler/blob/master/test_solve.f90
- @if echo $@ | grep -q "ftest\|rtest\|dtest"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../frdsrc ./ ; \
+ @printf "\nPreparing the source code for $@.\n"
+ @if echo $@ | grep -q "gtest" && echo $(FFLAGS) | grep -q "fast"; then \
+ bash $(TOOLS_DIR)/gsrc ./ ; \
+ fi
+ @if echo $@ | grep -q "ftest\|mtest\|rtest"; then \
+ bash $(TOOLS_DIR)/flsrc ./ ; \
+ fi
+ @if echo $@ | grep -q "vtest" && echo $(FFLAGS) | grep -q "fast"; then \
+ bash $(TOOLS_DIR)/vsrc ./ ; \
+ fi
+ @if echo $@ | grep -q "dtest"; then \
+ bash $(TOOLS_DIR)/dsrc ./ ; \
fi
@if echo $@ | grep -q "stest"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../sunsrc ./ ; \
+ bash $(TOOLS_DIR)/sunsrc ./ ; \
fi
@if echo $@ | grep -q "9test"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../9src ./ ; \
- fi
- @if echo $@ | grep -q "atest\|9test"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../a9src ./ ; \
+ bash $(TOOLS_DIR)/9src ./ ; \
fi
@printf "Done.\n"
-# Adapt the header file for the test.
-# Zaikun 20230411: Instead of modifying the header file, we should use the -D option of the
-# preprocessors (cpp or fpp). See the definition of $(EXTRA_FLAGS) for details.
-#header_%: IK = $(shell expr 8 \* $$(echo $@ | sed "s/.*_i//" | sed "s/_.*//"))
-#header_%: RP = $(shell expr 8 \* $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//"))
-#header_%: DBG = $(shell echo $@ | sed "s/.*_d//" | sed "s/_.*//")
-#header_%: QPAVLB = $(shell expr $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//") / 16)
-#header_%: source_%
-# @printf "\nPreparing the header file for the test.\n"
-# $(SEDI) "s|^#define PRIMA_QP_AVAILABLE [0-9]*|#define PRIMA_QP_AVAILABLE $(QPAVLB)|" $(HEADERS)
-# $(SEDI) "s|^#define PRIMA_INTEGER_KIND [0-9]*|#define PRIMA_INTEGER_KIND $(IK)|" $(HEADERS)
-# $(SEDI) "s|^#define PRIMA_REAL_PRECISION [0-9]*|#define PRIMA_REAL_PRECISION $(RP)|" $(HEADERS)
-# $(SEDI) "s|^#define PRIMA_DEBUGGING [0-9]*|#define PRIMA_DEBUGGING $(DBG)|" $(HEADERS)
-# $(SEDI) "s|^#define PRIMA_AGGRESSIVE_OPTIONS [0-9]*|#define PRIMA_AGGRESSIVE_OPTIONS $(AGGRESSIVE)|" $(HEADERS)
-# @printf "Done.\n"
-
####################################################################################################
# Cleaning up.
diff --git a/fortran/tests/makefiles/Makefile.lincoa b/fortran/tests/makefiles/Makefile.lincoa
index 8f7d38f463..044720f947 100644
--- a/fortran/tests/makefiles/Makefile.lincoa
+++ b/fortran/tests/makefiles/Makefile.lincoa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/fortran/tests/makefiles/Makefile.newuoa b/fortran/tests/makefiles/Makefile.newuoa
index 21b997723f..590e94296e 100644
--- a/fortran/tests/makefiles/Makefile.newuoa
+++ b/fortran/tests/makefiles/Makefile.newuoa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/fortran/tests/makefiles/Makefile.uobyqa b/fortran/tests/makefiles/Makefile.uobyqa
index ea6d432fb6..76af8ad682 100644
--- a/fortran/tests/makefiles/Makefile.uobyqa
+++ b/fortran/tests/makefiles/Makefile.uobyqa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/fortran/tests/test.F90 b/fortran/tests/test.F90
index 261fb5c7f3..c7486ca868 100644
--- a/fortran/tests/test.F90
+++ b/fortran/tests/test.F90
@@ -6,7 +6,7 @@ program test
!
! Started: September 2021
!
-! Last Modified: Sunday, June 25, 2023 AM09:10:43
+! Last Modified: Sun 17 Aug 2025 07:48:05 AM CST
!--------------------------------------------------------------------------------------------------!
#if !defined PRIMA_TESTDIM
@@ -17,11 +17,16 @@ program test
use, non_intrinsic :: test_solver_mod, only : test_solver
implicit none
-integer :: yw
+integer :: seed
-! YW is the random seed for the tests. It is altered weekly to test the solvers as much as possible.
-yw = 100 * modulo(year(), 100) + week()
-print *, 'The random seed is', yw
+! SEED is the random seed for the tests.
+! The default value of SEED below is altered weekly to test the solvers as much as possible.
+seed = 100 * modulo(year(), 100) + week()
+#if defined PRIMA_TESTSEED
+seed = PRIMA_TESTSEED
+#endif
+
+print *, 'The random seed is', seed
! PRIMA_TESTDIM is the dimension of the test problem. It can be 'small', 'big', or 'large'.
! When it is 'small', then `test_solver` also accepts `mindim`, `maxdim`, and `dimstride`
@@ -29,6 +34,6 @@ program test
! the test is hard coded in `test_*.f90` for each solver.
print *, 'The test dimension is ', PRIMA_TESTDIM
-call test_solver(randseed=yw, testdim=PRIMA_TESTDIM)
+call test_solver(randseed=seed, testdim=PRIMA_TESTDIM)
end program test
diff --git a/fortran/tests/test_bobyqa.f90 b/fortran/tests/test_bobyqa.f90
index bf69bb6541..4bcf756441 100644
--- a/fortran/tests/test_bobyqa.f90
+++ b/fortran/tests/test_bobyqa.f90
@@ -1,3 +1,54 @@
+module recursive_mod
+implicit none
+private
+public :: recursive_fun2
+
+contains
+
+subroutine chrosen(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+integer :: n
+real(RP), parameter :: alpha = 4.0_RP
+n = size(x)
+f = sum((x(1:n - 1) - 1.0_RP)**2 + alpha * (x(2:n) - x(1:n - 1)**2)**2);
+end subroutine chrosen
+
+subroutine recursive_fun1(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: bobyqa_mod, only : bobyqa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: xl(size(x))
+real(RP) :: xu(size(x))
+real(RP) :: x_loc(size(x))
+xl = -2.0_RP
+xu = 2.0_RP
+x_loc = x
+call bobyqa(chrosen, x_loc, f, xl=xl, xu=xu)
+end subroutine recursive_fun1
+
+subroutine recursive_fun2(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: bobyqa_mod, only : bobyqa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: xl(size(x))
+real(RP) :: xu(size(x))
+real(RP) :: x_loc(size(x))
+xl = -2.0_RP
+xu = 2.0_RP
+x_loc = x
+call bobyqa(recursive_fun1, x_loc, f, xl=xl, xu=xu)
+end subroutine recursive_fun2
+
+end module recursive_mod
+
+
module test_solver_mod
!--------------------------------------------------------------------------------------------------!
! This module tests BOBYQA on a few simple problems.
@@ -6,7 +57,7 @@ module test_solver_mod
!
! Started: September 2021
!
-! Last Modified: Tuesday, July 04, 2023 AM01:02:33
+! Last Modified: Sat 14 Feb 2026 10:14:52 AM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -17,7 +68,7 @@ module test_solver_mod
contains
-subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdim)
+subroutine test_solver(probs, mindim, maxdim, nrand, randseed, testdim)
use, non_intrinsic :: bobyqa_mod, only : bobyqa
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, TEN, ZERO, REALMAX
@@ -25,9 +76,10 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
use, non_intrinsic :: infnan_mod, only : is_neginf
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: noise_mod, only : noisy, noisy_calfun, orig_calfun
-use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, DIMSTRIDE_DFT, NRAND_DFT, RANDSEED_DFT
+use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, RANDSEED_DFT
use, non_intrinsic :: prob_mod, only : PNLEN, PROB_T, construct, destruct
use, non_intrinsic :: rand_mod, only : setseed, rand, randn
+use, non_intrinsic :: recursive_mod, only : recursive_fun2
use, non_intrinsic :: string_mod, only : strip, istr
implicit none
@@ -35,7 +87,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN), intent(in), optional :: probs(:)
integer(IK), intent(in), optional :: mindim
integer(IK), intent(in), optional :: maxdim
-integer(IK), intent(in), optional :: dimstride
integer(IK), intent(in), optional :: nrand
integer, intent(in), optional :: randseed
character(len=*), intent(in), optional :: testdim
@@ -47,10 +98,7 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN) :: probname
character(len=PNLEN) :: probs_loc(100) ! Maximal number of problems to test: 100
integer :: randseed_loc
-integer :: rseed
-integer(IK) :: dim_list(100) ! Maximal number of dimensions to test: 100
-integer(IK) :: dimstride_loc
-integer(IK) :: idim
+integer :: seed
integer(IK) :: iprint
integer(IK) :: iprob
integer(IK) :: irand
@@ -59,24 +107,27 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
integer(IK) :: maxhist
integer(IK) :: mindim_loc
integer(IK) :: n
-integer(IK) :: ndim
integer(IK) :: nnpt
integer(IK) :: nprobs
integer(IK) :: npt
-integer(IK) :: npt_list(10)
+integer(IK) :: npt_list(100) ! Maximal number of prescribed NPT to test: 100
integer(IK) :: nrand_loc
-integer(IK), parameter :: bign = 300_IK
-integer(IK), parameter :: largen = 1600_IK
+integer(IK), parameter :: bign = 300_IK ! Dimension of the big problems
+integer(IK), parameter :: largen = 1600_IK ! Dimension of the large problems
real(RP) :: f
real(RP) :: f_unc
real(RP) :: ftarget
real(RP) :: rhobeg
real(RP) :: rhoend
real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: fhist_unc(:)
real(RP), allocatable :: x(:)
real(RP), allocatable :: x0(:)
real(RP), allocatable :: x_unc(:)
real(RP), allocatable :: xhist(:, :)
+real(RP), allocatable :: xhist_unc(:, :)
+real(RP), allocatable :: xl(:)
+real(RP), allocatable :: xu(:)
type(PROB_T) :: prob
if (present(probs)) then
@@ -99,12 +150,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
maxdim_loc = MAXDIM_DFT
end if
-if (present(dimstride)) then
- dimstride_loc = dimstride
-else
- dimstride_loc = DIMSTRIDE_DFT
-end if
-
if (present(nrand)) then
nrand_loc = nrand
else
@@ -124,25 +169,21 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end if
-! Test the big problem
+! Test the big/large problem
if (testdim_loc == 'big' .or. testdim_loc == 'large') then
probname = bigprob
n = merge(bign, largen, testdim_loc == 'big')
call construct(prob, probname, n)
do irand = 1, 1 ! The test is expensive
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- npt = max(n + 2_IK, int(5.0 * rand() * real(n, RP), kind(npt)))
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + n + randseed_loc)
+ call setseed(seed)
+ npt = int(4.0 * rand() * real(n, RP), kind(npt))
iprint = 2_IK
- if (int(npt) + 2000 > huge(0_IK)) then
- maxfun = huge(0_IK)
- else
- maxfun = npt + int(2000.0_RP * rand(), IK)
- end if
+ maxfun = npt + int(min(1000.0_RP, real(2_IK*npt, RP)) * rand(), IK)
maxhist = maxfun
- ftarget = -REALMAX
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 6.0_RP))
+ rhoend = min(0.1_RP * rhobeg, max(1.0E-3_RP, rhobeg * 10.0_RP**(-3.0_RP * rand())))
call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
x = noisy(prob % x0)
orig_calfun => prob % calfun
@@ -162,92 +203,108 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
else
do iprob = 1, nprobs
+
probname = probs_loc(iprob)
- ndim = (maxdim_loc - mindim_loc) / dimstride_loc + 1_IK
- dim_list(1:ndim) = mindim_loc + dimstride_loc*[(idim - 1_IK, idim=1_IK, ndim)]
- if (strip(probname) == 'ptinsq') then
- dim_list(1:ndim) = int(ceiling(real(dim_list(1:ndim)) / 2.0) * 2, IK) ! Must be even
- end if
- do idim = 1, ndim
- n = dim_list(idim)
- call construct(prob, probname, n) ! Construct the testing problem.
+
+ do irand = 1, max(0_IK, nrand_loc) + 1_IK
+ ! Initialize the random seed using IRAND, RP, and RANDSEED_LOC. Do not include IK so
+ ! that the results for different IK are the same.
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + randseed_loc)
+ call setseed(seed)
+
+ ! Set the problem dimension N to a random value in the range [MINDIM, MAXDIM].
+ n = mindim_loc + floor(rand() * real(maxdim_loc - mindim_loc + 1_IK, RP), kind(n))
+ if (strip(probname) == 'ptinsq') then
+ n = 2_IK * int(ceiling(real(n) / 2.0), IK) ! Ensure that N is even for PTINSQ
+ end if
+
+ ! Construct the testing problem.
+ call construct(prob, probname, n)
! NPT_LIST defines some extreme values of NPT.
- nnpt = 10
- npt_list(1:nnpt) = [1_IK, &
+ nnpt = 11
+ npt_list(1:nnpt) = [0_IK, 1_IK, &
& n + 1_IK, n + 2_IK, n + 3_IK, &
& 2_IK * n, 2_IK * n + 1_IK, 2_IK * n + 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK - 1_IK, (n + 1_IK) * (n + 2_IK) / 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK + 1_IK]
- do irand = 1, nnpt + max(0_IK, nrand_loc)
- ! Initialize the random seed using N, IRAND, RP, and RANDSEED_LOC. Do not include IK so
- ! that the results for different IK are the same.
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- if (irand <= nnpt) then
- npt = npt_list(irand)
- else
- npt = int(TEN * rand() * real(n, RP), kind(npt))
- end if
- if (rand() <= 0.2) then
- npt = 0
- end if
- iprint = int(sign(min(3.0_RP, 1.5_RP * abs(randn())), randn()), kind(iprint))
- maxfun = int(2.0E2_RP * rand() * real(n, RP), kind(maxfun))
- if (rand() <= 0.2) then
- maxfun = 0
- end if
- maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
- if (rand() <= 0.2) then
- maxhist = -maxhist
- end if
- if (rand() <= 0.2) then
- ftarget = -TEN**abs(TWO * randn())
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ftarget = REALMAX
- else
- ftarget = -REALMAX
- end if
-
- rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 5.0_RP))
- if (rand() <= 0.2) then
- rhoend = rhobeg
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- rhobeg = ZERO
- end if
- call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
- x0 = noisy(prob % x0)
- orig_calfun => prob % calfun
-
- print '(/A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, ', Random test ', irand
-
- call safealloc(x, n)
- x = x0
- call bobyqa(noisy_calfun, x, f, xl=prob % xl, xu=prob % xu, npt=npt, &
- & rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, fhist=fhist, &
- & xhist=xhist, ftarget=ftarget, iprint=iprint)
-
- if (prob % probtype == 'u') then ! Run the test without constraints
- call safealloc(x_unc, n)
- x_unc = x0
- call bobyqa(noisy_calfun, x_unc, f_unc, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
- & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, iprint=iprint)
- call validate(all(abs(x - x_unc) <= 0), 'X == X_UNC', srname)
- call validate(abs(f - f_unc) <= 0 .or. (is_neginf(f) .and. is_neginf(f_unc)), 'F == F_UNC', srname)
- end if
-
- deallocate (x)
- nullify (orig_calfun)
- end do
-
- ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
- call destruct(prob) ! Destruct the testing problem.
+
+ if (irand <= 1) then
+ npt = npt_list(ceiling(real(nnpt, RP)*rand())) ! Randomly select NPT from NPT_LIST
+ else
+ npt = int(TEN * rand() * real(n, RP), kind(npt))
+ end if
+
+ iprint = int(randn(), kind(iprint))
+ maxfun = int(1.0E2_RP * rand() * real(n, RP), kind(maxfun))
+ maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
+ if (rand() <= 0.1) then
+ maxhist = -maxhist
+ end if
+ if (rand() <= 0.8) then
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
+ elseif (rand() <= 0.5) then ! Note that the value of rand() changes.
+ ftarget = REALMAX
+ else
+ ftarget = -REALMAX
+ end if
+
+ rhobeg = noisy(prob % Delta0)
+ rhoend = max(1.0E-5_RP, rhobeg * 10.0_RP**(6.0_RP * rand() - 5.0_RP))
+ if (rand() <= 0.1) then
+ rhoend = rhobeg
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ rhobeg = ZERO
+ end if
+
+ call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
+ x0 = noisy(prob % x0)
+ orig_calfun => prob % calfun
+
+ print '(/A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, ', Random test ', irand
+
+ call safealloc(x, n)
+ x = x0
+ call bobyqa(noisy_calfun, x, f, xl=prob % xl, xu=prob % xu, npt=npt, &
+ & rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, fhist=fhist, &
+ & xhist=xhist, ftarget=ftarget, iprint=iprint)
+
+ if (prob % probtype == 'u') then ! Run the test without constraints
+ call safealloc(x_unc, n)
+ x_unc = x0
+ call bobyqa(noisy_calfun, x_unc, f_unc, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
+ & maxhist=maxhist, fhist=fhist_unc, xhist=xhist_unc, ftarget=ftarget, iprint=iprint)
+ call validate(all(abs(x - x_unc) <= 0), 'X == X_UNC', srname)
+ call validate(abs(f - f_unc) <= 0 .or. (is_neginf(f) .and. is_neginf(f_unc)), 'F == F_UNC', srname)
+ end if
+
+ deallocate (x)
+ nullify (orig_calfun)
end do
+
+ ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
+ call destruct(prob) ! Destruct the testing problem.
end do
-end if
+ ! Test recursive call.
+ ! The depth of the recursion is 2. The first recursion is in RECURSIVE_FUN1, and the second is in
+ ! RECURSIVE_FUN2. RECURSIVE_FUN1(Y) is defined by minimizing the CHROSEN function subject to
+ ! -2 <= X <= 2 with Y being the starting point. RECURSIVE_FUN2(Y) is defined by RECURSIVE_FUN1
+ ! in a similar way. Note that RECURSIVE_FUN1 is essentially a constant function.
+ n = 3_IK
+ print '(/A, I0)', 'Testing recursive call of '//solname//' on a problem with N = ', n
+ call safealloc(xl, n)
+ xl = -2.0_RP
+ call safealloc(xu, n)
+ xu = 2.0_RP
+ call safealloc(x, n)
+ x = randn(n)
+ call bobyqa(recursive_fun2, x, f, xl=xl, xu=xu, iprint=2_IK)
+ deallocate (xl, xu, x)
+
+end if
+
end subroutine test_solver
diff --git a/fortran/tests/test_cobyla.f90 b/fortran/tests/test_cobyla.f90
index c7e5ccbea2..2d79ea6250 100644
--- a/fortran/tests/test_cobyla.f90
+++ b/fortran/tests/test_cobyla.f90
@@ -1,3 +1,50 @@
+module recursive_mod
+implicit none
+private
+public :: recursive_fun2
+
+contains
+
+subroutine chrosen(x, f, constr)
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP), intent(out) :: constr(:)
+integer :: n
+real(RP), parameter :: alpha = 4.0_RP
+n = size(x)
+f = sum((x(1:n - 1) - 1.0_RP)**2 + alpha * (x(2:n) - x(1:n - 1)**2)**2);
+constr = x(1:n - 1) - x(2:n)**2;
+end subroutine chrosen
+
+subroutine recursive_fun1(x, f, constr)
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: cobyla_mod, only : cobyla
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP), intent(out) :: constr(:)
+real(RP) :: x_loc(size(x))
+x_loc = x
+call cobyla(chrosen, int(size(x) - 1, IK), x_loc, f, nlconstr=constr)
+end subroutine recursive_fun1
+
+subroutine recursive_fun2(x, f, constr)
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: cobyla_mod, only : cobyla
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP), intent(out) :: constr(:)
+real(RP) :: x_loc(size(x))
+x_loc = x
+call cobyla(recursive_fun1, int(size(x) - 1, IK), x_loc, f, nlconstr=constr)
+end subroutine recursive_fun2
+
+end module recursive_mod
+
+
module test_solver_mod
!--------------------------------------------------------------------------------------------------!
! This module tests COBYLA on a few simple problems.
@@ -6,7 +53,7 @@ module test_solver_mod
!
! Started: September 2021
!
-! Last Modified: Monday, August 14, 2023 PM10:50:08
+! Last Modified: Sat 14 Feb 2026 10:48:16 AM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -17,7 +64,7 @@ module test_solver_mod
contains
-subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdim)
+subroutine test_solver(probs, mindim, maxdim, nrand, randseed, testdim)
use, non_intrinsic :: cobyla_mod, only : cobyla
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, TEN, ZERO, REALMAX
@@ -25,9 +72,10 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
use, non_intrinsic :: infnan_mod, only : is_neginf
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: noise_mod, only : noisy, noisy_calcfc, orig_calcfc
-use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, DIMSTRIDE_DFT, NRAND_DFT, RANDSEED_DFT
+use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, RANDSEED_DFT
use, non_intrinsic :: prob_mod, only : PNLEN, PROB_T, construct, destruct
use, non_intrinsic :: rand_mod, only : setseed, rand, randn
+use, non_intrinsic :: recursive_mod, only : recursive_fun2
use, non_intrinsic :: string_mod, only : strip, istr
implicit none
@@ -35,7 +83,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN), intent(in), optional :: probs(:)
integer(IK), intent(in), optional :: mindim
integer(IK), intent(in), optional :: maxdim
-integer(IK), intent(in), optional :: dimstride
integer(IK), intent(in), optional :: nrand
integer, intent(in), optional :: randseed
character(len=*), intent(in), optional :: testdim
@@ -48,10 +95,7 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN) :: probname
character(len=PNLEN) :: probs_loc(100) ! Maximal number of problems to test: 100
integer :: randseed_loc
-integer :: rseed
-integer(IK) :: dim_list(100) ! Maximal number of dimensions to test: 100
-integer(IK) :: dimstride_loc
-integer(IK) :: idim
+integer :: seed
integer(IK) :: iprint
integer(IK) :: iprob
integer(IK) :: irand
@@ -62,11 +106,10 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
integer(IK) :: maxhist
integer(IK) :: mindim_loc
integer(IK) :: n
-integer(IK) :: ndim
integer(IK) :: nprobs
integer(IK) :: nrand_loc
-integer(IK), parameter :: bign = 80_IK
-integer(IK), parameter :: largen = 1000_IK
+integer(IK), parameter :: bign = 80_IK ! Dimension of the big problems
+integer(IK), parameter :: largen = 1000_IK ! Dimension of the large problems
real(RP) :: cstrv
real(RP) :: ctol
real(RP) :: f
@@ -74,18 +117,20 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
real(RP) :: ftarget
real(RP) :: rhobeg
real(RP) :: rhoend
-real(RP), allocatable :: Aineq(:, :)
-real(RP), allocatable :: bineq(:)
real(RP), allocatable :: Aeq(:, :)
+real(RP), allocatable :: Aineq(:, :)
real(RP), allocatable :: beq(:)
+real(RP), allocatable :: bineq(:)
real(RP), allocatable :: chist(:)
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: fhist_alt(:)
real(RP), allocatable :: nlchist(:, :)
real(RP), allocatable :: nlconstr(:)
-real(RP), allocatable :: fhist(:)
real(RP), allocatable :: x(:)
real(RP), allocatable :: x0(:)
real(RP), allocatable :: x_alt(:)
real(RP), allocatable :: xhist(:, :)
+real(RP), allocatable :: xhist_alt(:, :)
real(RP), allocatable :: xl(:)
real(RP), allocatable :: xu(:)
type(PROB_T) :: prob
@@ -113,16 +158,10 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
maxdim_loc = MAXDIM_DFT
end if
-if (present(dimstride)) then
- dimstride_loc = dimstride
-else
- dimstride_loc = DIMSTRIDE_DFT
-end if
-
if (present(nrand)) then
nrand_loc = nrand
else
- nrand_loc = NRAND_DFT * 5_IK ! More random tests than default since we cannot vary NPT as other solvers.
+ nrand_loc = NRAND_DFT
end if
if (present(randseed)) then
@@ -138,22 +177,18 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end if
-! Test the big problem
+! Test the big/large problem
if (testdim_loc == 'big' .or. testdim_loc == 'large') then
probname = bigprob
n = merge(bign, largen, testdim_loc == 'big')
do irand = 1, 1 ! The test is expensive
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- m = int(min(int(10.0_RP * rand() * real(n, RP)), 10**min(range(0), range(0_IK))), IK)
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + n + randseed_loc)
+ call setseed(seed)
+ m = int(min(int(3.0_RP * rand() * real(n, RP)), 10**min(range(0), range(0_IK))), IK)
m = min(m, floor(real(huge(m)) / 8.0, IK) - n - 2_IK) ! Avoid integer overflow when calculating UNIT_MEMO in PREPROC/HISTORY
call construct(prob, probname, n, m)
iprint = 2_IK
- if (int(n) + 2000 > huge(0_IK)) then
- maxfun = huge(0_IK)
- else
- maxfun = n + int(2000.0_RP * rand(), IK)
- end if
+ maxfun = n + int(min(1000.0_RP, real(4_IK*n, RP)) * rand(), IK)
maxhist = maxfun
maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
if (rand() <= 0.5) then
@@ -161,9 +196,9 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
else
ctol = ZERO
end if
- ftarget = -REALMAX
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 6.0_RP))
+ rhoend = min(0.1_RP * rhobeg, max(1.0E-3_RP, rhobeg * 10.0_RP**(-3.0_RP * rand())))
call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
x = noisy(prob % x0)
orig_calcfc => prob % calcfc
@@ -200,130 +235,145 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
else
do iprob = 1, nprobs
+
probname = probs_loc(iprob)
- if (any(probname == fix_dim_probs)) then
- call construct(prob, probname) ! Construct the testing problem.
- ndim = 1
- dim_list(1) = prob % n
- else
- ndim = (maxdim_loc - mindim_loc) / dimstride_loc + 1_IK
- dim_list(1:ndim) = mindim_loc + dimstride_loc*[(idim - 1_IK, idim=1_IK, ndim)]
- end if
- do idim = 1, ndim
+
+ do irand = 1, max(0_IK, nrand_loc) + 1_IK
+ ! Initialize the random seed using IRAND, RP, and RANDSEED_LOC. Do not include IK so
+ ! that the results for different IK are the same.
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + randseed_loc)
+ call setseed(seed)
+
+ ! Set the problem dimension N to a random value in the range [MINDIM, MAXDIM].
+ n = mindim_loc + floor(rand() * real(maxdim_loc - mindim_loc + 1_IK, RP), kind(n))
+ if (strip(probname) == 'ptinsq' .and. modulo(n, 2_IK) == 1) then
+ n = n + 1_IK ! Must be even
+ end if
+
+ ! Construct the testing problem.
if (any(probname == fix_dim_probs)) then
call construct(prob, probname)
else
- call construct(prob, probname, n=dim_list(idim))
+ call construct(prob, probname, n=n)
end if
m = prob % m
n = prob % n
- do irand = 1, max(1_IK, nrand_loc)
- ! Initialize the random seed using N, IRAND, RP, and RANDSEED_LOC. Do not include IK so
- ! that the results for different IK are the same.
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- iprint = int(sign(min(3.0_RP, 1.5_RP * abs(randn())), randn()), kind(iprint))
- maxfun = int(2.0E2_RP * rand() * real(n, RP), kind(maxfun))
- if (rand() <= 0.2) then
- maxfun = 0
- end if
- maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
- if (rand() <= 0.2) then
- maxhist = -maxhist
- end if
- maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
- if (rand() <= 0.2) then
- maxfilt = 0
- end if
- if (rand() <= 0.2) then
- ctol = randn() * TEN**(-abs(TWO * randn()))
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ctol = REALMAX
- else
- ctol = ZERO
- end if
- if (rand() <= 0.2) then
- ftarget = -TEN**abs(TWO * randn())
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ftarget = REALMAX
- else
- ftarget = -REALMAX
- end if
-
- rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 5.0_RP))
- if (rand() <= 0.2) then
- rhoend = rhobeg
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- rhobeg = ZERO
- end if
- call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
- x0 = noisy(prob % x0)
- orig_calcfc => prob % calcfc
- call safealloc(xl, n)
- xl = prob % xl
- call safealloc(xu, n)
- xu = prob % xu
- call safealloc(Aineq, int(size(prob % Aineq, 1), IK), int(size(prob % Aineq, 2), IK))
- Aineq = prob % Aineq
- call safealloc(bineq, int(size(prob % bineq), IK))
- bineq = prob % bineq
- call safealloc(Aeq, int(size(prob % Aeq, 1), IK), int(size(prob % Aeq, 2), IK))
- Aeq = prob % Aeq
- call safealloc(beq, int(size(prob % beq), IK))
- beq = prob % beq
-
- print '(/A, I0, A, I0, A, I0, A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' M = ', m, &
- & ' Mineq = ', size(Aineq, 1), ' Meq = ', size(Aeq, 1), ', Random test ', irand
-
- call safealloc(x, n)
- x = x0
- call safealloc(nlconstr, m)
- call cobyla(noisy_calcfc, m, x, f, cstrv=cstrv, nlconstr=nlconstr, Aineq=Aineq, bineq=bineq, &
- & Aeq=Aeq, beq=beq, xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, &
- & maxfun=maxfun, maxhist=maxhist, fhist=fhist, xhist=xhist, nlchist=nlchist, chist=chist, &
- & ctol=ctol, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
-
- if (prob % probtype == 'l') then ! Run the test without nonlinear constraints
- call safealloc(x_alt, n)
- x_alt = x0
- call cobyla(noisy_calcfc, m, x_alt, f_alt, Aineq=Aineq, bineq=bineq, Aeq=Aeq, beq=beq, &
- & xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, &
- & fhist=fhist, xhist=xhist, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
- call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
- call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
- end if
-
- if (prob % probtype == 'b') then ! Run the test without linear/nonlinear constraints
- call safealloc(x_alt, n)
- x_alt = x0
- call cobyla(noisy_calcfc, m, x_alt, f_alt, xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, &
- & maxfun=maxfun, maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
- call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
- call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
- end if
-
- if (prob % probtype == 'u') then ! Run the test without constraints
- call safealloc(x_alt, n)
- x_alt = x0
- call cobyla(noisy_calcfc, m, x_alt, f_alt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
- &maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
- call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
- call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
- end if
-
- deallocate (x)
- deallocate (nlconstr)
- nullify (orig_calcfc)
- end do
-
- ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
- call destruct(prob) ! Destruct the testing problem.
+
+ iprint = int(randn(), kind(iprint))
+ maxfun = int(1.0E2_RP * rand() * real(n, RP), kind(maxfun))
+ maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
+ if (rand() <= 0.1) then
+ maxhist = -maxhist
+ end if
+ maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
+ if (rand() <= 0.1) then
+ maxfilt = 0
+ end if
+ if (rand() <= 0.1) then
+ ctol = randn() * TEN**(-abs(TWO * randn()))
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ ctol = REALMAX
+ else
+ ctol = ZERO
+ end if
+ if (rand() <= 0.8) then
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
+ elseif (rand() <= 0.5) then ! Note that the value of rand() changes.
+ ftarget = REALMAX
+ else
+ ftarget = -REALMAX
+ end if
+
+ rhobeg = noisy(prob % Delta0)
+ rhoend = max(1.0E-5_RP, rhobeg * 10.0_RP**(6.0_RP * rand() - 5.0_RP))
+ if (rand() <= 0.1) then
+ rhoend = rhobeg
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ rhobeg = ZERO
+ end if
+
+ call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
+ x0 = noisy(prob % x0)
+ orig_calcfc => prob % calcfc
+
+ call safealloc(xl, n)
+ xl = prob % xl
+ call safealloc(xu, n)
+ xu = prob % xu
+ call safealloc(Aineq, int(size(prob % Aineq, 1), IK), int(size(prob % Aineq, 2), IK))
+ Aineq = prob % Aineq
+ call safealloc(bineq, int(size(prob % bineq), IK))
+ bineq = prob % bineq
+ call safealloc(Aeq, int(size(prob % Aeq, 1), IK), int(size(prob % Aeq, 2), IK))
+ Aeq = prob % Aeq
+ call safealloc(beq, int(size(prob % beq), IK))
+ beq = prob % beq
+
+ print '(/A, I0, A, I0, A, I0, A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' M = ', m, &
+ & ' Mineq = ', size(Aineq, 1), ' Meq = ', size(Aeq, 1), ', Random test ', irand
+
+ call safealloc(x, n)
+ x = x0
+ call safealloc(nlconstr, m)
+ call cobyla(noisy_calcfc, m, x, f, cstrv=cstrv, nlconstr=nlconstr, Aineq=Aineq, bineq=bineq, &
+ & Aeq=Aeq, beq=beq, xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, &
+ & maxfun=maxfun, maxhist=maxhist, fhist=fhist, xhist=xhist, nlchist=nlchist, chist=chist, &
+ & ctol=ctol, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
+
+ if (prob % probtype == 'l') then ! Run the test without nonlinear constraints
+ call safealloc(x_alt, n)
+ x_alt = x0
+ call cobyla(noisy_calcfc, m, x_alt, f_alt, Aineq=Aineq, bineq=bineq, Aeq=Aeq, beq=beq, &
+ & xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, &
+ & fhist=fhist_alt, xhist=xhist_alt, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
+ call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
+ call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
+ end if
+
+ if (prob % probtype == 'b') then ! Run the test without linear/nonlinear constraints
+ call safealloc(x_alt, n)
+ x_alt = x0
+ call cobyla(noisy_calcfc, m, x_alt, f_alt, xl=xl, xu=xu, rhobeg=rhobeg, rhoend=rhoend, &
+ & maxfun=maxfun, maxhist=maxhist, fhist=fhist_alt, xhist=xhist_alt, ftarget=ftarget, &
+ & maxfilt=maxfilt, iprint=iprint)
+ call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
+ call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
+ end if
+
+ if (prob % probtype == 'u') then ! Run the test without constraints
+ call safealloc(x_alt, n)
+ x_alt = x0
+ call cobyla(noisy_calcfc, m, x_alt, f_alt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
+ &maxhist=maxhist, fhist=fhist_alt, xhist=xhist_alt, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
+ call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
+ call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
+ end if
+
+ deallocate (x)
+ deallocate (nlconstr)
+ nullify (orig_calcfc)
end do
+
+ ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
+ call destruct(prob) ! Destruct the testing problem.
end do
-end if
+ ! Test recursive call.
+ ! The depth of the recursion is 2. The first recursion is in RECURSIVE_FUN1, and the second is in
+ ! RECURSIVE_FUN2. RECURSIVE_FUN1(Y) is defined by minimizing the CHROSEN function subject to
+ ! X(1:N-1) - X(2:N)**2 <= 0 with Y being the starting point. RECURSIVE_FUN2(Y) is defined by
+ ! RECURSIVE_FUN1 in a similar way. Note that RECURSIVE_FUN1 is essentially a constant function.
+ n = 3_IK
+ print '(/A, I0)', 'Testing recursive call of '//solname//' on a problem with N = ', n
+ call safealloc(x, n)
+ x = randn(n)
+ call safealloc(nlconstr, n - 1_IK)
+ call cobyla(recursive_fun2, n - 1_IK, x, f, nlconstr=nlconstr, iprint=2_IK)
+ deallocate (x, nlconstr)
+
+end if
+
end subroutine test_solver
diff --git a/fortran/tests/test_lincoa.f90 b/fortran/tests/test_lincoa.f90
index 2c9497161d..4f46790953 100644
--- a/fortran/tests/test_lincoa.f90
+++ b/fortran/tests/test_lincoa.f90
@@ -1,3 +1,80 @@
+module hilbert_mod
+implicit none
+private
+public :: hilbert
+
+contains
+
+function hilbert(m, n) result(H)
+use, non_intrinsic :: consts_mod, only : RP, IK
+implicit none
+integer(IK), intent(in) :: m
+integer(IK), intent(in) :: n
+real(RP) :: H(m, n)
+integer(IK) :: i, j
+do j = 1, n
+ do i = 1, m
+ H(i, j) = 1.0_RP / real(i + j - 1_IK, RP)
+ end do
+end do
+end function hilbert
+
+end module hilbert_mod
+
+
+module recursive_mod
+implicit none
+private
+public :: recursive_fun2
+
+contains
+
+subroutine chrosen(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+integer :: n
+real(RP), parameter :: alpha = 4.0_RP
+n = size(x)
+f = sum((x(1:n - 1) - 1.0_RP)**2 + alpha * (x(2:n) - x(1:n - 1)**2)**2);
+end subroutine chrosen
+
+subroutine recursive_fun1(x, f)
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: hilbert_mod, only : hilbert
+use, non_intrinsic :: lincoa_mod, only : lincoa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: Aineq(2 * size(x), size(x))
+real(RP) :: bineq(2 * size(x))
+real(RP) :: x_loc(size(x))
+Aineq = hilbert(int(size(Aineq, 1), IK), int(size(Aineq, 2), IK))
+bineq = 1.0_RP
+x_loc = x
+call lincoa(chrosen, x_loc, f, Aineq=Aineq, bineq=bineq)
+end subroutine recursive_fun1
+
+subroutine recursive_fun2(x, f)
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: hilbert_mod, only : hilbert
+use, non_intrinsic :: lincoa_mod, only : lincoa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: Aineq(2 * size(x), size(x))
+real(RP) :: bineq(2 * size(x))
+real(RP) :: x_loc(size(x))
+Aineq = hilbert(int(size(Aineq, 1), IK), int(size(Aineq, 2), IK))
+bineq = 1.0_RP
+x_loc = x
+call lincoa(recursive_fun1, x_loc, f, Aineq=Aineq, bineq=bineq)
+end subroutine recursive_fun2
+
+end module recursive_mod
+
+
module test_solver_mod
!--------------------------------------------------------------------------------------------------!
! This module tests LINCOA on a few simple problems.
@@ -6,7 +83,7 @@ module test_solver_mod
!
! Started: September 2021
!
-! Last Modified: Tuesday, July 18, 2023 AM11:14:27
+! Last Modified: Sat 14 Feb 2026 10:48:51 AM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -17,17 +94,19 @@ module test_solver_mod
contains
-subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdim)
+subroutine test_solver(probs, mindim, maxdim, nrand, randseed, testdim)
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, TEN, ZERO, REALMAX
use, non_intrinsic :: debug_mod, only : validate
+use, non_intrinsic :: hilbert_mod, only : hilbert
use, non_intrinsic :: infnan_mod, only : is_neginf
use, non_intrinsic :: lincoa_mod, only : lincoa
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: noise_mod, only : noisy, noisy_calfun, orig_calfun
-use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, DIMSTRIDE_DFT, NRAND_DFT, RANDSEED_DFT
+use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, RANDSEED_DFT
use, non_intrinsic :: prob_mod, only : PNLEN, PROB_T, construct, destruct
use, non_intrinsic :: rand_mod, only : setseed, rand, randn
+use, non_intrinsic :: recursive_mod, only : recursive_fun2
use, non_intrinsic :: string_mod, only : strip, istr
implicit none
@@ -35,7 +114,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN), intent(in), optional :: probs(:)
integer(IK), intent(in), optional :: mindim
integer(IK), intent(in), optional :: maxdim
-integer(IK), intent(in), optional :: dimstride
integer(IK), intent(in), optional :: nrand
integer, intent(in), optional :: randseed
character(len=*), intent(in), optional :: testdim
@@ -48,10 +126,7 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN) :: probname
character(len=PNLEN) :: probs_loc(100) ! Maximal number of problems to test: 100
integer :: randseed_loc
-integer :: rseed
-integer(IK) :: dim_list(100) ! Maximal number of dimensions to test: 100
-integer(IK) :: dimstride_loc
-integer(IK) :: idim
+integer :: seed
integer(IK) :: iprint
integer(IK) :: iprob
integer(IK) :: irand
@@ -62,14 +137,13 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
integer(IK) :: maxhist
integer(IK) :: mindim_loc
integer(IK) :: n
-integer(IK) :: ndim
integer(IK) :: nnpt
integer(IK) :: nprobs
integer(IK) :: npt
-integer(IK) :: npt_list(10)
+integer(IK) :: npt_list(100) ! Maximal number of prescribed NPT to test: 100
integer(IK) :: nrand_loc
-integer(IK), parameter :: bign = 300_IK
-integer(IK), parameter :: largen = 1000_IK
+integer(IK), parameter :: bign = 300_IK ! Dimension of the big problems
+integer(IK), parameter :: largen = 1000_IK ! Dimension of the large problems
real(RP) :: cstrv
real(RP) :: ctol
real(RP) :: f
@@ -83,10 +157,12 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
real(RP), allocatable :: beq(:)
real(RP), allocatable :: chist(:)
real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: fhist_alt(:)
real(RP), allocatable :: x(:)
real(RP), allocatable :: x0(:)
real(RP), allocatable :: x_alt(:)
real(RP), allocatable :: xhist(:, :)
+real(RP), allocatable :: xhist_alt(:, :)
real(RP), allocatable :: xl(:)
real(RP), allocatable :: xu(:)
type(PROB_T) :: prob
@@ -113,12 +189,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
maxdim_loc = MAXDIM_DFT
end if
-if (present(dimstride)) then
- dimstride_loc = dimstride
-else
- dimstride_loc = DIMSTRIDE_DFT
-end if
-
if (present(nrand)) then
nrand_loc = nrand
else
@@ -138,32 +208,28 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end if
-! Test the big problem
+! Test the big/large problem
if (testdim_loc == 'big' .or. testdim_loc == 'large') then
probname = bigprob
n = merge(bign, largen, testdim_loc == 'big')
do irand = 1, 1 ! The test is expensive
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- m = int(min(int(10.0_RP * rand() * real(n, RP)), 10**min(range(0), range(0_IK))), IK)
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + n + randseed_loc)
+ call setseed(seed)
+ m = int(min(int(2.0_RP * rand() * real(n, RP)), 10**min(range(0), range(0_IK))), IK)
+ m = min(m, floor(real(huge(m)) / 8.0, IK) - n - 2_IK) ! Avoid integer overflow when calculating UNIT_MEMO in PREPROC/HISTORY
call construct(prob, probname, n, m)
- npt = max(n + 2_IK, int(5.0 * rand() * real(n, RP), kind(npt)))
+ npt = int(4.0 * rand() * real(n, RP), kind(npt))
iprint = 2_IK
- if (int(npt) + 2000 > huge(0_IK)) then
- maxfun = huge(0_IK)
- else
- maxfun = npt + int(2000.0_RP * rand(), IK)
- end if
- maxhist = maxfun
+ maxfun = npt + int(min(1000.0_RP, real(2_IK*npt, RP)) * rand(), IK)
maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
if (rand() <= 0.5) then
ctol = TEN**(-abs(5.0 * randn()))
else
ctol = ZERO
end if
- ftarget = -REALMAX
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 6.0_RP))
+ rhoend = min(0.1_RP * rhobeg, max(1.0E-3_RP, rhobeg * 10.0_RP**(-3.0_RP * rand())))
call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
x = noisy(prob % x0)
orig_calfun => prob % calfun
@@ -197,139 +263,147 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
else
do iprob = 1, nprobs
+
probname = probs_loc(iprob)
- if (any(probname == fix_dim_probs)) then
- call construct(prob, probname) ! Construct the testing problem.
- ndim = 1
- dim_list(1) = prob % n
- else
- ndim = (maxdim_loc - mindim_loc) / dimstride_loc + 1_IK
- dim_list(1:ndim) = mindim_loc + dimstride_loc*[(idim - 1_IK, idim=1_IK, ndim)]
- end if
- if (strip(probname) == 'ptinsq') then
- dim_list(1:ndim) = int(ceiling(real(dim_list(1:ndim)) / 2.0) * 2, IK) ! Must be even
- end if
- do idim = 1, ndim
+
+ do irand = 1, max(0_IK, nrand_loc) + 1_IK
+ ! Initialize the random seed using IRAND, RP, and RANDSEED_LOC. Do not include IK so
+ ! that the results for different IK are the same.
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + randseed_loc)
+ call setseed(seed)
+
+ ! Set the problem dimension N to a random value in the range [MINDIM, MAXDIM].
+ n = mindim_loc + floor(rand() * real(maxdim_loc - mindim_loc + 1_IK, RP), kind(n))
+ if (strip(probname) == 'ptinsq') then
+ n = 2_IK * int(ceiling(real(n) / 2.0), IK) ! Ensure that N is even for PTINSQ
+ end if
+
+ ! Construct the testing problem.
if (any(probname == fix_dim_probs)) then
call construct(prob, probname)
else
- call construct(prob, probname, n=dim_list(idim))
+ call construct(prob, probname, n=n)
end if
n = prob % n
! NPT_LIST defines some extreme values of NPT.
- nnpt = 10
- npt_list(1:nnpt) = [1_IK, &
+ nnpt = 11
+ npt_list(1:nnpt) = [0_IK, 1_IK, &
& n + 1_IK, n + 2_IK, n + 3_IK, &
& 2_IK * n, 2_IK * n + 1_IK, 2_IK * n + 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK - 1_IK, (n + 1_IK) * (n + 2_IK) / 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK + 1_IK]
- do irand = 1, nnpt + max(0_IK, nrand_loc)
- ! Initialize the random seed using N, IRAND, RP, and RANDSEED_LOC. Do not include IK so
- ! that the results for different IK are the same.
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- if (irand <= nnpt) then
- npt = npt_list(irand)
- else
- npt = int(TEN * rand() * real(n, RP), kind(npt))
- end if
- if (rand() <= 0.2) then
- npt = 0
- end if
- iprint = int(sign(min(3.0_RP, 1.5_RP * abs(randn())), randn()), kind(iprint))
- maxfun = int(2.0E2_RP * rand() * real(n, RP), kind(maxfun))
- if (rand() <= 0.2) then
- maxfun = 0
- end if
- maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
- if (rand() <= 0.2) then
- maxhist = -maxhist
- end if
- maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
- if (rand() <= 0.2) then
- maxfilt = 0
- end if
- if (rand() <= 0.2) then
- ctol = randn() * TEN**(-abs(TWO * randn()))
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ctol = REALMAX
- else
- ctol = ZERO
- end if
- if (rand() <= 0.2) then
- ftarget = -TEN**abs(TWO * randn())
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ftarget = REALMAX
- else
- ftarget = -REALMAX
- end if
-
- rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 5.0_RP))
- if (rand() <= 0.2) then
- rhoend = rhobeg
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- rhobeg = ZERO
- end if
-
- call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
- x0 = noisy(prob % x0)
-
- orig_calfun => prob % calfun
-
- call safealloc(xl, n)
- xl = prob % xl
- call safealloc(xu, n)
- xu = prob % xu
- call safealloc(Aineq, int(size(prob % Aineq, 1), IK), int(size(prob % Aineq, 2), IK))
- Aineq = prob % Aineq
- call safealloc(bineq, int(size(prob % bineq), IK))
- bineq = prob % bineq
- call safealloc(Aeq, int(size(prob % Aeq, 1), IK), int(size(prob % Aeq, 2), IK))
- Aeq = prob % Aeq
- call safealloc(beq, int(size(prob % beq), IK))
- beq = prob % beq
-
- print '(/A, I0, A, I0, A, I0, A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, &
- & ' Mineq = ', size(Aineq, 1), ' Meq = ', size(Aeq, 1), ', Random test ', irand
-
- call safealloc(x, n)
- x = x0
- call lincoa(noisy_calfun, x, f, cstrv=cstrv, Aineq=Aineq, bineq=bineq, Aeq=Aeq, beq=beq, &
- & xl=xl, xu=xu, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, fhist=fhist, &
- & xhist=xhist, chist=chist, ctol=ctol, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
-
- if (prob % probtype == 'b') then ! Run the test without linear constraints
- call safealloc(x_alt, n)
- x_alt = x0
- call lincoa(noisy_calfun, x_alt, f_alt, xl=xl, xu=xu, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
- & maxhist=maxhist, fhist=fhist, xhist=xhist, chist=chist, ctol=ctol, ftarget=ftarget, maxfilt=maxfilt, &
- & iprint=iprint)
- call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
- call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
- end if
-
- if (prob % probtype == 'u') then ! Run the test without constraints
- call safealloc(x_alt, n)
- x_alt = x0
- call lincoa(noisy_calfun, x_alt, f_alt, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
- & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
- call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
- call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
- end if
-
- deallocate (x)
- nullify (orig_calfun)
- end do
-
- ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
- call destruct(prob) ! Destruct the testing problem.
+
+ if (irand <= 1) then
+ npt = npt_list(ceiling(real(nnpt, RP)*rand())) ! Randomly select NPT from NPT_LIST
+ else
+ npt = int(TEN * rand() * real(n, RP), kind(npt))
+ end if
+
+ iprint = int(randn(), kind(iprint))
+ maxfun = int(1.0E2_RP * rand() * real(n, RP), kind(maxfun))
+ maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
+ if (rand() <= 0.1) then
+ maxhist = -maxhist
+ end if
+ maxfilt = int(TWO * rand() * real(maxfun, RP), kind(maxfilt))
+ if (rand() <= 0.1) then
+ maxfilt = 0
+ end if
+ if (rand() <= 0.1) then
+ ctol = randn() * TEN**(-abs(TWO * randn()))
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ ctol = REALMAX
+ else
+ ctol = ZERO
+ end if
+ if (rand() <= 0.8) then
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
+ elseif (rand() <= 0.5) then ! Note that the value of rand() changes.
+ ftarget = REALMAX
+ else
+ ftarget = -REALMAX
+ end if
+
+ rhobeg = noisy(prob % Delta0)
+ rhoend = max(1.0E-5_RP, rhobeg * 10.0_RP**(6.0_RP * rand() - 5.0_RP))
+ if (rand() <= 0.1) then
+ rhoend = rhobeg
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ rhobeg = ZERO
+ end if
+
+ call safealloc(x0, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
+ x0 = noisy(prob % x0)
+ orig_calfun => prob % calfun
+
+ call safealloc(xl, n)
+ xl = prob % xl
+ call safealloc(xu, n)
+ xu = prob % xu
+ call safealloc(Aineq, int(size(prob % Aineq, 1), IK), int(size(prob % Aineq, 2), IK))
+ Aineq = prob % Aineq
+ call safealloc(bineq, int(size(prob % bineq), IK))
+ bineq = prob % bineq
+ call safealloc(Aeq, int(size(prob % Aeq, 1), IK), int(size(prob % Aeq, 2), IK))
+ Aeq = prob % Aeq
+ call safealloc(beq, int(size(prob % beq), IK))
+ beq = prob % beq
+
+ print '(/A, I0, A, I0, A, I0, A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, &
+ & ' Mineq = ', size(Aineq, 1), ' Meq = ', size(Aeq, 1), ', Random test ', irand
+
+ call safealloc(x, n)
+ x = x0
+ call lincoa(noisy_calfun, x, f, cstrv=cstrv, Aineq=Aineq, bineq=bineq, Aeq=Aeq, beq=beq, &
+ & xl=xl, xu=xu, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, maxhist=maxhist, fhist=fhist, &
+ & xhist=xhist, chist=chist, ctol=ctol, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
+
+ if (prob % probtype == 'b') then ! Run the test without linear constraints
+ call safealloc(x_alt, n)
+ x_alt = x0
+ call lincoa(noisy_calfun, x_alt, f_alt, xl=xl, xu=xu, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
+ & maxhist=maxhist, fhist=fhist_alt, xhist=xhist_alt, ctol=ctol, ftarget=ftarget, &
+ & maxfilt=maxfilt, iprint=iprint)
+ call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
+ call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
+ end if
+
+ if (prob % probtype == 'u') then ! Run the test without constraints
+ call safealloc(x_alt, n)
+ x_alt = x0
+ call lincoa(noisy_calfun, x_alt, f_alt, npt=npt, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
+ & maxhist=maxhist, fhist=fhist_alt, xhist=xhist_alt, ftarget=ftarget, maxfilt=maxfilt, iprint=iprint)
+ call validate(all(abs(x - x_alt) <= 0), 'X == X_ALT', srname)
+ call validate(abs(f - f_alt) <= 0 .or. (is_neginf(f) .and. is_neginf(f_alt)), 'F == F_ALT', srname)
+ end if
+
+ deallocate (x)
+ nullify (orig_calfun)
end do
+
+ ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
+ call destruct(prob) ! Destruct the testing problem.
end do
-end if
+ ! Test recursive call.
+ ! The depth of the recursion is 2. The first recursion is in RECURSIVE_FUN1, and the second is in
+ ! RECURSIVE_FUN2. RECURSIVE_FUN1(Y) is defined by minimizing the CHROSEN function subject to
+ ! Aineq * X <= Bineq with Y being the starting point. RECURSIVE_FUN2(Y) is defined by
+ ! RECURSIVE_FUN1 in a similar way. Note that RECURSIVE_FUN1 is essentially a constant function.
+ n = 3_IK
+ print '(/A, I0)', 'Testing recursive call of '//solname//' on a problem with N = ', n
+ deallocate (Aineq, bineq)
+ ! For unknown reason, `safealloc` does not work here with `nvfortran -O1`, nvfortran 23.7.
+ allocate (Aineq(1:2_IK * n, 1:n), bineq(1:2_IK * n), x(1:n))
+ Aineq = hilbert(int(size(Aineq, 1), IK), int(size(Aineq, 2), IK))
+ bineq = 1.0_RP
+ x = randn(n)
+ call lincoa(recursive_fun2, x, f, Aineq=Aineq, bineq=bineq, iprint=2_IK)
+
+end if
+
end subroutine test_solver
diff --git a/fortran/tests/test_newuoa.f90 b/fortran/tests/test_newuoa.f90
index 814c4b445e..8f90dd845d 100644
--- a/fortran/tests/test_newuoa.f90
+++ b/fortran/tests/test_newuoa.f90
@@ -1,3 +1,46 @@
+module recursive_mod
+implicit none
+private
+public :: recursive_fun2
+
+contains
+
+subroutine chrosen(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+integer :: n
+real(RP), parameter :: alpha = 4.0_RP
+n = size(x)
+f = sum((x(1:n - 1) - 1.0_RP)**2 + alpha * (x(2:n) - x(1:n - 1)**2)**2);
+end subroutine chrosen
+
+subroutine recursive_fun1(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: newuoa_mod, only : newuoa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: x_loc(size(x))
+x_loc = x
+call newuoa(chrosen, x_loc, f)
+end subroutine recursive_fun1
+
+subroutine recursive_fun2(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: newuoa_mod, only : newuoa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: x_loc(size(x))
+x_loc = x
+call newuoa(recursive_fun1, x_loc, f)
+end subroutine recursive_fun2
+
+end module recursive_mod
+
+
module test_solver_mod
!--------------------------------------------------------------------------------------------------!
! This module tests NEWUOA on a few simple problems.
@@ -6,7 +49,7 @@ module test_solver_mod
!
! Started: September 2021
!
-! Last Modified: Tuesday, June 27, 2023 AM07:43:43
+! Last Modified: Mon 25 Aug 2025 12:06:42 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -17,15 +60,16 @@ module test_solver_mod
contains
-subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdim)
+subroutine test_solver(probs, mindim, maxdim, nrand, randseed, testdim)
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, TEN, ZERO, REALMAX
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: newuoa_mod, only : newuoa
use, non_intrinsic :: noise_mod, only : noisy, noisy_calfun, orig_calfun
-use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, DIMSTRIDE_DFT, NRAND_DFT, RANDSEED_DFT
+use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, RANDSEED_DFT
use, non_intrinsic :: prob_mod, only : PNLEN, PROB_T, construct, destruct
use, non_intrinsic :: rand_mod, only : setseed, rand, randn
+use, non_intrinsic :: recursive_mod, only : recursive_fun2
use, non_intrinsic :: string_mod, only : strip, istr
implicit none
@@ -33,7 +77,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN), intent(in), optional :: probs(:)
integer(IK), intent(in), optional :: mindim
integer(IK), intent(in), optional :: maxdim
-integer(IK), intent(in), optional :: dimstride
integer(IK), intent(in), optional :: nrand
integer, intent(in), optional :: randseed
character(len=*), intent(in), optional :: testdim
@@ -42,12 +85,9 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=*), parameter :: solname = 'newuoa'
character(len=:), allocatable :: testdim_loc
character(len=PNLEN) :: probname
-character(len=PNLEN) :: probs_loc(100)
+character(len=PNLEN) :: probs_loc(100) ! Maximal number of problems to test: 100
integer :: randseed_loc
-integer :: rseed
-integer(IK) :: dim_list(100) ! Maximal number of dimensions to test: 100
-integer(IK) :: dimstride_loc
-integer(IK) :: idim
+integer :: seed
integer(IK) :: iprint
integer(IK) :: iprob
integer(IK) :: irand
@@ -56,14 +96,13 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
integer(IK) :: maxhist
integer(IK) :: mindim_loc
integer(IK) :: n
-integer(IK) :: ndim
integer(IK) :: nnpt
integer(IK) :: nprobs
integer(IK) :: npt
-integer(IK) :: npt_list(10)
+integer(IK) :: npt_list(100) ! Maximal number of prescribed NPT to test: 100
integer(IK) :: nrand_loc
-integer(IK), parameter :: bign = 300_IK
-integer(IK), parameter :: largen = 1600_IK
+integer(IK), parameter :: bign = 300_IK ! Dimension of the big problems
+integer(IK), parameter :: largen = 1600_IK ! Dimension of the large problem.
real(RP) :: f
real(RP) :: ftarget
real(RP) :: rhobeg
@@ -93,12 +132,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
maxdim_loc = MAXDIM_DFT
end if
-if (present(dimstride)) then
- dimstride_loc = dimstride
-else
- dimstride_loc = DIMSTRIDE_DFT
-end if
-
if (present(nrand)) then
nrand_loc = nrand
else
@@ -118,25 +151,21 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end if
-! Test the big problem
+! Test the big/large problem
if (testdim_loc == 'big' .or. testdim_loc == 'large') then
probname = bigprob
n = merge(bign, largen, testdim_loc == 'big')
call construct(prob, probname, n)
do irand = 1, 1 ! The test is expensive
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- npt = max(n + 2_IK, int(5.0 * rand() * real(n, RP), kind(npt)))
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + n + randseed_loc)
+ call setseed(seed)
+ npt = int(4.0 * rand() * real(n, RP), kind(npt))
iprint = 2_IK
- if (int(npt) + 2000 > huge(0_IK)) then
- maxfun = huge(0_IK)
- else
- maxfun = npt + int(2000.0_RP * rand(), IK)
- end if
+ maxfun = npt + int(min(1000.0_RP, real(2_IK*npt, RP)) * rand(), IK)
maxhist = maxfun
- ftarget = -REALMAX
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 6.0_RP))
+ rhoend = min(0.1_RP * rhobeg, max(1.0E-3_RP, rhobeg * 10.0_RP**(-3.0_RP * rand())))
call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
x = noisy(prob % x0)
orig_calfun => prob % calfun
@@ -155,76 +184,87 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
else
do iprob = 1, nprobs
+
probname = probs_loc(iprob)
- ndim = (maxdim_loc - mindim_loc) / dimstride_loc + 1_IK
- dim_list(1:ndim) = mindim_loc + dimstride_loc*[(idim - 1_IK, idim=1_IK, ndim)]
- do idim = 1, ndim
- n = dim_list(idim)
- call construct(prob, probname, n) ! Construct the testing problem.
+
+ do irand = 1, max(0_IK, nrand_loc) + 1_IK
+ ! Initialize the random seed using IRAND, RP, and RANDSEED_LOC. Do not include IK so
+ ! that the results for different IK are the same.
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + randseed_loc)
+ call setseed(seed)
+
+ ! Set the problem dimension N to a random value in the range [MINDIM, MAXDIM].
+ n = mindim_loc + floor(rand() * real(maxdim_loc - mindim_loc + 1_IK, RP), kind(n))
+
+ ! Construct the testing problem.
+ call construct(prob, probname, n)
! NPT_LIST defines some extreme values of NPT.
- nnpt = 10
- npt_list(1:nnpt) = [1_IK, &
+ nnpt = 11
+ npt_list(1:nnpt) = [0_IK, 1_IK, &
& n + 1_IK, n + 2_IK, n + 3_IK, &
& 2_IK * n, 2_IK * n + 1_IK, 2_IK * n + 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK - 1_IK, (n + 1_IK) * (n + 2_IK) / 2_IK, &
& (n + 1_IK) * (n + 2_IK) / 2_IK + 1_IK]
- do irand = 1, nnpt + max(0_IK, nrand_loc)
- ! Initialize the random seed using N, IRAND, RP, and RANDSEED_LOC. Do not include IK so
- ! that the results for different IK are the same.
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- if (irand <= nnpt) then
- npt = npt_list(irand)
- else
- npt = int(TEN * rand() * real(n, RP), kind(npt))
- end if
- if (rand() <= 0.2) then
- npt = 0
- end if
- iprint = int(sign(min(3.0_RP, 1.5_RP * abs(randn())), randn()), kind(iprint))
- maxfun = int(2.0E2_RP * rand() * real(n, RP), kind(maxfun))
- if (rand() <= 0.2) then
- maxfun = 0
- end if
- maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
- if (rand() <= 0.2) then
- maxhist = 0
- end if
- if (rand() <= 0.2) then
- ftarget = -TEN**abs(TWO * randn())
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ftarget = REALMAX
- else
- ftarget = -REALMAX
- end if
-
- rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 5.0_RP))
- if (rand() <= 0.2) then
- rhoend = rhobeg
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- rhobeg = ZERO
- end if
- call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
- x = noisy(prob % x0)
- orig_calfun => prob % calfun
-
- print '(/A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, ', Random test ', irand
- call newuoa(noisy_calfun, x, f, rhobeg=rhobeg, rhoend=rhoend, npt=npt, maxfun=maxfun, &
- & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, iprint=iprint)
-
- deallocate (x)
- nullify (orig_calfun)
- end do
-
- ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
- call destruct(prob) ! Destruct the testing problem.
+ if (irand <= 1) then
+ npt = npt_list(ceiling(real(nnpt, RP)*rand())) ! Randomly select NPT from NPT_LIST
+ else
+ npt = int(TEN * rand() * real(n, RP), kind(npt))
+ end if
+
+ iprint = int(randn(), kind(iprint))
+ maxfun = int(1.0E2_RP * rand() * real(n, RP), kind(maxfun))
+ maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
+ if (rand() <= 0.1) then
+ maxhist = -maxhist
+ end if
+ if (rand() <= 0.8) then
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
+ elseif (rand() <= 0.5) then ! Note that the value of rand() changes.
+ ftarget = REALMAX
+ else
+ ftarget = -REALMAX
+ end if
+
+ rhobeg = noisy(prob % Delta0)
+ rhoend = max(1.0E-5_RP, rhobeg * 10.0_RP**(6.0_RP * rand() - 5.0_RP))
+ if (rand() <= 0.1) then
+ rhoend = rhobeg
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ rhobeg = ZERO
+ end if
+
+ call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
+ x = noisy(prob % x0)
+ orig_calfun => prob % calfun
+
+ print '(/A, I0, A, I0, A, I0)', strip(probname)//': N = ', n, ' NPT = ', npt, ', Random test ', irand
+ call newuoa(noisy_calfun, x, f, rhobeg=rhobeg, rhoend=rhoend, npt=npt, maxfun=maxfun, &
+ & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, iprint=iprint)
+
+ deallocate (x)
+ nullify (orig_calfun)
end do
+
+ ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
+ call destruct(prob) ! Destruct the testing problem.
end do
-end if
+ ! Test recursive call.
+ ! The depth of the recursion is 2. The first recursion is in RECURSIVE_FUN1, and the second is in
+ ! RECURSIVE_FUN2. RECURSIVE_FUN1(Y) is defined by minimizing the CHROSEN function with Y being
+ ! the starting point. RECURSIVE_FUN2(Y) is defined by RECURSIVE_FUN1 in a similar way. Note
+ ! that RECURSIVE_FUN1 is essentially a constant function.
+ n = 3_IK
+ print '(/A, I0)', 'Testing recursive call of '//solname//' on a problem with N = ', n
+ call safealloc(x, n)
+ x = randn(n)
+ call newuoa(recursive_fun2, x, f, iprint=2_IK)
+ deallocate (x)
+
+end if
+
end subroutine test_solver
diff --git a/fortran/tests/test_uobyqa.f90 b/fortran/tests/test_uobyqa.f90
index f45d6842b9..1291b356f6 100644
--- a/fortran/tests/test_uobyqa.f90
+++ b/fortran/tests/test_uobyqa.f90
@@ -1,3 +1,46 @@
+module recursive_mod
+implicit none
+private
+public :: recursive_fun2
+
+contains
+
+subroutine chrosen(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+integer :: n
+real(RP), parameter :: alpha = 4.0_RP
+n = size(x)
+f = sum((x(1:n - 1) - 1.0_RP)**2 + alpha * (x(2:n) - x(1:n - 1)**2)**2);
+end subroutine chrosen
+
+subroutine recursive_fun1(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: uobyqa_mod, only : uobyqa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: x_loc(size(x))
+x_loc = x
+call uobyqa(chrosen, x_loc, f)
+end subroutine recursive_fun1
+
+subroutine recursive_fun2(x, f)
+use, non_intrinsic :: consts_mod, only : RP
+use, non_intrinsic :: uobyqa_mod, only : uobyqa
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP) :: x_loc(size(x))
+x_loc = x
+call uobyqa(recursive_fun1, x_loc, f)
+end subroutine recursive_fun2
+
+end module recursive_mod
+
+
module test_solver_mod
!--------------------------------------------------------------------------------------------------!
! This module tests UOBYQA on a few simple problems.
@@ -6,7 +49,7 @@ module test_solver_mod
!
! Started: September 2021
!
-! Last Modified: Tuesday, June 27, 2023 AM07:44:03
+! Last Modified: Mon 25 Aug 2025 12:06:56 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -17,14 +60,15 @@ module test_solver_mod
contains
-subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdim)
+subroutine test_solver(probs, mindim, maxdim, nrand, randseed, testdim)
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, TEN, ZERO, REALMAX
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: noise_mod, only : noisy, noisy_calfun, orig_calfun
-use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, DIMSTRIDE_DFT, NRAND_DFT, RANDSEED_DFT
+use, non_intrinsic :: param_mod, only : MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, RANDSEED_DFT
use, non_intrinsic :: prob_mod, only : PNLEN, PROB_T, construct, destruct
use, non_intrinsic :: rand_mod, only : setseed, rand, randn
+use, non_intrinsic :: recursive_mod, only : recursive_fun2
use, non_intrinsic :: string_mod, only : strip, istr
use, non_intrinsic :: uobyqa_mod, only : uobyqa
@@ -33,7 +77,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN), intent(in), optional :: probs(:)
integer(IK), intent(in), optional :: mindim
integer(IK), intent(in), optional :: maxdim
-integer(IK), intent(in), optional :: dimstride
integer(IK), intent(in), optional :: nrand
integer, intent(in), optional :: randseed
character(len=*), intent(in), optional :: testdim
@@ -44,8 +87,7 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
character(len=PNLEN) :: probname
character(len=PNLEN) :: probs_loc(100)
integer :: randseed_loc
-integer :: rseed
-integer(IK) :: dimstride_loc
+integer :: seed
integer(IK) :: iprint
integer(IK) :: iprob
integer(IK) :: irand
@@ -57,8 +99,8 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
integer(IK) :: nprobs
integer(IK) :: npt
integer(IK) :: nrand_loc
-integer(IK), parameter :: bign = 80_IK
-integer(IK), parameter :: largen = 160_IK
+integer(IK), parameter :: bign = 80_IK ! Dimension of the big problems
+integer(IK), parameter :: largen = 160_IK ! Dimension of the large problems
real(RP) :: f
real(RP) :: ftarget
real(RP) :: rhobeg
@@ -88,12 +130,6 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
maxdim_loc = MAXDIM_DFT
end if
-if (present(dimstride)) then
- dimstride_loc = dimstride
-else
- dimstride_loc = DIMSTRIDE_DFT
-end if
-
if (present(nrand)) then
nrand_loc = nrand
else
@@ -113,25 +149,21 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end if
-! Test the big problem
+! Test the big/large problem
if (testdim_loc == 'big' .or. testdim_loc == 'large') then
probname = bigprob
n = merge(bign, largen, testdim_loc == 'big')
call construct(prob, probname, n)
do irand = 1, 1 ! The test is expensive
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- iprint = 2_IK
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + n + randseed_loc)
+ call setseed(seed)
npt = (n + 2_IK) * (n + 1_IK) / 2_IK
- if (int(npt) + 2000 > huge(0_IK)) then
- maxfun = huge(0_IK)
- else
- maxfun = npt + int(2000.0_RP * rand(), IK)
- end if
+ iprint = 2_IK
+ maxfun = npt + int(min(1000.0_RP, real(2_IK*npt, RP)) * rand(), IK)
maxhist = maxfun
- ftarget = -REALMAX
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 6.0_RP))
+ rhoend = min(0.1_RP * rhobeg, max(1.0E-3_RP, rhobeg * 10.0_RP**(-3.0_RP * rand())))
call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
x = noisy(prob % x0)
orig_calfun => prob % calfun
@@ -145,60 +177,78 @@ subroutine test_solver(probs, mindim, maxdim, dimstride, nrand, randseed, testdi
end do
! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
call destruct(prob) ! Destruct the testing problem.
+
else
+
do iprob = 1, nprobs
+
probname = probs_loc(iprob)
- do n = mindim_loc, maxdim_loc, dimstride_loc
- call construct(prob, probname, n) ! Construct the testing problem.
-
- do irand = 1, nrand_loc
- ! Initialize the random seed using N, IRAND, RP, and RANDSEED_LOC. Do not include IK so
- ! that the results for different IK are the same.
- rseed = int(sum(istr(solname)) + sum(istr(probname)) + n + irand + RP + randseed_loc)
- call setseed(rseed)
- iprint = int(sign(min(3.0_RP, 1.5_RP * abs(randn())), randn()), kind(iprint))
- maxfun = int(2.0E2_RP * rand() * real(n, RP), kind(maxfun))
- if (rand() <= 0.2) then
- maxfun = 0
- end if
- maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
- if (rand() <= 0.2) then
- maxhist = 0
- end if
- if (rand() <= 0.2) then
- ftarget = -TEN**abs(TWO * randn())
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- ftarget = REALMAX
- else
- ftarget = -REALMAX
- end if
-
- rhobeg = noisy(prob % Delta0)
- rhoend = max(1.0E-6_RP, rhobeg * 1.0E1_RP**(6.0_RP * rand() - 5.0_RP))
- if (rand() <= 0.2) then
- rhoend = rhobeg
- elseif (rand() <= 0.2) then ! Note that the value of rand() changes.
- rhobeg = ZERO
- end if
- call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
- x = noisy(prob % x0)
- orig_calfun => prob % calfun
-
- print '(/A, I0, A, I0)', strip(probname)//': N = ', n, ', Random test ', irand
- call uobyqa(noisy_calfun, x, f, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
- & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, iprint=iprint)
-
- deallocate (x)
- nullify (orig_calfun)
- end do
-
- ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
- call destruct(prob) ! Destruct the testing problem.
+
+ do irand = 1, max(0_IK, nrand_loc) + 1_IK
+ ! Initialize the random seed using IRAND, RP, and RANDSEED_LOC. Do not include IK so
+ ! that the results for different IK are the same.
+ seed = int(sum(istr(solname)) + sum(istr(probname)) + irand * RP + randseed_loc)
+ call setseed(seed)
+
+ ! Set the problem dimension N to a random value in the range [MINDIM, MAXDIM].
+ n = mindim_loc + floor(rand() * real(maxdim_loc - mindim_loc + 1_IK, RP), kind(n))
+
+ ! Construct the testing problem.
+ call construct(prob, probname, n)
+
+ iprint = int(randn(), kind(iprint))
+ maxfun = int(1.0E2_RP * rand() * real(n, RP), kind(maxfun))
+ maxhist = int(TWO * rand() * real(max(10_IK * n, maxfun), RP), kind(maxhist))
+ if (rand() <= 0.1) then
+ maxhist = -maxhist
+ end if
+ if (rand() <= 0.8) then
+ ftarget = -TEN**abs(real(min(range(ftarget), 12), RP) * rand())
+ elseif (rand() <= 0.5) then ! Note that the value of rand() changes.
+ ftarget = REALMAX
+ else
+ ftarget = -REALMAX
+ end if
+
+ rhobeg = noisy(prob % Delta0)
+ rhoend = max(1.0E-5_RP, rhobeg * 10.0_RP**(6.0_RP * rand() - 5.0_RP))
+ if (rand() <= 0.1) then
+ rhoend = rhobeg
+ elseif (rand() <= 0.1) then ! Note that the value of rand() changes.
+ rhobeg = ZERO
+ end if
+
+ call safealloc(x, n) ! Not all compilers support automatic allocation yet, e.g., Absoft.
+ x = noisy(prob % x0)
+ orig_calfun => prob % calfun
+
+ print '(/A, I0, A, I0)', strip(probname)//': N = ', n, ', Random test ', irand
+ call uobyqa(noisy_calfun, x, f, rhobeg=rhobeg, rhoend=rhoend, maxfun=maxfun, &
+ & maxhist=maxhist, fhist=fhist, xhist=xhist, ftarget=ftarget, iprint=iprint)
+
+ deallocate (x)
+ nullify (orig_calfun)
end do
+
+ ! DESTRUCT deallocates allocated arrays/pointers and nullify the pointers. Must be called.
+ call destruct(prob) ! Destruct the testing problem.
end do
-end if
+ ! Test recursive call.
+ ! The depth of the recursion is 2. The first recursion is in RECURSIVE_FUN1, and the second is in
+ ! RECURSIVE_FUN2. RECURSIVE_FUN1(Y) is defined by minimizing the CHROSEN function with Y being
+ ! the starting point. RECURSIVE_FUN2(Y) is defined by RECURSIVE_FUN1 in a similar way. Note
+ ! that RECURSIVE_FUN1 is essentially a constant function.
+ n = 3_IK
+ print '(/A, I0)', 'Testing recursive call of '//solname//' on a problem with N = ', n
+ call safealloc(x, n)
+ x = randn(n)
+ call uobyqa(recursive_fun2, x, f, iprint=2_IK)
+ deallocate (x)
+
+end if
+
end subroutine test_solver
diff --git a/fortran/tests/testsuite/inf.F90 b/fortran/tests/testsuite/inf.F90
new file mode 100644
index 0000000000..2b8ec34b71
--- /dev/null
+++ b/fortran/tests/testsuite/inf.F90
@@ -0,0 +1,164 @@
+#include "ppf.h"
+
+module inf_mod
+!--------------------------------------------------------------------------------------------------!
+! This is the replacement of fortran/common/inf.F90 for testing what if IS_FINITE always returns
+! TRUE, while is_inf, is_posinf, is_neginf, and is_nan always return FALSE.
+!
+! Coded by Zaikun ZHANG (www.zhangzk.net).
+!
+! Started: July 2020.
+!
+! Last Modified: Friday, October 06, 2023 PM08:29:32
+!--------------------------------------------------------------------------------------------------!
+
+use, non_intrinsic :: huge_mod, only : huge_value
+implicit none
+private
+public :: is_finite, is_posinf, is_neginf, is_inf
+
+#if PRIMA_QP_AVAILABLE == 1
+
+interface is_finite
+ module procedure is_finite_sp, is_finite_dp, is_finite_qp
+end interface is_finite
+
+interface is_posinf
+ module procedure is_posinf_sp, is_posinf_dp, is_posinf_qp
+end interface is_posinf
+
+interface is_neginf
+ module procedure is_neginf_sp, is_neginf_dp, is_neginf_qp
+end interface is_neginf
+
+interface is_inf
+ module procedure is_inf_sp, is_inf_dp, is_inf_qp
+end interface is_inf
+
+#else
+
+interface is_finite
+ module procedure is_finite_sp, is_finite_dp
+end interface is_finite
+
+interface is_posinf
+ module procedure is_posinf_sp, is_posinf_dp
+end interface is_posinf
+
+interface is_neginf
+ module procedure is_neginf_sp, is_neginf_dp
+end interface is_neginf
+
+interface is_inf
+ module procedure is_inf_sp, is_inf_dp
+end interface is_inf
+
+#endif
+
+
+contains
+
+
+pure elemental function is_finite_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+logical :: y
+y = .true.
+end function is_finite_sp
+
+pure elemental function is_finite_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+logical :: y
+y = .true.
+end function is_finite_dp
+
+pure elemental function is_posinf_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_posinf_sp
+
+pure elemental function is_posinf_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_posinf_dp
+
+pure elemental function is_neginf_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_neginf_sp
+
+pure elemental function is_neginf_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_neginf_dp
+
+pure elemental function is_inf_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_inf_sp
+
+pure elemental function is_inf_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_inf_dp
+
+
+#if PRIMA_QP_AVAILABLE == 1
+
+pure elemental function is_finite_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+logical :: y
+y = .true.
+end function is_finite_qp
+
+pure elemental function is_posinf_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_posinf_qp
+
+pure elemental function is_neginf_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_neginf_qp
+
+pure elemental function is_inf_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_inf_qp
+
+#endif
+
+
+end module inf_mod
diff --git a/fortran/tests/testsuite/infnan.F90 b/fortran/tests/testsuite/infnan.F90
new file mode 100644
index 0000000000..001468c169
--- /dev/null
+++ b/fortran/tests/testsuite/infnan.F90
@@ -0,0 +1,120 @@
+#include "ppf.h"
+
+module infnan_mod
+!--------------------------------------------------------------------------------------------------!
+! This module provides functions that check whether a real number X is infinite, NaN, or finite.
+!
+! N.B.:
+!
+! 1. We implement all the procedures for single, double, and quadruple precisions (when available).
+! When we interface the Fortran code with other languages (e.g., MATLAB), the procedures may be
+! invoked in both the Fortran code and the gateway (e.g., MEX gateway), which may use different real
+! precisions (e.g., the Fortran code may use single, but the MEX gateway uses double by default).
+!
+! 2. We decide not to use IEEE_IS_NAN and IEEE_IS_FINITE provided by the intrinsic IEEE_ARITHMETIC
+! available since Fortran 2003. The reason is as follows. The Fortran standards require these two
+! procedures to return default logical values. However, if the code is compiled by gfortran 9.3.0
+! with the option -fdefault-integer-8 (which is adopted by MEX and cannot be changed easily), then
+! the compiler will enforce the default logical value to be 64-bit, but the returned kinds of
+! IEEE_IS_NAN and IEEE_IS_FINITE will not be changed accordingly, and they will remain 32-bit if
+! that is the default logical kind. Therefore, the returned kinds of IEEE_IS_NAN and IEEE_IS_FINITE
+! may actually differ from the default logical kind due to this compiler option and hence violate
+! the Fortran standard! This is fatal, because a piece of perfectly standard-compliant code may fail
+! to be compiled due to type mismatches. It is similar with ifort 2021.2.0 and nagfor 7.0. See more
+! discussions at https://stackoverflow.com/questions/69060408.
+!
+! 3. The functions aim to work even when compilers are invoked with aggressive optimization flags,
+! such as `gfortran -Ofast`.
+!
+! 4. There are many ways to implement functions like IS_NAN. However, not all of them work with
+! aggressive optimization flags. For example, for gfortran 9.3.0, the IEEE_IS_NAN included in
+! IEEE_ARITHMETIC does not work with `gfortran -Ofast`. Another example, when X is NaN, (X == X) and
+! (X >= X) are evaluated as TRUE by Flang 7.1.0 and nvfortran 21.3-0, even if they are invoked
+! without any explicit optimization flag. See the following for discussions
+! https://stackoverflow.com/questions/15944614
+!
+! 5. The most naive implementation for IS_NAN is (X /= X). However, compilers (e.g., gfortran) may
+! complain about inequality comparison between floating-point numbers. In addition, it is likely to
+! fail when compilers are invoked with aggressive optimization flags.
+!
+! 6. The implementation below is totally empirical, in the sense that I have not studied in-depth
+! what the aggressive optimization flags really do, but only made some tests and found the
+! implementation that worked correctly. The story may change when compilers are changed/updated.
+!
+! 7. N.B.: Do NOT change the functions without thorough testing. Their implementations are delicate.
+! For example, when compilers are invoked with aggressive optimization flags,
+! (X <= HUGE(X) .AND. X >= -HUGE(X)) may differ from (ABS(X) <= HUGE(X)) ,
+! (X > HUGE(X) .OR. X < -HUGE(X)) may differ from (ABS(X) > HUGE(X)) , and
+! (ABS(X) > HUGE(X) .AND. X > 0) may differ from (X > HUGE(X)) .
+!
+! 8. IS_NAN must be implemented in a file separated from IS_INF and IS_FINITE (a separated module is
+! not enough). Otherwise, IS_NAN may not work with some compilers invoked with aggressive
+! optimization flags e.g., ifx -fast with ifx 2022.1.0 or flang -Ofast with flang 15.0.3.
+! Similarly, the intrinsic HUGE must be wrapped by HUGE_VALUE in a file separated from IS_INF and
+! IS_FINITE. Otherwise, IS_INF and IS_FINITE do not work with `gfortran-13 -Ofast`.
+!
+! 9. Even though the functions involve invocation of ABS and HUGE, their performance (in terms of
+! CPU time) turns out comparable to or even better than the functions in IEEE_ARITHMETIC.
+!
+! Coded by Zaikun ZHANG (www.zhangzk.net).
+!
+! Started: July 2020.
+!
+! Last Modified: Friday, October 06, 2023 PM08:31:21
+!--------------------------------------------------------------------------------------------------!
+
+use, non_intrinsic :: huge_mod, only : huge_value
+use, non_intrinsic :: inf_mod, only : is_finite, is_inf, is_posinf, is_neginf
+implicit none
+private
+public :: is_finite, is_posinf, is_neginf, is_inf, is_nan
+
+#if PRIMA_QP_AVAILABLE == 1
+
+interface is_nan
+ module procedure is_nan_sp, is_nan_dp, is_nan_qp
+end interface is_nan
+
+#else
+
+interface is_nan
+ module procedure is_nan_sp, is_nan_dp
+end interface is_nan
+
+#endif
+
+
+contains
+
+
+pure elemental function is_nan_sp(x) result(y)
+use, non_intrinsic :: consts_mod, only : SP
+implicit none
+real(SP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_nan_sp
+
+pure elemental function is_nan_dp(x) result(y)
+use, non_intrinsic :: consts_mod, only : DP
+implicit none
+real(DP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_nan_dp
+
+
+#if PRIMA_QP_AVAILABLE == 1
+
+pure elemental function is_nan_qp(x) result(y)
+use, non_intrinsic :: consts_mod, only : QP
+implicit none
+real(QP), intent(in) :: x
+logical :: y
+y = .false.
+end function is_nan_qp
+
+#endif
+
+
+end module infnan_mod
diff --git a/fortran/tests/testsuite/noise.f90 b/fortran/tests/testsuite/noise.f90
index cf9fe05c9e..bc7bb71047 100644
--- a/fortran/tests/testsuite/noise.f90
+++ b/fortran/tests/testsuite/noise.f90
@@ -6,7 +6,7 @@ module noise_mod
!
! Started: September 2021
!
-! Last Modified: Friday, September 08, 2023 PM07:27:45
+! Last Modified: Mon 25 Aug 2025 12:11:45 PM CST
!--------------------------------------------------------------------------------------------------!
use, non_intrinsic :: pintrf_mod, only : OBJ, OBJCON
@@ -238,7 +238,7 @@ function noisyfun0(x, f, noise_level, noise_type) result(noisy_f)
!elseif (r > 0.7_RP) then
!noisy_f = IEEE_VALUE(0.0_RP, IEEE_QUIET_NAN) ! NaN
elseif (r < 0.1_RP) then
- noisy_f = -10.0_RP * max(abs(f), ONE) ! Discontinuously decreased values
+ noisy_f = -10.0_RP * rand() * max(abs(f), ONE) ! Discontinuously decreased values
end if
call setseed(seedsav) ! Recover the random seed by SEEDSAV.
deallocate (seedsav) ! SEEDSAV is deallocated automatically at return.
diff --git a/fortran/tests/testsuite/param.f90 b/fortran/tests/testsuite/param.f90
index daec98c62b..533c2f80d7 100644
--- a/fortran/tests/testsuite/param.f90
+++ b/fortran/tests/testsuite/param.f90
@@ -6,20 +6,18 @@ module param_mod
!
! Started: September 2021
!
-! Last Modified: Friday, January 27, 2023 AM07:48:04
+! Last Modified: Sun 31 Aug 2025 10:18:53 AM CST
!--------------------------------------------------------------------------------------------------!
use, non_intrinsic :: consts_mod, only : RP, IK, TENTH
implicit none
private
-public :: DIMSTRIDE_DFT, MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, NOISE_LEVEL_DFT, RANDSEED_DFT, NOISE_TYPE_DFT
+public :: MINDIM_DFT, MAXDIM_DFT, NRAND_DFT, NOISE_LEVEL_DFT, RANDSEED_DFT, NOISE_TYPE_DFT
-! Use an odd stride so that both odd and even dimensional problems will be tested.
-integer(IK), parameter :: DIMSTRIDE_DFT = 5
! Testing univariate problems can help us to uncover some bugs that can only occur in extreme cases.
integer(IK), parameter :: MINDIM_DFT = 1
integer(IK), parameter :: MAXDIM_DFT = 20
-integer(IK), parameter :: NRAND_DFT = 2
+integer(IK), parameter :: NRAND_DFT = 3 ! Default number of random tests when the dimension is small
integer, parameter :: RANDSEED_DFT = 42
real(RP), parameter :: NOISE_LEVEL_DFT = TENTH
character(len=*), parameter :: NOISE_TYPE_DFT = 'gaussian'
diff --git a/fortran/tests/testsuite/prob.f90 b/fortran/tests/testsuite/prob.f90
index 76fd530514..eb88defd6e 100644
--- a/fortran/tests/testsuite/prob.f90
+++ b/fortran/tests/testsuite/prob.f90
@@ -40,7 +40,7 @@ module prob_mod
integer, parameter :: PNLEN = 64
type PROB_T
- character(len=PNLEN) :: probname
+ character(len=PNLEN) :: probname
character :: probtype
integer(IK) :: m
integer(IK) :: n
diff --git a/fortran/tests/testsuite/ptinsq.f90 b/fortran/tests/testsuite/ptinsq.f90
index f7c3c22eaf..edbb1b9067 100644
--- a/fortran/tests/testsuite/ptinsq.f90
+++ b/fortran/tests/testsuite/ptinsq.f90
@@ -59,7 +59,7 @@ end subroutine construct_ptinsq
subroutine calfun_ptinsq(x, f)
-use, non_intrinsic :: consts_mod, only : IK, RP
+use, non_intrinsic :: consts_mod, only : IK, RP, TEN, MAXPOW10
use, non_intrinsic :: debug_mod, only : assert
implicit none
@@ -84,7 +84,7 @@ subroutine calfun_ptinsq(x, f)
do i = 4, n, 2_IK
do j = 2, i - 2_IK, 2_IK
temp = (x(i - 1) - x(j - 1))**2 + (x(i) - x(j))**2
- temp = max(temp, 1.0E-6_RP)
+ temp = max(temp, TEN**max(-6, -MAXPOW10))
f = f + 1.0_RP / sqrt(temp)
end do
end do
diff --git a/fortran/tests/testsuite/rand.f90 b/fortran/tests/testsuite/rand.f90
index ad3c8b1bf6..0dc6f3f969 100644
--- a/fortran/tests/testsuite/rand.f90
+++ b/fortran/tests/testsuite/rand.f90
@@ -6,7 +6,7 @@ module rand_mod
!
! Started: September 2021
!
-! Last Modified: Thursday, September 07, 2023 PM06:44:08
+! Last Modified: Tue 19 Aug 2025 02:44:38 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -36,6 +36,48 @@ module rand_mod
contains
+function scramble_seed(n, seed) result(scrambled_seed)
+!--------------------------------------------------------------------------------------------------!
+! This function scrambles the input SEED using a linear congruential generator (LCG) algorithm.
+! The output is a vector of length N, where each entry is a scrambled version of the input seed.
+! The LCG parameters used are based on the MINSTD algorithm.
+! -------------------------------------------------------------------------------------------------!
+use, non_intrinsic :: consts_mod, only : INT64
+implicit none
+
+! Inputs
+integer, intent(in) :: n
+integer, intent(in) :: seed
+
+! Outputs
+integer :: scrambled_seed(n)
+
+! LCG parameters (MINSTD)
+integer(INT64), parameter :: modulus = 2147483647_INT64 ! 2^31 - 1
+integer(INT64), parameter :: multiplier = 48271_INT64
+
+! Local variables
+integer :: i
+integer :: j
+integer(INT64) :: iseed
+
+! Map seed to [1, modulus-1] in case it is larger (unlikely).
+iseed = abs(modulo(int(seed, INT64), modulus))
+if (iseed == 0) iseed = 1 ! Ensure non-zero seed
+
+! Apply LCG to generate n scrambled seeds.
+do i = 1, n
+ do j = 1, 16 ! Loop to ensure good scrambling; without this, flang-new 20 produces poor results
+ ! Compute the next ISEED. Since MULTIPLIER is a prime number, and the input ISEED < MODULUS,
+ ! the output ISEED is non-zero.
+ iseed = modulo(multiplier * iseed, modulus) ! 64-bit safe multiplication, avoiding overflow
+ end do
+ ! Convert to the default integer and store.
+ scrambled_seed(i) = int(iseed)
+end do
+end function scramble_seed
+
+
subroutine getseed(seed)
!--------------------------------------------------------------------------------------------------!
! This subroutine gets the current random seed, saving it in SEED.
@@ -72,49 +114,15 @@ end subroutine getseed
subroutine setseed0(seed)
!--------------------------------------------------------------------------------------------------!
! This procedure uses SEED to initialize the random seed. See SEED_TO_PUT for details.
-! N.B.: We use exclusively the DEFAULT INTEGER and the DOUBLE-PRECISION REAL in this procedure.
+! N.B.: We use exclusively the DEFAULT INTEGER in this procedure.
!--------------------------------------------------------------------------------------------------!
-use, non_intrinsic :: consts_mod, only : DP
-use, non_intrinsic :: debug_mod, only : errstop
-use, non_intrinsic :: infos_mod, only : MEMORY_ALLOCATION_FAILS
implicit none
integer, intent(in) :: seed
-
-character(len=*), parameter :: srname = 'SETSEED0'
-integer :: alloc_status
-integer :: p
-integer :: i
integer :: n ! Should be a default INTEGER according to F2018.
-integer, allocatable :: seed_to_put(:)
-real(DP), allocatable :: cos_seed(:)
call random_seed(size=n)
-
-if (allocated(seed_to_put)) deallocate (seed_to_put)
-allocate (seed_to_put(1:n), stat=alloc_status)
-if (.not. (alloc_status == 0 .and. allocated(seed_to_put))) then
- call errstop(srname, 'Memory allocation fails', MEMORY_ALLOCATION_FAILS)
-end if
-
-if (allocated(cos_seed)) deallocate (cos_seed)
-allocate (cos_seed(1:n), stat=alloc_status)
-if (.not. (alloc_status == 0 .and. allocated(cos_seed))) then
- call errstop(srname, 'Memory allocation fails', MEMORY_ALLOCATION_FAILS)
-end if
-
-! Some compilers cannot guarantee ABS(COS) <= 1 when the variable is huge. This may cause overflow.
-! Note that 1.0_DP cannot be written as ONE, because KIND(ONE) = RP, which may not be DP.
-cos_seed = min(max(cos(real([(i, i=seed - (n - 1), seed)], DP)), -1.0_DP), 1.0_DP)
-seed_to_put = ceiling(0.9_DP * real(huge(0), DP) * cos_seed)
-deallocate (cos_seed)
-! P takes a `+1` at the end, so that it is guarantee to be positive.
-p = int(real(huge(0), DP) / 1.0E2_DP) + 1
-seed_to_put = modulo(seed_to_put, p) + 1
-
-call random_seed(put=seed_to_put)
-deallocate (seed_to_put)
-
+call random_seed(put=scramble_seed(n, seed))
end subroutine setseed0
subroutine setseed1(seed)
@@ -177,7 +185,7 @@ function rand1(n) result(x)
implicit none
integer(IK), intent(in) :: n
-real(RP) :: x(max(n, 0_IK))
+real(RP) :: x(n)
call random_number(harvest=x)
end function
@@ -190,7 +198,7 @@ function rand2(m, n) result(x)
implicit none
integer(IK), intent(in) :: m, n
-real(RP) :: x(max(m, 0_IK), max(n, 0_IK))
+real(RP) :: x(m, n)
call random_number(harvest=x)
end function
@@ -208,8 +216,8 @@ function randn0() result(x)
real(RP) :: u
real(RP) :: v
-call random_number(harvest=u)
-call random_number(harvest=v)
+u = rand()
+v = rand()
x = sqrt(-TWO * log(ONE - u)) * cos(TWO * PI * v)
end function randn0
@@ -221,13 +229,13 @@ function randn1(n) result(x)
implicit none
integer(IK), intent(in) :: n
-real(RP) :: x(max(n, 0_IK))
+real(RP) :: x(n)
-real(RP) :: u(size(x))
-real(RP) :: v(size(x))
+real(RP) :: u(n)
+real(RP) :: v(n)
-call random_number(harvest=u)
-call random_number(harvest=v)
+u = rand(n)
+v = rand(n)
x = sqrt(-TWO * log(ONE - u)) * cos(TWO * PI * v)
end function randn1
@@ -239,13 +247,13 @@ function randn2(m, n) result(x)
implicit none
integer(IK), intent(in) :: m, n
-real(RP) :: x(max(m, 0_IK), max(n, 0_IK))
+real(RP) :: x(m, n)
-real(RP) :: u(size(x, 1), size(x, 2))
-real(RP) :: v(size(x, 1), size(x, 2))
+real(RP) :: u(m, n)
+real(RP) :: v(m, n)
-call random_number(harvest=u)
-call random_number(harvest=v)
+u = rand(m, n)
+v = rand(m, n)
x = sqrt(-TWO * log(ONE - u)) * cos(TWO * PI * v)
end function randn2
diff --git a/fortran/tests/tools/9src b/fortran/tests/tools/9src
new file mode 100755
index 0000000000..ed458078a7
--- /dev/null
+++ b/fortran/tests/tools/9src
@@ -0,0 +1,166 @@
+#!/usr/bin/env bash
+# This script pre-processes the Fortran source code for g95
+# It does not support REAL32, REAL64, REAL128 in ISO_FORTRAN_ENV.
+# It does not recognize the `back` keyword in `min/maxloc`, which is available since F2008.
+# It does not support allocatable characters (variable length strings), which is F2003.
+# It does not support `error stop code`, which is F2003.
+
+DIR="$(realpath "$1")"
+CONSTSF90="consts.F90"
+CONSTS="$DIR/common/$CONSTSF90"
+MEMORYF90="memory.F90"
+MEMORY="$DIR/common/$MEMORYF90"
+STRINGF90="string.f90"
+STRING="$DIR/common/$STRINGF90"
+CMN_FPRINTF90="fprint.f90"
+CMN_FPRINT="$DIR/common/$CMN_FPRINTF90"
+MEX_FPRINTF90="fprint.F90"
+MEX_FPRINT="$DIR/common/$MEX_FPRINTF90"
+UOBYQB="$DIR/uobyqa/uobyqb.f90"
+NEWUOB="$DIR/newuoa/newuob.f90"
+BOBYQB="$DIR/bobyqa/bobyqb.f90"
+LINCOB="$DIR/lincoa/lincob.f90"
+COBYLB="$DIR/cobyla/cobylb.f90"
+GETACTF90="getact.f90"
+GETACT="$DIR/lincoa/$GETACTF90"
+PREPROCF90="preproc.f90"
+PREPROC="$DIR/common/$PREPROCF90"
+LINALGF90="linalg.f90"
+LINALG="$DIR/common/$LINALGF90"
+CMN_FPRINTF90="fprint.f90"
+CMN_FPRINT="$DIR/common/$CMN_FPRINTF90"
+MEX_FPRINTF90="fprint.F90"
+MEX_FPRINT="$DIR/common/$MEX_FPRINTF90"
+DEBUGF90="debug.F90"
+DEBUG="$DIR/common/$DEBUGF90"
+NOISEF90="noise.f90"
+NOISE="$DIR/testsuite/$NOISEF90"
+FMXAPIF90="fmxapi.F90"
+FMXAPI="$DIR/$FMXAPIF90"
+MESSAGEF90="message.f90"
+MESSAGE="$DIR/common/$MESSAGEF90"
+
+
+if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
+ printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
+ exit 1
+fi
+
+if [[ -f "$CONSTS" ]] ; then
+ sed -i "/end module consts_mod/d" "$CONSTS"
+
+ STR="use, intrinsic :: iso_fortran_env, only : INT16, INT32, INT64, SP => REAL32, DP => REAL64"
+ sed -i "/$STR/d" "$CONSTS"
+ STR="HP => REAL16"
+ sed -i "/$STR/d" "$CONSTS"
+ STR="QP => REAL128"
+ sed -i "/$STR/d" "$CONSTS"
+ OLD_STR="! Define the real kind to be used in the Fortran code."
+ NEW_STR="$OLD_STR\ninteger, parameter :: SP = kind(0.0), DP = kind(0.0D0)\n#if PRIMA_QP_AVAILABLE\ninteger, parameter :: QP = selected_real_kind(p=30)\n#endif"
+ sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
+ OLD_STR="integer, parameter :: IK = INT16"
+ NEW_STR="integer, parameter :: IK = selected_int_kind(4)"
+ sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
+ echo "integer, parameter :: INT16 = selected_int_kind(4)" >> "$CONSTS"
+ OLD_STR="integer, parameter :: IK = INT32"
+ NEW_STR="integer, parameter :: IK = selected_int_kind(7)"
+ sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
+ echo "integer, parameter :: INT32 = selected_int_kind(7)" >> "$CONSTS"
+ OLD_STR="integer, parameter :: IK = INT64"
+ NEW_STR="integer, parameter :: IK = selected_int_kind(14)"
+ sed -i "s/$OLD_STR/$NEW_STR/" "$CONSTS"
+ echo "integer, parameter :: INT64 = selected_int_kind(14)" >> "$CONSTS"
+
+ echo "end module consts_mod" >> "$CONSTS"
+fi
+
+if [[ -f "$MEMORY" ]] ; then
+ OLD_STR="y = int(storage_size(x) \/ 8, kind(y))"
+ NEW_STR="y = int(8, kind(y))"
+ sed -i "s/$OLD_STR/$NEW_STR/" "$MEMORY"
+fi
+
+if [[ -f "$STRING" ]] ; then
+ sed -i "/ndgt_loc = min(ndgt_loc, floor(real(MAX_NUM_STR_LEN - 5) \/ 2.0))/d" "$STRING"
+ sed -i "/nexp_loc = min(nexp_loc, floor(real(MAX_NUM_STR_LEN - 5) \/ 2.0))/d" "$STRING"
+fi
+
+for FILE in "$CMN_FPRINT" "$MEX_FPRINT" ; do
+ if [[ -f "$FILE" ]] ; then
+ sed -i "s|funit_loc = -1|funit_loc = 42|g" "$FILE"
+ sed -i "s|newunit|unit|g" "$FILE"
+ fi
+done
+
+# g95 does not support internal subroutines as arguments.
+if [[ -f "$COBYLB" ]] ; then
+ sed -ni '1,/\s*!\s*Calculation ends\s*!/p;/end subroutine cobylb/,$p' "$COBYLB"
+ sed -i "s|calcfc_internal|calcfc|" "$COBYLB"
+fi
+
+for FILE in "$NEWUOB" "$UOBYQB" "$BOBYQB" ; do
+ if [[ -f "$FILE" ]] ; then
+ sed -i "s|terminate=terminate)|0.0_RP, [0.0_RP], terminate)|g" "$FILE"
+ fi
+done
+if [[ -f "$LINCOB" ]]; then
+ sed -i "s|terminate=terminate)|[0.0_RP], terminate)|g" "$LINCOB"
+fi
+
+
+if [[ -f "$GETACT" ]] ; then
+ sed -i "s/,\s*back\s*=\s*\.true\.//" "$GETACT"
+fi
+
+for FILE in "$PREPROC" "$LINALG" "$CMN_FPRINT" "$MEX_FPRINT" "$DEBUG" "$NOISE" "$FMXAPI" ; do
+ if [[ -f "$FILE" ]] ; then
+ sed -i "s/character(len=:), allocatable/character(len=1024)/" "$FILE"
+ sed -i "/character(len=\*), parameter :: newline/d" "$FILE"
+ sed -i "s|newline|achar(10)|g" "$FILE"
+ fi
+done
+
+if [[ -f "$STRING" ]] ; then
+ sed -i "s/character(len=:), allocatable/character(len=1024)/" "$STRING"
+ sed -i "/LEN(S) <= MAX_NUM_STR_LEN/d" "$STRING"
+ sed -i "s/write (str, sformat) x/write(*,*) sformat; write (str, *) x/" "$STRING"
+ sed -i "/character(len=\*), parameter :: newline/d" "$STRING"
+ sed -i "s|newline|achar(10)|g" "$STRING"
+fi
+
+if [[ -f "$MEMORY" ]] ; then
+ sed -i "s/character(len=:), allocatable/character(len=1024)/" "$MEMORY"
+ sed -i "/allocate (character/d" "$MEMORY"
+ sed -i "/call validate(allocated(x), 'X is allocated', srname)/d" "$MEMORY"
+ sed -i "s/integer :: alloc_status/integer :: alloc_status = 0/" "$MEMORY"
+fi
+
+# message.f90 does not work due to the changes to string.f90, especially to real2str_vector.
+(cd "$DIR" && grep -R 'call .*msg' | sed 's|:.*$||' | grep -v 'debug.F90\|fmxapi.F90' | xargs sed -i "s|call .*msg.*$|write(*,*) solver|" && cd - || exit 1) > /dev/null
+(cd "$DIR" && grep -R 'use.*message_mod' | sed 's|:.*$||' | xargs sed -i "/use.*message_mod/d" && cd - || exit 1) > /dev/null
+printf "module message_mod \n end module message_mod" > "$MESSAGE"
+
+if [[ -f "$DEBUG" ]] ; then
+ sed -i "s|^\s*error stop.*$|stop|" "$DEBUG"
+fi
+
+for SOL in cobyla uobyqa newuoa bobyqa lincoa ; do
+ FILE="$DIR/test_$SOL.f90"
+ if [[ -f $FILE ]] ; then
+ sed -i "s|character(len=:), allocatable|character(len=1024)|g" "$FILE"
+ sed -i "/safealloc(testdim.*$/d" "$FILE"
+ fi
+done
+
+if [[ -d "$DIR"/lincoa ]] ; then
+ for FILE in "$DIR"/lincoa/* ; do
+ if [[ -f "$FILE" ]] ; then
+ sed -i "s|count(xl > -REALMAX)|size(xl)|" "$FILE"
+ sed -i "s|count(xu < REALMAX)|size(xu)|" "$FILE"
+ fi
+ done
+ sed -i -e '/amat = reshape(shape=shape(amat)/,+3d' "$DIR"/lincoa/lincoa.f90
+ sed -i '/idmat/d' "$DIR"/lincoa/lincoa.f90
+fi
+
+exit 0
diff --git a/fortran/tests/checktest b/fortran/tests/tools/checktest
similarity index 80%
rename from fortran/tests/checktest
rename to fortran/tests/tools/checktest
index e871528212..be27656855 100755
--- a/fortran/tests/checktest
+++ b/fortran/tests/tools/checktest
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
#
# N.B.: Execute checktest by "bash ./checktest" instead of just "./checktest", because
# "./checktest" may not be executable on all operating systems.
@@ -11,34 +11,41 @@ fi
LOGFILE="$2"
WARNINGS="$(grep -i "warning\|Remark\|Questionable\|attention\|F[0-9]*-W\|F[0-9]*-I" "$LOGFILE" \
+ | grep -vi "Fortran runtime warning: IEEE.*exception not supported" \
| grep -vi "WARNING -- When --chk x is specified" \
| grep -vi "[0-9]\s*warning" \
| grep -vi "in __debug_mod_MOD_warning" \
| grep -vi "forrtl: warning (406): fort: (1): In call to WHATDAY, an array temporary was created for argument #1" \
| grep -vi "MAXFILT is too small" \
- | grep -vi "Only the history of the last [0-9]* iteration(s) is recorded" \
+ | grep -vi "Only the history of the last [0-9]* function evaluation(s) is recorded" \
| grep -vi "circle\.f90(36): remark #7712: This variable has not been used\.\s*\[ANGLE\]" \
| grep -vi "warningid\|warningmsg" \
- | grep -vi "Warning: TRSTEP: Assertion failed: D is initialized." \
+ | grep -vi "Warning: SAVEHIST: Assertion fails: XHIST" \
+ | grep -vi "Warning: TRSTEP: Assertion fails: D is initialized" \
| grep -vi "Warning: UOBYQA: \|Warning: NEWUOA: \|Warning: BOBYQA: \|Warning: LINCOA: \|Warning: COBYLA: " \
| grep -vi "maybe due to overflow, or the input is not an integer" \
+ | grep -vi "Assertion fails: SUM(VLAG(1:NPT)) == 1" \
| grep -vi "invalid maxfun\|invalid maxhist\|invalid npt\|invalid rhobeg\|invalid rhoend\|invalid ftarget\|invalid ctol\|invalid maxfilt\|errstop")"
-ERRORS="$(grep -i "error\|fail\|violation\|exception\|invalid\|bad\|overflow\|segmentation\|fault\|illegal\|F[0-9]*-S\|F[0-9]*-F\|F[0-9]*-E\|Could\ not\ resolve\|not\ defined\|Cannot connect to licence server (error 113)" "$LOGFILE" \
+ERRORS="$(grep -i "error\|fail\|violation\|exception\|invalid\|bad\|overflow\|segmentation\|fault\|illegal\|Subscript out of range\|F[0-9]*-S\|F[0-9]*-F\|F[0-9]*-E\|Could not resolve\|not defined\|command not found\|Cannot connect to licence server (error 113)" "$LOGFILE" \
+ | grep -vi "Fortran runtime warning: IEEE.*exception not supported" \
| grep -vi "default" \
| grep -vi "[0-9]\s*error" \
| grep -vi "[0-9a-z]bad\|bad[0-9a-z]" \
| grep -vi "[0-9a-z]fail" \
| grep -vi "Warning: .*: Failed to open file .*_output" \
- | grep -vi "Warning:\ Floating\ overflow\ occurred" \
+ | grep -vi "Warning: Floating overflow occurred" \
+ | grep -vi "Warning: Floating underflow occurred" \
+ | grep -vi "because rounding errors are" \
| grep -vi "precaution against overflow" \
| grep -vi "precaution against rounding error" \
- | grep -vi "Warning:\ Floating\ invalid\ operation\ occurred" \
+ | grep -vi "Warning: Floating invalid operation occurred" \
| grep -vi "Processor does not support trapping of floating-point exceptions" \
- | grep -vi "Assertion failed: Q interpolates FVAL at XPT" \
- | grep -vi "Assertion failed: H = W^{-1} in (3.12) of the NEWUOA paper" \
- | grep -vi "Assertion failed: BETA_TEST == BETA" \
- | grep -vi "Assertion failed: SUM(VLAG(1:NPT)) == 1" \
+ | grep -vi "Warning: SAVEHIST: Assertion fails: XHIST" \
+ | grep -vi "Assertion fails: Q interpolates FVAL at XPT" \
+ | grep -vi "Assertion fails: H = W^{-1} in (3.12) of the NEWUOA paper" \
+ | grep -vi "Assertion fails: BETA_TEST == BETA" \
+ | grep -vi "Assertion fails: SUM(VLAG(1:NPT)) == 1" \
| grep -vi "flist:.*: Invalid format" \
| grep -vi "BAD_TRSTEP" \
| grep -vi "moderr\|errorid\|errormsg\|errstop" \
@@ -46,7 +53,7 @@ ERRORS="$(grep -i "error\|fail\|violation\|exception\|invalid\|bad\|overflow\|se
| grep -vi "berr\|erres" \
| grep -vi "violation = " \
| grep -vi "int violation" \
- | grep -vi "Warning: TRSTEP: Assertion failed: D is initialized." \
+ | grep -vi "Warning: TRSTEP: Assertion fails: D is initialized" \
| grep -vi "Warning: UOBYQA: Invalid\|Warning: NEWUOA: Invalid\|Warning: BOBYQA: Invalid\|Warning: LINCOA: Invalid\|Warning: COBYLA: Invalid" \
| grep -vi "maybe due to overflow, or the input is not an integer" \
| grep -vi "invalid maxfun\|invalid maxhist\|invalid npt\|invalid rhobeg\|invalid rhoend\|invalid ftarget\|invalid ctol\|invalid maxfilt\|errstop")"
diff --git a/fortran/tests/frdsrc b/fortran/tests/tools/dsrc
similarity index 84%
rename from fortran/tests/frdsrc
rename to fortran/tests/tools/dsrc
index 4d1903e38e..e770429865 100755
--- a/fortran/tests/frdsrc
+++ b/fortran/tests/tools/dsrc
@@ -1,9 +1,8 @@
-#!/bin/bash
-# This script pre-processes the Fortran source code for classic flang 7.0.1, Huawei Bisheng flang
-# 1.3.3, NVIDIA nvfortran 22.3, and AOCC 3.2.0 flang, which raise a false positive error of
+#!/usr/bin/env bash
+# This script pre-processes the Fortran source code for Huawei Bisheng Flang 2.1.0,
+# Arm Fortran Compiler 23.1, and AOCC 5.1 Flang, which raise a false positive error about
# out-bound subscripts when invoked with the -Mbounds flag.
-# See https://github.com/flang-compiler/flang/issues/1238 and
-# https://forums.developer.nvidia.com/t/bug-in-nvfortran-22-3-false-positive-of-out-bound-subscripts
+# See https://github.com/flang-compiler/flang/issues/1238
DIR="$(realpath "$1")"
LINALG="$DIR/common/linalg.f90"
diff --git a/fortran/common/flint b/fortran/tests/tools/flint
similarity index 76%
rename from fortran/common/flint
rename to fortran/tests/tools/flint
index 94e6a6ccde..308737ebc5 100755
--- a/fortran/common/flint
+++ b/fortran/tests/tools/flint
@@ -1,35 +1,35 @@
-#!/bin/bash
+#!/usr/bin/env bash
# This script checks the Fortran code of the solver corresponding to this directory.
-# Usage: flint [--compiler_name [--all]] or flint -c|--clean
+# Usage: flint [--compiler_name [--all]] or flint --clean
# Parse inputs.
if [[ $# -gt 2 ]] ; then
- printf "Usage: flint [--compiler_name [--all]] or flint -c|--clean\n"
+ printf "Usage: flint [--compiler_name [--all]] or flint --clean\n"
exit 2
fi
TEST_ALL="N"
CLEAN="N"
-# sunf95 is case sensitive. Put it as the first.
-# Disable atest for now, since it encounters licensing errors randomly
-printf "\n****** ATEST is disabled ******\n"
+# Decide the list of tests to make.
# N.B.: () defines an array
-COMPILER_LIST=(sunf95 gfortran nagfor g95 ifort nvfortran flang aflang ifx)
-CLIST=(s g n 9 i v f d x)
+# sunf95 is case sensitive. Put it as the first.
+COMPILER_LIST=(sunf95 gfortran amdflang nagfor g95 ifort nvfortran ifx)
+CLIST=(s g m n 9 i v x)
COMP_LIST=""
for i in "${!COMPILER_LIST[@]}"; do
if type "${COMPILER_LIST[$i]}" &> /dev/null ; then
- if [[ "${COMPILER_LIST[$i]}" == "flang" ]] ; then
- # Do not make ftest if flang is provided by ARM or AMD; rtest and dtest should be made instead.
- if ! type "${COMPILER_LIST[$i]}" | grep -q '\/opt\/arm\|\/opt\/AMD' ; then
- COMP_LIST="$COMP_LIST ${CLIST[$i]}"
- fi
- else
- COMP_LIST="$COMP_LIST ${CLIST[$i]}"
- fi
+ COMP_LIST="$COMP_LIST ${CLIST[$i]}"
fi
done
+# Do not make ftest if flang is provided by ARM, AMD, or AOMP; rtest, dtest, or mtest should be made instead.
+if type flang &> /dev/null && ! type "${COMPILER_LIST[$i]}" | grep -iq '\/opt\/arm\|\/opt\/AMD\|aomp' ; then
+ COMP_LIST="$COMP_LIST f"
+fi
+# Make dtest if AOCC flang is installed.
+if [[ -n "$(find -L /opt/AMD -type f -name flang -exec test -x {} \; -print 2>/dev/null)" ]] ; then
+ COMP_LIST="$COMP_LIST d"
+fi
# Parse the arguments
while [[ -n "$1" ]]; do
@@ -46,12 +46,12 @@ while [[ -n "$1" ]]; do
-i|--ifort)
COMP_LIST=" i"
;;
+ -m|--amdflang)
+ COMP_LIST=" m"
+ ;;
-n|--nagfor)
COMP_LIST=" n"
;;
- -a|--absoft)
- COMP_LIST=" a"
- ;;
-9|--g95)
COMP_LIST=" 9"
;;
@@ -64,7 +64,7 @@ while [[ -n "$1" ]]; do
-f|--flang)
COMP_LIST=" f"
;;
- -d|--aflang)
+ -d|--aoccflang)
COMP_LIST=" d"
;;
-x|--ifx)
@@ -95,7 +95,7 @@ ROOT_DIR=../..
TIME=$(date +%s)
RANDNUM="$((RANDOM*RANDOM))"
TEST_DIR=/tmp/"$(basename -- "$0")"_"$TIME"_"$RANDNUM"
-printf "Test directory:\n\n%s\n" "$TEST_DIR"
+printf "Test directory:\n\n%s\n\n" "$TEST_DIR"
# We export TEST_DIR, which will be referred to by the Makefiles.
export TEST_DIR
TEST_ROOT="$TEST_DIR"/prima
@@ -104,9 +104,11 @@ FTEST_SOLVER_DIR="$FTEST_DIR"/test."$SOLVER"
# The log directory in the testing directory.
TEST_LOG_DIR="$FTEST_SOLVER_DIR"/log
+printf "Test log directory:\n\n%s\n\n" "$TEST_LOG_DIR"
+printf "Solver log directory:\n\n%s\n\n" "$SOLVER_LOG_DIR"
# The checktest script
-CHCKTST="$FTEST_DIR"/checktest
+CHCKTST="$FTEST_DIR"/tools/checktest
# Remove the old logs.
mkdir -p "$SOLVER_LOG_DIR"
@@ -157,15 +159,19 @@ for COMP in $COMP_LIST; do
for FLG in $FLG_LIST ; do
printf "%s\t" "$FLG"
export FFLAGS=$FLG
+ make clean
INFO="$(make "$COMP"test_"$TEST"_tst_c."$SOLVER" 2>&1 \
- | grep -v "binary\ file\ matches" \
- | grep -i "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could\ not\ resolve\|not\ defined\|not\ public\ entity" \
+ | grep -v "grep -v" \
+ | grep -v "binary file matches\|info, callback_fcn" \
+ | grep -i "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could not resolve\|not defined\|not public entity" \
| grep -vi "[0-9]\s*warning\|[0-9]\s*error\|[0-9]\s*info\|infos.f90\|information\|xhist, info)\|zmat, info)\|--warning\|--error" \
- | grep -vi "pedantic-errors\|Werror\|warn\ errors\|diag-error-limit\|colour=error\|rounding\ error\|constraint violation\|default" \
- | grep -v "^- \|^| \|^\* \|^+ \|^X \|\# " \
+ | grep -vi "pedantic-errors\|Werror\|warn errors\|diag-error-limit\|colour=error\|rounding error\|constraint violation\|default" \
+ | grep -vi "warning: future releases of the clang compiler will prefer GCC installations containing libstdc" \
+ | grep -vi "is DOING NOTHING" \
+ | grep -v "^\s*- \|^\s*| \|^\s*\* \|^\s*+ \|^\s*X \|\# " \
)"
- echo "$INFO" | grep -i --color "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could\ not\ resolve\|not\ defined\|not\ public\ entity"
- if echo "$INFO" | grep -iq "error\|abort\|invalid\|violation\|fault\|illegal\|fail\|Could\ not\ resolve\|not\ defined\|not\ public\ entity" ; then
+ echo "$INFO" | grep -i --color "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could not resolve\|not defined\|not public entity"
+ if echo "$INFO" | grep -iq "error\|abort\|invalid\|violation\|fault\|illegal\|fail\|Could not resolve\|not defined\|not public entity" ; then
if [[ -f "$TEST_LOG_DIR"/"$COMP"test_"$TEST"_tst_c.log ]] ; then
LOGFILE="$COMP"test_"$TEST"_tst_c.log
else
diff --git a/fortran/tests/tools/flsrc b/fortran/tests/tools/flsrc
new file mode 100755
index 0000000000..2840a7bff5
--- /dev/null
+++ b/fortran/tests/tools/flsrc
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# This script pre-processes the Fortran source code for LLVM Flang and its descendants, including
+# AMD AOMP Flang (amdflang) and Arm Flang (armflang).
+
+DIR="$(realpath "$1")"
+LINALG="$DIR/common/linalg.f90"
+#GET_INTRISIC_LINALG="$DIR/common/get_intrinsic_linalg"
+#INTRINSIC_LINALG="$DIR/common/intrinsic_linalg.f90"
+
+if [[ "$(uname)" == Darwin || "$(uname)" == FreeBSD ]] ; then
+ SEDI="sed -i ''"
+else
+ SEDI="sed -i"
+fi
+
+if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
+ printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
+ exit 1
+fi
+
+if [[ -f "$LINALG" ]] ; then
+ echo "!!! Warning: $0 is DOING NOTHING !!!" >&2
+ # # See https://github.com/llvm/llvm-project/issues/89528
+ # STR="Y is NaN if and only if X contains NaN"
+ # $SEDI "/$STR/d" "$LINALG"
+
+ # The following should have been fixed by commit 399df3ae055566c3f6133118ebab6fdcc16cebb5
+ # # See https://github.com/llvm/llvm-project/issues/180957
+ # STR="Y >= 0 unless X contains NaN"
+ # $SEDI "/$STR/d" "$LINALG"
+
+ # # With flang 20.1.8, matprod in linalg.f90 often fails to compute matrix multiplications
+ # # correctly, generating garbage values (NaN or numbers that are nearly zero). No idea why.
+ # # The following is a workaround the replaces matprod with the intrinsic matmul.
+ # # Remove this and test again when flang is updated.
+ # Update 20250906: With flang 21.1.0, matprod seems to work fine now.
+ # if [[ -f "$GET_INTRISIC_LINALG" ]] ; then
+ # bash "$GET_INTRISIC_LINALG"
+ # mv "$INTRINSIC_LINALG" "$LINALG"
+ # fi
+fi
+
+exit 0
diff --git a/fortran/tests/tools/gsrc b/fortran/tests/tools/gsrc
new file mode 100755
index 0000000000..24edec1ee4
--- /dev/null
+++ b/fortran/tests/tools/gsrc
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# This script pre-processes the Fortran source code for gfortran when it is invoked with -Ofast.
+
+DIR="$(realpath "$1")"
+STRINGF90="string.f90"
+STRING="$DIR/common/$STRINGF90"
+
+if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
+ printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
+ exit 1
+fi
+
+if [[ "$(uname)" == Darwin || "$(uname)" == FreeBSD ]] ; then
+ SEDI='sed -i ""'
+else
+ SEDI='sed -i'
+fi
+
+# See https://fortran-lang.discourse.group/t/is-this-expected/6486
+if [[ -f "$STRING" ]] ; then
+ $SEDI '/call assert(x >= REALMAX/,+1d' "$STRING"
+ $SEDI '/call assert(x <= -REALMAX/,+1d' "$STRING"
+fi
diff --git a/fortran/common/mlint b/fortran/tests/tools/mlint
similarity index 80%
rename from fortran/common/mlint
rename to fortran/tests/tools/mlint
index 4dd4ff7e34..0fb4f54b85 100755
--- a/fortran/common/mlint
+++ b/fortran/tests/tools/mlint
@@ -1,6 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
# This script checks the Fortran code of the solver corresponding to this directory.
-# Usage: mlint [--compiler_name [-all]] or mlint -c|--clean
+# Usage: mlint [--compiler_name [-all]] or mlint --clean
# This test needs the MathWorks fintrf.h. Exit if the file cannot be found.
FINTRFH=$(locate '/extern/include/fintrf.h' 2>/dev/null | head -n 1)
@@ -13,32 +13,34 @@ fi
# Parse inputs.
if [[ $# -gt 2 ]] ; then
- printf "Usage: mlint [--compiler_name [-all]] or mlint -c|--clean\n"
+ printf "Usage: mlint [--compiler_name [-all]] or mlint --clean\n"
exit 2
fi
TEST_ALL="N"
CLEAN="N"
-# af95 does not support internal subroutine as an actual argument. So it is not included
-# sunf95 is case sensitive. Put it as the first.
+# Decide the list of tests to make.
# N.B.: () defines an array
-COMPILER_LIST=(sunf95 gfortran nagfor g95 ifort nvfortran flang aflang ifx)
-CLIST=(s g n 9 i v f d x)
+# sunf95 is case sensitive. Put it as the first.
+COMPILER_LIST=(sunf95 gfortran amdflang nagfor g95 ifort nvfortran ifx)
+CLIST=(s g m n 9 i v x)
COMP_LIST=""
for i in "${!COMPILER_LIST[@]}"; do
if type "${COMPILER_LIST[$i]}" &> /dev/null ; then
- if [[ "${COMPILER_LIST[$i]}" == "flang" ]] ; then
- # Do not make ftest if flang is provided by ARM or AMD; rtest and dtest should be made instead.
- if ! type "${COMPILER_LIST[$i]}" | grep -q '\/opt\/arm\|\/opt\/AMD' ; then
- COMP_LIST="$COMP_LIST ${CLIST[$i]}"
- fi
- else
- COMP_LIST="$COMP_LIST ${CLIST[$i]}"
- fi
+ COMP_LIST="$COMP_LIST ${CLIST[$i]}"
fi
done
+# Do not make ftest if flang is provided by ARM, AMD, or AOMP; rtest, dtest, or mtest should be made instead.
+if type flang &> /dev/null && ! type "${COMPILER_LIST[$i]}" | grep -iq '\/opt\/arm\|\/opt\/AMD\|aomp' ; then
+ COMP_LIST="$COMP_LIST f"
+fi
+# Make dtest if AOCC flang is installed.
+if [[ -n "$(find -L /opt/AMD -type f -executable -name flang -print 2>/dev/null)" ]] ; then
+ COMP_LIST="$COMP_LIST d"
+fi
+
# Parse the arguments
while [[ -n "$1" ]]; do
case "$1" in
@@ -54,6 +56,9 @@ while [[ -n "$1" ]]; do
-i|--ifort)
COMP_LIST=" i"
;;
+ -m|--amdflang)
+ COMP_LIST=" m"
+ ;;
-n|--nagfor)
COMP_LIST=" n"
;;
@@ -69,7 +74,7 @@ while [[ -n "$1" ]]; do
-f|--flang)
COMP_LIST=" f"
;;
- -d|--aflang)
+ -d|--aoccflang)
COMP_LIST=" d"
;;
-x|--ifx)
@@ -100,7 +105,7 @@ ROOT_DIR=../..
TIME=$(date +%s)
RANDNUM="$((RANDOM*RANDOM))"
TEST_DIR=/tmp/"$(basename -- "$0")"_"$TIME"_"$RANDNUM"
-printf "Test directory:\n\n%s\n" "$TEST_DIR"
+printf "Test directory:\n\n%s\n\n" "$TEST_DIR"
# We export TEST_DIR, which will be referred to by the Makefiles.
export TEST_DIR
TEST_ROOT="$TEST_DIR"/prima
@@ -109,9 +114,11 @@ MTEST_SOLVER_DIR="$MTEST_DIR"/test."$SOLVER"
# The log directory in the testing directory.
TEST_LOG_DIR="$MTEST_SOLVER_DIR"/log
+printf "Test log directory:\n\n%s\n\n" "$TEST_LOG_DIR"
+printf "Solver log directory:\n\n%s\n\n" "$SOLVER_LOG_DIR"
# The checktest script
-CHCKTST="$MTEST_DIR"/checktest
+CHCKTST="$MTEST_DIR"/tools/checktest
# Remove the old logs.
mkdir -p "$SOLVER_LOG_DIR"
@@ -163,16 +170,19 @@ for COMP in $COMP_LIST; do
printf "%s\t" "$FLG"
export FFLAGS=$FLG
INFO="$(make "$COMP"test_"$TEST"_tst."$SOLVER" 2>&1 \
- | grep -v "binary\ file\ matches" \
- | grep -i "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could\ not\ resolve" \
+ | grep -v "grep -v" \
+ | grep -v "binary file matches\|info, callback_fcn" \
+ | grep -i "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could not resolve" \
| grep -vi "[0-9]\s*warning\|[0-9]\s*error\|[0-9]\s*info\|infos.f90\|information\|xhist, info)\|zmat, info)\|--warning\|--error" \
- | grep -vi "pedantic-errors\|Werror\|warn\ errors\|diag-error-limit\|colour=error\|rounding\ error\|constraint\ violation\|default" \
+ | grep -vi "pedantic-errors\|Werror\|warn errors\|diag-error-limit\|colour=error\|rounding error\|constraint violation\|default" \
| grep -vi "INFO_DFT\|==\s*info\|errorid\|errormsg" \
| grep -vi "\[xopt, fopt, info, nf, xhist, fhist\] = FUNCTION_NAME" \
+ | grep -vi "warning: future releases of the clang compiler will prefer GCC installations containing libstdc" \
+ | grep -vi "is DOING NOTHING" \
| grep -v "^- \|^| \|^\* \|^+ \|^X \|\# " \
)"
- echo "$INFO" | grep -i --color "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could\ not\ resolve"
- if echo "$INFO" | grep -iq "error\|abort\|invalid\|violation\|fault\|illegal\|fail\|Could\ not\ resolve" ; then
+ echo "$INFO" | grep -i --color "starts\|warning\|error\|info\|abort\|invalid\|violation\|fault\|illegal\|fail\|questionable\|remark\|attention\|Could not resolve"
+ if echo "$INFO" | grep -iq "error\|abort\|invalid\|violation\|fault\|illegal\|fail\|Could not resolve" ; then
if [[ -f "$TEST_LOG_DIR"/"$COMP"test_"$TEST"_tst.log ]] ; then
LOGFILE="$COMP"test_"$TEST"_tst.log
else
diff --git a/fortran/tests/tools/run_gdb b/fortran/tests/tools/run_gdb
new file mode 100755
index 0000000000..7b52ae1c05
--- /dev/null
+++ b/fortran/tests/tools/run_gdb
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# This script runs gdb on a given program ($1) and captures the exit code in a specified file ($2).
+# -return-child-result directs gdb to return the exit code of the program being debugged.
+#
+# N.B.: We record the exit code in the specified file rather than exiting with it directly, so that
+# the Makefile receipt continues to run subsequent tests. The exit code can be checked when needed
+# by reading the contents of the specified file.
+
+echo 0 > "$2"
+
+# `-iex "set auto-load safe-path /"` is used to avoid gdb warnings such as
+# File "XXX" auto-loading has been declined by your `auto-load safe-path' set to "YYY"
+gdb -return-child-result -batch -iex "set auto-load safe-path /" -ex run -ex "bt full" -ex quit "$1" 2>&1 || echo $? > "$2"
+
+exit 0 # Exit successfully anyway.
diff --git a/matlab/mex_gateways/tests/s9src b/fortran/tests/tools/s9src
similarity index 92%
rename from matlab/mex_gateways/tests/s9src
rename to fortran/tests/tools/s9src
index 46d17f0723..0715d1b2d7 100755
--- a/matlab/mex_gateways/tests/s9src
+++ b/fortran/tests/tools/s9src
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# This script pre-processes the Fortran source code for sunf95 and g95. They do not allow passing
# internal procedures as actual arguments, but the MEX gateways need to do so for CALFUN and CALCFC.
@@ -22,7 +22,7 @@ else
fi
# Replace the line starting with "mwPointer :: $FPTR" by "external :: $CBFUN".
-sed -i "s/^\s*mwPointer\s*::\s*$FPTR/external\ ::\ $CBFUN/" "$MEXF"
+sed -i "s/^\s*mwPointer\s*::\s*$FPTR/external :: $CBFUN/" "$MEXF"
# Remove the line starting with "$FPTR = ".
sed -i "/^\s*$FPTR\s*=/d" "$MEXF"
diff --git a/fortran/tests/sunsrc b/fortran/tests/tools/sunsrc
similarity index 81%
rename from fortran/tests/sunsrc
rename to fortran/tests/tools/sunsrc
index fb189a0632..d7ed55de01 100755
--- a/fortran/tests/sunsrc
+++ b/fortran/tests/tools/sunsrc
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# This script pre-processes the Fortran source code for sunf95.
# 1. It does not recognize overloaded intrinsic functions (particularly, their returning type/kind)
# correctly, e.g., INT in LINALG_MOD.
@@ -70,10 +70,10 @@ if [[ -f "$BOBTR" ]] ; then
sed -i "s|d($TRUELOC) = cth * d($TRUELOC) + sth * s($TRUELOC)|ind = $TRUELOC\nd(ind) = cth*d(ind)+ sth*s(ind)|" "$BOBTR"
sed -i "s|dredg = inprod(d($TRUELOC), gnew($TRUELOC))|dredg = inprod(d(ind), gnew(ind))|" "$BOBTR"
- sed -i "s|gredsq = sum(gnew(trueloc(xbdi == 0))\*\*2)|ind = $TRUELOC\ngredsq = sum(gnew(ind)\*\*2)|" "$BOBTR"
- sed -i "s|shs = inprod(s($TRUELOC), hs($TRUELOC))|ind = $TRUELOC\nshs = inprod(s(ind), hs(ind))|" "$BOBTR"
- sed -i "s|dhs = inprod(d($TRUELOC), hs($TRUELOC))|hs = inprod(d(ind), hs(ind))|" "$BOBTR"
- sed -i "s|dhd = inprod(d($TRUELOC), hdred($TRUELOC))|hs = inprod(d(ind), hdred(ind))|" "$BOBTR"
+ sed -i "s|\(^\s*\)gredsq = sum(gnew(trueloc(xbdi == 0))\*\*2)|\1ind = $TRUELOC\n\1gredsq = sum(gnew(ind)\*\*2)|" "$BOBTR"
+ sed -i "s|\(^\s*\)shs = inprod(s($TRUELOC), hs($TRUELOC))|\1ind = $TRUELOC\n\1shs = inprod(s(ind), hs(ind))|" "$BOBTR"
+ sed -i "s|dhs = inprod(d($TRUELOC), hs($TRUELOC))|dhs = inprod(d(ind), hs(ind))|" "$BOBTR"
+ sed -i "s|dhd = inprod(d($TRUELOC), hdred($TRUELOC))|dhd = inprod(d(ind), hdred(ind))|" "$BOBTR"
fi
@@ -104,8 +104,8 @@ fi
# sunf95 encounters internal errors when handling message.f90.
# In addition, message.f90 does not work due to the changes to string.f90, especially to real2str_vector.
-cd "$DIR" && grep -R 'call .*msg' | sed 's|:.*$||' | grep -v 'debug.F90\|fmxapi.F90' | xargs sed -i "s|call .*msg.*$|write(*,*) solver|" && cd - || exit 1
-cd "$DIR" && grep -R 'use.*message_mod' | sed 's|:.*$||' | xargs sed -i "/use.*message_mod/d" && cd - || exit 1
+cd "$DIR" && grep -R 'call .*msg' | sed 's|:.*$||' | grep -v 'debug.F90\|fmxapi.F90' | xargs sed -i "s|call .*msg.*$|write(*,*) solver|" && cd - > /dev/null || exit 1
+cd "$DIR" && grep -R 'use.*message_mod' | sed 's|:.*$||' | xargs sed -i "/use.*message_mod/d" && cd - > /dev/null || exit 1
printf "module message_mod \n end module message_mod" > "$MESSAGE"
if [[ -f "$DEBUG" ]] ; then
@@ -128,4 +128,18 @@ if [[ -f "$COBYLB" ]] ; then
sed -i "s|calcfc_internal|calcfc|" "$COBYLB"
fi
+# With sunf95, the recursive call of subroutines does not work correctly. Assertions will fail,
+# including "NF <= MAXFUN", "XFILT does not contain NaN", "FFILT does not contain NaN", etc.
+# Delete the lines between 'Test recursive call' and 'end module test_solver_mod' in
+# test_SOLVER.f90, and then add 'end subroutine test_solver\nend module test_solver_mod'
+# at the end of the file.
+for SOLVER in uobyqa newuoa bobyqa lincoa cobyla; do
+ TEST_SOLVERF90="$DIR/test_${SOLVER}.f90"
+ if [[ -f "$TEST_SOLVERF90" ]] ; then
+ sed -i '/Test recursive call/,/^end module test_solver_mod/d' "$TEST_SOLVERF90" || exit 1
+ echo -e "end if\nend subroutine test_solver\nend module test_solver_mod" >> $TEST_SOLVERF90
+ fi
+done
+
+
exit 0
diff --git a/fortran/tests/tools/vsrc b/fortran/tests/tools/vsrc
new file mode 100755
index 0000000000..00fa01a5c9
--- /dev/null
+++ b/fortran/tests/tools/vsrc
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+# This script pre-processes the Fortran source code for nvfortran.
+
+DIR="$(realpath "$1")"
+POWALG="$DIR/common/powalg.f90"
+
+if ! basename "$DIR" | grep -q ".test\|test." || ! [[ -d "$DIR" ]] ; then
+ printf "\n%s is not a testing directory.\n\nExit.\n\n" "$DIR"
+ exit 1
+fi
+
+# Do nothing as of nvfortran 26.1, although bugs still exist:
+# https://forums.developer.nvidia.com/t/a-bug-of-nvfortran-26-1regarding-empry-arrays/358946
+# https://github.com/zequipe/test_compiler/blob/master/test_empty_array.f90
+
+exit 0
diff --git a/fortran/uobyqa/flint b/fortran/uobyqa/flint
index 764125c9f1..75b810d749 120000
--- a/fortran/uobyqa/flint
+++ b/fortran/uobyqa/flint
@@ -1 +1 @@
-../common/flint
\ No newline at end of file
+../tests/tools/flint
\ No newline at end of file
diff --git a/fortran/uobyqa/geometry.f90 b/fortran/uobyqa/geometry.f90
index a39380a939..807623c03c 100644
--- a/fortran/uobyqa/geometry.f90
+++ b/fortran/uobyqa/geometry.f90
@@ -8,7 +8,7 @@ module geometry_uobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Monday, August 28, 2023 AM07:24:38
+! Last Modified: Tue 10 Feb 2026 02:43:25 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -41,6 +41,7 @@ function setdrop_tr(kopt, ximproved, d, pl, rho, xpt) result(knew)
use, non_intrinsic :: consts_mod, only : RP, IK, ONE, DEBUGGING
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
+use, non_intrinsic :: linalg_mod, only : trueloc
use, non_intrinsic :: powalg_mod, only : calvlag
implicit none
@@ -92,10 +93,10 @@ function setdrop_tr(kopt, ximproved, d, pl, rho, xpt) result(knew)
! However, Powell's LINCOA code is different. In his code, the KNEW after a trust-region step is
! picked in lines 72--96 of the update.f for LINCOA, where DISTSQ is calculated as the square of the
! distance to XPT(KOPT, :) (Powell recorded the interpolation points in rows). However, note that
-! the trust-region trial point has not been included in to XPT yet --- it can not be included
-! without knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code
-! picks KNEW based on the distance to the un-updated "optimal point", which is unreasonable.
-! This has been corrected in our implementation of LINCOA, yet it does not boost the performance.
+! the trust-region trial point has not been included into XPT yet --- it cannot be included without
+! knowing KNEW (see lines 332-344 and 404--431 of lincob.f). Hence Powell's LINCOA code picks KNEW
+! based on the distance to the un-updated "optimal point", which is unreasonable. This has been
+! corrected in our implementation of LINCOA, yet it does not boost the performance.
if (ximproved) then
distsq = sum((xpt - spread(xpt(:, kopt) + d, dim=2, ncopies=npt))**2, dim=1)
!!MATLAB: distsq = sum((xpt - (xpt(:, kopt) + d)).^2) % d should be a column! Implicit expansion
@@ -132,21 +133,24 @@ function setdrop_tr(kopt, ximproved, d, pl, rho, xpt) result(knew)
score(kopt) = -ONE
end if
+! SCORE(K) is NaN implies VLAG(K) is NaN, but we want ABS(VLAG) to be big. So we exclude such K.
+score(trueloc(is_nan(score))) = -ONE
+
+knew = 0
! It makes almost no difference if we change the IF below to `IF (ANY(SCORE>0))`, which is used
! in Powell's BOBYQA and LINCOA code.
if (any(score > 1) .or. (ximproved .and. any(score > 0))) then ! Powell's UOBYQA and NEWUOA code
- ! SCORE(K) is NaN implies VLAG(K) is NaN, but we want ABS(VLAG) to be big. So we exclude such K.
- knew = int(maxloc(score, mask=(.not. is_nan(score)), dim=1), kind(knew))
- !!MATLAB: [~, knew] = max(score, [], 'omitnan');
-elseif (ximproved) then
- ! Powell's code does not include the following instructions. With Powell's code, if VLAG
- ! consists of only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the
- ! following value, to make sure that the new trial point is included in the interpolation set.
- ! However, the updating subroutine will likely need to skip the update of the Lagrange
- ! polynomial, or they would be destroyed by the NaNs.
+ knew = int(maxloc(score, dim=1), kind(knew))
+ !!MATLAB: [~, knew] = max(score);
+end if
+
+! Powell's code does not include the following instructions. With Powell's code, if VLAG consists of
+! only NaN, then KNEW can be 0 even when XIMPROVED is TRUE. Here, we set KNEW to the following value,
+! to make sure that the new trial point is included in the interpolation set. However, the updating
+! subroutine will likely need to skip the update of the Lagrange polynomial, or they would be
+! destroyed by the NaNs.
+if ((ximproved .and. knew == 0) .or. knew < 0) then ! KNEW < 0 is impossible in theory.
knew = int(maxloc(distsq, dim=1), kind(knew))
-else
- knew = 0
end if
!====================!
@@ -191,6 +195,7 @@ function geostep(knew, kopt, delbar, pl, xpt) result(d)
use, non_intrinsic :: debug_mod, only : assert
use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
use, non_intrinsic :: linalg_mod, only : matprod, inprod, norm, vec2smat, smat_mul_vec
+use, non_intrinsic :: powalg_mod, only : calvlag
implicit none
@@ -230,6 +235,8 @@ function geostep(knew, kopt, delbar, pl, xpt) result(d)
real(RP) :: vhg
real(RP) :: vhv
real(RP) :: vlin
+real(RP) :: vlag(size(xpt, 2))
+real(RP) :: vlagc(size(xpt, 2))
real(RP) :: vmu
real(RP) :: vnorm
real(RP) :: vv
@@ -276,14 +283,15 @@ function geostep(knew, kopt, delbar, pl, xpt) result(d)
end if
else ! GG is 0 or NaN due to rounding errors. Set DCAUCHY to a displacement from XOPT to XPT(:, KNEW).
dcauchy = xpt(:, knew) - xopt
- dcauchy = min(HALF, delbar / norm(dcauchy)) * dcauchy
+ scaling = delbar / norm(dcauchy)
+ dcauchy = max(0.6_RP * scaling, min(HALF, scaling)) * dcauchy ! 0.6: ensure |D| > DELBAR/2
if (inprod(g, dcauchy) * inprod(dcauchy, matprod(h, dcauchy)) < 0) then
dcauchy = -dcauchy
end if
end if
! Return if H or G contains NaN or H is zero. Powell's code does not do this.
-if (is_nan(sum(abs(h)) + sum(abs(g))) .or. all(abs(h) <= 0)) then
+if (any(is_nan(h)) .or. any(is_nan(g)) .or. all(abs(h) <= 0)) then
d = dcauchy
return
end if
@@ -350,7 +358,7 @@ function geostep(knew, kopt, delbar, pl, xpt) result(d)
if (.not. (gnorm * dd > 0.5E-2_RP * delbar * abs(dhd) .and. vv > 1.0E-4_RP * dd)) then
! It may happen that D = 0 due to overflow in DD, which is used to define SCALING.
- if (sum(abs(d)) <= 0 .or. is_nan(sum(abs(d)))) then
+ if (sum(abs(d)) <= 0 .or. .not. is_finite(sum(abs(d)))) then
d = dcauchy
end if
return
@@ -421,7 +429,12 @@ function geostep(knew, kopt, delbar, pl, xpt) result(d)
end if
end if
d = tempd * d + tempv * v
-if (is_nan(sum(abs(d)))) then
+
+! Replace D with DCAUCHY if needed. Powell's code does not have this part. Indeed, only the KNEW-th
+! entries of VLAG and VLAGC are needed.
+vlag = calvlag(pl, d, xopt, kopt)
+vlagc = calvlag(pl, dcauchy, xopt, kopt)
+if (abs(vlagc(knew)) > TWO * abs(vlag(knew)) .or. is_nan(vlag(knew))) then
d = dcauchy
end if
diff --git a/fortran/uobyqa/initialize.f90 b/fortran/uobyqa/initialize.f90
index 6e1fc70a18..40939292f6 100644
--- a/fortran/uobyqa/initialize.f90
+++ b/fortran/uobyqa/initialize.f90
@@ -8,7 +8,7 @@ module initialize_uobyqa_mod
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
!
-! Last Modified: Friday, May 12, 2023 PM06:51:54
+! Last Modified: Tue 10 Feb 2026 02:01:01 PM CET
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -117,7 +117,9 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, x0, kopt, nf, fhist,
! Initialize XHIST, FHIST, and FVAL. Otherwise, compilers may complain that they are not
! (completely) initialized if the initialization aborts due to abnormality (see CHECKEXIT).
-! Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! N.B.: 1. Initializing them to NaN would be more reasonable (NaN is not available in Fortran).
+! 2. Do not initialize the models if the current initialization aborts due to abnormality. Otherwise,
+! errors or exceptions may occur, as FVAL and XPT etc are uninitialized.
xhist = -REALMAX
fhist = REALMAX
fval = REALMAX
@@ -143,7 +145,7 @@ subroutine initxf(calfun, iprint, maxfun, ftarget, rhobeg, x0, kopt, nf, fhist,
! evaluate F at XBASE + 2*XPT(:, K) or XBASE - XPT(:, K) IMMEDIATELY after XBASE + XPT(:, K).
! This increases the probability of finding a smaller function value earlier in the sampling
! process for the first model. This process itself can be regarded as a simple direct search. It
- ! is important for the performance of the algorithm during the early stage, even before the
+ ! is IMPORTANT for the performance of the algorithm during the early stage, even before the
! first model is built.
if (modulo(k, 2_IK) == 0) then
if (fval(k) < fval(1)) then
@@ -312,7 +314,7 @@ subroutine initq(fval, xpt, pq, info)
end do
if (present(info)) then
- if (is_nan(sum(abs(pq)))) then
+ if (any(is_nan(pq))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
@@ -437,7 +439,7 @@ subroutine initl(xpt, pl, info)
end do
if (present(info)) then
- if (is_nan(sum(abs(pl)))) then
+ if (any(is_nan(pl))) then
info = NAN_INF_MODEL
else
info = INFO_DFT
diff --git a/fortran/uobyqa/mlint b/fortran/uobyqa/mlint
index 13013f2f37..9dd46ff833 120000
--- a/fortran/uobyqa/mlint
+++ b/fortran/uobyqa/mlint
@@ -1 +1 @@
-../common/mlint
\ No newline at end of file
+../tests/tools/mlint
\ No newline at end of file
diff --git a/fortran/uobyqa/uobyqa.f90 b/fortran/uobyqa/uobyqa.f90
index 5d71145f84..2c116827ff 100644
--- a/fortran/uobyqa/uobyqa.f90
+++ b/fortran/uobyqa/uobyqa.f90
@@ -19,7 +19,7 @@ module uobyqa_mod
!
! Started: February 2022
!
-! Last Modified: Saturday, June 17, 2023 AM10:47:03
+! Last Modified: Wed 10 Sep 2025 02:03:43 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -30,11 +30,11 @@ module uobyqa_mod
contains
-subroutine uobyqa(calfun, x, f, &
- & nf, rhobeg, rhoend, ftarget, maxfun, iprint, eta1, eta2, gamma1, gamma2, &
- & xhist, fhist, maxhist, info)
+subroutine uobyqa(calfun, x, &
+ & f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, eta1, eta2, gamma1, gamma2, &
+ & xhist, fhist, maxhist, callback_fcn, info)
!--------------------------------------------------------------------------------------------------!
-! Among all the arguments, only CALFUN, X, and F are obligatory. The others are OPTIONAL and you can
+! Among all the arguments, only CALFUN and X are obligatory. The others are OPTIONAL and you can
! neglect them unless you are familiar with the algorithm. Any unspecified optional input will take
! the default value detailed below. For instance, we may invoke the solver as follows.
!
@@ -44,7 +44,7 @@ subroutine uobyqa(calfun, x, f, &
! or
!
! ! First define CALFUN and X, and then do the following.
-! call uobyqa(calfun, x, f, rhobeg = 0.5D0, rhoend = 1.0D-3, maxfun = 100)
+! call uobyqa(calfun, x, f, rhobeg = 1.0D0, rhoend = 1.0D-6)
!
! See examples/uobyqa_exmp.f90 for a concrete example.
!
@@ -134,6 +134,9 @@ subroutine uobyqa(calfun, x, f, &
! CONSTS_MOD (see consts.F90 under the directory named "common").
! Use *HIST with caution!!! (N.B.: the algorithm is NOT designed for large problems).
!
+! CALLBACK_FCN
+! Input, function to report progress and optionally request termination.
+!
! INFO
! Output, INTEGER(IK) scalar.
! INFO is the exit flag. It will be set to one of the following values defined in the module
@@ -156,12 +159,12 @@ subroutine uobyqa(calfun, x, f, &
use, non_intrinsic :: consts_mod, only : MAXFUN_DIM_DFT
use, non_intrinsic :: consts_mod, only : RHOBEG_DFT, RHOEND_DFT, FTARGET_DFT, IPRINT_DFT
use, non_intrinsic :: consts_mod, only : RP, IK, TWO, HALF, TEN, TENTH, EPS
-use, non_intrinsic :: debug_mod, only : assert, warning
+use, non_intrinsic :: debug_mod, only : assert, warning, validate
use, non_intrinsic :: evaluate_mod, only : moderatex
use, non_intrinsic :: history_mod, only : prehist
-use, non_intrinsic :: infnan_mod, only : is_nan, is_finite
+use, non_intrinsic :: infnan_mod, only : is_nan, is_finite, is_posinf
use, non_intrinsic :: memory_mod, only : safealloc
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: preproc_mod, only : preproc
use, non_intrinsic :: string_mod, only : num2str
@@ -170,24 +173,29 @@ subroutine uobyqa(calfun, x, f, &
implicit none
-! Arguments
+! Compulsory arguments
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
real(RP), intent(inout) :: x(:) ! X(N)
-real(RP), intent(out) :: f
-integer(IK), intent(out), optional :: nf
-real(RP), intent(in), optional :: rhobeg
-real(RP), intent(in), optional :: rhoend
-real(RP), intent(in), optional :: ftarget
-integer(IK), intent(in), optional :: maxfun
+
+! Optional inputs
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in), optional :: iprint
+integer(IK), intent(in), optional :: maxfun
+integer(IK), intent(in), optional :: maxhist
real(RP), intent(in), optional :: eta1
real(RP), intent(in), optional :: eta2
+real(RP), intent(in), optional :: ftarget
real(RP), intent(in), optional :: gamma1
real(RP), intent(in), optional :: gamma2
+real(RP), intent(in), optional :: rhobeg
+real(RP), intent(in), optional :: rhoend
+
+! Optional outputs
+integer(IK), intent(out), optional :: info
+integer(IK), intent(out), optional :: nf
+real(RP), intent(out), optional :: f
real(RP), intent(out), optional, allocatable :: fhist(:) ! FHIST(MAXFHIST)
real(RP), intent(out), optional, allocatable :: xhist(:, :) ! XHIST(N, MAXXHIST)
-integer(IK), intent(in), optional :: maxhist
-integer(IK), intent(out), optional :: info
! Local variables
character(len=*), parameter :: solver = 'UOBYQA'
@@ -199,8 +207,10 @@ subroutine uobyqa(calfun, x, f, &
integer(IK) :: n
integer(IK) :: nf_loc
integer(IK) :: nhist
+integer(IK) :: npt
real(RP) :: eta1_loc
real(RP) :: eta2_loc
+real(RP) :: f_loc
real(RP) :: ftarget_loc
real(RP) :: gamma1_loc
real(RP) :: gamma2_loc
@@ -212,6 +222,8 @@ subroutine uobyqa(calfun, x, f, &
! Sizes
n = int(size(x), kind(n))
+npt = (n + 1_IK) * (n + 2_IK) / 2_IK
+call validate(npt > 0, 'NPT > 0', srname) ! Validate that NPT does not overflow.
! Replace any NaN in X by ZERO and Inf/-Inf in X by REALMAX/-REALMAX.
x = moderatex(x)
@@ -240,7 +252,7 @@ subroutine uobyqa(calfun, x, f, &
if (present(rhoend)) then
rhoend_loc = rhoend
elseif (rhobeg_loc > 0) then
- rhoend_loc = max(EPS, min(TENTH * rhobeg_loc, RHOEND_DFT))
+ rhoend_loc = max(EPS, min((RHOEND_DFT / RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
else
rhoend_loc = RHOEND_DFT
end if
@@ -254,7 +266,7 @@ subroutine uobyqa(calfun, x, f, &
if (present(maxfun)) then
maxfun_loc = maxfun
else
- maxfun_loc = max(MAXFUN_DIM_DFT * n, (n + 1_IK) * (n + 2_IK) / 2_IK + 1_IK)
+ maxfun_loc = max(MAXFUN_DIM_DFT * n, npt + 1_IK)
end if
if (present(iprint)) then
@@ -296,7 +308,7 @@ subroutine uobyqa(calfun, x, f, &
if (present(maxhist)) then
maxhist_loc = maxhist
else
- maxhist_loc = maxval([maxfun_loc, 1_IK + (n + 1_IK) * (n + 2_IK) / 2_IK, MAXFUN_DIM_DFT * n])
+ maxhist_loc = maxval([maxfun_loc, npt + 1_IK, MAXFUN_DIM_DFT * n])
end if
! Preprocess the inputs in case some of them are invalid.
@@ -311,13 +323,22 @@ subroutine uobyqa(calfun, x, f, &
!-------------------- Call UOBYQB, which performs the real calculations. --------------------------!
-call uobyqb(calfun, iprint_loc, maxfun_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
- & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f, fhist_loc, xhist_loc, info_loc)
+if (present(callback_fcn)) then
+ call uobyqb(calfun, iprint_loc, maxfun_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f_loc, fhist_loc, xhist_loc, info_loc, callback_fcn)
+else
+ call uobyqb(calfun, iprint_loc, maxfun_loc, eta1_loc, eta2_loc, ftarget_loc, gamma1_loc, &
+ & gamma2_loc, rhobeg_loc, rhoend_loc, x, nf_loc, f_loc, fhist_loc, xhist_loc, info_loc)
+end if
!--------------------------------------------------------------------------------------------------!
! Write the outputs.
+if (present(f)) then
+ f = f_loc
+end if
+
if (present(nf)) then
nf = nf_loc
end if
@@ -361,7 +382,7 @@ subroutine uobyqa(calfun, x, f, &
! If MAXFHIST_IN >= NF_LOC > MAXFHIST_LOC, warn that not all history is recorded.
if ((present(xhist) .or. present(fhist)) .and. maxhist_loc < nf_loc) then
- call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' iteration(s) is recorded')
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
end if
! Postconditions
@@ -375,7 +396,8 @@ subroutine uobyqa(calfun, x, f, &
end if
if (present(fhist)) then
call assert(size(fhist) == nhist, 'SIZE(FHIST) == NHIST', srname)
- call assert(.not. any(fhist < f), 'F is the smallest in FHIST', srname)
+ call assert(.not. any(is_nan(fhist) .or. is_posinf(fhist)), 'FHIST does not contain NaN/+Inf', srname)
+ call assert(.not. any(fhist < f_loc), 'F is the smallest in FHIST', srname)
end if
end if
diff --git a/fortran/uobyqa/uobyqb.f90 b/fortran/uobyqa/uobyqb.f90
index 2917434841..2d8eeed04f 100644
--- a/fortran/uobyqa/uobyqb.f90
+++ b/fortran/uobyqa/uobyqb.f90
@@ -8,7 +8,7 @@ module uobyqb_mod
!
! Started: February 2022
!
-! Last Modified: Friday, September 15, 2023 PM03:12:56
+! Last Modified: Wed 08 Apr 2026 06:39:00 PM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -20,7 +20,7 @@ module uobyqb_mod
subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, rhobeg, rhoend, &
- & x, nf, f, fhist, xhist, info)
+ & x, nf, f, fhist, xhist, info, callback_fcn)
!--------------------------------------------------------------------------------------------------!
! This subroutine performs the major calculations of UOBYQA.
!
@@ -45,16 +45,16 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! Common modules
use, non_intrinsic :: checkexit_mod, only : checkexit
-use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TENTH, EPS, REALMAX, DEBUGGING
-use, non_intrinsic :: debug_mod, only : assert
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, HALF, TENTH, REALMAX, DEBUGGING
+use, non_intrinsic :: debug_mod, only : assert, validate
use, non_intrinsic :: evaluate_mod, only : evaluate
use, non_intrinsic :: history_mod, only : savehist, rangehist
-use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf
-use, non_intrinsic :: infos_mod, only : INFO_DFT, SMALL_TR_RADIUS, MAXTR_REACHED
+use, non_intrinsic :: infnan_mod, only : is_nan, is_posinf, is_finite
+use, non_intrinsic :: infos_mod, only : INFO_DFT, SMALL_TR_RADIUS, MAXTR_REACHED, CALLBACK_TERMINATE, NAN_INF_MODEL
use, non_intrinsic :: linalg_mod, only : vec2smat, smat_mul_vec, norm
use, non_intrinsic :: memory_mod, only : safealloc
use, non_intrinsic :: message_mod, only : fmsg, rhomsg, retmsg
-use, non_intrinsic :: pintrf_mod, only : OBJ
+use, non_intrinsic :: pintrf_mod, only : OBJ, CALLBACK
use, non_intrinsic :: powalg_mod, only : quadinc
use, non_intrinsic :: ratio_mod, only : redrat
use, non_intrinsic :: redrho_mod, only : redrho
@@ -70,6 +70,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! Inputs
procedure(OBJ) :: calfun ! N.B.: INTENT cannot be specified if a dummy procedure is not a POINTER
+procedure(CALLBACK), optional :: callback_fcn
integer(IK), intent(in) :: iprint
integer(IK), intent(in) :: maxfun
real(RP), intent(in) :: eta1
@@ -93,6 +94,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! Local variables
character(len=*), parameter :: solver = 'UOBYQA'
character(len=*), parameter :: srname = 'UOBYQB'
+integer(IK) :: k
integer(IK) :: knew_geo
integer(IK) :: knew_tr
integer(IK) :: kopt
@@ -112,6 +114,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
logical :: reduce_rho
logical :: shortd
logical :: small_trrad
+logical :: terminate
logical :: trfail
logical :: ximproved
real(RP) :: crvmin
@@ -133,13 +136,15 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
real(RP) :: ratio
real(RP) :: rho
real(RP) :: xbase(size(x))
+real(RP) :: xdrop(size(x))
real(RP) :: xpt(size(x), size(distsq))
real(RP), allocatable :: pl(:, :)
-real(RP), parameter :: trtol = 1.0E-2_RP ! Tolerance used in TRSTEP.
+real(RP), parameter :: trtol = 1.0E-2_RP ! Convergence tolerance of trust-region subproblem solver
! Sizes.
n = int(size(x), kind(n))
npt = (n + 1_IK) * (n + 2_IK) / 2_IK
+call validate(npt > 0, 'NPT > 0', srname) ! Validate that NPT does not overflow.
maxxhist = int(size(xhist, 2), kind(maxxhist))
maxfhist = int(size(fhist), kind(maxfhist))
maxhist = max(maxxhist, maxfhist)
@@ -150,6 +155,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
call assert(n >= 1, 'N >= 1', srname)
call assert(maxfun >= npt + 1, 'MAXFUN >= NPT + 1', srname)
call assert(rhobeg >= rhoend .and. rhoend > 0, 'RHOBEG >= RHOEND > 0', srname)
+ call assert(all(is_finite(x)), 'X is finite', srname)
call assert(eta1 >= 0 .and. eta1 <= eta2 .and. eta2 < 1, '0 <= ETA1 <= ETA2 < 1', srname)
call assert(gamma1 > 0 .and. gamma1 < 1 .and. gamma2 > 1, '0 < GAMMA1 < 1 < GAMMA2', srname)
call assert(maxhist >= 0 .and. maxhist <= maxfun, '0 <= MAXHIST <= MAXFUN', srname)
@@ -165,10 +171,37 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! Initialize XBASE, XPT, FVAL, and KOPT, together with the history and NF.
call initxf(calfun, iprint, maxfun, ftarget, rhobeg, x, kopt, nf, fhist, fval, xbase, xhist, xpt, subinfo)
+! Report the current best value, and check if user asks for early termination.
+terminate = .false.
+if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, 0_IK, terminate=terminate)
+ if (terminate) then
+ subinfo = CALLBACK_TERMINATE
+ end if
+end if
+
! Initialize X and F according to KOPT.
x = xbase + xpt(:, kopt)
f = fval(kopt)
+! Finish the initialization if INITXF completed normally and CALLBACK did not request termination;
+! otherwise, do not proceed, as XPT etc may be uninitialized, leading to errors or exceptions.
+if (subinfo == INFO_DFT) then
+ ! Initialize the Lagrange polynomials represented by PL. Allocate memory for it first. In
+ ! general, to make the implementation simple and straightforward, we use automatic arrays rather
+ ! than allocable ones whenever possible. However, PL is an exception, as its size is O(N^4). If
+ ! SAFEALLOC fails, an informative error will be raised, which is preferred to a silent or
+ ! ambiguous failure.
+ call safealloc(pl, npt - 1_IK, npt)
+ call initl(xpt, pl)
+
+ ! Initialize the quadratic model represented by PQ.
+ call initq(fval, xpt, pq)
+ if (.not. (all(is_finite(pq)))) then
+ subinfo = NAN_INF_MODEL
+ end if
+end if
+
! Check whether to return due to abnormal cases that may occur during the initialization.
if (subinfo /= INFO_DFT) then
info = subinfo
@@ -176,19 +209,22 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
call rangehist(nf, xhist, fhist)
! Print a return message according to IPRINT.
call retmsg(solver, info, iprint, nf, f, x)
+ ! Postconditions
+ if (DEBUGGING) then
+ call assert(nf <= maxfun, 'NF <= MAXFUN', srname)
+ call assert(size(x) == n .and. .not. any(is_nan(x)), 'SIZE(X) == N, X does not contain NaN', srname)
+ call assert(.not. (is_nan(f) .or. is_posinf(f)), 'F is not NaN/+Inf', srname)
+ call assert(size(xhist, 1) == n .and. size(xhist, 2) == maxxhist, 'SIZE(XHIST) == [N, MAXXHIST]', srname)
+ call assert(.not. any(is_nan(xhist(:, 1:min(nf, maxxhist)))), 'XHIST does not contain NaN', srname)
+ ! The last calculated X can be Inf (finite + finite can be Inf numerically).
+ call assert(size(fhist) == maxfhist, 'SIZE(FHIST) == MAXFHIST', srname)
+ call assert(.not. any(is_nan(fhist(1:min(nf, maxfhist))) .or. is_posinf(fhist(1:min(nf, maxfhist)))), &
+ & 'FHIST does not contain NaN/+Inf', srname)
+ call assert(.not. any(fhist(1:min(nf, maxfhist)) < f), 'F is the smallest in FHIST', srname)
+ end if
return
end if
-! Initialize the Lagrange polynomials represented by PL. Allocate memory for it first. In general,
-! to make the implementation simple and straightforward, we use automatic arrays rather than
-! allocable ones whenever possible. However, PL is an exception, as its size is O(N^4). If SAFEALLOC
-! fails, an informative error will be raised, which is preferred to a silent or ambiguous failure.
-call safealloc(pl, npt - 1_IK, npt)
-call initl(xpt, pl)
-
-! Initialize the quadratic model represented by PQ.
-call initq(fval, xpt, pq)
-
! Set some more initial values.
! We must initialize RATIO. Otherwise, when SHORTD = TRUE, compilers may raise a run-time error that
! RATIO is undefined. But its value will not be used: when SHORTD = FALSE, its value will be
@@ -215,10 +251,16 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! than setting directly DELTA = MAX(NEW_DELTA, RHO).
gamma3 = max(ONE, min(0.75_RP * gamma2, 1.5_RP))
-! MAXTR is the maximal number of trust-region iterations. Each trust-region iteration takes 1 or 2
-! function evaluations unless the trust-region step is short or fails to reduce the trust-region
-! model but the geometry step is not invoked. Thus the following MAXTR is unlikely to be reached.
-maxtr = max(maxfun, 2_IK * maxfun) ! MAX: precaution against overflow, which will make 2*MAXFUN < 0.
+! MAXTR is the maximal number of trust-region iterations. Here, we set it to HUGE(MAXTR) - 1 so that
+! the algorithm will not terminate due to MAXTR. However, this may not be allowed in other languages
+! such as MATLAB. In that case, we can set MAXTR to 10*MAXFUN, which is unlikely to reach because
+! each trust-region iteration takes 1 or 2 function evaluations unless the trust-region step is short
+! or fails to reduce the trust-region model but the geometry step is not invoked.
+! N.B.: Do NOT set MAXTR to HUGE(MAXTR), as it may cause overflow and infinite cycling in the DO
+! loop. See
+! https://fortran-lang.discourse.group/t/loop-variable-reaching-integer-huge-causes-infinite-loop
+! https://fortran-lang.discourse.group/t/loops-dont-behave-like-they-should
+maxtr = huge(maxtr) - 1_IK !!MATLAB: maxtr = 10 * maxfun;
info = MAXTR_REACHED
! Begin the iterative procedure.
@@ -228,19 +270,31 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! REDUCE_RHO: Should we reduce rho?
! UOBYQA never sets IMPROVE_GEO and REDUCE_RHO to TRUE simultaneously.
do tr = 1, maxtr
+ ! CLOSE_ITPSET: Are the interpolation points close to XOPT? It affects IMPROVE_GEO, REDUCE_RHO.
+ ! N.B. (Zaikun 20240331): In Powell's algorithms, CLOSE_ITPSET is defined after XPT is updated
+ ! according to the trust-region trial step.
+ distsq = sum((xpt - spread(xpt(:, kopt), dim=2, ncopies=npt))**2, dim=1)
+ !!MATLAB: distsq = sum((xpt - xpt(:, kopt)).^2) % Implicit expansion
+ close_itpset = all(distsq <= 4.0_RP * delta**2) ! Powell's NEWUOA code.
+ ! Below are some alternative definitions of CLOSE_ITPSET.
+ ! N.B.: The threshold for CLOSE_ITPSET is at least DELBAR, the trust region radius for GEOSTEP.
+ ! !close_itpset = all(distsq <= 4.0_RP * rho**2) ! Powell's code.
+ ! !close_itpset = all(distsq <= max((TWO * delta)**2, (TEN * rho)**2)) ! Powell's BOBYQA code.
+ ! !close_itpset = all(distsq <= max(delta**2, 4.0_RP * rho**2)) ! Powell's LINCOA code.
+
! Generate trust region step D, and also calculate a lower bound on the Hessian of Q.
g = pq(1:n) + smat_mul_vec(pq(n + 1:npt - 1), xpt(:, kopt))
h = vec2smat(pq(n + 1:npt - 1))
call trstep(delta, g, h, trtol, d, crvmin)
+ dnorm = min(delta, norm(d))
! Check whether D is too short to invoke a function evaluation.
- dnorm = min(delta, norm(d))
- shortd = (dnorm < HALF * rho)
+ shortd = (dnorm <= HALF * rho) ! `<=` works better than `<` in case of underflow.
! Set QRED to the reduction of the quadratic model when the move D is made from XOPT. QRED
! should be positive. If it is nonpositive due to rounding errors, we will not take this step.
qred = -quadinc(pq, d, xpt(:, kopt)) ! QRED = Q(XOPT) - Q(XOPT + D)
- trfail = (.not. qred > EPS * rho**2) ! QRED is tiny/negative or NaN.
+ trfail = (.not. qred > 1.0E-6 * rho**2) ! QRED is tiny/negative or NaN.
if (shortd .or. trfail) then
! Powell's code does not reduce DELTA as follows. This comes from NEWUOA and works well.
@@ -250,27 +304,30 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
end if
else
! Calculate the next value of the objective function.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective function X, assuming it to have the value at the closest point.
x = xbase + (xpt(:, kopt) + d)
- call evaluate(calfun, x, f)
- nf = nf + 1_IK
+ distsq = [(sum((x - (xbase + xpt(:, k)))**2, dim=1), k=1, npt)] ! Implied do-loop
+ !!MATLAB: distsq = sum((x - (xbase + xpt))**2, 1) % Implicit expansion
+ k = int(minloc(distsq, dim=1), kind(k))
+ if (distsq(k) <= (1.0E-4 * rhoend)**2) then
+ f = fval(k)
+ else
+ ! Evaluate the objective function at X, taking care of possible Inf/NaN values.
+ call evaluate(calfun, x, f)
+ nf = nf + 1_IK
+ ! Save X and F into the history.
+ call savehist(nf, x, xhist, f, fhist)
+ end if
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Trust region', iprint, nf, delta, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
-
- ! Check whether to exit
- subinfo = checkexit(maxfun, nf, f, ftarget, x)
- if (subinfo /= INFO_DFT) then
- info = subinfo
- exit
- end if
! Update DNORM_REC and MODERR_REC.
- ! DNORM_REC records the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC records the DNORM of the recent function evaluations with the current RHO.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC records the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC records the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) + qred
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
@@ -292,9 +349,21 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! DDMOVE is norm square of DMOVE in the UOBYQA paper. See Steps 6--7 in Sec. 5 of the paper.
ddmove = ZERO
if (knew_tr > 0) then
- ddmove = sum((xpt(:, knew_tr) - xpt(:, kopt))**2) ! KOPT is unupdated.
+ xdrop = xpt(:, knew_tr)
! Update PL, PQ, XPT, FVAL, and KOPT so that XPT(:, KNEW_TR) becomes XOPT + D.
call update(knew_tr, d, f, moderr, kopt, fval, pl, pq, xpt)
+ if (.not. (all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
+ ddmove = sum((xdrop - xpt(:, kopt))**2) ! KOPT is updated.
+ end if
+
+ ! Check whether to exit
+ subinfo = checkexit(maxfun, nf, f, ftarget, x)
+ if (subinfo /= INFO_DFT) then
+ info = subinfo
+ exit
end if
end if ! End of IF (SHORTD .OR. TRFAIL). The normal trust-region calculation ends.
@@ -311,15 +380,6 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! ACCURATE_MOD: Are the recent models sufficiently accurate? Used only if SHORTD is TRUE.
accurate_mod = all(abs(moderr_rec) <= 0.125_RP * crvmin * rho**2) .and. all(dnorm_rec <= rho)
- ! CLOSE_ITPSET: Are the interpolation points close to XOPT?
- distsq = sum((xpt - spread(xpt(:, kopt), dim=2, ncopies=npt))**2, dim=1)
- !!MATLAB: distsq = sum((xpt - xpt(:, kopt)).^2) % Implicit expansion
- close_itpset = all(distsq <= 4.0_RP * delta**2) ! Powell's NEWUOA code.
- ! Below are some alternative definitions of CLOSE_ITPSET.
- ! N.B.: The threshold for CLOSE_ITPSET is at least DELBAR, the trust region radius for GEOSTEP.
- ! !close_itpset = all(distsq <= 4.0_RP * rho**2) ! Powell's code.
- ! !close_itpset = all(distsq <= max((TWO * delta)**2, (TEN * rho)**2)) ! Powell's BOBYQA code.
- ! !close_itpset = all(distsq <= max(delta**2, 4.0_RP * rho**2)) ! Powell's LINCOA code.
! ADEQUATE_GEO: Is the geometry of the interpolation set "adequate"?
adequate_geo = (shortd .and. accurate_mod) .or. close_itpset
! SMALL_TRRAD: Is the trust-region radius small? This indicator seems not impactive in practice.
@@ -335,7 +395,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! checking whether the interpolation error bound in (28) is (sufficiently) small. If the bound
! is small, then set ACCURATE_MOD to TRUE. Otherwise, it identifies a "bad" interpolation point
! that makes a significant contribution to the bound, with a preference to the interpolation
- ! points that are a far away from the current trust-region center. Such a point will be replaced
+ ! points that are far away from the current trust-region center. Such a point will be replaced
! with a new point obtained by the geometry step. If all the interpolation points are close
! enough to the trust-region center, then they are all considered to be good.
! 3. Our implementation defines ACCURATE_MOD by a method from NEWUOA and BOBYQA, which is also
@@ -354,13 +414,13 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! N.B.: If SHORTD is TRUE at the very first iteration, then REDUCE_RHO will be set to TRUE.
! Powell's code does not have TRFAIL in BAD_TRSTEP; it terminates if TRFAIL is TRUE.
- ! BAD_TRSTEP (for IMPROVE_GEO): Is the last trust-region step bad? For UOBYQA, it is CRITICAL to
+ ! BAD_TRSTEP (for IMPROVE_GEO): Is the last trust-region step bad? For UOBYQA, it is CRUCIAL to
! include DMOVE <= 4.0_RP*RHO**2 in the definition of BAD_TRSTEP for IMPROVE_GEO.
bad_trstep = (shortd .or. trfail .or. (ratio <= eta1 .and. ddmove <= 4.0_RP * delta**2) .or. knew_tr == 0)
!bad_trstep = (shortd .or. trfail .or. ratio <= eta1 .or. knew_tr == 0) ! Works poorly!
improve_geo = bad_trstep .and. .not. adequate_geo
! BAD_TRSTEP (for REDUCE_RHO): Is the last trust-region step bad?
- bad_trstep = (shortd .or. trfail .or. ratio <= 0 .or. knew_tr == 0) ! Performs better than the below from Powell.
+ bad_trstep = (shortd .or. trfail .or. ratio <= 0 .or. knew_tr == 0) ! Performs better than the one below from Powell.
!bad_trstep = (shortd .or. trfail .or. (ratio <= 0 .and. ddmove <= 4.0_RP * delta**2) .or. knew_tr == 0)
reduce_rho = bad_trstep .and. adequate_geo .and. small_trrad
@@ -393,6 +453,8 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
! Improve the geometry of the interpolation set by removing a point and adding a new one.
if (improve_geo) then
! XPT(:, KNEW_GEO) will become XOPT + D below. KNEW_GEO /= KOPT unless there is a bug.
+ distsq = sum((xpt - spread(xpt(:, kopt), dim=2, ncopies=npt))**2, dim=1)
+ !!MATLAB: distsq = sum((xpt - xpt(:, kopt)).^2) % Implicit expansion
knew_geo = int(maxloc(distsq, dim=1), kind(knew_geo))
! DELBAR is the trust-region radius for the geometry improvement subproblem.
@@ -406,33 +468,47 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
d = geostep(knew_geo, kopt, delbar, pl, xpt)
! Calculate the next value of the objective function.
+ ! If X is close to one of the points in the interpolation set, then we do not evaluate the
+ ! objective function X, assuming it to have the value at the closest point.
x = xbase + (xpt(:, kopt) + d)
- call evaluate(calfun, x, f)
- nf = nf + 1_IK
+ distsq = [(sum((x - (xbase + xpt(:, k)))**2, dim=1), k=1, npt)] ! Implied do-loop
+ !!MATLAB: distsq = sum((x - (xbase + xpt))**2, 1) % Implicit expansion
+ k = int(minloc(distsq, dim=1), kind(k))
+ if (distsq(k) <= (1.0E-4 * rhoend)**2) then
+ f = fval(k)
+ else
+ ! Evaluate the objective function at X, taking care of possible Inf/NaN values.
+ call evaluate(calfun, x, f)
+ nf = nf + 1_IK
+ ! Save X and F into the history.
+ call savehist(nf, x, xhist, f, fhist)
+ end if
! Print a message about the function evaluation according to IPRINT.
call fmsg(solver, 'Geometry', iprint, nf, delbar, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
-
- ! Check whether to exit
- subinfo = checkexit(maxfun, nf, f, ftarget, x)
- if (subinfo /= INFO_DFT) then
- info = subinfo
- exit
- end if
! Update DNORM_REC and MODERR_REC.
- ! DNORM_REC records the DNORM of the latest 3 function evaluations with the current RHO.
+ ! DNORM_REC records the DNORM of the recent function evaluations with the current RHO.
dnorm = min(delbar, norm(d)) ! In theory, DNORM = DELBAR in this case.
dnorm_rec = [dnorm_rec(2:size(dnorm_rec)), dnorm]
! MODERR is the error of the current model in predicting the change in F due to D.
- ! MODERR_REC records the prediction errors of the latest 3 models with the current RHO.
+ ! MODERR_REC records the prediction errors of the recent models with the current RHO.
moderr = f - fval(kopt) - quadinc(pq, d, xpt(:, kopt)) ! QUADINC = Q(XOPT + D) - Q(XOPT)
moderr_rec = [moderr_rec(2:size(moderr_rec)), moderr]
! Update PL, PQ, XPT, FVAL, and KOPT so that XPT(:, KNEW_GEO) becomes XOPT + D.
call update(knew_geo, d, f, moderr, kopt, fval, pl, pq, xpt)
+ if (.not. (all(is_finite(pq)))) then
+ info = NAN_INF_MODEL
+ exit
+ end if
+
+ ! Check whether to exit
+ subinfo = checkexit(maxfun, nf, f, ftarget, x)
+ if (subinfo /= INFO_DFT) then
+ info = subinfo
+ exit
+ end if
end if ! End of IF (IMPROVE_GEO). The procedure of improving geometry ends.
! The calculations with the current RHO are complete. Enhance the resolution of the algorithm
@@ -446,7 +522,7 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
rho = redrho(rho, rhoend)
! Print a message about the reduction of RHO according to IPRINT.
call rhomsg(solver, iprint, nf, delta, fval(kopt), rho, xbase + xpt(:, kopt))
- ! DNORM_REC and MODERR_REC are corresponding to the latest 3 function evaluations with
+ ! DNORM_REC and MODERR_REC are corresponding to the recent function evaluations with
! the current RHO. Update them after reducing RHO.
dnorm_rec = REALMAX
moderr_rec = REALMAX
@@ -458,6 +534,15 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
if (sum(xpt(:, kopt)**2) >= 1.0E3_RP * delta**2) then
call shiftbase(kopt, pl, pq, xbase, xpt)
end if
+
+ ! Report the current best value, and check if user asks for early termination.
+ if (present(callback_fcn)) then
+ call callback_fcn(xbase + xpt(:, kopt), fval(kopt), nf, tr, terminate=terminate)
+ if (terminate) then
+ info = CALLBACK_TERMINATE
+ exit
+ end if
+ end if
end do ! End of DO TR = 1, MAXTR. The iterative procedure ends.
! Deallocate PL. F2003 automatically deallocate local ALLOCATABLE variables at exit, yet we prefer
@@ -465,22 +550,25 @@ subroutine uobyqb(calfun, iprint, maxfun, eta1, eta2, ftarget, gamma1, gamma2, r
deallocate (pl)
! Return from the calculation, after trying the Newton-Raphson step if it has not been tried yet.
-if (info == SMALL_TR_RADIUS .and. shortd .and. nf < maxfun) then
- x = xbase + (xpt(:, kopt) + d)
+! Ensure that D has not been updated after SHORTD == TRUE occurred, or the code below is incorrect.
+x = xbase + (xpt(:, kopt) + d)
+if (info == SMALL_TR_RADIUS .and. shortd .and. norm(x - (xbase + xpt(:, kopt))) > TENTH * rhoend .and. nf < maxfun) then
call evaluate(calfun, x, f)
nf = nf + 1_IK
+ ! Save X, F into the history.
+ call savehist(nf, x, xhist, f, fhist)
! Print a message about the function evaluation according to IPRINT.
! Zaikun 20230512: DELTA has been updated. RHO is only indicative here. TO BE IMPROVED.
call fmsg(solver, 'Trust region', iprint, nf, rho, f, x)
- ! Save X, F into the history.
- call savehist(nf, x, xhist, f, fhist)
+ if (f < fval(kopt)) then
+ xpt(:, kopt) = xpt(:, kopt) + d
+ fval(kopt) = f
+ end if
end if
-! Choose the [X, F] to return: either the current [X, F] or [XBASE + XOPT, FOPT].
-if (fval(kopt) < f .or. is_nan(f)) then
- x = xbase + xpt(:, kopt)
- f = fval(kopt)
-end if
+! Choose the [X, F] to return.
+x = xbase + xpt(:, kopt)
+f = fval(kopt)
! Arrange FHIST and XHIST so that they are in the chronological order.
call rangehist(nf, xhist, fhist)
diff --git a/fortran/uobyqa/update.f90 b/fortran/uobyqa/update.f90
index 4385480509..4b7dd8566e 100644
--- a/fortran/uobyqa/update.f90
+++ b/fortran/uobyqa/update.f90
@@ -8,7 +8,7 @@ module update_uobyqa_mod
!
! Started: July 2020
!
-! Last Modified: Saturday, April 08, 2023 AM12:28:26
+! Last Modified: Thu 14 Aug 2025 07:37:19 AM CST
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -85,6 +85,7 @@ subroutine update(knew, d, f, moderr, kopt, fval, pl, pq, xpt)
pl(:, knew) = pl(:, knew) / vlag(knew)
plnew = pl(:, knew)
pl = pl - outprod(plnew, vlag)
+! N.B.: The use of OUTPROD is expensive memory-wise, but it is not our concern in this implementation.
pl(:, knew) = plnew
! Update the quadratic model.
diff --git a/matlab/examples/README.txt b/matlab/examples/README.txt
index 733aefa04d..26917d9867 100644
--- a/matlab/examples/README.txt
+++ b/matlab/examples/README.txt
@@ -1,10 +1,12 @@
This directory contains simple examples that illustrate how to use the MATLAB version of the package.
-Authors:
- Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
- and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
- Department of Applied Mathematics,
- The Hong Kong Polytechnic University
+N.B.: Make sure that you have installed the package by running the `setup` script in the root
+directory before trying the examples. You only need to do the installation once.
+
+Author:
+ Zaikun ZHANG (www.zhangzk.net)
+ School of Mathematics,
+ Sun Yat-sen University, China.
Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
diff --git a/matlab/examples/rosenbrock_example.m b/matlab/examples/rosenbrock_example.m
index 9d76756ace..a5ff8a4d95 100644
--- a/matlab/examples/rosenbrock_example.m
+++ b/matlab/examples/rosenbrock_example.m
@@ -1,20 +1,22 @@
function rosenbrock_example()
%ROSENBROCK_EXAMPLE illustrates how to use prima.
%
+% N.B.: Make sure that you have installed the package by running the
+% `startup.m` script in the root directory before trying the examples.
+% You only need to do the installation once.
+%
% ***********************************************************************
-% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University
+% Authors: Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+% and Zaikun ZHANG (www.zhangzk.net)
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Attribute: public (can be called directly by users)
-%
+%
% TODO: None
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fprintf('\nMinimize the chained Rosenbrock function with three variables subject to various constraints:\n');
x0 = [0; 0; 0]; % starting point
diff --git a/matlab/interfaces/+newuoa_mat/private/consts.m b/matlab/interfaces/+newuoa_mat/private/consts.m
index 9717cf7bc6..e6b9aefd14 100644
--- a/matlab/interfaces/+newuoa_mat/private/consts.m
+++ b/matlab/interfaces/+newuoa_mat/private/consts.m
@@ -13,7 +13,7 @@
case 'maxfun_dim_dft'
const = 500;
case {'funcmax', 'constrmax'}
- const = 10^40;
+ const = 10^30;
% Maximal amount of memory (Byte) allowed for XHIST, FHIST, NLCHIST
case 'maxmemory'
diff --git a/matlab/interfaces/+newuoa_mat/private/newuob.m b/matlab/interfaces/+newuoa_mat/private/newuob.m
index 303444e2d3..2828cf910b 100644
--- a/matlab/interfaces/+newuoa_mat/private/newuob.m
+++ b/matlab/interfaces/+newuoa_mat/private/newuob.m
@@ -99,14 +99,14 @@
% artificial RATIO but use SHORTD to be more explicit. See IMPROVE_GEO and REDUCE_RHO for details.
ratio = -1;
- % Normally, each trust-region iteration takes one function evaluation. The following setting
+ % Normally, each trust region iteration takes one function evaluation. The following setting
% essentially imposes no constraint on the maximal number of trust region iterations.
maxtr = 10 * maxfun;
% MAXTR is unlikely to be reached, but we define the following default value for INFO for safety.
info = infos('maxtr_reached');
% Begin the iterative procedure.
- % After solving a trust-region subproblem, NEWUOA uses 3 boolean variables to control the work flow.
+ % After solving a trust region subproblem, NEWUOA uses 3 boolean variables to control the work flow.
% SHORTD - Is the trust region trial step too short to invoke a function evaluation?
% IMPROVE_GEO - Will we improve the model after the trust region iteration?
% REDUCE_RHO - Will we reduce rho after the trust region iteration?
@@ -237,9 +237,9 @@
% N.B.:
% 1. This part is OPTIONAL, but it is crucial for the performance on some problems. See
% Section 8 of the NEWUOA paper.
- % 2. TRYQALT is called only after a trust-region step but not after a geometry step, maybe
+ % 2. TRYQALT is called only after a trust region step but not after a geometry step, maybe
% because the model is expected to be good after a geometry step.
- % 3. If KNEW_TR = 0 after a trust-region step, TRYQALT is not invoked. In this case, the
+ % 3. If KNEW_TR = 0 after a trust region step, TRYQALT is not invoked. In this case, the
% interpolation set is unchanged, so it seems reasonable to keep the model unchanged.
% 4. In theory, FVAL - FOPT in the call of TRYQALT can be replaced by FVAL + C with any
% constant C. This constant will not affect the result in precise arithmetic. Powell chose
@@ -259,7 +259,7 @@
% according to IMPROVE_GEO and REDUCE_RHO. Now we decide these two indicators.
% First define IMPROVE_GEO, which corresponds to Box 8 of the NEWUOA paper.
- % The geometry of XPT likely needs improvement if the trust-region step bad --- either too short
+ % The geometry of XPT likely needs improvement if the trust region step bad --- either too short
% (SHORTD = TRUE) or the reduction ratio is small (RATIO < 0.1_RP). However, if REDUCE_RHO_1 is
% TRUE, meaning that the step is short and the latest model errors have been small, then we do
% not need to improve the geometry; instead, RHO will be reduced.
@@ -275,23 +275,23 @@
% 2. If SHORTD = FALSE and KNEW_TR = 0, then IMPROVE_GEO = TRUE. Therefore, IMPROVE_GEO = TRUE
% if it is impossible to obtain a good XPT by replacing a current point with the one suggested
% by the trust region step.
- % 3. If REDUCE_RHO = FALSE and SHORTD = TRUE, then the trust-region step is not tried at all,
+ % 3. If REDUCE_RHO = FALSE and SHORTD = TRUE, then the trust region step is not tried at all,
% i.e., no function evaluation is invoked at XOPT + D (when REDUCE_RHO = TRUE, the step is not
- % tried either, but the same step will be generated again at the next trust-region iteration
+ % tried either, but the same step will be generated again at the next trust region iteration
% after RHO is reduced and DELTA is updated; see the end of Section 2 of the NEWUOA paper).
- % 4. If SHORTD = FALSE and KNEW_TR = 0, then the trust-region step invokes a function evaluation
+ % 4. If SHORTD = FALSE and KNEW_TR = 0, then the trust region step invokes a function evaluation
% at XOPT + D, but [XOPT + D, F(XOPT +D)] is not included into [XPT, FVAL]. In other words, this
% function value is discarded. THEORETICALLY, KNEW_TR = 0 only if RATIO <= 0, so that a function
% value that renders a reduction is never discarded; however, KNEW_TR may turn out 0 due to NaN
% even if RATIO > 0. See SETDROP_TR for details.
% 5. If SHORTD = FALSE and KNEW_TR > 0 and RATIO < 0.1_RP, then [XPT, FVAL] is updated so that
% [XPT(KNEW_TR), FVAL(KNEW_TR)] = [XOPT + D, F(XOPT + D)], and the model is updated accordingly,
- % but such a model will not be used in the next trust-region iteration, because a geometry step
+ % but such a model will not be used in the next trust region iteration, because a geometry step
% will be invoked to improve the geometry of the interpolation set and update the model again.
% 6. DELTA has been updated before arriving here: if REDUCE_RHO = FALSE and SHORTD = TRUE, then
% DELTA was reduced by a factor of 10; if SHORTD = FALSE, then DELTA was updated by TRRAD after
- % the trust-region iteration.
- % 7. If SHORTD = FALSE and KNEW_TR > 0, then XPT has been updated after the trust-region
+ % the trust region iteration.
+ % 7. If SHORTD = FALSE and KNEW_TR > 0, then XPT has been updated after the trust region
% iteration; if RATIO > 0 in addition, then XOPT has been updated as well.
xdist = sqrt(sum((xpt - repmat(xopt, 1, npt)).^2, 1)');
@@ -299,7 +299,7 @@
improve_geo = (~reduce_rho_1) && (max(xdist) > 2 * delta) && bad_trstep;
% If all the interpolation points are close to XOPT and the trust region is small, but the
- % trust-region step is "bad" (SHORTD or RATIO <= 0), then we shrink RHO (update the criterion
+ % trust region step is "bad" (SHORTD or RATIO <= 0), then we shrink RHO (update the criterion
% for the "closeness" and SHORTD). REDUCE_RHO_2 corresponds to Box 10 of the NEWUOA paper.
% N.B.:
% 1. The definition of REDUCE_RHO_2 is equivalent to the following:
@@ -371,7 +371,7 @@
% DNORMSAVE contains the DNORM of the latest 3 function evaluations with the current RHO.
%------------------------------------------------------------------------------------------%
- % Powell's code does not update DNORM. Therefore, DNORM is the length of last trust-region
+ % Powell's code does not update DNORM. Therefore, DNORM is the length of last trust region
% trial step, which seems inconsistent with what is described in Section 7 (around (7.7)) of
% the NEWUOA paper. Seemingly we should keep DNORM = ||D|| as we do here. The value of DNORM
% will be used when defining REDUCE_RHO.
diff --git a/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m b/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
index 8e83cd7245..c6e55906f3 100644
--- a/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
+++ b/matlab/interfaces/+newuoa_mat/private/setdrop_tr.m
@@ -3,7 +3,7 @@
npt = size(xpt, 2);
% This subroutine sets KNEW to the index of the interpolation point to be deleted AFTER A TRUST
% REGION STEP. KNEW will be set in a way ensuring that the geometry of XPT is "optimal" after
- % XPT(:, KNEW) is replaced by XNEW = XOPT + D, where D is the trust-region step. Note that the
+ % XPT(:, KNEW) is replaced by XNEW = XOPT + D, where D is the trust region step. Note that the
% information of XNEW is included in VLAG and BETA, which are calculated according to D.
%
% N.B.: At the entry of this function is invoked, XOPT may differ from XPT(:, KOPT), because XOPT is
diff --git a/matlab/interfaces/README.txt b/matlab/interfaces/README.txt
index 777602e8b8..cb5d28fcc3 100644
--- a/matlab/interfaces/README.txt
+++ b/matlab/interfaces/README.txt
@@ -1,10 +1,9 @@
This directory contains the MATLAB interfaces for PRIMA.
-Authors:
- Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
- and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
- Department of Applied Mathematics,
- The Hong Kong Polytechnic University
+Author:
+ Zaikun ZHANG (www.zhangzk.net)
+ School of Mathematics,
+ Sun Yat-sen University, China.
Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
diff --git a/matlab/interfaces/bobyqa.m b/matlab/interfaces/bobyqa.m
index 89ea42a7d0..bed7a529a0 100644
--- a/matlab/interfaces/bobyqa.m
+++ b/matlab/interfaces/bobyqa.m
@@ -19,7 +19,7 @@
%
% solves the problem formulated above, where
% *** fun is the name or function handle of the objective function
-% *** x0 is the starting point; x0 CANNOT be []
+% *** x0 is the starting point; x0 CANNOT be omitted or set to []
% *** lb and ub, which are vectors of the same length as x, are the
% lower and upper bound in the bound constraint lb <= x <= ub;
% set lb = [] if no lower bound, and ub = [] if no upper bound
@@ -45,28 +45,26 @@
% possible values are
% 0: the lower bound for the trust region radius is reached
% 1: the target function value is achieved
-% 2: a trust region step failed to reduce the quadratic model
+% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
-% 4, 7, 8, 9: rounding errors become severe in the Fortran code
+% 7: rounding errors become severe in the Fortran code
% 13: all variables are fixed by the constraints
% 14: a linear feasibility problem received and solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
-% -1: NaN occurs in x
-% -2: the objective function returns an NaN or nearly infinite
-% value (only in the classical mode)
-% -3: NaN occurs in the models
+% 20: the trust region iteration has been performed for 2*maxfun times
+% -1: NaN occurs in x (possible only in the classical mode)
+% -2: the objective function returns an Inf/NaN value (possible only in classical mode)
+% -3: NaN occurs in the models (possible only in classical mode)
% -4: constraints are infeasible
-% exitflag = 5, 10, 11, 12 are possible exitflags of the Fortran
-% code but cannot be returned by BOBYQA
% *** output is a structure with the following fields:
% funcCount: number of function evaluations
+% xhist: history of iterates (if options.output_xhist = true)
% constrviolation: constrviolation of x (if problem is
% constrained; should be 0 since BOBYQA is a feasible method)
% fhist: history of function values
% chist: history of constraint violations (should be all 0)
% solver: backend solver that does the computation, i.e., 'bobyqa'
% message: return message
-% warnings: a cell array that records all the warnings raised
+% warnings: a cell array that records all the warnings raised
% during the computation
%
% 4. Options
@@ -89,18 +87,33 @@
% default: 2*length(x0)+1
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
+% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
+% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
+% of the trust region radius. Roughly speaking, the trust region radius
+% is contracted by a factor of gamma1 when the reduction ratio is below
+% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
+% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
+% and gamma2 = 2.
% *** scale: a boolean value indicating whether to scale the problem
% according to bounds or not; default: false; if the problem is to be
% scaled, then rhobeg and rhoend mentioned above will be used as the
% initial and final trust region radii for the scaled problem
% *** honour_x0: a boolean value indicating whether to respect the
-% user-defined x0 or not; default: false
+% user-defined x0 or not; default: false if the user provides a rhobeg
+% in (0, Inf), and true otherwise.
% *** iprint: a flag deciding how much information will be printed during
% the computation; possible values are value 0 (default), 1, -1, 2,
-% -2, 3, or -3:
-% 0: there will be no printing;
+% -2, 3, or -3.
+% 0: there will be no printing; this is the default;
% 1: a message will be printed to the screen at the return, showing
% the best vector of variables found and its objective function value;
% 2: in addition to 1, at each "new stage" of the computation, a message
@@ -137,7 +150,7 @@
% *** chkfunval: a boolean value indicating whether to verify the returned
% function value or not; default: false
% (if it is true, BOBYQA will check whether the returned value of fx
-% matches fun(x), which costs a function evaluation; designed only
+% matches fun(x) or not, which costs a function evaluation; designed only
% for debugging)
%
% For example, the following code
@@ -328,6 +341,7 @@
mexdir = fullfile(mfiledir, 'private');
fsolver = str2func(get_mexname(solver, precision, debug_flag, variant, mexdir));
try
+ setenv('GFORTRAN_ERROR_BACKTRACE', '1'); % Enable Fortran backtrace if the compiler is gfortran
[x, fx, exitflag, nf, xhist, fhist] = ...
fsolver(fun, x0, lb, ub, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, ...
maxfun, npt, iprint, maxhist, double(output_xhist));
diff --git a/matlab/interfaces/cobyla.m b/matlab/interfaces/cobyla.m
index ad461c4bd8..d1654d85ae 100644
--- a/matlab/interfaces/cobyla.m
+++ b/matlab/interfaces/cobyla.m
@@ -28,10 +28,11 @@
% x = cobyla(fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon)
%
% solves the problem formulated above, where
+%
% *** fun is the name or function handle of the objective function; if
% there is no objective function (i.e., we have a feasibility problem),
% then set fun = []
-% *** x0 is the starting point; x0 CANNOT be []
+% *** x0 is the starting point; x0 CANNOT be omitted or set to []
% *** Aineq and bineq are the coefficient matrix and right-hand side of
% the linear inequality constraint Aineq * x <= bineq; if there is
% no such constraint, set Aineq = [], bineq = []
@@ -79,31 +80,33 @@
%
% *** x is the approximate solution to the optimization problem
% *** fx is fun(x)
-% *** exitflag is an integer indicating why COBYLA returns; the
-% possible values are
+% *** exitflag is an integer indicating why COBYLA returns; the possible values are
% 0: the lower bound for the trust region radius is reached
% 1: the target function value is achieved
-% 2: a trust region step failed to reduce the quadratic model
+% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
-% 4, 7, 8, 9: rounding errors become severe in the Fortran code
+% 7: rounding errors become severe in the Fortran code
% 13: all variables are fixed by the constraints
% 14: a linear feasibility problem received and solved
% 15: a linear feasibility problem received but not solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
-% -1: NaN occurs in x
-% -2: the objective/constraint function returns NaN or nearly
-% infinite values (only in the classical mode)
-% -3: NaN occurs in the models
+% 20: the trust region iteration has been performed for 2*maxfun times
+% -1: NaN occurs in x (possible only in the classical mode)
+% -2: the objective/constraint function returns an Inf/NaN value (possible only in classical
+% mode)
+% -3: NaN occurs in the models (possible only in classical mode)
% -4: constraints are infeasible
-% exitflag = 5, 10, 11, 12 are possible exitflags of the Fortran
-% code but cannot be returned by COBYLA
% *** output is a structure with the following fields:
% funcCount: number of function evaluations
% nlcineq: cineq(x) (if there is nonlcon)
% nlceq: ceq(x) (if there is nonlcon)
% constrviolation: constrviolation of x (if problem is constrained)
+% xhist: history of iterates (if options.output_xhist = true)
% fhist: history of function values
% chist: history of constraint violations
+% nlcihist: history of nonlinear inequality constraint values (if
+% options.output_nlchist = true)
+% nlcehist: history of nonlinear equality constraint values (if
+% options.output_nlchist = true)
% solver: backend solver that does the computation, i.e., 'cobyla'
% message: return message
% warnings: a cell array that records all the warnings raised
@@ -129,25 +132,30 @@
% positive and not larger than rhobeg; default: 1e-6
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
-% *** scale: a boolean value indicating whether to scale the problem
-% according to bounds or not; default: false; if the problem is to
-% be scaled, then rhobeg and rhoend mentioned above will be used as
-% the initial and final trust region radii for the scaled problem
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
% of the trust region radius. Roughly speaking, the trust region radius
% is contracted by a factor of gamma1 when the reduction ratio is below
% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
-% above eta2. It is required that 0 < eta1 <= eta2 < 1 and
-% 0 < gamma1 < 1 < gamma2. Normally, eta1 <= 0.25. It is not recommended
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
% and gamma2 = 2.
+% *** scale: a boolean value indicating whether to scale the problem
+% according to bounds or not; default: false; if the problem is to
+% be scaled, then rhobeg and rhoend mentioned above will be used as
+% the initial and final trust region radii for the scaled problem
% *** iprint: a flag deciding how much information will be printed during
% the computation; possible values are value 0 (default), 1, -1, 2,
-% -2, 3, or -3:
-% 0: there will be no printing;
+% -2, 3, or -3.
+% 0: there will be no printing; this is the default;
% 1: a message will be printed to the screen at the return, showing
% the best vector of variables found and its objective function value;
% 2: in addition to 1, at each "new stage" of the computation, a message
@@ -189,10 +197,10 @@
% "filter" used for selecting the returned solution; default: 2000
% *** debug: a boolean value indicating whether to debug or not; default: false
% *** chkfunval: a boolean value indicating whether to verify the returned
-% function and constraint (if applicable) value or not; default: false
-% (if it is true, COBYLA will check whether the returned values of fun
-% and nonlcon matches fun(x) and nonlcon(x) or not, which costs
-% function/constraint evaluations; designed only for debugging)
+% function and constraint (if applicable) values or not; default: false
+% (if it is true, COBYLA will check whether the returned values of fun and
+% nonlcon match fun(x) and nonlcon(x) or not, which costs function/constraint
+% evaluations; designed only for debugging)
%
% For example, the following code
%
@@ -437,6 +445,7 @@
% The mexified Fortran Function is a private function generating only private errors;
% however, public errors can occur due to, e.g., evalobj; error handling needed.
try
+ setenv('GFORTRAN_ERROR_BACKTRACE', '1'); % Enable Fortran backtrace if the compiler is gfortran
[x, fx, constrviolation, nlconstr, exitflag, nf, xhist, fhist, chist, nlchist] = ...
fsolver(funcon, x0, f_x0, nlconstr_x0, Aineq, bineq, Aeq, beq, lb, ub, rhobeg, rhoend, ...
eta1, eta2, gamma1, gamma2, ftarget, ctol, cweight, maxfun, iprint, maxhist, ...
diff --git a/matlab/interfaces/lincoa.m b/matlab/interfaces/lincoa.m
index b955988537..1632505cb2 100644
--- a/matlab/interfaces/lincoa.m
+++ b/matlab/interfaces/lincoa.m
@@ -22,10 +22,11 @@
% x = lincoa(fun, x0, Aineq, bineq, Aeq, beq, lb, ub)
%
% solves the problem formulated above, where
+%
% *** fun is the name or function handle of the objective function; if
% there is no objective function (i.e., we have a feasibility problem),
% then set fun = []
-% *** x0 is the starting point; x0 CANNOT be []
+% *** x0 is the starting point; x0 CANNOT be omitted or set to []
% *** Aineq and bineq are the coefficient matrix and right-hand side of
% the linear inequality constraint Aineq * x <= bineq; if there is
% no such constraint, set Aineq = [], bineq = []
@@ -57,26 +58,23 @@
%
% *** x is the approximate solution to the optimization problem
% *** fx is fun(x)
-% *** exitflag is an integer indicating why LINCOA returns; the
-% possible values are
+% *** exitflag is an integer indicating why LINCOA returns; the possible values are
% 0: the lower bound for the trust region radius is reached
% 1: the target function value is achieved
-% 2: a trust region step failed to reduce the quadratic model
+% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
-% 4, 7, 8, 9: rounding errors become severe in the Fortran code
+% 7: rounding errors become severe in the Fortran code
% 13: all variables are fixed by the constraints
-% 14: a feasibility problem received and solved
-% 15: a feasibility problem received but not solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
-% -1: NaN occurs in x
-% -2: the objective function returns an NaN or nearly infinite
-% value (only in the classical mode)
-% -3: NaN occurs in the models
+% 14: a linear feasibility problem received and solved
+% 15: a linear feasibility problem received but not solved
+% 20: the trust region iteration has been performed for 2*maxfun times
+% -1: NaN occurs in x (possible only in the classical mode)
+% -2: the objective function returns an Inf/NaN value (possible only in classical mode)
+% -3: NaN occurs in the models (possible only in classical mode)
% -4: constraints are infeasible
-% exitflag = 5, 10, 11, 12 are possible exitflags of the Fortran
-% code but cannot be returned by LINCOA
% *** output is a structure with the following fields:
% funcCount: number of function evaluations
+% xhist: history of iterates (if options.output_xhist = true)
% constrviolation: constrviolation of x (if problem is constrained)
% fhist: history of function values
% chist: history of constraint violations
@@ -107,16 +105,30 @@
% default: 2*length(x0)+1
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
+% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
+% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
+% of the trust region radius. Roughly speaking, the trust region radius
+% is contracted by a factor of gamma1 when the reduction ratio is below
+% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
+% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
+% and gamma2 = 2.
% *** scale: a boolean value indicating whether to scale the problem
% according to bounds or not; default: false; if the problem is to be
% scaled, then rhobeg and rhoend mentioned above will be used as the
-% initial and final trust region radii for the scaled problem
+% initial and final trust region radii for the scaled problem
% *** iprint: a flag deciding how much information will be printed during
% the computation; possible values are value 0 (default), 1, -1, 2,
-% -2, 3, or -3:
-% 0: there will be no printing;
+% -2, 3, or -3.
+% 0: there will be no printing; this is the default;
% 1: a message will be printed to the screen at the return, showing
% the best vector of variables found and its objective function value;
% 2: in addition to 1, at each "new stage" of the computation, a message
@@ -155,8 +167,8 @@
% *** chkfunval: a boolean value indicating whether to verify the returned
% function value or not; default: false
% (if it is true, LINCOA will check whether the returned value of fun
-% matches fun(x) or not, which costs a function evaluation;
-% designed only for debugging)
+% matches fun(x) or not, which costs a function evaluation; designed only
+% for debugging)
%
% For example, the following code
%
@@ -360,6 +372,7 @@
% The mexified Fortran function is a private function generating only private errors;
% however, public errors can occur due to, e.g., evalobj; error handling needed
try
+ setenv('GFORTRAN_ERROR_BACKTRACE', '1'); % Enable Fortran backtrace if the compiler is gfortran
[x, fx, constrviolation, exitflag, nf, xhist, fhist, chist] = ...
fsolver(fun, x0, Aineq, bineq, Aeq, beq, lb, ub, rhobeg, rhoend, eta1, eta2, gamma1, ...
gamma2, ftarget, ctol, cweight, maxfun, npt, iprint, maxhist, double(output_xhist), maxfilt);
diff --git a/matlab/interfaces/newuoa.m b/matlab/interfaces/newuoa.m
index 5ec8df2bdf..104a913109 100644
--- a/matlab/interfaces/newuoa.m
+++ b/matlab/interfaces/newuoa.m
@@ -34,7 +34,7 @@
% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
% 14: a linear feasibility problem received and solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
+% 20: the trust region iteration has been performed for 2*maxfun times
% -1: NaN occurs in x (possible only in the classical mode)
% -2: the objective function returns an Inf/NaN value (possible only in classical mode)
% -3: NaN occurs in the models (possible only in classical mode)
@@ -44,7 +44,7 @@
% fhist: history of function values
% solver: backend solver that does the computation, i.e., 'newuoa'
% message: return message
-% warnings: a cell array that records all the warnings raised
+% warnings: a cell array that records all the warnings raised
% during the computation
%
% 3. Options
@@ -66,15 +66,20 @@
% default: 2*length(x0)+1
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
% of the trust region radius. Roughly speaking, the trust region radius
% is contracted by a factor of gamma1 when the reduction ratio is below
% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
-% above eta2. It is required that 0 < eta1 <= eta2 < 1 and
-% 0 < gamma1 < 1 < gamma2. Normally, eta1 <= 0.25. It is not recommended
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
% and gamma2 = 2.
% *** iprint: a flag deciding how much information will be printed during
@@ -117,8 +122,8 @@
% *** chkfunval: a boolean value indicating whether to verify the returned
% function value or not; default: false
% (if it is true, NEWUOA will check whether the returned value of fx
-% matches fun(x) or not, which costs a function evaluation;
-% designed only for debugging)
+% matches fun(x) or not, which costs a function evaluation; designed only
+% for debugging)
%
% For example, the following code
%
@@ -280,6 +285,7 @@
fsolver = str2func(get_mexname(solver, precision, debug_flag, variant, mexdir));
% The mexified Fortran Function is a private function generating only private errors;
% however, public errors can occur due to, e.g., evalobj; error handling needed.
+ setenv('GFORTRAN_ERROR_BACKTRACE', '1'); % Enable Fortran backtrace if the compiler is gfortran
[x, fx, exitflag, nf, xhist, fhist] = ...
fsolver(fun, x0, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, maxfun, npt, ...
iprint, maxhist, double(output_xhist));
diff --git a/matlab/interfaces/prima.m b/matlab/interfaces/prima.m
index 7a8689cd32..6f338152e2 100644
--- a/matlab/interfaces/prima.m
+++ b/matlab/interfaces/prima.m
@@ -28,7 +28,7 @@
% *** fun is the name or function handle of the objective function; if
% there is no objective function (i.e., we have a feasibility problem),
% then set fun = []
-% *** x0 is the starting point; x0 CANNOT be []
+% *** x0 is the starting point; x0 CANNOT be omitted or set to []
% *** Aineq and bineq are the coefficient matrix and right-hand side of
% the linear inequality constraint Aineq * x <= bineq; if there is
% no such constraint, set Aineq = [], bineq = []
@@ -82,24 +82,23 @@
%
% *** x is the approximate solution to the optimization problem
% *** fx is fun(x)
-% *** exitflag is an integer indicating why PRIMA or its backend solver
-% returns; the possible values are
+% *** exitflag is an integer indicating why PRIMA or its backend solver returns;
+% the possible values are
% 0: the lower bound for the trust region radius is reached
% 1: the target function value is achieved
-% 2: a trust region step failed to reduce the quadratic model
+% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
-% 4, 7, 8, 9: rounding errors become severe in the Fortran code
+% 7: rounding errors become severe in the Fortran code
+% 8: a linear constraint has zero gradient
% 13: all variables are fixed by the constraints
% 14: a linear feasibility problem received and solved
% 15: a linear feasibility problem received but not solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
-% -1: NaN occurs in x
-% -2: the objective/constraint function returns NaN or nearly
-% infinite values (only in the classical mode)
-% -3: NaN occurs in the models
+% 20: the trust region iteration has been performed for 2*maxfun times
+% -1: NaN occurs in x (possible only in the classical mode)
+% -2: the objective/constraint function returns an Inf/NaN value (possible only in classical
+% mode)
+% -3: NaN occurs in the models (possible only in classical mode)
% -4: constraints are infeasible
-% exitflag = 5, 10, 11, 12 are possible exitflags of the Fortran
-% code but cannot be returned by PRIMA or its solvers
% *** output is a structure with the following fields:
% funcCount: number of function evaluations
% nlcineq: cineq(x) (if there is nonlcon)
@@ -114,7 +113,7 @@
% options.output_nlchist = true)
% solver: backend solver that does the computation
% message: return message
-% warnings: a cell array that record all the warnings raised
+% warnings: a cell array that records all the warnings raised
% during the computation
%
% 4. Options
@@ -146,27 +145,34 @@
% 'cobyla' (for general constrained or unconstrained problems)
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
% of the trust region radius. Roughly speaking, the trust region radius
% is contracted by a factor of gamma1 when the reduction ratio is below
% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
-% above eta2. It is required that 0 < eta1 <= eta2 < 1 and
-% 0 < gamma1 < 1 < gamma2. Normally, eta1 <= 0.25. It is not recommended
-% to set eta1 >= 0.5. Default: values hard-coded in Powell's Fortran code.
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
+% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
+% and gamma2 = 2.
% *** scale: (only for BOBYQA, LINCOA, and COBYLA) a boolean value
% indicating whether to scale the problem according to bounds or not;
% default: false; if the problem is to be scaled, then rhobeg and rhoend
% mentioned above will be used as the initial and final trust region
% radii for the scaled problem
% *** honour_x0: (only for BOBYQA) a boolean value indicating whether to
-% respect the user-defined x0; default: false
+% respect the user-defined x0 or not; default: false if the user provides
+% a rhobeg in (0, Inf), and true otherwise.
% *** iprint: a flag deciding how much information will be printed during
% the computation; possible values are value 0 (default), 1, -1, 2,
-% -2, 3, or -3:
-% 0: there will be no printing;
+% -2, 3, or -3.
+% 0: there will be no printing; this is the default;
% 1: a message will be printed to the screen at the return, showing
% the best vector of variables found and its objective function value;
% 2: in addition to 1, at each "new stage" of the computation, a message
@@ -194,8 +200,8 @@
% If maxhist is so large that recording the history takes too much memory,
% the Fortran code will reset maxhist to a smaller value. The maximal
% amount of memory defined the Fortran code is 2GB.
-% Let L = length(x) + 2*(number of nonlinear constraints). Assuming
-% that maxfun <= 500*L, then any problem with L <= 400 is not affected.
+% Let L = length(x) + 2*(number of nonlinear constraints). Assume
+% that maxfun <= 500*L. Then any problem with L <= 400 is not affected.
% *******************************************************************
% *** output_xhist: a boolean value indicating whether to output the
% history of the iterates; if it is set to true, then the output
@@ -212,7 +218,7 @@
% *** chkfunval: a boolean value indicating whether to verify the returned
% function and constraint (if applicable) values or not; default: false
% (if it is true, PRIMA will check whether the returned values of fun and
-% nonlcon match fun(x) and nonlcon(x), which costs function/constraint
+% nonlcon match fun(x) and nonlcon(x) or not, which costs function/constraint
% evaluations; designed only for debugging)
%
% For example, the following code
@@ -261,7 +267,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Attribute: public (can be called directly by users)
%
-% Remarks
+% Remarks:
%
% 1. Public function v.s. private function
% 1.1. Public functions are functions that can be directly called by users.
@@ -318,18 +324,6 @@
% 3. probinfo
% !!! TREAT probinfo AS A READONLY VARIABLE AFTER PREPRIMA !!!
% !!! DO NOT CHANGE probinfo AFTER PREPRIMA !!!
-%
-% TODO:
-% 1. Implicit NONE and variable declaration in the Fortran code
-% 2. Change the interface of preprima to
-% probinfo = preprima(argin{:}, interface_type),
-% where interface_type is one of 'unconstrained',
-% 'bound-constrained', 'linearly-constrained',
-% 'nonlinearly-constrained'.
-% All the information needed by the solvers should be included in probinfo.
-% 3. To add a new solver, we only need to call preprima, call the solver
-% using the information in probinfo, record the results in output, and
-% then call postprima.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% prima starts
diff --git a/matlab/interfaces/private/getmax.m b/matlab/interfaces/private/getmax.m
index 058bf66176..2184293d39 100644
--- a/matlab/interfaces/private/getmax.m
+++ b/matlab/interfaces/private/getmax.m
@@ -22,19 +22,26 @@
precision = 'double';
end
-% The following values are intended to be the returns of the Fortran intrinsics RADIX, HUGE, and
-% MAXEXPONENT corresponding to the given precision. They are the largest finite value, base of the
-% model that represents the floating point numbers, and maximal exponent of the model.
-% As of 20230207, the following values are consistent with gfortran, ifort, ifx, nagfor, nvfortran,
-% Classic flang, AOCC flang, sunf95, and g95.
-radix = 2;
-if strcmpi(precision, 'single')
- maxfloat = realmax('single') ;
- maxexponent = 128;
-else % Even if precision = 'quadruple'
- maxfloat = realmax('double') ;
- maxexponent = 1024;
+% maxfloat, minfloat, and maxpow10 are intended to the return of the Fortran intrinsics HUGE, TINY,
+% and RANGE corresponding to the given precision. They are the largest finite value, the smallest
+% positive normalized value, and the decimal exponent range of the floating point numbers, respectively.
+switch lower(precision)
+case 'half' % IEEE 754 half precision (float16); followed by nagfor 7.1+ when providing REAL16.
+ maxfloat = 2^16 - 2^5; %65504;
+ minfloat = 2^(-14); %6.103515e-05;
+case 'single'
+ maxfloat = realmax('single');
+ minfloat = realmin('single');
+case {'double', 'quadruple'}
+ maxfloat = realmax('double');
+ minfloat = realmin('double');
+otherwise
+ % Private/unexpected error
+ error(sprintf('%s:InvalidInput', funname), '%s: UNEXPECTED ERROR: invalid precision received.', funname);
end
+% According to Sec. 16.9.170 of Fortran 2023 Interpretation Document J3/24-007, the Fortran intrinsic
+% RANGE returns the following value.
+maxpow10 = fix(min(log10(maxfloat), -log10(minfloat)));
% The following values are intended to be consistent with BOUNDMAX, FUNCMAX, and CONSTRMAX defined
% in the Fortran code.
@@ -44,7 +51,7 @@
case {'bound'}
maxnum = 0.25 * maxfloat;
case {'fun', 'func', 'function', 'con', 'constr', 'constraint'}
- maxnum = radix^min(100, maxexponent / 2);
+ maxnum = 10^max(4, min(30, floor(maxpow10 / 2)));
otherwise
% Private/unexpected error
error(sprintf('%s:InvalidInput', funname), '%s: UNEXPECTED ERROR: invalid data_type received.', funname);
diff --git a/matlab/interfaces/private/postprima.m b/matlab/interfaces/private/postprima.m
index fae83dbf44..e3c858bb3a 100644
--- a/matlab/interfaces/private/postprima.m
+++ b/matlab/interfaces/private/postprima.m
@@ -3,10 +3,8 @@
% output variables.
%
% ***********************************************************************
-% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University
+% Authors: Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+% and Zaikun ZHANG (www.zhangzk.net)
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
@@ -44,15 +42,17 @@
obligatory_options_fields = {'classical', 'debug', 'chkfunval', 'precision'};
% Who is calling this function? Is it a correct invoker?
-invoker_list = ['prima', all_solvers()];
+invoker_list = [all_solvers(), 'prima'];
+% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
+invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
-if (length(callstack) == 1) || ~ismember(callstack(2).name, invoker_list)
+if length(callstack) > 1 && ismember(callstack(2).name, invoker_list)
+ invoker = callstack(2).name; % Name of the function who calls this function
+else
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s should only be called by %s.', funname, funname, strjoin(invoker_list, ', '));
-else
- invoker = callstack(2).name; % Name of the function who calls this function
end
% Verify the input before starting the real business
@@ -411,7 +411,7 @@
xhist(~probinfo.fixedx, :) = freexhist;
end
-% Include the possibly revised xhist to output if necessary.if
+% Include the possibly revised xhist to output if necessary.
if output_has_xhist
output.xhist = xhist;
end
@@ -458,7 +458,7 @@
% Record the return message in output.message according to exitflag
-switch exitflag % If preprima works properly, then 5, 6, 10, 11, 12 should never happen
+switch exitflag % The flags commented out should never happen. They are skipped for backward compatibility.
case 0
output.message = sprintf('Return from %s because the trust region radius reaches its lower bound.', solver);
case 1
@@ -467,8 +467,8 @@
output.message = sprintf('Return from %s because a trust region step has failed to reduce the quadratic model.', solver);
case 3
output.message = sprintf('Return from %s because the objective function has been evaluated maxfun times.', solver);
-case 4
- output.message = sprintf('Return from %s because of much cancellation in a denominator.', solver);
+%case 4
+% output.message = sprintf('Return from %s because of much cancellation in a denominator.', solver);
%case 5
% output.message = sprintf('Return from %s because npt is not in the required interval.', solver);
%case 6
@@ -476,9 +476,9 @@
case 7
output.message = sprintf('Return from %s because rounding errors are becoming damaging.', solver);
case 8
- output.message = sprintf('Return from %s because rounding errors prevent reasonable changes to x.', solver);
-case 9
- output.message = sprintf('Return from %s because the denominator of the updating formula is zero.', solver);
+ output.message = sprintf('Return from %s because one of the linear constraints has zero gradient.', solver);
+%case 9
+% output.message = sprintf('Return from %s because the denominator of the updating formula is zero.', solver);
%case 10
% output.message = sprintf('Return from %s because n should not be less than 2.', solver);
%case 11
@@ -492,9 +492,9 @@
case 15
output.message = sprintf('%s receives a linear feasibility problem but does not find a feasible point.', invoker);
case 20
- output.message = sprintf('Return from %s because the trust-region iteration has been performed maxtr (= 2*maxfun) times.', invoker);
+ output.message = sprintf('Return from %s because the trust region iteration has been performed maxtr (= 2*maxfun) times.', invoker);
wid = sprintf('%s:MaxtrReached', invoker);
- wmsg = sprintf('%s: The maximal number of trust-region iterations is reached. This is rare. Check that the algorithm works properly.', invoker);
+ wmsg = sprintf('%s: The maximal number of trust region iterations is reached. This is rare. Check that the algorithm works properly.', invoker);
warning(wid, '%s', wmsg);
if isfield(output, 'warnings')
output.warnings = [output.warnings, wmsg];
@@ -504,11 +504,7 @@
case -1
output.message = sprintf('Return from %s because NaN occurs in x.', solver);
case -2 % This cannot happen if the moderated extreme barrier is implemented, which is the case when options.classical is false.
- if strcmp(solver, 'cobyla')
- output.message = sprintf('Return from %s because the objective function returns an NaN or nearly infinite value, or the constraints return a NaN.', solver);
- else
- output.message = sprintf('Return from %s because the objective function returns an NaN or nearly infinite value.', solver);
- end
+ output.message = sprintf('Return from %s because the objective function returns an NaN or nearly infinite value, or the constraints return a NaN.', solver);
case -3
output.message = sprintf('Return from %s because NaN occurs in the models.', solver);
case -4
@@ -592,13 +588,9 @@
% especially if the problem is scaled or reduced.
% 3. The precision of the constraints seem to be lower for cobyla and lincoa due to the
% matrix-vector products.
- %%%%%%%%%%%%%%%%%%%%%% Old values %%%%%%%%%%%%%%%%%%%%%%
- %cobyla_prec = 1e-4;
- %bobyqa_prec = 1e-10;
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cobyla_prec = 1e-5;
lincoa_prec = 1e-5;
- bobyqa_prec = 1e-12;
+ bobyqa_prec = 1e-7;
% Check whether constrviolation is correct
constrviolation = 0;
diff --git a/matlab/interfaces/private/preprima.m b/matlab/interfaces/private/preprima.m
index 51aa554762..8ce24d4039 100644
--- a/matlab/interfaces/private/preprima.m
+++ b/matlab/interfaces/private/preprima.m
@@ -1,11 +1,9 @@
function [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options, probinfo] = preprima(fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options)
-%PREPRIMA preprocesses the input to prima and its solvers.
+%PREPRIMA preprocesses the input to prima.
%
% ***********************************************************************
-% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University
+% Authors: Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+% and Zaikun ZHANG (www.zhangzk.net)
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
@@ -26,14 +24,16 @@
% Who is calling this function? Is it a correct invoker?
invoker_list = [all_solvers(), 'prima'];
+% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
+invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
-if (length(callstack) == 1 || ~ismember(callstack(2).name, invoker_list))
+if length(callstack) > 1 && ismember(callstack(2).name, invoker_list)
+ invoker = callstack(2).name; % Name of the function who calls this function
+else
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s should only be called by %s.', funname, funname, strjoin(invoker_list, ', '));
-else
- invoker = callstack(2).name; % Name of the function who calls this function
end
if (nargin ~= 1) && (nargin ~= 10)
@@ -115,8 +115,8 @@
probinfo.raw_data = struct('objective', fun, 'x0', x0, 'Aineq', Aineq, 'bineq', bineq, ...
'Aeq', Aeq, 'beq', beq, 'lb', lb, 'ub', ub, 'nonlcon', nonlcon, 'options', options);
-% Decide the precision ('single', 'double', or 'quadruple') of the real calculation within the
-% Fortran solvers. This is needed ONLY by `boundmax`, `funcmax`, and `constrmax` defined below by
+% Decide the precision ('half', 'single', 'double', or 'quadruple') of the real calculation within
+% the Fortran solvers. This is needed ONLY by `boundmax`, `funcmax`, and `constrmax` defined below by
% calling `getmax`. These three numbers will be used in `pre_x0`, `pre_fun`, and `pre_nonlcon`
% respectively in the sequel. Note the following.
% 1. `precision` takes effect only if Fortran solvers are called (i.e., when options.fortran = true).
@@ -261,7 +261,7 @@
% have to be revised accordingly. We will raise a warning when revising
% an option that is in user_options_fields. No warning is needed if we
% are dealing with an option that is not in user_options_fields.
-[options, probinfo.user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, warnings);
+[options, probinfo.user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, fixedx, warnings);
% Revise x0 for bound and linearly constrained problems.
% This is necessary for LINCOA, which accepts only feasible x0.
@@ -299,7 +299,7 @@
probinfo.scaling_factor = ones(size(x0));
probinfo.shift = zeros(size(x0));
if options.scale && ~probinfo.nofreex && ~probinfo.infeasible
- [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, scaling_factor, shift, ~, warnings] = scale_problem(invoker, fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, warnings);
+ [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, scaling_factor, shift, warnings] = scale_problem(invoker, fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, warnings);
% Scale and shift the problem so that all the bounds become [-1, 1]
% It is done only if all variables have both lower and upper bounds
probinfo.scaled = true;
@@ -380,6 +380,8 @@
% Possible invokers
invoker_list = [all_solvers(), 'prima'];
+% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
+invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
@@ -431,11 +433,7 @@
if ~isempty(unknown_fields)
wid = sprintf('%s:UnknownProbField', invoker);
- if length(unknown_fields) == 1
- wmsg = sprintf('%s: problem with an unknown field %s; it is ignored.', invoker, strjoin(unknown_fields, ', '));
- else
- wmsg = sprintf('%s: problem with unknown fields %s; they are ignored.', invoker, strjoin(unknown_fields, ', '));
- end
+ wmsg = sprintf('%s: problem with unknown field(s) %s, which will be ignored.', invoker, strjoin(unknown_fields, ', '));
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
@@ -861,7 +859,7 @@
return
%%%%%%%%%%%%%%%%% Function for option preprocessing %%%%%%%%%%
-function [options, user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, warnings)
+function [options, user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, fixedx, warnings)
% NOTE: We treat field names case-sensitively.
@@ -877,6 +875,8 @@
% Possible invokers
invoker_list = [solver_list, 'prima'];
+% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
+invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name;
@@ -893,6 +893,7 @@
maxfun = 500*lenx0;
rhobeg = 1; % The default rhobeg and rhoend will be revised if solver = 'bobyqa'
rhoend = 1e-6;
+rho_ratio = rhoend/rhobeg;
ftarget = -inf;
ctol = sqrt(eps); % Tolerance for constraint violation; a point with a constraint violation at most ctol is considered feasible
cweight = 1e8; % The weight of constraint violation in the selection of the returned x
@@ -901,7 +902,9 @@
fortran = true; % Call the Fortran code?
scale = false; % Scale the problem according to bounds? Scale only if the bounds reflect well the scale of the problem
scale = (scale && max(ub-lb) 0);
iprint = 0;
quiet = true;
debug_flag = false; % Do not use 'debug' as the name, which is a MATLAB function
@@ -910,6 +913,10 @@
output_nlchist = false; % Output the history of the nonlinear constraints?
min_maxfilt = 200; % The smallest value of maxfilt; if maxfilt is too small, the returned x may not be the best one visited
maxfilt = 10*min_maxfilt; % Length of the filter used for selecting the returned x in constrained problems
+eta1 = 0.1; % Reduction ratio threshold for shrinking the trust region
+eta2 = 0.7; % Reduction ratio threshold for expanding the trust region
+gamma1 = 0.5; % Factor for shrinking the trust region
+gamma2 = 2; % Factor for expanding the trust region
if ~(isa(options, 'struct') || isempty(options))
% Public/normal error
@@ -1010,11 +1017,7 @@
% even though we have declared that this field will be ignored.
if ~isempty(unknown_fields)
wid = sprintf('%s:UnknownOption', invoker);
- if length(unknown_fields) == 1
- wmsg = sprintf('%s: unknown option %s; it is ignored.', invoker, strjoin(unknown_fields, ', '));
- else
- wmsg = sprintf('%s: unknown options %s; they are ignored.', invoker, strjoin(unknown_fields, ', '));
- end
+ wmsg = sprintf('%s: unknown option(s) %s, which will be ignored.', invoker, strjoin(unknown_fields, ', '));
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
@@ -1066,11 +1069,11 @@
% Revise default rhobeg and rhoend according to options.scale and solver
if options.scale
rhobeg = 0.5; % This value cannot be bigger than 1. Otherwise, BOBYQA will complain.
- rhoend = 1e-6;
+ rhoend = max(eps, min(rho_ratio*rhobeg, rhoend));
end
if strcmpi(solver, 'bobyqa') && ~options.scale
rhobeg = max(eps, min(rhobeg, min(ub-lb)/4));
- rhoend = max(eps, min(0.1*rhobeg, rhoend));
+ rhoend = max(eps, min(rho_ratio*rhobeg, rhoend));
end
@@ -1102,8 +1105,13 @@
elseif any(strcmpi(solver, {'newuoa', 'bobyqa', 'lincoa'})) && (~isintegerscalar(options.npt) || isnan(options.npt) || options.npt < lenx0+2 || options.npt > (lenx0+1)*(lenx0+2)/2)
% newuoa, bobyqa and lincoa requires n+2 <= npt <= (n+1)*)(n+2)/2;
% uobyqa and cobyla do not use npt.
+ lenx0_orig = length(fixedx);
+ if any(fixedx) && isintegerscalar(options.npt) && options.npt <= (lenx0_orig+1)*(lenx0_orig+2)/2 && options.npt > (lenx0+1)*(lenx0+2)/2
+ wmsg = sprintf('%s: npt becomes invalid because some variables are fixed by bounds; %s requires it to be an integer and n+2 <= npt <= (n+1)*(n+2)/2; it is set to 2n+1.', invoker, solver);
+ else
+ wmsg = sprintf('%s: invalid npt; %s requires it to be an integer and n+2 <= npt <= (n+1)*(n+2)/2; it is set to 2n+1.', invoker, solver);
+ end
wid = sprintf('%s:InvalidNpt', invoker);
- wmsg = sprintf('%s: invalid npt; %s requires it to be an integer and n+2 <= npt <= (n+1)*(n+2)/2; it is set to 2n+1.', invoker, solver);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
else
@@ -1176,12 +1184,12 @@
% Validate options.rhobeg
% NOTE: if the problem is to be scaled, then options.rhobeg and options.rhoend
-% will be used as the initial and final trust-region radii for the scaled problem.
+% will be used as the initial and final trust region radii for the scaled problem.
validated = false;
if isfield(options, 'rhobeg')
- if ~isrealscalar(options.rhobeg) || options.rhobeg <= 0 || isnan(options.rhobeg) || options.rhobeg == inf
+ if ~(isrealscalar(options.rhobeg) && isfinite(options.rhobeg) && options.rhobeg > 0)
wid = sprintf('%s:InvalidRhobeg', invoker);
- wmsg = sprintf('%s: invalid rhobeg; it should be a positive number; it is set to max(%g, rhoend).', invoker, rhobeg);
+ wmsg = sprintf('%s: invalid rhobeg; it should be a positive number; it is set to max(%g, 10*rhoend).', invoker, rhobeg);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
elseif strcmpi(solver, 'bobyqa') % Validate options.rhobeg for bobyqa
@@ -1206,7 +1214,7 @@
if ~validated % options.rhobeg has not got a valid value yet
% Take into account `rhoend` if it has got a valid value. We do not do this for `bobyqa`, which
% requires that rhobeg <= min(xu-xl)/2.
- if isfield(options, 'rhoend') && isrealscalar(options.rhoend) && options.rhoend >= 0 && options.rhoend < inf && ~strcmpi(solver, 'bobyqa')
+ if isfield(options, 'rhoend') && isrealscalar(options.rhoend) && isfinite(options.rhoend) && options.rhoend >= 0 && ~strcmpi(solver, 'bobyqa')
options.rhobeg = max(rhobeg, 10*options.rhoend);
else
options.rhobeg = rhobeg;
@@ -1217,9 +1225,9 @@
% Validate options.rhoend
validated = false;
if isfield(options, 'rhoend')
- if ~isrealscalar(options.rhoend) || options.rhoend > options.rhobeg || isnan(options.rhoend)
+ if ~(isrealscalar(options.rhoend) && isfinite(options.rhoend) && options.rhoend >= 0 && options.rhoend <= options.rhobeg)
wid = sprintf('%s:InvalidRhoend', invoker);
- wmsg = sprintf('%s: invalid rhoend; we should have rhobeg >= rhoend > 0; it is set to min(0.1*rhobeg, %g).', invoker, rhoend);
+ wmsg = sprintf('%s: invalid rhoend; we should have rhobeg >= rhoend >= 0; it is set to min(%g*rhobeg, %g).', invoker, rho_ratio, rhoend);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
else
@@ -1227,14 +1235,18 @@
end
end
if ~validated % options.rhoend has not got a valid value yet
- options.rhoend = min(0.1*options.rhobeg, rhoend);
+ options.rhoend = min(rho_ratio*options.rhobeg, rhoend);
end
options.rhoend = double(max(options.rhoend, eps));
+% The following revision may update rhoend slightly. It prevents rhoend > rhobeg due to rounding
+% errors, which would not be accepted by the solvers.
+options.rhoend = min(options.rhoend, options.rhobeg);
+
% Validate options.ftarget
validated = false;
if isfield(options, 'ftarget')
- if ~isrealscalar(options.ftarget) || isnan(options.ftarget)
+ if ~isrealscalar(options.ftarget)
wid = sprintf('%s:InvalidFtarget', invoker);
wmsg = sprintf('%s: invalid ftarget; it should be a real number; it is set to %g.', invoker, ftarget);
warning(wid, '%s', wmsg);
@@ -1247,11 +1259,14 @@
options.ftarget = ftarget;
end
options.ftarget = double(options.ftarget);
+if isnan(options.ftarget) % If options.ftarget is NaN, we interpret it as no ftarget is provided.
+ options.ftarget = -Inf;
+end
% Validate options.ctol
validated = false;
if isfield(options, 'ctol')
- if ~isrealscalar(options.ctol) || options.ctol < 0 || isnan(options.ctol)
+ if ~(isrealscalar(options.ctol) && options.ctol >=0)
wid = sprintf('%s:InvalidCtol', invoker);
wmsg = sprintf('%s: invalid ctol; it should be a nonnegative number; it is set to %g.', invoker, ctol);
warning(wid, '%s', wmsg);
@@ -1268,7 +1283,7 @@
% Validate options.cweight
validated = false;
if isfield(options, 'cweight')
- if ~isrealscalar(options.cweight) || options.cweight < 0 || isnan(options.cweight)
+ if ~(isrealscalar(options.cweight) && options.cweight >=0)
wid = sprintf('%s:InvalidCweight', invoker);
wmsg = sprintf('%s: invalid cweight; it should be a nonnegative number; it is set to %g.', invoker, cweight);
warning(wid, '%s', wmsg);
@@ -1515,9 +1530,9 @@
% Validate options.maxhist
validated = false;
if isfield(options, 'maxhist')
- if ~isintegerscalar(options.maxhist) || options.maxhist < 0
+ if ~isintegerscalar(options.maxhist) || options.maxhist <= 0
wid = sprintf('%s:InvalidMaxhist', invoker);
- wmsg = sprintf('%s: invalid maxhist; it should be a nonnegative integer; it is set to maxfun.', invoker);
+ wmsg = sprintf('%s: invalid maxhist; it should be a positive integer; it is set to maxfun.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
else
@@ -1567,7 +1582,7 @@
% Validate options.maxfilt
validated = false;
if isfield(options, 'maxfilt')
- if ~isintegerscalar(options.maxfilt) || options.maxfilt < 1
+ if ~isintegerscalar(options.maxfilt) || options.maxfilt <= 0
wid = sprintf('%s:InvalidMaxfilt', invoker);
wmsg = sprintf('%s: invalid maxfilt; it should be a positive integer; it is set to %d.', invoker, maxfilt);
warning(wid, '%s', wmsg);
@@ -1591,11 +1606,11 @@
user_eta1_correct = false; % Does the user provide a correct eta1? Needed when validating eta2.
validated = false;
if isfield(options, 'eta1')
- if ~isrealscalar(options.eta1) || options.eta1 < 0 || options.eta1 >= 1
+ if ~(isrealscalar(options.eta1) && options.eta1 >= 0 && options.eta1 < 1)
wid = sprintf('%s:InvalidEta1', invoker);
- if isfield(options, 'eta2') && isrealscalar(options.eta2) && options.eta2 > 0 && options.eta2 <= 1
+ if isfield(options, 'eta2') && isrealscalar(options.eta2) && options.eta2 >= 0 && options.eta2 < 1
% The user provides a correct eta2; we define eta1 as follows.
- options.eta1 = max(eps, options.eta2/7);
+ options.eta1 = options.eta2/7;
wmsg = sprintf('%s: invalid eta1; it should be in the interval [0, 1) and not more than eta2; it is set to %g.', invoker, options.eta1);
validated = true;
else
@@ -1610,23 +1625,23 @@
end
end
if ~validated
- options.eta1 = NaN; % NaN means that Fortran will take the hard-coded default value.
+ options.eta1 = eta1;
end
options.eta1 = double(options.eta1);
% Validate options.eta2
validated = false;
if isfield(options, 'eta2')
- if ~isrealscalar(options.eta2) || (isnan(options.eta1) && options.eta2 < 0) || options.eta2 < options.eta1 || options.eta2 > 1
+ if ~(isrealscalar(options.eta2) && options.eta2 >= options.eta1 && options.eta2 < 1)
wid = sprintf('%s:InvalidEta2', invoker);
if user_eta1_correct
% The user provides a correct eta1; we define eta2 as follows.
options.eta2 = (options.eta1 + 2)/3;
validated = true;
- wmsg = sprintf('%s: invalid eta2; it should be in the interval [0, 1] and not less than eta1; it is set to %g.', invoker, options.eta2);
+ wmsg = sprintf('%s: invalid eta2; it should be in the interval [0, 1) and not less than eta1; it is set to %g.', invoker, options.eta2);
else
% The user does not provide a correct eta1; we take the default eta2 hard coded in Powell's code.
- wmsg = sprintf('%s: invalid eta2; it should be in the interval [0, 1] and not less than eta1; it will be set to the default value.', invoker);
+ wmsg = sprintf('%s: invalid eta2; it should be in the interval [0, 1) and not less than eta1; it will be set to the default value.', invoker);
end
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
@@ -1635,14 +1650,18 @@
end
end
if ~validated
- options.eta2 = NaN; % NaN means that Fortran will take the hard-coded default value.
+ options.eta2 = eta2;
end
options.eta2 = double(options.eta2);
+% The following revision may update eta1 slightly. It prevents eta1 > eta2 due to rounding
+% errors, which would not be accepted by the solvers.
+options.eta1 = min(options.eta1, options.eta2);
+
% Validate options.gamma1
validated = false;
if isfield(options, 'gamma1')
- if ~isrealscalar(options.gamma1) || options.gamma1 <= 0 || options.gamma1 >= 1
+ if ~(isrealscalar(options.gamma1) && options.gamma1 > 0 && options.gamma1 < 1)
wid = sprintf('%s:InvalidGamma1', invoker);
wmsg = sprintf('%s: invalid gamma1; it should be in the interval (0, 1); it will be set to the default value.', invoker);
warning(wid, '%s', wmsg);
@@ -1652,14 +1671,14 @@
end
end
if ~validated
- options.gamma1 = NaN; % NaN means that Fortran will take the hard-coded default value.
+ options.gamma1 = gamma1;
end
options.gamma1 = double(options.gamma1);
% Validate options.gamma2
validated = false;
if isfield(options, 'gamma2')
- if ~isrealscalar(options.gamma2) || options.gamma2 < 1 || options.gamma2 >= inf
+ if ~(isrealscalar(options.gamma2) && isfinite(options.gamma2) && options.gamma2 >= 1)
wid = sprintf('%s:InvalidGamma2', invoker);
wmsg = sprintf('%s: invalid gamma2; it should be a real number not less than 1; it will be set to the default value.', invoker);
warning(wid, '%s', wmsg);
@@ -1669,7 +1688,7 @@
end
end
if ~validated
- options.gamma2 = NaN; % NaN means that Fortran will take the hard-coded default value.
+ options.gamma2 = gamma2;
end
options.gamma2 = double(options.gamma2);
@@ -1677,7 +1696,7 @@
return
%%%%%%%%%%%%%%%%%%%%%% Function for scaling the problem %%%%%%%%%%%%%%%%
-function [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, scaling_factor, shift, substantially_scaled, warnings] = scale_problem(invoker, fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, warnings)
+function [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, scaling_factor, shift, warnings] = scale_problem(invoker, fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, warnings)
% x_before_scaling = scaling_factor.*x_after_scaling + shift
% Question: What about scaling according to the magnitude of x0, lb, ub,
@@ -1689,10 +1708,6 @@
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
-substantially_scaled_threshold = 2;
-% We consider the problem substantially scaled_threshold if
-% max([1; scaling_factor])/min([1; scaling_factor]) > substantially_scaled_threshold
-
% Zaikun 2020-05-24: we change the scaling strategy; do not scale the problem
% unless all variables have both lower and upper bounds
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1747,12 +1762,6 @@
% warnings = [warnings, wmsg];
%end
-substantially_scaled = false;
-%if (max([scaling_factor; 1 ./ scaling_factor]) > substantially_scaled_threshold)
-if max([1; scaling_factor])/min([1; scaling_factor]) > substantially_scaled_threshold
- substantially_scaled = true;
-end
-
if min(scaling_factor) < eps
% Private/unexpected error
error(sprintf('%s:InvalidScaling', funname), '%s: UNEXPECTED ERROR: invalid scaling factor returned when called by %s.', funname, invoker);
@@ -1763,6 +1772,8 @@
function [options, warnings] = select_solver(invoker, options, probinfo, warnings)
invoker_list = {'prima'};
+% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
+invoker_list = [invoker_list, strcat(invoker_list, '.m')];
% Only prima needs select_solver. We may have other invokers in the future!
solver_list = all_solvers();
% We may add other solvers in the future!
@@ -1850,12 +1861,13 @@
% Revise options.rhobeg and options.rhoend according to the selected solver.
% For the moment, only BOBYQA needs such a revision.
-if strcmp(solver, 'bobyqa') && options.rhobeg > min(probinfo.refined_data.ub-probinfo.refined_data.lb)/2
+if strcmp(solver, 'bobyqa') && ~probinfo.nofreex && options.rhobeg > min(probinfo.refined_data.ub-probinfo.refined_data.lb)/2
+ rho_ratio = options.rhoend / options.rhobeg;
options.rhobeg = max(eps, min(probinfo.refined_data.ub-probinfo.refined_data.lb)/4);
- options.rhoend = max(eps, min(0.1*options.rhobeg, options.rhoend));
+ options.rhoend = max(eps, min(rho_ratio*options.rhobeg, options.rhoend));
if ismember('rhobeg', probinfo.user_options_fields) || ismember('rhoend', probinfo.user_options_fields)
wid = sprintf('%s:InvalidRhobeg', invoker);
- wmsg = sprintf('%s: rhobeg is set to %g and rhoend to %g according to the selected solver bobyqa, which requires rhoend <= rhobeg <= min(ub-lb)/2.', invoker, options.rhobeg, options.rhoend);
+ wmsg = sprintf('%s: rhobeg is set to %g and rhoend to %g according to the selected solver %s, which requires rhoend <= rhobeg <= min(ub-lb)/2.', invoker, options.rhobeg, options.rhoend, solver);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
@@ -1938,47 +1950,30 @@
constrviolation = get_cstrv(x, Aineq, bineq, Aeq, beq, lb, ub, nlcineq, nlceq);
return
-%%%%%% Function for revising x0 or rhobeg when the solver is BOBYQA %%%%
+%%%%%% Function for revising x0 or rhobeg according to the bound constraints %%%%%%
function [x0, options, warnings] = pre_rhobeg_x0(invoker, x0, lb, ub, user_options_fields, options, warnings)
-% The Fortran code of BOBYQA will revise x0 so that the distance between x0
-% and the inactive bounds is at least rhobeg. We do the revision here in
-% order to raise a warning when such a revision occurs. The revision scheme
-% is slightly different from the one by Powell in his Fortran code, which sets
+% Revise x0 so that the distance between x0 and the inactive bounds is at least rhobeg.
+% This is needed by solvers that intend to respect bound constraints. Only BOBYQA as of 20240121.
+% The revision scheme is slightly different from the one by Powell in his Fortran code,
+% which sets
% x0 (lb < x0 < lb + rhobeg) = lb + rhobeg
% x0 (ub > x0 > ub - rhobeg) = ub - rhobeg
% Note that lb <= x0 <= ub and rhobeg <= (ub-lb)/2 after pre_options and project.
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
-solver_list = {'bobyqa'}; % Only BOBYQA needs pre_rhobeg_x0. May have others in the future.
+solver_list = {'bobyqa'}; % Only BOBYQA needs pre_rhobeg_x0 as of 20240121. May have others in the future.
if ~ismember(lower(options.solver), solver_list)
% Private/unexpected error
error(sprintf('%s:InvalidSolver', funname), '%s: UNEXPECTED ERROR: %s serves only %s.', funname, funname, strjoin(solver_list, ', '));
end
-if isfield(options, 'honour_x0') && options.honour_x0 % In this case, we respect the user-defined x0 and revise rhobeg
- rhobeg_old = options.rhobeg;
- lbx = (lb > -inf & x0 - lb <= eps*max(abs(lb), 1)); % x0 essentially equals lb
- ubx = (ub < inf & x0 - ub >= -eps*max(abs(ub), 1)); % x0 essentially equals ub
- x0(lbx) = lb(lbx);
- x0(ubx) = ub(ubx);
- options.rhobeg = max(eps, min([options.rhobeg; x0(~lbx) - lb(~lbx); ub(~ubx) - x0(~ubx)]));
- if rhobeg_old - options.rhobeg > eps*max(1, rhobeg_old)
- options.rhoend = max(eps, min(0.1*options.rhobeg, options.rhoend)); % We do not revise rhoend unless rhobeg is revised
- if ismember('rhobeg', user_options_fields) || ismember('rhoend', user_options_fields)
- wid = sprintf('%s:ReviseRhobeg', invoker);
- wmsg = sprintf('%s: rhobeg is revised to %g and rhoend to %g so that the distance between x0 and the inactive bounds is at least rhobeg.', invoker, options.rhobeg, options.rhoend);
- warning(wid, '%s', wmsg);
- warnings = [warnings, wmsg];
- end
- else
- options.rhoend = min(options.rhoend, options.rhobeg); % This may update rhoend slightly
- end
-else
+% Revise x0 if allowed.
+if isfield(options, 'honour_x0') && ~options.honour_x0
% N.B.: The following code is valid only if lb <= x0 <= ub and rhobeg <= min(ub-lb)/2, which
% hold after `pre_options` and `project` are invoked.
- x0_old = x0;
+ x0_in = x0;
lbx = (x0 <= lb + 0.5*options.rhobeg);
lbx_plus = (x0 > lb + 0.5*options.rhobeg) & (x0 < lb + options.rhobeg);
ubx_minus = (x0 < ub - 0.5*options.rhobeg) & (x0 > ub - options.rhobeg);
@@ -1987,13 +1982,34 @@
x0(lbx_plus) = lb(lbx_plus) + options.rhobeg;
x0(ubx_minus) = ub(ubx_minus) - options.rhobeg;
x0(ubx) = ub(ubx);
- if any(abs(x0_old-x0) > 0)
+ if any(abs(x0_in-x0) > 0)
wid = sprintf('%s:ReviseX0', invoker);
wmsg = sprintf('%s: x0 is revised so that the distance between x0 and the inactive bounds is at least rhobeg; set options.honour_x0 to true if you prefer to keep x0.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
end
+
+% Revise rhobeg if needed.
+% N.B.: If x0 has been revised above (i.e., options.honour_x0 is false), then the following revision
+% is unnecessary in precise arithmetic. However, it may still be needed due to rounding errors.
+rhobeg_in = options.rhobeg;
+rho_ratio = options.rhoend / options.rhobeg;
+lbx = (lb > -inf & x0 - lb <= eps*max(abs(lb), 1)); % x0 essentially equals lb
+ubx = (ub < inf & x0 - ub >= -eps*max(abs(ub), 1)); % x0 essentially equals ub
+x0(lbx) = lb(lbx);
+x0(ubx) = ub(ubx);
+options.rhobeg = max(eps, min([options.rhobeg; x0(~lbx) - lb(~lbx); ub(~ubx) - x0(~ubx)]));
+if rhobeg_in - options.rhobeg > eps*max(1, rhobeg_in)
+ options.rhoend = max(eps, min(rho_ratio*options.rhobeg, options.rhoend)); % We do not revise rhoend unless rhobeg is revised
+ if ismember('rhobeg', user_options_fields) || ismember('rhoend', user_options_fields)
+ wid = sprintf('%s:ReviseRhobeg', invoker);
+ wmsg = sprintf('%s: rhobeg is revised to %g and rhoend to %g so that the distance between x0 and the inactive bounds is at least rhobeg.', invoker, options.rhobeg, options.rhoend);
+ warning(wid, '%s', wmsg);
+ warnings = [warnings, wmsg];
+ end
+end
+options.rhoend = min(options.rhoend, options.rhobeg); % This may update rhoend slightly
return
diff --git a/matlab/interfaces/private/project.m b/matlab/interfaces/private/project.m
index 0395bac83e..d0f33e8dc4 100644
--- a/matlab/interfaces/private/project.m
+++ b/matlab/interfaces/private/project.m
@@ -11,10 +11,8 @@
% a drawback of the Courant penalty function).
%
% ***********************************************************************
-% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University
+% Authors: Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+% and Zaikun ZHANG (www.zhangzk.net)
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
diff --git a/matlab/interfaces/uobyqa.m b/matlab/interfaces/uobyqa.m
index f5dc3fa0e4..a8870ae928 100644
--- a/matlab/interfaces/uobyqa.m
+++ b/matlab/interfaces/uobyqa.m
@@ -27,23 +27,19 @@
%
% *** x is the approximate solution to the optimization problem
% *** fx is fun(x)
-% *** exitflag is an integer indicating why UOBYQA returns; the
-% possible values are
-% 0: the lower bound for the trust-region radius is reached
+% *** exitflag is an integer indicating why UOBYQA returns; the possible values are
+% 0: the lower bound for the trust region radius is reached
% 1: the target function value is achieved
-% 2: a trust-region step failed to reduce the quadratic model
+% 2: a trust region step failed to reduce the quadratic model (possible only in classical mode)
% 3: the objective function has been evaluated maxfun times
-% 4, 7, 8, 9: rounding errors become severe in the Fortran code
% 14: a linear feasibility problem received and solved
-% 20: the trust-region iteration has been performed for 10*maxfun times
-% -1: NaN occurs in x
-% -2: the objective function returns and NaN or nearly infinite
-% value (only in the classical mode)
-% -3: NaN occurs in the models
-% exitflag = 5, 10, 11, 12 are possible exitflags of the Fortran
-% code but cannot be returned by UOBYQA
+% 20: the trust region iteration has been performed for 2*maxfun times
+% -1: NaN occurs in x (possible only in the classical mode)
+% -2: the objective function returns an Inf/NaN value (possible only in classical mode)
+% -3: NaN occurs in the models (possible only in classical mode)
% *** output is a structure with the following fields:
% funcCount: number of function evaluations
+% xhist: history of iterates (if options.output_xhist = true)
% fhist: history of function values
% solver: backend solver that does the computation, i.e., 'uobyqa'
% message: return message
@@ -59,20 +55,34 @@
% The options include
% *** maxfun: maximal number of function evaluations; default: 500*length(x0)
% *** ftarget: target function value; default: -Inf
-% *** rhobeg: initial trust-region radius; typically, rhobeg should be in
+% *** rhobeg: initial trust region radius; typically, rhobeg should be in
% the order of one tenth of the greatest expected change to a variable;
% rhobeg should be positive; default: 1
-% *** rhoend: final trust-region radius; rhoend reflects the precision
+% *** rhoend: final trust region radius; rhoend reflects the precision
% of the approximate solution obtained by UOBYQA; rhoend should be
% positive and not larger than rhobeg; default: 1e-6
% *** fortran: a boolean value indicating whether to call Fortran code or
% not; default: true
+% *** precision: a string indicating the precision of the real numbers used in
+% the internal calculations of the package; possible values are 'half',
+% 'single', 'double', and 'quadruple'; if it is set to a value other than
+% 'double', then fortran will be set to true; default: 'double'
% *** classical: a boolean value indicating whether to call the classical
-% version of Powell's Fortran code or not; default: false
+% version of Powell's Fortran code or not; if it is set to true, then fortran
+% will be set to true; default: false
+% *** eta1, eta2, gamma1, gamma2 (only if classical = false)
+% eta1, eta2, gamma1, and gamma2 are parameters in the updating scheme
+% of the trust region radius. Roughly speaking, the trust region radius
+% is contracted by a factor of gamma1 when the reduction ratio is below
+% eta1, and enlarged by a factor of gamma2 when the reduction ratio is
+% above eta2. It is required that 0 <= eta1 <= eta2 < 1 and
+% 0 < gamma1 < 1 <= gamma2. Normally, eta1 <= 0.25. It is not recommended
+% to set eta1 >= 0.5. Default: eta1 = 0.1, eta2 = 0.7, gamma1 = 0.5,
+% and gamma2 = 2.
% *** iprint: a flag deciding how much information will be printed during
% the computation; possible values are value 0 (default), 1, -1, 2,
-% -2, 3, or -3:
-% 0: there will be no printing;
+% -2, 3, or -3.
+% 0: there will be no printing; this is the default;
% 1: a message will be printed to the screen at the return, showing
% the best vector of variables found and its objective function value;
% 2: in addition to 1, at each "new stage" of the computation, a message
@@ -81,7 +91,7 @@
% 3: in addition to 2, each function evaluation with its variables will
% be printed to the screen;
% -1, -2, -3: the same information as 1, 2, 3 will be printed, not to
-% the screen but to a file named SOLVER_output.txt; the file will be
+% the screen but to a file named UOBYQA_output.txt; the file will be
% created if it does not exist; the new output will be appended to
% the end of this file if it already exists.
% N.B.:
@@ -279,6 +289,7 @@
% The mexified Fortran Function is a private function generating only private errors;
% however, public errors can occur due to, e.g., evalobj; error handling needed.
try
+ setenv('GFORTRAN_ERROR_BACKTRACE', '1'); % Enable Fortran backtrace if the compiler is gfortran
[x, fx, exitflag, nf, xhist, fhist] = ...
fsolver(fun, x0, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, maxfun, ...
iprint, maxhist, double(output_xhist));
diff --git a/matlab/mex_gateways/R2025a/README.txt b/matlab/mex_gateways/R2025a/README.txt
new file mode 100644
index 0000000000..86ae4099a5
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/README.txt
@@ -0,0 +1,14 @@
+ This directory contains adapted MEX gateways to circumvent the MATLAB R2025a bug that it segfaults
+ on Linux if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+
+ The MEX files use a module variable to store the function handle, which is essentially a
+ global variable and is not thread-safe or recursion-safe.
+
+ See MathWorks Technical Support Case 07931486 and
+ https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+ https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+ https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+ https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+
+Zaikun ZHANG (www.zhangzk.net)
+July 20, 2025
diff --git a/matlab/mex_gateways/R2025a/bobyqa_mex.F90 b/matlab/mex_gateways/R2025a/bobyqa_mex.F90
new file mode 100644
index 0000000000..42fd1ef29f
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/bobyqa_mex.F90
@@ -0,0 +1,158 @@
+!--------------------------------------------------------------------------------------------------!
+! The MEX gateway for BOBYQA
+! This is an adapted MEX gateway to circumvent the MATLAB R2025a bug that it segfaults on Linux
+! if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+!
+! Authors:
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
+!
+! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
+!
+! Started in July 2020
+!
+! Last Modified: Tue 12 May 2026 04:52:57 PM CST
+!--------------------------------------------------------------------------------------------------!
+
+#include "fintrf.h"
+
+
+module calfun_mod
+implicit none
+private
+public :: fun_ptr, calfun
+
+mwPointer :: fun_ptr ! Pointer to the objective function handle
+
+contains
+
+subroutine calfun(x, f)
+use, non_intrinsic :: cbfun_mod, only : evalcb
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+call evalcb(fun_ptr, x, f)
+end subroutine calfun
+
+end module calfun_mod
+
+
+subroutine mexFunction(nargout, poutput, nargin, pinput)
+!--------------------------------------------------------------------------------------------------!
+! This is the entry point to the Fortran MEX function. If the compiled MEX file is named as
+! FUNCTION_NAME.mex*** (extension depends on the platform), then in MATLAB we can call:
+! [x, f, info, nf, xhist, fhist] = ...
+! FUNCTION_NAME(fun, x0, lb, ub, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, ...
+! maxfun, npt, iprint, maxhist, output_xhist)
+!--------------------------------------------------------------------------------------------------!
+
+! Generic modules
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+
+! Fortran MEX API modules
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyNArgin, fmxVerifyNArgout
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyClassShape
+use, non_intrinsic :: fmxapi_mod, only : fmxReadMPtr, fmxWriteMPtr
+
+! Solver-specific modules
+use, non_intrinsic :: calfun_mod, only : fun_ptr, calfun
+use, non_intrinsic :: bobyqa_mod, only : bobyqa
+
+implicit none
+
+! mexFunction arguments nargout and nargin are of type INTEGER in MATLAB 2019a documents.
+integer, intent(in) :: nargout, nargin
+mwPointer, intent(in) :: pinput(nargin)
+mwPointer, intent(out) :: poutput(nargout)
+
+! Local variables
+integer(IK) :: info
+integer(IK) :: iprint
+integer(IK) :: maxfun
+integer(IK) :: maxhist
+integer(IK) :: nf
+integer(IK) :: npt
+logical :: output_xhist
+real(RP) :: eta1
+real(RP) :: eta2
+real(RP) :: f
+real(RP) :: ftarget
+real(RP) :: gamma1
+real(RP) :: gamma2
+real(RP) :: rhobeg
+real(RP) :: rhoend
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: lb(:)
+real(RP), allocatable :: ub(:)
+real(RP), allocatable :: x(:)
+real(RP), allocatable :: xhist(:, :)
+
+! Validate the number of arguments
+call fmxVerifyNArgin(nargin, 16)
+call fmxVerifyNArgout(nargout, 6)
+
+! Verify that input 1 is a function handle; the other inputs will be verified when read.
+call fmxVerifyClassShape(pinput(1), 'function_handle', 'rank0')
+
+! Read the inputs
+fun_ptr = pinput(1) ! FUN_PTR is a pointer to the function handle
+call fmxReadMPtr(pinput(2), x)
+call fmxReadMPtr(pinput(3), lb)
+call fmxReadMPtr(pinput(4), ub)
+call fmxReadMPtr(pinput(5), rhobeg)
+call fmxReadMPtr(pinput(6), rhoend)
+call fmxReadMPtr(pinput(7), eta1)
+call fmxReadMPtr(pinput(8), eta2)
+call fmxReadMPtr(pinput(9), gamma1)
+call fmxReadMPtr(pinput(10), gamma2)
+call fmxReadMPtr(pinput(11), ftarget)
+call fmxReadMPtr(pinput(12), maxfun)
+call fmxReadMPtr(pinput(13), npt)
+call fmxReadMPtr(pinput(14), iprint)
+call fmxReadMPtr(pinput(15), maxhist)
+call fmxReadMPtr(pinput(16), output_xhist)
+
+! Call the Fortran code
+! There are different cases because XHIST may or may not be passed to the Fortran code.
+if (output_xhist) then
+ call bobyqa(calfun, x, f, lb, ub, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, eta1, eta2, &
+ & gamma1, gamma2, xhist = xhist, fhist = fhist, maxhist = maxhist, info = info)
+else
+ call bobyqa(calfun, x, f, lb, ub, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, eta1, eta2, &
+ & gamma1, gamma2, fhist = fhist, maxhist = maxhist, info = info)
+end if
+
+! After the Fortran code, XHIST may not be allocated, because it may not have been passed to the
+! Fortran code. We allocate it here. Otherwise, fmxWriteMPtr will fail.
+if (.not. allocated(xhist)) then
+ call safealloc(xhist, int(size(x), IK), 0_IK)
+end if
+
+! Write the outputs
+call fmxWriteMPtr(x, poutput(1))
+call fmxWriteMPtr(f, poutput(2))
+call fmxWriteMPtr(info, poutput(3))
+call fmxWriteMPtr(nf, poutput(4))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
+! N.B.:
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code.
+
+! Free memory. We prefer explicit deallocation to the automatic one.
+deallocate (x) ! Allocated by fmxReadMPtr.
+deallocate (lb) ! Allocated by fmxReadMPtr.
+deallocate (ub) ! Allocated by fmxReadMPtr.
+deallocate (xhist) ! Allocated by the solver
+deallocate (fhist) ! Allocated by the solver
+
+end subroutine mexFunction
diff --git a/matlab/mex_gateways/R2025a/classical_cobyla.f90 b/matlab/mex_gateways/R2025a/classical_cobyla.f90
new file mode 100644
index 0000000000..f45ff452c8
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/classical_cobyla.f90
@@ -0,0 +1,509 @@
+! This is an adapted version of cobyla.f90 (classical) to circumvent the MATLAB R2025a bug that it segfaults on
+! Linux if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+
+
+module calcfc_internal_mod
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: pintrf_mod, only : OBJCON
+implicit none
+private
+public :: m_nlcon_internal, ixl_ptr, ixu_ptr, xl_ptr, xu_ptr, Aeq_ptr, beq_ptr, Aineq_ptr, bineq_ptr, calcfc_ptr, calcfc_internal
+
+integer (IK) :: m_nlcon_internal
+integer (IK), pointer :: ixl_ptr(:), ixu_ptr(:)
+real(RP), pointer :: xl_ptr(:), xu_ptr(:), Aeq_ptr(:, :), beq_ptr(:), Aineq_ptr(:, :), bineq_ptr(:)
+procedure(OBJCON), pointer :: calcfc_ptr
+
+contains
+
+subroutine calcfc_internal(x_internal, f_internal, constr_internal)
+use, non_intrinsic :: linalg_mod, only : matprod
+implicit none
+real(RP), intent(in) :: x_internal(:)
+real(RP), intent(out) :: f_internal
+real(RP), intent(out) :: constr_internal(:)
+real(RP) :: constr_nlc(m_nlcon_internal)
+
+call calcfc_ptr(x_internal, f_internal, constr_nlc)
+constr_internal = -[x_internal(ixl_ptr) - xl_ptr(ixl_ptr), xu_ptr(ixu_ptr) - x_internal(ixu_ptr), &
+ & matprod(Aeq_ptr, x_internal) - beq_ptr, beq_ptr-matprod(Aeq_ptr, x_internal), &
+ & bineq_ptr-matprod(Aineq_ptr, x_internal), -constr_nlc]
+end subroutine calcfc_internal
+
+end module calcfc_internal_mod
+
+
+module cobyla_mod
+!--------------------------------------------------------------------------------------------------!
+! Classical mode. Not maintained. Strongly discouraged. Please use the modernized version instead.
+!
+! The usage is the same as the modernized version.
+!--------------------------------------------------------------------------------------------------!
+
+implicit none
+private
+public :: cobyla
+
+
+contains
+
+
+subroutine cobyla(calcfc, m_nlcon, x, f, &
+ & cstrv, nlconstr, &
+ & Aineq, bineq, &
+ & Aeq, beq, &
+ & xl, xu, &
+ & f0, nlconstr0, &
+ & nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, &
+ & eta1, eta2, gamma1, gamma2, xhist, fhist, chist, nlchist, maxhist, maxfilt, info)
+
+! Common modules
+use, non_intrinsic :: consts_mod, only : DEBUGGING
+use, non_intrinsic :: consts_mod, only : MAXFUN_DIM_DFT, MAXFILT_DFT, IPRINT_DFT
+use, non_intrinsic :: consts_mod, only : RHOBEG_DFT, RHOEND_DFT, CTOL_DFT, CWEIGHT_DFT, FTARGET_DFT
+use, non_intrinsic :: consts_mod, only : RP, IK, ZERO, ONE, TWO, HALF, TEN, TENTH, EPS, REALMAX
+use, non_intrinsic :: debug_mod, only : assert, errstop, warning
+use, non_intrinsic :: evaluate_mod, only : evaluate, moderatex, moderatec
+use, non_intrinsic :: history_mod, only : prehist
+use, non_intrinsic :: infnan_mod, only : is_finite, is_nan
+use, non_intrinsic :: linalg_mod, only : trueloc, matprod
+use, non_intrinsic :: memory_mod, only : safealloc
+use, non_intrinsic :: pintrf_mod, only : OBJCON
+use, non_intrinsic :: preproc_mod, only : preproc
+use, non_intrinsic :: string_mod, only : num2str
+
+! Solver-specific modules
+use, non_intrinsic :: calcfc_internal_mod, only : m_nlcon_internal, ixl_ptr, ixu_ptr, xl_ptr, xu_ptr, &
+ & Aeq_ptr, beq_ptr, Aineq_ptr, bineq_ptr, calcfc_ptr, calcfc_internal
+use, non_intrinsic :: cobylb_mod, only : cobylb
+
+implicit none
+
+! Compulsory arguments
+procedure(OBJCON) :: calcfc
+real(RP), intent(inout) :: x(:)
+real(RP), intent(out) :: f
+integer(IK), intent(in) :: m_nlcon
+
+! Optional inputs
+integer(IK), intent(in), optional :: iprint
+integer(IK), intent(in), optional :: maxfilt
+integer(IK), intent(in), optional :: maxfun
+integer(IK), intent(in), optional :: maxhist
+real(RP), intent(in), optional :: Aeq(:, :) ! Aeq(Meq, N)
+real(RP), intent(in), optional :: Aineq(:, :) ! Aineq(Mineq, N)
+real(RP), intent(in), optional :: beq(:) ! Beq(Meq)
+real(RP), intent(in), optional :: bineq(:) ! Bineq(Mineq)
+real(RP), intent(in), optional :: nlconstr0(:) ! NLCONSTR0(M_NLCON)
+real(RP), intent(in), optional :: ctol
+real(RP), intent(in), optional :: cweight
+real(RP), intent(in), optional :: eta1
+real(RP), intent(in), optional :: eta2
+real(RP), intent(in), optional :: f0
+real(RP), intent(in), optional :: ftarget
+real(RP), intent(in), optional :: gamma1
+real(RP), intent(in), optional :: gamma2
+real(RP), intent(in), optional :: rhobeg
+real(RP), intent(in), optional :: rhoend
+real(RP), intent(in), optional :: xl(:) ! XL(N)
+real(RP), intent(in), optional :: xu(:) ! XU(N)
+
+! Optional outputs
+integer(IK), intent(out), optional :: info
+integer(IK), intent(out), optional :: nf
+real(RP), intent(out), optional :: nlconstr(:)
+real(RP), intent(out), allocatable, optional :: chist(:)
+real(RP), intent(out), allocatable, optional :: nlchist(:, :)
+real(RP), intent(out), allocatable, optional :: fhist(:)
+real(RP), intent(out), allocatable, optional :: xhist(:, :)
+real(RP), intent(out), optional :: cstrv
+
+! Local variables
+character(len=*), parameter :: ifmt = '(I0)' ! I0: use the minimum number of digits needed to print
+character(len=*), parameter :: solver = 'COBYLA'
+character(len=*), parameter :: srname = 'COBYLA'
+integer(IK) :: info_loc
+integer(IK) :: iprint_loc
+integer(IK) :: m
+integer(IK) :: maxfilt_loc
+integer(IK) :: maxfun_loc
+integer(IK) :: maxhist_loc
+integer(IK) :: meq
+integer(IK) :: mineq
+integer(IK) :: mxl
+integer(IK) :: mxu
+integer(IK) :: n
+integer(IK) :: nf_loc
+integer(IK) :: nhist
+integer(IK), allocatable, target :: ixl(:)
+integer(IK), allocatable, target :: ixu(:)
+real(RP) :: cstrv_loc
+real(RP) :: ctol_loc
+real(RP) :: cweight_loc
+real(RP) :: eta1_loc
+real(RP) :: eta2_loc
+real(RP) :: ftarget_loc
+real(RP) :: gamma1_loc
+real(RP) :: gamma2_loc
+real(RP) :: rhobeg_loc
+real(RP) :: rhoend_loc
+real(RP), allocatable, target :: Aeq_loc(:, :) ! Aeq_LOC(Meq, N)
+real(RP), allocatable, target :: Aineq_loc(:, :) ! Aineq_LOC(Mineq, N)
+real(RP), allocatable, target :: beq_loc(:) ! Beq_LOC(Meq)
+real(RP), allocatable, target :: bineq_loc(:) ! Bineq_LOC(Mineq)
+real(RP), allocatable :: constr_loc(:)
+real(RP), allocatable :: chist_loc(:)
+real(RP), allocatable :: conhist_loc(:, :)
+real(RP), allocatable :: fhist_loc(:)
+real(RP), allocatable :: xhist_loc(:, :)
+real(RP), allocatable, target :: xl_loc(:) ! XL_LOC(N)
+real(RP), allocatable, target :: xu_loc(:) ! XU_LOC(N)
+
+
+! Sizes
+if (present(bineq)) then
+ mineq = int(size(bineq), kind(mineq))
+else
+ mineq = 0
+end if
+if (present(beq)) then
+ meq = int(size(beq), kind(meq))
+else
+ meq = 0
+end if
+if (present(xl)) then
+ mxl = int(count(xl > -REALMAX), kind(mxl))
+else
+ mxl = 0
+end if
+if (present(xu)) then
+ mxu = int(count(xu < REALMAX), kind(mxu))
+else
+ mxu = 0
+end if
+m = mxu+mxl+2_IK*meq+mineq+m_nlcon
+n = int(size(x), kind(n))
+
+! Preconditions
+if (DEBUGGING) then
+ call assert(mineq >= 0, 'Mineq >= 0', srname)
+ call assert(n >= 1, 'N >= 1', srname)
+ call assert(present(Aineq) .eqv. present(bineq), 'Aineq and Bineq are both present or both absent', srname)
+ if (present(Aineq)) then
+ call assert((size(Aineq, 1) == mineq .and. size(Aineq, 2) == n) &
+ & .or. (size(Aineq, 1) == 0 .and. size(Aineq, 2) == 0 .and. mineq == 0), &
+ & 'SIZE(Aineq) == [Mineq, N] unless Aineq and Bineq are both empty', srname)
+ end if
+ call assert(present(Aeq) .eqv. present(beq), 'Aeq and Beq are both present or both absent', srname)
+ if (present(Aeq)) then
+ call assert((size(Aeq, 1) == meq .and. size(Aeq, 2) == n) &
+ & .or. (size(Aeq, 1) == 0 .and. size(Aeq, 2) == 0 .and. meq == 0), &
+ & 'SIZE(Aeq) == [Meq, N] unless Aeq and Beq are both empty', srname)
+ end if
+ if (present(nlconstr0)) then
+ call assert(present(f0), 'If NLCONSTR0 is present, then F0 is present', srname)
+ end if
+ if (present(f0)) then
+ call assert(is_nan(f0) .or. present(nlconstr0), 'If F0 is present and not NaN, then NLCONSTR0 is present', srname)
+ end if
+end if
+
+! Exit if the size of NLCONSTR0 is inconsistent with M_NLCON.
+if (present(nlconstr0)) then
+ if (size(nlconstr0) /= m_nlcon) then
+ if (DEBUGGING) then
+ call errstop(srname, 'SIZE(NLCONSTR0) /= M_NLCON. Exiting')
+ else
+ call warning(srname, 'SIZE(NLCONSTR0) /= M_NLCON. Exiting')
+ return
+ end if
+ end if
+end if
+
+
+! Read the inputs.
+
+
+call safealloc(Aineq_loc, mineq, n) ! NOT removable even in F2003, as Aineq may be absent or of size 0-by-0.
+if (present(Aineq) .and. mineq > 0) then
+ ! We must check Mineq > 0. Otherwise, the size of Aineq_LOC may be changed to 0-by-0 due to
+ ! automatic (re)allocation if that is the size of Aineq; we allow Aineq to be 0-by-0, but
+ ! Aineq_LOC should be n-by-0.
+ Aineq_loc = Aineq
+end if
+
+call safealloc(bineq_loc, mineq) ! NOT removable even in F2003, as Bineq may be absent.
+if (present(bineq)) then
+ bineq_loc = bineq
+end if
+
+call safealloc(Aeq_loc, meq, n) ! NOT removable even in F2003, as Aeq may be absent or of size 0-by-0.
+if (present(Aeq) .and. meq > 0) then
+ ! We must check Meq > 0. Otherwise, the size of Aeq_LOC may be changed to 0-by-0 due to
+ ! automatic (re)allocation if that is the size of Aeq; we allow Aeq to be 0-by-0, but
+ ! Aeq_LOC should be n-by-0.
+ Aeq_loc = Aeq
+end if
+
+call safealloc(beq_loc, meq) ! NOT removable even in F2003, as Beq may be absent.
+if (present(beq)) then
+ beq_loc = beq
+end if
+
+call safealloc(xl_loc, n) ! NOT removable even in F2003, as XL may be absent.
+if (present(xl)) then
+ xl_loc = xl
+else
+ xl_loc = -REALMAX
+end if
+call safealloc(ixl, mxl)
+ixl = trueloc(xl_loc > -REALMAX)
+
+call safealloc(xu_loc, n) ! NOT removable even in F2003, as XU may be absent.
+if (present(xu)) then
+ xu_loc = xu
+else
+ xu_loc = REALMAX
+end if
+call safealloc(ixu, mxu)
+ixu = trueloc(xu_loc < REALMAX)
+
+! Allocate memory for CONSTR_LOC.
+call safealloc(constr_loc, m) ! NOT removable even in F2003!
+!! If the user provides the function & constraint value at X0, then set up F_X0 and NLCONSTR_X0.
+if (present(f0) .and. present(nlconstr0) .and. all(is_finite(x))) then
+ f = f0
+ constr_loc = moderatec(-[x(ixl) - xl_loc(ixl), xu_loc(ixu) - x(ixu), &
+ & matprod(Aeq_loc, x) - beq_loc, beq_loc-matprod(Aeq_loc, x), &
+ & bineq_loc-matprod(Aineq_loc, x), -nlconstr0])
+ constr_loc = -constr_loc
+ cstrv_loc = maxval([ZERO, -constr_loc])
+else
+ ! Replace any NaN in X by ZERO and Inf/-Inf in X by REALMAX/-REALMAX.
+ x = moderatex(x)
+ call evaluate(calcfc_internal, x, f, constr_loc)
+ constr_loc = -constr_loc
+ cstrv_loc = maxval([ZERO, -constr_loc])
+end if
+
+! If RHOBEG is present, then RHOBEG_LOC is a copy of RHOBEG; otherwise, RHOBEG_LOC takes the default
+! value for RHOBEG, taking the value of RHOEND into account. Note that RHOEND is considered only if
+! it is present and it is VALID (i.e., finite and positive). The other inputs are read similarly.
+if (present(rhobeg)) then
+ rhobeg_loc = rhobeg
+elseif (present(rhoend)) then
+ ! Fortran does not take short-circuit evaluation of logic expressions. Thus it is WRONG to
+ ! combine the evaluation of PRESENT(RHOEND) and the evaluation of IS_FINITE(RHOEND) as
+ ! "IF (PRESENT(RHOEND) .AND. IS_FINITE(RHOEND))". The compiler may choose to evaluate the
+ ! IS_FINITE(RHOEND) even if PRESENT(RHOEND) is false!
+ if (is_finite(rhoend) .and. rhoend > 0) then
+ rhobeg_loc = max(TEN*rhoend, RHOBEG_DFT)
+ else
+ rhobeg_loc = RHOBEG_DFT
+ end if
+else
+ rhobeg_loc = RHOBEG_DFT
+end if
+
+if (present(rhoend)) then
+ rhoend_loc = rhoend
+elseif (rhobeg_loc > 0) then
+ rhoend_loc = max(EPS, min((RHOEND_DFT/RHOBEG_DFT) * rhobeg_loc, RHOEND_DFT))
+else
+ rhoend_loc = RHOEND_DFT
+end if
+
+if (present(ctol)) then
+ ctol_loc = ctol
+else
+ ctol_loc = CTOL_DFT
+end if
+
+if (present(cweight)) then
+ cweight_loc = cweight
+else
+ cweight_loc = CWEIGHT_DFT
+end if
+
+if (present(ftarget)) then
+ ftarget_loc = ftarget
+else
+ ftarget_loc = FTARGET_DFT
+end if
+
+if (present(maxfun)) then
+ maxfun_loc = maxfun
+else
+ maxfun_loc = MAXFUN_DIM_DFT*n
+end if
+
+if (present(iprint)) then
+ iprint_loc = iprint
+else
+ iprint_loc = IPRINT_DFT
+end if
+
+if (present(eta1)) then
+ eta1_loc = eta1
+elseif (present(eta2)) then
+ if (eta2 > ZERO .and. eta2 < ONE) then
+ eta1_loc = max(EPS, eta2/7.0_RP)
+ end if
+else
+ eta1_loc = TENTH
+end if
+
+if (present(eta2)) then
+ eta2_loc = eta2
+elseif (eta1_loc > ZERO .and. eta1_loc < ONE) then
+ eta2_loc = (eta1_loc+TWO) / 3.0_RP
+else
+ eta2_loc = 0.7_RP
+end if
+
+if (present(gamma1)) then
+ gamma1_loc = gamma1
+else
+ gamma1_loc = HALF
+end if
+
+if (present(gamma2)) then
+ gamma2_loc = gamma2
+else
+ gamma2_loc = TWO
+end if
+
+if (present(maxhist)) then
+ maxhist_loc = maxhist
+else
+ maxhist_loc = maxval([maxfun_loc, n+2_IK, MAXFUN_DIM_DFT*n])
+end if
+
+if (present(maxfilt)) then
+ maxfilt_loc = maxfilt
+else
+ maxfilt_loc = MAXFILT_DFT
+end if
+
+! Preprocess the inputs in case some of them are invalid. It does nothing if all inputs are valid.
+call preproc(solver, n, iprint_loc, maxfun_loc, maxhist_loc, ftarget_loc, rhobeg_loc, rhoend_loc, &
+ & m = m, ctol = ctol_loc, cweight = cweight_loc, eta1 = eta1_loc, eta2 = eta2_loc, gamma1 = gamma1_loc, &
+ & gamma2 = gamma2_loc, maxfilt = maxfilt_loc)
+
+! Further revise MAXHIST_LOC according to MAXMEMORY, and allocate memory for the history.
+! In MATLAB/Python/Julia/R implementation, we should simply set MAXHIST = MAXFUN and initialize
+! CHIST = NaN(1, MAXFUN), NLCHIST = NaN(M_NLCON, MAXFUN), FHIST = NaN(1, MAXFUN), XHIST = NaN(N, MAXFUN)
+! if they are requested; replace MAXFUN with 0 for the history that is not requested.
+call prehist(maxhist_loc, n, present(xhist), xhist_loc, present(fhist), fhist_loc, &
+ & present(chist), chist_loc, m, present(nlchist), conhist_loc)
+
+! Copy M_NLCON, IXL, IXU, XL, XU, Aeq, Beq, Aineq, Bineq, and calcfc to the local variables.
+m_nlcon_internal = m_nlcon
+ixl_ptr => ixl
+ixu_ptr => ixu
+xl_ptr => xl_loc
+xu_ptr => xu_loc
+Aeq_ptr => Aeq_loc
+beq_ptr => beq_loc
+Aineq_ptr => Aineq_loc
+bineq_ptr => bineq_loc
+calcfc_ptr => calcfc
+
+!--------------------------------------------------------------------------------------------------!
+!-------------------- Call COBYLB, which performs the real calculations. --------------------------!
+!!!! ETA1, ETA2, GAMMA1, GAMMA2, MAXFILT, CTOL, CWEIGHT are not used in the classical mode. !!!!
+call cobylb(calcfc_internal, iprint_loc, maxfun_loc, rhobeg_loc, rhoend_loc, constr_loc, x, cstrv_loc, f, info_loc, &
+ & nf_loc, xhist_loc, fhist_loc, chist_loc, conhist_loc)
+!--------------------------------------------------------------------------------------------------!
+!--------------------------------------------------------------------------------------------------!
+
+! Write the outputs.
+
+! Copy CONSTR_LOC to NLCONSTR if needed.
+if (present(nlconstr)) then
+ nlconstr = constr_loc(m-m_nlcon+1:m)
+end if
+deallocate (constr_loc)
+
+if (present(cstrv)) then
+ cstrv = cstrv_loc
+end if
+
+if (present(nf)) then
+ nf = nf_loc
+end if
+
+if (present(info)) then
+ info = info_loc
+end if
+
+! Copy XHIST_LOC to XHIST if needed.
+if (present(xhist)) then
+ nhist = min(nf_loc, int(size(xhist_loc, 2), IK))
+ !----------------------------------------------------!
+ call safealloc(xhist, n, nhist) ! Removable in F2003.
+ !----------------------------------------------------!
+ xhist = xhist_loc(:, 1:nhist)
+ ! N.B.:
+ ! 0. Allocate XHIST as long as it is present, even if the size is 0; otherwise, it will be
+ ! illegal to enquire XHIST after exit.
+ ! 1. Even though Fortran 2003 supports automatic (re)allocation of allocatable arrays upon
+ ! intrinsic assignment, we keep the line of SAFEALLOC, because some very new compilers (Absoft
+ ! Fortran 21.0) are still not standard-compliant in this respect.
+ ! 2. NF may not be present. Hence we should NOT use NF but NF_LOC.
+ ! 3. When SIZE(XHIST_LOC, 2) > NF_LOC, which is the normal case in practice, XHIST_LOC contains
+ ! GARBAGE in XHIST_LOC(:, NF_LOC+1 : END). Therefore, we MUST cap XHIST at NF_LOC so that
+ ! XHIST contains only valid history. For this reason, there is no way to avoid allocating
+ ! two copies of memory for XHIST unless we declare it to be a POINTER instead of ALLOCATABLE.
+end if
+! F2003 automatically deallocate local ALLOCATABLE variables at exit, yet we prefer to deallocate
+! them immediately when they finish their jobs.
+deallocate (xhist_loc)
+
+! Copy FHIST_LOC to FHIST if needed.
+if (present(fhist)) then
+ nhist = min(nf_loc, int(size(fhist_loc), IK))
+ !--------------------------------------------------!
+ call safealloc(fhist, nhist) ! Removable in F2003.
+ !--------------------------------------------------!
+ fhist = fhist_loc(1:nhist) ! The same as XHIST, we must cap FHIST at NF_LOC.
+end if
+deallocate (fhist_loc)
+
+! Copy CHIST_LOC to CHIST if needed.
+if (present(chist)) then
+ nhist = min(nf_loc, int(size(chist_loc), IK))
+ !--------------------------------------------------!
+ call safealloc(chist, nhist) ! Removable in F2003.
+ !--------------------------------------------------!
+ chist = chist_loc(1:nhist) ! The same as XHIST, we must cap CHIST at NF_LOC.
+end if
+deallocate (chist_loc)
+
+! Copy CONHIST_LOC to NLCHIST if needed.
+if (present(nlchist)) then
+ nhist = min(nf_loc, int(size(conhist_loc, 2), IK))
+ !----------------------------------------------------------!
+ call safealloc(nlchist, m_nlcon, nhist) ! Removable in F2003.
+ !----------------------------------------------------------!
+ nlchist = conhist_loc(m-m_nlcon+1:m, 1:nhist) ! The same as XHIST, we must cap NLCHIST at NF_LOC.
+end if
+deallocate (conhist_loc)
+
+! If NF_LOC > MAXHIST_LOC, warn that not all history is recorded.
+if ((present(xhist) .or. present(fhist) .or. present(chist) .or. present(nlchist)) .and. maxhist_loc < nf_loc) then
+ call warning(solver, 'Only the history of the last '//num2str(maxhist_loc)//' function evaluation(s) is recorded')
+end if
+
+
+end subroutine cobyla
+
+
+end module cobyla_mod
diff --git a/matlab/mex_gateways/R2025a/cobyla_mex.F90 b/matlab/mex_gateways/R2025a/cobyla_mex.F90
new file mode 100644
index 0000000000..e5ca012ea8
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/cobyla_mex.F90
@@ -0,0 +1,217 @@
+!--------------------------------------------------------------------------------------------------!
+! The MEX gateway for COBYLA
+! This is an adapted MEX gateway to circumvent the MATLAB R2025a bug that it segfaults on Linux
+! if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+!
+! Authors:
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
+!
+! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
+!
+! Started in July 2020
+!
+! Last Modified: Tue 12 May 2026 04:52:35 PM CST
+!--------------------------------------------------------------------------------------------------!
+
+#include "fintrf.h"
+
+
+module calcfc_mod
+implicit none
+private
+public :: funcon_ptr, calcfc
+
+mwPointer :: funcon_ptr ! Pointer to the objective/constraint function handle
+
+contains
+
+subroutine calcfc(x, f, nlconstr)
+use, non_intrinsic :: cbfun_mod, only : evalcb
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+real(RP), intent(out) :: nlconstr(:)
+call evalcb(funcon_ptr, x, f, nlconstr)
+end subroutine calcfc
+
+end module calcfc_mod
+
+
+subroutine mexFunction(nargout, poutput, nargin, pinput)
+!--------------------------------------------------------------------------------------------------!
+! This is the entry point to the Fortran MEX function. If the compiled MEX file is named as
+! FUNCTION_NAME.mex*** (extension depends on the platform), then in MATLAB we can call:
+! [x, f, cstrv, nlconstr, info, nf, xhist, fhist, chist, nlchist] = ...
+! FUNCTION_NAME(funcon, x0, f0, nlconstr0, Aineq, bineq, Aeq, beq, lb, ub, rhobeg, rhoend, ...
+! eta1, eta2, gamma1, gamma2, ftarget, ctol, cweight, maxfun, iprint, maxhist, output_xhist, ...
+! output_nlchist, maxfilt)
+!--------------------------------------------------------------------------------------------------!
+
+! Generic modules
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+
+! Fortran MEX API modules
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyNArgin, fmxVerifyNArgout
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyClassShape
+use, non_intrinsic :: fmxapi_mod, only : fmxReadMPtr, fmxWriteMPtr
+
+! Solver-specific modules
+use, non_intrinsic :: calcfc_mod, only : funcon_ptr, calcfc
+use, non_intrinsic :: cobyla_mod, only : cobyla
+
+implicit none
+
+! mexFunction arguments nargout and nargin are of type INTEGER in MATLAB 2019a documents.
+integer, intent(in) :: nargout, nargin
+mwPointer, intent(in) :: pinput(nargin)
+mwPointer, intent(out) :: poutput(nargout)
+
+! Local variables
+integer(IK) :: info
+integer(IK) :: iprint
+integer(IK) :: m_nlcon
+integer(IK) :: maxfilt
+integer(IK) :: maxfun
+integer(IK) :: maxhist
+integer(IK) :: nf
+logical :: output_nlchist
+logical :: output_xhist
+real(RP) :: cstrv
+real(RP) :: ctol
+real(RP) :: cweight
+real(RP) :: eta1
+real(RP) :: eta2
+real(RP) :: f
+real(RP) :: f0
+real(RP) :: ftarget
+real(RP) :: gamma1
+real(RP) :: gamma2
+real(RP) :: rhobeg
+real(RP) :: rhoend
+real(RP), allocatable :: Aeq(:, :)
+real(RP), allocatable :: Aineq(:, :)
+real(RP), allocatable :: beq(:)
+real(RP), allocatable :: bineq(:)
+real(RP), allocatable :: chist(:)
+real(RP), allocatable :: nlchist(:, :)
+real(RP), allocatable :: nlconstr(:)
+real(RP), allocatable :: nlconstr0(:)
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: lb(:)
+real(RP), allocatable :: ub(:)
+real(RP), allocatable :: x(:)
+real(RP), allocatable :: xhist(:, :)
+
+! Validate the number of arguments
+call fmxVerifyNArgin(nargin, 25)
+call fmxVerifyNArgout(nargout, 10)
+
+! Verify that input 1 is a function handle; the other inputs will be verified when read.
+call fmxVerifyClassShape(pinput(1), 'function_handle', 'rank0')
+
+! Read the inputs
+funcon_ptr = pinput(1) ! FUNCON_PTR is a pointer to the function handle
+call fmxReadMPtr(pinput(2), x)
+call fmxReadMPtr(pinput(3), f0)
+call fmxReadMPtr(pinput(4), nlconstr0)
+call fmxReadMPtr(pinput(5), Aineq)
+call fmxReadMPtr(pinput(6), bineq)
+call fmxReadMPtr(pinput(7), Aeq)
+call fmxReadMPtr(pinput(8), beq)
+call fmxReadMPtr(pinput(9), lb)
+call fmxReadMPtr(pinput(10), ub)
+call fmxReadMPtr(pinput(11), rhobeg)
+call fmxReadMPtr(pinput(12), rhoend)
+call fmxReadMPtr(pinput(13), eta1)
+call fmxReadMPtr(pinput(14), eta2)
+call fmxReadMPtr(pinput(15), gamma1)
+call fmxReadMPtr(pinput(16), gamma2)
+call fmxReadMPtr(pinput(17), ftarget)
+call fmxReadMPtr(pinput(18), ctol)
+call fmxReadMPtr(pinput(19), cweight)
+call fmxReadMPtr(pinput(20), maxfun)
+call fmxReadMPtr(pinput(21), iprint)
+call fmxReadMPtr(pinput(22), maxhist)
+call fmxReadMPtr(pinput(23), output_xhist)
+call fmxReadMPtr(pinput(24), output_nlchist)
+call fmxReadMPtr(pinput(25), maxfilt)
+
+! Get the sizes
+m_nlcon = int(size(nlconstr0), kind(m_nlcon)) ! M_NLCON is a compulsory input of the Fortran code.
+
+! Allocate memory for nlconstr
+call safealloc(nlconstr, m_nlcon)
+
+! Call the Fortran code
+! There are different cases because XHIST/CONHIST may or may not be passed to the Fortran code.
+if (output_xhist .and. output_nlchist) then
+ call cobyla(calcfc, m_nlcon, x, f, cstrv, nlconstr, Aineq, bineq, Aeq, beq, lb, ub, &
+ & f0, nlconstr0, nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, eta1, eta2, &
+ & gamma1, gamma2, xhist = xhist, fhist = fhist, chist = chist, nlchist = nlchist, maxhist = maxhist, &
+ & maxfilt = maxfilt, info = info)
+elseif (output_xhist) then
+ call cobyla(calcfc, m_nlcon, x, f, cstrv, nlconstr, Aineq, bineq, Aeq, beq, lb, ub, &
+ & f0, nlconstr0, nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, eta1, eta2, &
+ & gamma1, gamma2, xhist = xhist, fhist = fhist, chist = chist, maxhist = maxhist, maxfilt = maxfilt, &
+ & info = info)
+elseif (output_nlchist) then
+ call cobyla(calcfc, m_nlcon, x, f, cstrv, nlconstr, Aineq, bineq, Aeq, beq, lb, ub, &
+ & f0, nlconstr0, nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, eta1, eta2, &
+ & gamma1, gamma2, fhist = fhist, chist = chist, nlchist = nlchist, maxhist = maxhist, &
+ & maxfilt = maxfilt, info = info)
+else
+ call cobyla(calcfc, m_nlcon, x, f, cstrv, nlconstr, Aineq, bineq, Aeq, beq, lb, ub, &
+ & f0, nlconstr0, nf, rhobeg, rhoend, ftarget, ctol, cweight, maxfun, iprint, eta1, eta2, &
+ & gamma1, gamma2, fhist = fhist, chist = chist, maxhist = maxhist, maxfilt = maxfilt, info = info)
+end if
+
+! After the Fortran code, XHIST or CONHIST may not be allocated, because it may not have been passed
+! to the Fortran code. We allocate it here. Otherwise, fmxWriteMPtr will fail.
+if (.not. allocated(xhist)) then
+ call safealloc(xhist, int(size(x), IK), 0_IK)
+end if
+if (.not. allocated(nlchist)) then
+ call safealloc(nlchist, int(size(nlconstr), IK), 0_IK)
+end if
+
+! Write the outputs
+call fmxWriteMPtr(x, poutput(1))
+call fmxWriteMPtr(f, poutput(2))
+call fmxWriteMPtr(cstrv, poutput(3))
+call fmxWriteMPtr(nlconstr, poutput(4))
+call fmxWriteMPtr(info, poutput(5))
+call fmxWriteMPtr(nf, poutput(6))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(7))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(8), 'row')
+call fmxWriteMPtr(chist(1:min(nf, int(size(chist), IK))), poutput(9), 'row')
+call fmxWriteMPtr(nlchist(:, 1:min(nf, int(size(nlchist, 2), IK))), poutput(10))
+! N.B.:
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code. Similar for CHIST and CONHIST.
+
+! Free memory. We prefer explicit deallocation to the automatic one.
+deallocate (x) ! Allocated by fmxReadMPtr.
+deallocate (nlconstr0) ! Allocated by fmxReadMPtr.
+deallocate (Aineq) ! Allocated by fmxReadMPtr.
+deallocate (bineq) ! Allocated by fmxReadMPtr.
+deallocate (Aeq) ! Allocated by fmxReadMPtr.
+deallocate (beq) ! Allocated by fmxReadMPtr.
+deallocate (lb) ! Allocated by fmxReadMPtr.
+deallocate (ub) ! Allocated by fmxReadMPtr.
+deallocate (nlconstr) ! Allocated manually
+deallocate (xhist) ! Allocated by the solver
+deallocate (fhist) ! Allocated by the solver
+deallocate (chist) ! Allocated by the solver
+deallocate (nlchist) ! Allocated by the solver
+
+end subroutine mexFunction
diff --git a/matlab/mex_gateways/R2025a/lincoa_mex.F90 b/matlab/mex_gateways/R2025a/lincoa_mex.F90
new file mode 100644
index 0000000000..5563b476fb
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/lincoa_mex.F90
@@ -0,0 +1,183 @@
+!--------------------------------------------------------------------------------------------------!
+! The MEX gateway for LINCOA
+! This is an adapted MEX gateway to circumvent the MATLAB R2025a bug that it segfaults on Linux
+! if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+!
+! Authors:
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
+!
+! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
+!
+! Started in July 2020
+!
+! Last Modified: Tue 12 May 2026 04:50:57 PM CST
+!--------------------------------------------------------------------------------------------------!
+
+#include "fintrf.h"
+
+
+module calfun_mod
+implicit none
+private
+public :: fun_ptr, calfun
+
+mwPointer :: fun_ptr ! Pointer to the objective function handle
+
+contains
+
+subroutine calfun(x, f)
+use, non_intrinsic :: cbfun_mod, only : evalcb
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+call evalcb(fun_ptr, x, f)
+end subroutine calfun
+
+end module calfun_mod
+
+
+subroutine mexFunction(nargout, poutput, nargin, pinput)
+!--------------------------------------------------------------------------------------------------!
+! This is the entry point to the Fortran MEX function. If the compiled MEX file is named as
+! FUNCTION_NAME.mex*** (extension depends on the platform), then in MATLAB we can call:
+! [x, f, cstrv, info, nf, xhist, fhist, chist] = ...
+! FUNCTION_NAME(fun, x0, Aineq, bineq, Aeq, beq, lb, ub, rhobeg, rhoend, eta1, eta2, ...
+! gamma1, gamma2, ftarget, ctol, cweight, maxfun, npt, iprint, maxhist, output_xhist, maxfilt)
+!--------------------------------------------------------------------------------------------------!
+
+! Generic modules
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+
+! Fortran MEX API modules
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyNArgin, fmxVerifyNArgout
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyClassShape
+use, non_intrinsic :: fmxapi_mod, only : fmxReadMPtr, fmxWriteMPtr
+
+! Solver-specific modules
+use, non_intrinsic :: calfun_mod, only : fun_ptr, calfun
+use, non_intrinsic :: lincoa_mod, only : lincoa
+
+implicit none
+
+! mexFunction arguments nargout and nargin are of type INTEGER in MATLAB 2019a documents.
+integer, intent(in) :: nargout, nargin
+mwPointer, intent(in) :: pinput(nargin)
+mwPointer, intent(out) :: poutput(nargout)
+
+! Local variables
+integer(IK) :: info
+integer(IK) :: iprint
+integer(IK) :: maxfilt
+integer(IK) :: maxfun
+integer(IK) :: maxhist
+integer(IK) :: nf
+integer(IK) :: npt
+logical :: output_xhist
+real(RP) :: cstrv
+real(RP) :: ctol
+real(RP) :: cweight
+real(RP) :: eta1
+real(RP) :: eta2
+real(RP) :: f
+real(RP) :: ftarget
+real(RP) :: gamma1
+real(RP) :: gamma2
+real(RP) :: rhobeg
+real(RP) :: rhoend
+real(RP), allocatable :: Aeq(:, :)
+real(RP), allocatable :: Aineq(:, :)
+real(RP), allocatable :: beq(:)
+real(RP), allocatable :: bineq(:)
+real(RP), allocatable :: chist(:)
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: lb(:)
+real(RP), allocatable :: ub(:)
+real(RP), allocatable :: x(:)
+real(RP), allocatable :: xhist(:, :)
+
+! Validate the number of arguments
+call fmxVerifyNArgin(nargin, 23)
+call fmxVerifyNArgout(nargout, 8)
+
+! Verify that input 1 is a function handle; the other inputs will be verified when read.
+call fmxVerifyClassShape(pinput(1), 'function_handle', 'rank0')
+
+! Read the inputs
+fun_ptr = pinput(1) ! FUN_PTR is a pointer to the function handle
+call fmxReadMPtr(pinput(2), x)
+call fmxReadMPtr(pinput(3), Aineq)
+call fmxReadMPtr(pinput(4), bineq)
+call fmxReadMPtr(pinput(5), Aeq)
+call fmxReadMPtr(pinput(6), beq)
+call fmxReadMPtr(pinput(7), lb)
+call fmxReadMPtr(pinput(8), ub)
+call fmxReadMPtr(pinput(9), rhobeg)
+call fmxReadMPtr(pinput(10), rhoend)
+call fmxReadMPtr(pinput(11), eta1)
+call fmxReadMPtr(pinput(12), eta2)
+call fmxReadMPtr(pinput(13), gamma1)
+call fmxReadMPtr(pinput(14), gamma2)
+call fmxReadMPtr(pinput(15), ftarget)
+call fmxReadMPtr(pinput(16), ctol)
+call fmxReadMPtr(pinput(17), cweight)
+call fmxReadMPtr(pinput(18), maxfun)
+call fmxReadMPtr(pinput(19), npt)
+call fmxReadMPtr(pinput(20), iprint)
+call fmxReadMPtr(pinput(21), maxhist)
+call fmxReadMPtr(pinput(22), output_xhist)
+call fmxReadMPtr(pinput(23), maxfilt)
+
+! Call the Fortran code
+! There are different cases because XHIST may or may not be passed to the Fortran code.
+if (output_xhist) then
+ call lincoa(calfun, x, f, cstrv, Aineq, bineq, Aeq, beq, lb, ub, nf, rhobeg, rhoend, ftarget, &
+ & ctol, cweight, maxfun, npt, iprint, eta1, eta2, gamma1, gamma2, xhist = xhist, fhist = fhist, &
+ & chist = chist, maxhist = maxhist, maxfilt = maxfilt, info = info)
+else
+ call lincoa(calfun, x, f, cstrv, Aineq, bineq, Aeq, beq, lb, ub, nf, rhobeg, rhoend, ftarget, &
+ & ctol, cweight, maxfun, npt, iprint, eta1, eta2, gamma1, gamma2, fhist = fhist, chist = chist, &
+ & maxhist = maxhist, maxfilt = maxfilt, info = info)
+end if
+
+! After the Fortran code, XHIST may not be allocated, because it may not have been passed to the
+! Fortran code. We allocate it here. Otherwise, fmxWriteMPtr will fail.
+if (.not. allocated(xhist)) then
+ call safealloc(xhist, int(size(x), IK), 0_IK)
+end if
+
+! Write the outputs
+call fmxWriteMPtr(x, poutput(1))
+call fmxWriteMPtr(f, poutput(2))
+call fmxWriteMPtr(cstrv, poutput(3))
+call fmxWriteMPtr(info, poutput(4))
+call fmxWriteMPtr(nf, poutput(5))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(6))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(7), 'row')
+call fmxWriteMPtr(chist(1:min(nf, int(size(chist), IK))), poutput(8), 'row')
+! N.B.:
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code. Similar for CHIST.
+
+! Free memory. We prefer explicit deallocation to the automatic one.
+deallocate (x) ! Allocated by fmxReadMPtr.
+deallocate (Aineq) ! Allocated by fmxReadMPtr.
+deallocate (bineq) ! Allocated by fmxReadMPtr.
+deallocate (Aeq) ! Allocated by fmxReadMPtr.
+deallocate (beq) ! Allocated by fmxReadMPtr.
+deallocate (lb) ! Allocated by fmxReadMPtr.
+deallocate (ub) ! Allocated by fmxReadMPtr.
+deallocate (xhist) ! Allocated by the solver
+deallocate (fhist) ! Allocated by the solver
+deallocate (chist) ! Allocated by the solver
+
+end subroutine mexFunction
diff --git a/matlab/mex_gateways/R2025a/newuoa_mex.F90 b/matlab/mex_gateways/R2025a/newuoa_mex.F90
new file mode 100644
index 0000000000..d3363cd97b
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/newuoa_mex.F90
@@ -0,0 +1,152 @@
+!--------------------------------------------------------------------------------------------------!
+! The MEX gateway for NEWUOA
+! This is an adapted MEX gateway to circumvent the MATLAB R2025a bug that it segfaults on Linux
+! if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+!
+! Authors:
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
+!
+! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
+!
+! Started in July 2020
+!
+! Last Modified: Tue 12 May 2026 04:51:49 PM CST
+!--------------------------------------------------------------------------------------------------!
+
+#include "fintrf.h"
+
+
+module calfun_mod
+implicit none
+private
+public :: fun_ptr, calfun
+
+mwPointer :: fun_ptr ! Pointer to the objective function handle
+
+contains
+
+subroutine calfun(x, f)
+use, non_intrinsic :: cbfun_mod, only : evalcb
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+call evalcb(fun_ptr, x, f)
+end subroutine calfun
+
+end module calfun_mod
+
+
+subroutine mexFunction(nargout, poutput, nargin, pinput)
+!--------------------------------------------------------------------------------------------------!
+! This is the entry point to the Fortran MEX function. If the compiled MEX file is named as
+! FUNCTION_NAME.mex*** (extension depends on the platform), then in MATLAB we can call:
+! [x, f, info, nf, xhist, fhist] = ...
+! FUNCTION_NAME(fun, x0, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, maxfun, npt, ...
+! iprint, maxhist, output_xhist)
+!--------------------------------------------------------------------------------------------------!
+
+! Generic modules
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+
+! Fortran MEX API modules
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyNArgin, fmxVerifyNArgout
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyClassShape
+use, non_intrinsic :: fmxapi_mod, only : fmxReadMPtr, fmxWriteMPtr
+
+! Solver-specific modules
+use, non_intrinsic :: calfun_mod, only : fun_ptr, calfun
+use, non_intrinsic :: newuoa_mod, only : newuoa
+
+implicit none
+
+! mexFunction arguments nargout and nargin are of type INTEGER in MATLAB 2019a documents.
+integer, intent(in) :: nargout, nargin
+mwPointer, intent(in) :: pinput(nargin)
+mwPointer, intent(out) :: poutput(nargout)
+
+! Local variables
+integer(IK) :: info
+integer(IK) :: iprint
+integer(IK) :: maxfun
+integer(IK) :: maxhist
+integer(IK) :: nf
+integer(IK) :: npt
+logical :: output_xhist
+real(RP) :: eta1
+real(RP) :: eta2
+real(RP) :: f
+real(RP) :: ftarget
+real(RP) :: gamma1
+real(RP) :: gamma2
+real(RP) :: rhobeg
+real(RP) :: rhoend
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: x(:)
+real(RP), allocatable :: xhist(:, :)
+
+! Validate the number of arguments
+call fmxVerifyNArgin(nargin, 14)
+call fmxVerifyNArgout(nargout, 6)
+
+! Verify that input 1 is a function handle; the other inputs will be verified when read.
+call fmxVerifyClassShape(pinput(1), 'function_handle', 'rank0')
+
+! Read the inputs
+fun_ptr = pinput(1) ! FUN_PTR is a pointer to the function handle
+call fmxReadMPtr(pinput(2), x)
+call fmxReadMPtr(pinput(3), rhobeg)
+call fmxReadMPtr(pinput(4), rhoend)
+call fmxReadMPtr(pinput(5), eta1)
+call fmxReadMPtr(pinput(6), eta2)
+call fmxReadMPtr(pinput(7), gamma1)
+call fmxReadMPtr(pinput(8), gamma2)
+call fmxReadMPtr(pinput(9), ftarget)
+call fmxReadMPtr(pinput(10), maxfun)
+call fmxReadMPtr(pinput(11), npt)
+call fmxReadMPtr(pinput(12), iprint)
+call fmxReadMPtr(pinput(13), maxhist)
+call fmxReadMPtr(pinput(14), output_xhist)
+
+! Call the Fortran code
+! There are different cases because XHIST may or may not be passed to the Fortran code.
+if (output_xhist) then
+ call newuoa(calfun, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &
+ & eta1, eta2, gamma1, gamma2, xhist = xhist, fhist = fhist, maxhist = maxhist, info = info)
+else
+ call newuoa(calfun, x, f, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint, &
+ & eta1, eta2, gamma1, gamma2, fhist = fhist, maxhist = maxhist, info = info)
+end if
+
+! After the Fortran code, XHIST may not be allocated, because it may not have been passed to the
+! Fortran code. We allocate it here. Otherwise, fmxWriteMPtr will fail.
+if (.not. allocated(xhist)) then
+ call safealloc(xhist, int(size(x), IK), 0_IK)
+end if
+
+! Write the outputs
+call fmxWriteMPtr(x, poutput(1))
+call fmxWriteMPtr(f, poutput(2))
+call fmxWriteMPtr(info, poutput(3))
+call fmxWriteMPtr(nf, poutput(4))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
+! N.B.:
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code.
+
+! Free memory. We prefer explicit deallocation to the automatic one.
+deallocate (x) ! Allocated by fmxReadMPtr.
+deallocate (xhist) ! Allocated by the solver
+deallocate (fhist) ! Allocated by the solver
+
+end subroutine mexFunction
diff --git a/matlab/mex_gateways/R2025a/uobyqa_mex.F90 b/matlab/mex_gateways/R2025a/uobyqa_mex.F90
new file mode 100644
index 0000000000..f41f89799b
--- /dev/null
+++ b/matlab/mex_gateways/R2025a/uobyqa_mex.F90
@@ -0,0 +1,150 @@
+!--------------------------------------------------------------------------------------------------!
+! The MEX gateway for UOBYQA
+! This is an adapted MEX gateway to circumvent the MATLAB R2025a bug that it segfaults on Linux
+! if the Fortran MEX function contains an internal procedure that is passed as an actual argument.
+! This MEX uses a module variable FUN_PTR to store the function handle, which is essentially a
+! global variable and is not thread-safe or recursion-safe.
+! See MathWorks Technical Support Case 07931486 and
+! https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+! https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+! https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+! https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+!
+! Authors:
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
+!
+! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
+!
+! Started in July 2020
+!
+! Last Modified: Tue 12 May 2026 04:51:20 PM CST
+!--------------------------------------------------------------------------------------------------!
+
+#include "fintrf.h"
+
+
+module calfun_mod
+implicit none
+private
+public :: fun_ptr, calfun
+
+mwPointer :: fun_ptr ! Pointer to the objective function handle
+
+contains
+
+subroutine calfun(x, f)
+use, non_intrinsic :: cbfun_mod, only : evalcb
+use, non_intrinsic :: consts_mod, only : RP
+implicit none
+real(RP), intent(in) :: x(:)
+real(RP), intent(out) :: f
+call evalcb(fun_ptr, x, f)
+end subroutine calfun
+
+end module calfun_mod
+
+
+subroutine mexFunction(nargout, poutput, nargin, pinput)
+!--------------------------------------------------------------------------------------------------!
+! This is the entry point to the Fortran MEX function. If the compiled MEX file is named as
+! FUNCTION_NAME.mex*** (extension depends on the platform), then in MATLAB we can call:
+! [x, f, info, nf, xhist, fhist] = ...
+! FUNCTION_NAME(fun, x0, rhobeg, rhoend, eta1, eta2, gamma1, gamma2, ftarget, maxfun, ...
+! iprint, maxhist, output_xhist)
+!--------------------------------------------------------------------------------------------------!
+
+! Generic modules
+use, non_intrinsic :: consts_mod, only : RP, IK
+use, non_intrinsic :: memory_mod, only : safealloc
+
+! Fortran MEX API modules
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyNArgin, fmxVerifyNArgout
+use, non_intrinsic :: fmxapi_mod, only : fmxVerifyClassShape
+use, non_intrinsic :: fmxapi_mod, only : fmxReadMPtr, fmxWriteMPtr
+
+! Solver-specific modules
+use, non_intrinsic :: calfun_mod, only : fun_ptr, calfun
+use, non_intrinsic :: uobyqa_mod, only : uobyqa
+
+implicit none
+
+! mexFunction arguments nargout and nargin are of type INTEGER in MATLAB 2019a documents.
+integer, intent(in) :: nargout, nargin
+mwPointer, intent(in) :: pinput(nargin)
+mwPointer, intent(out) :: poutput(nargout)
+
+! Local variables
+integer(IK) :: info
+integer(IK) :: iprint
+integer(IK) :: maxfun
+integer(IK) :: maxhist
+integer(IK) :: nf
+logical :: output_xhist
+real(RP) :: eta1
+real(RP) :: eta2
+real(RP) :: f
+real(RP) :: ftarget
+real(RP) :: gamma1
+real(RP) :: gamma2
+real(RP) :: rhobeg
+real(RP) :: rhoend
+real(RP), allocatable :: fhist(:)
+real(RP), allocatable :: x(:)
+real(RP), allocatable :: xhist(:, :)
+
+! Validate the number of arguments
+call fmxVerifyNArgin(nargin, 13)
+call fmxVerifyNArgout(nargout, 6)
+
+! Verify that input 1 is a function handle; the other inputs will be verified when read.
+call fmxVerifyClassShape(pinput(1), 'function_handle', 'rank0')
+
+! Read the inputs
+fun_ptr = pinput(1) ! FUN_PTR is a pointer to the function handle
+call fmxReadMPtr(pinput(2), x)
+call fmxReadMPtr(pinput(3), rhobeg)
+call fmxReadMPtr(pinput(4), rhoend)
+call fmxReadMPtr(pinput(5), eta1)
+call fmxReadMPtr(pinput(6), eta2)
+call fmxReadMPtr(pinput(7), gamma1)
+call fmxReadMPtr(pinput(8), gamma2)
+call fmxReadMPtr(pinput(9), ftarget)
+call fmxReadMPtr(pinput(10), maxfun)
+call fmxReadMPtr(pinput(11), iprint)
+call fmxReadMPtr(pinput(12), maxhist)
+call fmxReadMPtr(pinput(13), output_xhist)
+
+! Call the Fortran code
+! There are different cases because XHIST may or may not be passed to the Fortran code.
+if (output_xhist) then
+ call uobyqa(calfun, x, f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, &
+ & eta1, eta2, gamma1, gamma2, xhist = xhist, fhist = fhist, maxhist = maxhist, info = info)
+else
+ call uobyqa(calfun, x, f, nf, rhobeg, rhoend, ftarget, maxfun, iprint, &
+ & eta1, eta2, gamma1, gamma2, fhist = fhist, maxhist = maxhist, info = info)
+end if
+
+! After the Fortran code, XHIST may not be allocated, because it may not have been passed to the
+! Fortran code. We allocate it here. Otherwise, fmxWriteMPtr will fail.
+if (.not. allocated(xhist)) then
+ call safealloc(xhist, int(size(x), IK), 0_IK)
+end if
+
+! Write the outputs
+call fmxWriteMPtr(x, poutput(1))
+call fmxWriteMPtr(f, poutput(2))
+call fmxWriteMPtr(info, poutput(3))
+call fmxWriteMPtr(nf, poutput(4))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
+! N.B.:
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code.
+
+! Free memory. We prefer explicit deallocation to the automatic one.
+deallocate (x) ! Allocated by fmxReadMPtr.
+deallocate (xhist) ! Allocated by the solver
+deallocate (fhist) ! Allocated by the solver
+
+end subroutine mexFunction
diff --git a/matlab/mex_gateways/README.txt b/matlab/mex_gateways/README.txt
index 26e1ea311b..71c7b7a705 100644
--- a/matlab/mex_gateways/README.txt
+++ b/matlab/mex_gateways/README.txt
@@ -1,11 +1,10 @@
This directory contains the MEX gateways for the modernized Fortran implementation of Powell's
derivative-free optimization solvers.
-Authors:
- Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
- and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
- Department of Applied Mathematics,
- The Hong Kong Polytechnic University
+Author:
+ Zaikun ZHANG (www.zhangzk.net)
+ School of Mathematics,
+ Sun Yat-sen University, China.
Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
diff --git a/matlab/mex_gateways/bobyqa_mex.F90 b/matlab/mex_gateways/bobyqa_mex.F90
index d1e7446e8c..bdee7a9e90 100644
--- a/matlab/mex_gateways/bobyqa_mex.F90
+++ b/matlab/mex_gateways/bobyqa_mex.F90
@@ -2,16 +2,14 @@
! The MEX gateway for BOBYQA
!
! Authors:
-! Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-! and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-! Department of Applied Mathematics,
-! The Hong Kong Polytechnic University
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
!
! Started in July 2020
!
-! Last Modified: Saturday, January 28, 2023 PM11:55:58
+! Last Modified: Tue 12 May 2026 04:43:43 PM CST
!--------------------------------------------------------------------------------------------------!
#include "fintrf.h"
@@ -113,14 +111,13 @@ subroutine mexFunction(nargout, poutput, nargin, pinput)
call fmxWriteMPtr(f, poutput(2))
call fmxWriteMPtr(info, poutput(3))
call fmxWriteMPtr(nf, poutput(4))
-call fmxWriteMPtr(xhist(:, 1:min(int(nf), size(xhist, 2))), poutput(5))
-call fmxWriteMPtr(fhist(1:min(int(nf), size(fhist))), poutput(6), 'row')
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
! N.B.:
-! 1. INT(NF) converts NF to the default integer type; if not, MIN may complain.
-! 2. It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
-! limit in the Fortran code. Similar for CHIST.
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! limit in the Fortran code.
-! Free memory. Indeed, automatic deallocation would take place.
+! Free memory. We prefer explicit deallocation to the automatic one.
deallocate (x) ! Allocated by fmxReadMPtr.
deallocate (lb) ! Allocated by fmxReadMPtr.
deallocate (ub) ! Allocated by fmxReadMPtr.
diff --git a/matlab/mex_gateways/cbfun.F90 b/matlab/mex_gateways/cbfun.F90
index ee02c6c5a4..00f9f57a61 100644
--- a/matlab/mex_gateways/cbfun.F90
+++ b/matlab/mex_gateways/cbfun.F90
@@ -8,7 +8,7 @@ module cbfun_mod
!
! Started in July 2020
!
-! Last Modified: Thursday, July 20, 2023 AM10:15:20
+! Last Modified: Sat 19 Jul 2025 10:55:26 PM PDT
!--------------------------------------------------------------------------------------------------!
implicit none
diff --git a/matlab/mex_gateways/cobyla_mex.F90 b/matlab/mex_gateways/cobyla_mex.F90
index 46108e690d..0c2d43a674 100644
--- a/matlab/mex_gateways/cobyla_mex.F90
+++ b/matlab/mex_gateways/cobyla_mex.F90
@@ -2,16 +2,14 @@
! The MEX gateway for COBYLA
!
! Authors:
-! Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-! and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-! Department of Applied Mathematics,
-! The Hong Kong Polytechnic University
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
!
! Started in July 2020
!
-! Last Modified: Friday, August 11, 2023 PM05:55:38
+! Last Modified: Tue 12 May 2026 04:46:53 PM CST
!--------------------------------------------------------------------------------------------------!
#include "fintrf.h"
@@ -161,16 +159,15 @@ subroutine mexFunction(nargout, poutput, nargin, pinput)
call fmxWriteMPtr(nlconstr, poutput(4))
call fmxWriteMPtr(info, poutput(5))
call fmxWriteMPtr(nf, poutput(6))
-call fmxWriteMPtr(xhist(:, 1:min(int(nf), size(xhist, 2))), poutput(7))
-call fmxWriteMPtr(fhist(1:min(int(nf), size(fhist))), poutput(8), 'row')
-call fmxWriteMPtr(chist(1:min(int(nf), size(chist))), poutput(9), 'row')
-call fmxWriteMPtr(nlchist(:, 1:min(int(nf), size(nlchist, 2))), poutput(10))
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(7))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(8), 'row')
+call fmxWriteMPtr(chist(1:min(nf, int(size(chist), IK))), poutput(9), 'row')
+call fmxWriteMPtr(nlchist(:, 1:min(nf, int(size(nlchist, 2), IK))), poutput(10))
! N.B.:
-! 1. INT(NF) converts NF to the default integer type; if not, MIN may complain.
-! 2. It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
! limit in the Fortran code. Similar for CHIST and CONHIST.
-! Free memory. Indeed, automatic deallocation would take place.
+! Free memory. We prefer explicit deallocation to the automatic one.
deallocate (x) ! Allocated by fmxReadMPtr.
deallocate (nlconstr0) ! Allocated by fmxReadMPtr.
deallocate (Aineq) ! Allocated by fmxReadMPtr.
diff --git a/matlab/mex_gateways/debug.F90 b/matlab/mex_gateways/debug.F90
index 77089a1073..dcf50746f3 100644
--- a/matlab/mex_gateways/debug.F90
+++ b/matlab/mex_gateways/debug.F90
@@ -11,7 +11,7 @@ module debug_mod
!
! Started in July 2020
!
-! Last Modified: Sunday, May 21, 2023 PM05:46:15
+! Last Modified: Thursday, April 04, 2024 PM10:59:38
!--------------------------------------------------------------------------------------------------!
implicit none
@@ -40,9 +40,9 @@ subroutine assert(condition, description, srname)
!--------------------------------------------------------------------------------------------------!
! This subroutine checks whether ASSERTION is true.
! If no but DEBUGGING is true, print the following message and then stop the program:
-! SRNAME // 'Assertion failed: ' // DESCRIPTION
-! MATLAB analogue: assert(condition, sprintf('%s: Assertion failed: %s', srname, description))
-! Python analogue: assert condition, srname + ': Assertion failed: ' + description
+! SRNAME // 'Assertion fails: ' // DESCRIPTION
+! MATLAB analogue: assert(condition, sprintf('%s: Assertion fails: %s', srname, description))
+! Python analogue: assert condition, srname + ': Assertion fails: ' + description
! C analogue: assert(condition) /* An error message will be produced by the compiler */
!--------------------------------------------------------------------------------------------------!
! N.B.: As in C, we design ASSERT to operate only in the debug mode, i.e., when PRIMA_DEBUGGING == 1;
@@ -56,7 +56,7 @@ subroutine assert(condition, description, srname)
character(len=*), intent(in) :: description ! Description of the condition in human language
character(len=*), intent(in) :: srname ! Name of the subroutine that calls this procedure
if (DEBUGGING .and. .not. condition) then
- call errstop(trim(adjustl(srname)), 'Assertion failed: '//trim(adjustl(description)))
+ call errstop(trim(adjustl(srname)), 'Assertion fails: '//trim(adjustl(description)))
end if
end subroutine assert
@@ -65,8 +65,8 @@ subroutine validate(condition, description, srname)
!--------------------------------------------------------------------------------------------------!
! This subroutine checks whether CONDITION is true.
! If no, print the following message to and then stop the program:
-! SRNAME // 'Validation failed: ' // DESCRIPTION
-! MATLAB analogue: assert(condition, sprintf('%s: Validation failed: %s', srname, description))
+! SRNAME // 'Validation fails: ' // DESCRIPTION
+! MATLAB analogue: assert(condition, sprintf('%s: Validation fails: %s', srname, description))
! In Python or C, VALIDATE can be implemented following the Fortran implementation below.
! N.B.: ASSERT checks the condition only when debugging, but VALIDATE does it always.
!--------------------------------------------------------------------------------------------------!
@@ -75,7 +75,7 @@ subroutine validate(condition, description, srname)
character(len=*), intent(in) :: description ! Description of the condition in human language
character(len=*), intent(in) :: srname ! Name of the subroutine that calls this procedure
if (.not. condition) then
- call errstop(trim(adjustl(srname)), 'Validation failed: '//trim(adjustl(description)))
+ call errstop(trim(adjustl(srname)), 'Validation fails: '//trim(adjustl(description)))
end if
end subroutine validate
@@ -84,10 +84,10 @@ subroutine wassert(condition, description, srname)
!--------------------------------------------------------------------------------------------------!
! This subroutine checks whether CONDITION is true.
! If no but DEBUGGING is true, print the following message to STDERR (but do not stop the program):
-! SRNAME // 'Assertion failed: ' // DESCRIPTION
+! SRNAME // 'Assertion fails: ' // DESCRIPTION
! MATLAB analogue:
! !if ~condition
-! ! warning(sprintf('%s: Assertion failed: %s', srname, description))
+! ! warning(sprintf('%s: Assertion fails: %s', srname, description))
! !end
! In Python or C, WASSERT can be implemented following the Fortran implementation below.
! N.B.: When DEBUGGING is true, ASSERT stops the program with an error if the condition is false,
@@ -100,7 +100,7 @@ subroutine wassert(condition, description, srname)
character(len=*), intent(in) :: srname ! Name of the subroutine that calls this procedure
if (DEBUGGING .and. .not. condition) then
call backtr()
- call warning(trim(adjustl(srname)), 'Assertion failed: '//trim(adjustl(description)))
+ call warning(trim(adjustl(srname)), 'Assertion fails: '//trim(adjustl(description)))
end if
end subroutine wassert
@@ -155,8 +155,9 @@ subroutine backtr
use, non_intrinsic :: ifcore, only : tracebackqq
implicit none
call tracebackqq(user_exit_code=-1)
-! According to "Intel Fortran Compiler 19.1 Developer Guide and Reference", item "TRACEBACKQQ":
-! By specifying a user exit code of -1, control returns to the calling program. Specifying a user
+! According to
+! https://www.intel.com/content/www/us/en/docs/fortran-compiler/developer-guide-reference/2024-1/tracebackqq.html,
+! by specifying a user exit code of -1, control returns to the calling program. Specifying a user
! exit code with a positive value requests that specified value be returned to the operating system.
! The default value is 0, which causes the application to abort execution.
#endif
diff --git a/matlab/mex_gateways/fmxapi.F90 b/matlab/mex_gateways/fmxapi.F90
index c418dcd748..702e2577be 100644
--- a/matlab/mex_gateways/fmxapi.F90
+++ b/matlab/mex_gateways/fmxapi.F90
@@ -12,7 +12,7 @@ module fmxapi_mod
!
! Started in July 2020
!
-! Last Modified: Monday, May 08, 2023 PM06:05:21
+! Last Modified: Sunday, April 07, 2024 PM04:30:44
!--------------------------------------------------------------------------------------------------!
! N.B.:
@@ -460,7 +460,7 @@ subroutine read_rvector(px, x)
end if
end if
-! Deallocate X_DP. Indeed, automatic deallocation would take place.
+! Deallocate X_DP. We prefer explicit deallocation to the automatic one.
deallocate (x_dp)
end subroutine read_rvector
@@ -483,7 +483,7 @@ subroutine read_rmatrix(px, x)
real(DP), allocatable :: x_dp(:, :)
integer(IK) :: m, n
mwSize :: xsize
-character(len=:), allocatable :: wid, msg
+character(len=:), allocatable :: wid, eid, msg
! Check input type and size
call fmxVerifyClassShape(px, 'double', 'matrix')
@@ -491,7 +491,12 @@ subroutine read_rmatrix(px, x)
! Get size
m = int(mxGetM(px), kind(m))
n = int(mxGetN(px), kind(n))
-xsize = int(m * n, kind(xsize))
+xsize = int(m, kind(xsize)) * int(n, kind(xsize))
+if (abs(real(xsize, DP) - real(m, DP) * real(n, DP)) > cvsnTol * (abs(xsize) + 1)) then
+ eid = 'FMXAPI:LargeMultiplicationError'
+ msg = 'READ_RMATRIX: Large error occurs when handling the size of the given matrix (maybe due to overflow).'
+ call mexErrMsgIdAndTxt(eid, msg)
+end if
! Copy input to X_DP
call safealloc(x_dp, m, n) ! NOT removable
@@ -509,7 +514,7 @@ subroutine read_rmatrix(px, x)
end if
end if
-! Deallocate X_DP. Indeed, automatic deallocation would take place.
+! Deallocate X_DP. We prefer explicit deallocation to the automatic one.
deallocate (x_dp)
end subroutine read_rmatrix
diff --git a/matlab/mex_gateways/lincoa_mex.F90 b/matlab/mex_gateways/lincoa_mex.F90
index b712194c17..bd2ac0fc11 100644
--- a/matlab/mex_gateways/lincoa_mex.F90
+++ b/matlab/mex_gateways/lincoa_mex.F90
@@ -2,16 +2,14 @@
! The MEX gateway for LINCOA
!
! Authors:
-! Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-! and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-! Department of Applied Mathematics,
-! The Hong Kong Polytechnic University
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
!
! Started in July 2020
!
-! Last Modified: Monday, July 03, 2023 PM05:16:26
+! Last Modified: Tue 12 May 2026 04:47:40 PM CST
!--------------------------------------------------------------------------------------------------!
#include "fintrf.h"
@@ -132,15 +130,14 @@ subroutine mexFunction(nargout, poutput, nargin, pinput)
call fmxWriteMPtr(cstrv, poutput(3))
call fmxWriteMPtr(info, poutput(4))
call fmxWriteMPtr(nf, poutput(5))
-call fmxWriteMPtr(xhist(:, 1:min(int(nf), size(xhist, 2))), poutput(6))
-call fmxWriteMPtr(fhist(1:min(int(nf), size(fhist))), poutput(7), 'row')
-call fmxWriteMPtr(chist(1:min(int(nf), size(chist))), poutput(8), 'row')
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(6))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(7), 'row')
+call fmxWriteMPtr(chist(1:min(nf, int(size(chist), IK))), poutput(8), 'row')
! N.B.:
-! 1. INT(NF) converts NF to the default integer type; if not, MIN may complain.
-! 2. It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
! limit in the Fortran code. Similar for CHIST.
-! Free memory. Indeed, automatic deallocation would take place.
+! Free memory. We prefer explicit deallocation to the automatic one.
deallocate (x) ! Allocated by fmxReadMPtr.
deallocate (Aineq) ! Allocated by fmxReadMPtr.
deallocate (bineq) ! Allocated by fmxReadMPtr.
diff --git a/matlab/mex_gateways/newuoa_mex.F90 b/matlab/mex_gateways/newuoa_mex.F90
index 8b19dfc99d..7c04fd1112 100644
--- a/matlab/mex_gateways/newuoa_mex.F90
+++ b/matlab/mex_gateways/newuoa_mex.F90
@@ -2,16 +2,14 @@
! The MEX gateway for NEWUOA
!
! Authors:
-! Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-! and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-! Department of Applied Mathematics,
-! The Hong Kong Polytechnic University
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
!
! Started in July 2020
!
-! Last Modified: Saturday, February 12, 2022 PM02:37:27
+! Last Modified: Tue 12 May 2026 04:49:26 PM CST
!--------------------------------------------------------------------------------------------------!
#include "fintrf.h"
@@ -109,14 +107,13 @@ subroutine mexFunction(nargout, poutput, nargin, pinput)
call fmxWriteMPtr(f, poutput(2))
call fmxWriteMPtr(info, poutput(3))
call fmxWriteMPtr(nf, poutput(4))
-call fmxWriteMPtr(xhist(:, 1:min(int(nf), size(xhist, 2))), poutput(5))
-call fmxWriteMPtr(fhist(1:min(int(nf), size(fhist))), poutput(6), 'row')
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
! N.B.:
-! 1. INT(NF) converts NF to the default integer type; if not, MIN may complain.
-! 2. It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
! limit in the Fortran code.
-! Free memory. Indeed, automatic deallocation would take place.
+! Free memory. We prefer explicit deallocation to the automatic one.
deallocate (x) ! Allocated by fmxReadMPtr.
deallocate (xhist) ! Allocated by the solver
deallocate (fhist) ! Allocated by the solver
diff --git a/matlab/mex_gateways/tests/9src b/matlab/mex_gateways/tests/9src
deleted file mode 120000
index 7e59c4c051..0000000000
--- a/matlab/mex_gateways/tests/9src
+++ /dev/null
@@ -1 +0,0 @@
-../../../fortran/tests/9src
\ No newline at end of file
diff --git a/matlab/mex_gateways/tests/Makefile b/matlab/mex_gateways/tests/Makefile
index 844bded6a9..fcd070927f 100644
--- a/matlab/mex_gateways/tests/Makefile
+++ b/matlab/mex_gateways/tests/Makefile
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest.S: test solver S with compiler X
# Ctest.S_c: test solver S with compiler X; compilation only (do not run the binary)
# Ctest_iN: test solver S with compiler X and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/a9src b/matlab/mex_gateways/tests/a9src
deleted file mode 120000
index 56b28274a0..0000000000
--- a/matlab/mex_gateways/tests/a9src
+++ /dev/null
@@ -1 +0,0 @@
-../../../fortran/tests/a9src
\ No newline at end of file
diff --git a/matlab/mex_gateways/tests/checktest b/matlab/mex_gateways/tests/checktest
deleted file mode 120000
index ffc1ca649e..0000000000
--- a/matlab/mex_gateways/tests/checktest
+++ /dev/null
@@ -1 +0,0 @@
-../../../fortran/tests/checktest
\ No newline at end of file
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa b/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
index bc118f2535..b356aa4105 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.bobyqa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.cobyla b/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
index 859c259b0c..7998ba6132 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.cobyla
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.common b/matlab/mex_gateways/tests/makefiles/Makefile.common
index 2ff9d69dd1..719c462e8b 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.common
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.common
@@ -2,17 +2,17 @@
#
# The following compilers are tested:
# 9: G95
-# a: Absoft af95
# d: AOCC flang
-# f: Classic flang
+# f: LLVM flang
# g: GNU gfortran
+# m: AOMP flang
# n: NAG nagfor
# i: Intel ifort
# s: Oracle sunf95
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = 9, a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = 9, d, f, g, m, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_iN: test with compiler C and integer kind INT(N*8)
#
@@ -28,7 +28,7 @@
#$(VERBOSE).SILENT: # Order make to work quietly. Not desirable in this case.
-SHELL = /bin/bash
+SHELL = /usr/bin/env bash # Without this, the Makefile will not work as it contains bash-specific commands.
# The MathWorks header file for Fortran MEX. To respect the copyright, we do not include it in this
# package but use the file on this machine if available.
@@ -79,6 +79,8 @@ SOLVER_DIR := $(FORTRAN_DIR)/$(SOLVER)
MEXGATE_DIR := $(TEST_DIR)/..
# Source directories.
SRC_DIRS := $(COMMON_DIR) $(SOLVER_DIR)
+# Tools directory
+TOOLS_DIR := $(TEST_DIR)/tools
# MEX_DEBUGF90 is the name of the MEX file that defines `debug_mod`, which has a common version
# named CMN_DEBUGF90. We should use the MEX version in place of the common one when testing the MEX
@@ -101,14 +103,14 @@ SRC := $(COMMON_SRC) $(SOLVER_SRC) $(MEXGATE_SRC)
HEADERS := $(TEST_SOLVER_DIR)/$(COMMON)/*.h
# The checktest script.
-CHCKTST := $(TEST_DIR)/checktest
+CHCKTST := $(TOOLS_DIR)/checktest
# Define the tests.
######################################################################################
# Decide whether to test QP, i.e., REAL128.
# Flang and nvfortran do not support REAL128;
# AOCC Flang complains about a symbol lookup error: undefined symbol: "fort_rnumq_i8";
-TESTS_QP = atest gtest ntest itest stest 9test xtest
+TESTS_QP = gtest mtest ntest itest stest 9test xtest
TESTS_NO_QP = dtest ftest vtest
TESTS = $(TESTS_QP) $(TESTS_NO_QP)
# When listing the tests, we first put the ones that are more likely to raise errors.
@@ -132,8 +134,8 @@ $(foreach TST, $(TESTS_NO_QP), $(eval $(TST): SUBTESTS := \
# The tests with ifort, ifx, etc are slow due to the extensive runtime checks. To save time, we
# define the following tests. If itest_i2 succeeds, then itest_i4 and itest_i8 are likely (but not
# surely) to be OK, because the only difference in the code is the integer kind. Similar for others.
-TESTS_INT_QP = atest_i2 atest_i4 atest_i8 \
- gtest_i2 gtest_i4 gtest_i8 \
+TESTS_INT_QP = gtest_i2 gtest_i4 gtest_i8 \
+ mtest_i2 mtest_i4 mtest_i8 \
ntest_i2 ntest_i4 ntest_i8 \
itest_i2 itest_i4 itest_i8 \
stest_i2 stest_i4 stest_i8 \
@@ -169,16 +171,18 @@ else
endif
endif
-# Define SEDI.
-# When calling "sed -i" on macOS, it is obligatory to specify a string (e.g., .bak) after -i as the
-# extension for saving a backup. If the string is "", then no backup will be saved. If no string is
-# specified, then an error will be raised, saying "invalid command code".
-SEDI :=
-ifeq ($(OSTYPE), MAC)
- SEDI = @sed -i ""
-else
- SEDI = @sed -i
-endif
+## Define SEDI.
+## When calling "sed -i" on macOS and FreeBSD, it is obligatory to specify a string (e.g., .bak)
+## after -i as the extension for saving a backup. If the string is "", then no backup will be saved.
+## If no string is specified, then an error will be raised, saying "invalid command code".
+#SEDI :=
+#ifeq ($(OSTYPE), MAC)
+# SEDI := sed -i ""
+#else ifeq ($(OSTYPE), FREEBSD)
+# SEDI := sed -i ""
+#else
+# SEDI := sed -i
+#endif
## Define NPROCS to be the number of processors available to make tests in parallel.
#NPROCS :=
@@ -201,35 +205,8 @@ endif
# e.g., over/underflow.
####################################################################################################
-# Absoft af95
-# For Debian-based systems, -no-pie is needed; for other systems, this option may not work.
-AFORT :=
-ifneq ("$(wildcard /etc/debian_version)","")
- AFORT = af95 -no-pie
-else
- AFORT = af95
-endif
-AFORT := $(AFORT) -m1 -en -et -Rb -Rc -Rs -Rp
-
-atest_i2_r4_d1_tst atest_i4_r4_d1_tst atest_i8_r4_d1_tst atest_i2_r4_d0_tst atest_i4_r4_d0_tst atest_i8_r4_d0_tst: \
- FC1 := $(AFORT) $(FFLAGS1) -TENV:simd_zmask=off
-atest_i2_r4_d1_tst atest_i4_r4_d1_tst atest_i8_r4_d1_tst atest_i2_r4_d0_tst atest_i4_r4_d0_tst atest_i8_r4_d0_tst: \
- FC2 := $(AFORT) $(FFLAGS2) -TENV:simd_zmask=off
-
-atest_i2_r8_d1_tst atest_i4_r8_d1_tst atest_i8_r8_d1_tst atest_i2_r8_d0_tst atest_i4_r8_d0_tst atest_i8_r8_d0_tst: \
- FC1 := $(AFORT) $(FFLAGS1) \
- -TENV:simd_zmask=off -TENV:simd_omask=off -TENV:simd_imask=off #-TENV:simd_dmask=off -TENV:simd_umask=off
-atest_i2_r8_d1_tst atest_i4_r8_d1_tst atest_i8_r8_d1_tst atest_i2_r8_d0_tst atest_i4_r8_d0_tst atest_i8_r8_d0_tst: \
- FC2 := $(AFORT) $(FFLAGS2) \
- -TENV:simd_zmask=off -TENV:simd_omask=off -TENV:simd_imask=off #-TENV:simd_dmask=off -TENV:simd_umask=off
-
-atest_i2_r16_d1_tst atest_i4_r16_d1_tst atest_i8_r16_d1_tst atest_i2_r16_d0_tst atest_i4_r16_d0_tst atest_i8_r16_d0_tst: \
- FC1 := $(AFORT) $(FFLAGS1) -TENV:simd_zmask=off
-atest_i2_r16_d1_tst atest_i4_r16_d1_tst atest_i8_r16_d1_tst atest_i2_r16_d0_tst atest_i4_r16_d0_tst atest_i8_r16_d0_tst: \
- FC2 := $(AFORT) $(FFLAGS2) -TENV:simd_zmask=off
-
# AMD AOCC Flang
-AFLANG := $(shell find -L /opt/AMD -type f -executable -name flang -print 2>/dev/null | sort | tail -n 1)
+AFLANG := $(shell find -L /opt/AMD -type f -name flang -exec test -x {} \; -print 2>/dev/null | sort | tail -n 1)
AFLANG := $(AFLANG) -pedantic-errors -Werror
# Strangely, with `-Mchkptr`, the compiler may not print the error message of `-Mbounds` anymore.
DFORT := $(AFLANG) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Kieee #-Mchkptr
@@ -249,10 +226,32 @@ dtest_i2_r16_d1_tst dtest_i4_r16_d1_tst dtest_i8_r16_d1_tst dtest_i2_r16_d0_tst
dtest_i2_r16_d1_tst dtest_i4_r16_d1_tst dtest_i8_r16_d1_tst dtest_i2_r16_d0_tst dtest_i4_r16_d0_tst dtest_i8_r16_d0_tst: \
FC2 := $(DFORT) $(FFLAGS2) -ffp-exception-behavior=strict
-# Classic Flang
-FFORT := flang
-FFORT := $(FFORT) -pedantic-errors -Werror
-FFORT := $(FFORT) -std=f$(FSTD) -pedantic -Weverything -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mchkptr -Kieee
+# AMD AOMP Flang
+MFORT := $(shell command -v amdflang)
+MFORT := $(MFORT) -fimplicit-none -Werror
+
+mtest_i2_r4_d1_tst mtest_i4_r4_d1_tst mtest_i8_r4_d1_tst mtest_i2_r4_d0_tst mtest_i4_r4_d0_tst mtest_i8_r4_d0_tst: \
+ FC1 := $(MFORT) $(FFLAGS1)
+mtest_i2_r4_d1_tst mtest_i4_r4_d1_tst mtest_i8_r4_d1_tst mtest_i2_r4_d0_tst mtest_i4_r4_d0_tst mtest_i8_r4_d0_tst: \
+ FC2 := $(MFORT) $(FFLAGS2)
+
+mtest_i2_r8_d1_tst mtest_i4_r8_d1_tst mtest_i8_r8_d1_tst mtest_i2_r8_d0_tst mtest_i4_r8_d0_tst mtest_i8_r8_d0_tst: \
+ FC1 := $(MFORT) $(FFLAGS1)
+mtest_i2_r8_d1_tst mtest_i4_r8_d1_tst mtest_i8_r8_d1_tst mtest_i2_r8_d0_tst mtest_i4_r8_d0_tst mtest_i8_r8_d0_tst: \
+ FC2 := $(MFORT) $(FFLAGS2)
+
+mtest_i2_r16_d1_tst mtest_i4_r16_d1_tst mtest_i8_r16_d1_tst mtest_i2_r16_d0_tst mtest_i4_r16_d0_tst mtest_i8_r16_d0_tst: \
+ FC1 := $(MFORT) $(FFLAGS1)
+mtest_i2_r16_d1_tst mtest_i4_r16_d1_tst mtest_i8_r16_d1_tst mtest_i2_r16_d0_tst mtest_i4_r16_d0_tst mtest_i8_r16_d0_tst: \
+ FC2 := $(MFORT) $(FFLAGS2)
+
+# LLVM Flang
+# Note that AOMP and AOCC will also provide "flang". We define FFORT as follows to make sure the correct
+# one is being called. Otherwise, flang may be resolved to the one provided by AOMP or AOCC, which
+FFORT := $(shell find -L /usr/lib/llvm* /opt/homebrew /usr/local/opt /usr/local/Cellar -type f -name flang -exec test -x {} \; -print 2>/dev/null | sort | tail -n 1)
+FFORT := $(FFORT) -fimplicit-none -Werror
+# -std or -pedantic does not work due to the non-standard code in the MEX API provided by MathWorks
+#FFORT := $(FFORT) -std=f2018 -pedantic
ftest_i2_r4_d1_tst ftest_i4_r4_d1_tst ftest_i8_r4_d1_tst ftest_i2_r4_d0_tst ftest_i4_r4_d0_tst ftest_i8_r4_d0_tst: \
FC1 := $(FFORT) $(FFLAGS1)
@@ -273,12 +272,12 @@ ftest_i2_r16_d1_tst ftest_i4_r16_d1_tst ftest_i8_r16_d1_tst ftest_i2_r16_d0_tst
# In the debug mode, our code includes the BACKTRACE function if the compiler is gfortran. This
# makes the code not standard-conforming. So -std=f$(FSTD) is excluded for the following options.
# As of 2022, -Wextra implies -Wcompare-reals, -Wunused-parameter, and -Wdo-subscript.
-GFORT := gfortran
+GFORT := $(shell command -v gfortran)
GFORT := $(GFORT) -Wall -Wextra -pedantic -fmax-errors=1 -Wampersand -Wconversion -Wuninitialized \
-Wmaybe-uninitialized -Wsurprising -Waliasing -Wimplicit-interface -Wimplicit-procedure \
-Wintrinsics-std -Wunderflow -Wuse-without-only -Wunused-parameter \
-fPIC -fimplicit-none -fbacktrace -fcheck=all
- #-finit-real=nan -finit-integer=-9999999 \ # This will hide some warnings on uninitialized variables.
+ #-finit-real=nan -finit-integer=-9999999 # This will hide some warnings on uninitialized variables.
#-Wrealloc-lhs -Wrealloc-lhs-all
gtest_i2_r4_d1_tst gtest_i4_r4_d1_tst gtest_i8_r4_d1_tst gtest_i2_r4_d0_tst gtest_i4_r4_d0_tst gtest_i8_r4_d0_tst: \
@@ -309,38 +308,31 @@ gtest_i2_r16_d1_tst gtest_i4_r16_d1_tst gtest_i8_r16_d1_tst gtest_i2_r16_d0_tst
# We impose -assume norecursion, because -standard-semantics will enable -assume recursion, which
# will cause a warning; due to -warn errors, the compilation will abort. See
# https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/language-reference/a-to-z-reference/q-to-r/recursive-and-non-recursive.html
-IFORT := ifort
+IFORT := $(shell command -v ifort)
#IFORT := $(IFORT) -diag-error-limit 1
-IFORT := $(IFORT) -stand f95 -standard-semantics -assume norecursion -warn all -debug extended -fimplicit-none \
+IFORT := $(IFORT) -diag-disable=10448 # Ignore the warning about the deprecation of ifort
+IFORT := $(IFORT) -stand f95 -standard-semantics -assume norecursion -warn all -debug extended \
-traceback -debug-parameters all
itest_i2_r4_d1_tst itest_i4_r4_d1_tst itest_i8_r4_d1_tst itest_i2_r4_d0_tst itest_i4_r4_d0_tst itest_i8_r4_d0_tst: \
- FC1 := $(IFORT) $(FFLAGS1) -fp-trap=divzero
+ FC1 := $(IFORT) $(FFLAGS1)
itest_i2_r4_d1_tst itest_i4_r4_d1_tst itest_i8_r4_d1_tst itest_i2_r4_d0_tst itest_i4_r4_d0_tst itest_i8_r4_d0_tst: \
- FC2 := $(IFORT) $(FFLAGS2) -check all -fp-trap=divzero
+ FC2 := $(IFORT) $(FFLAGS2) -check all
itest_i2_r8_d1_tst itest_i4_r8_d1_tst itest_i8_r8_d1_tst itest_i2_r8_d0_tst itest_i4_r8_d0_tst itest_i8_r8_d0_tst: \
- FC1 := $(IFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC1 := $(IFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r8_d1_tst itest_i4_r8_d1_tst itest_i8_r8_d1_tst itest_i2_r8_d0_tst itest_i4_r8_d0_tst itest_i8_r8_d0_tst: \
- FC2 := $(IFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC2 := $(IFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r16_d1_tst itest_i4_r16_d1_tst itest_i8_r16_d1_tst itest_i2_r16_d0_tst itest_i4_r16_d0_tst itest_i8_r16_d0_tst: \
- FC1 := $(IFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC1 := $(IFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
itest_i2_r16_d1_tst itest_i4_r16_d1_tst itest_i8_r16_d1_tst itest_i2_r16_d0_tst itest_i4_r16_d0_tst itest_i8_r16_d0_tst: \
- FC2 := $(IFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC2 := $(IFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
# NAG nagfor
# In massive tests, we skip the useful -mtrace option (print memory allocation trace), as its output is enormous.
# It is necessary to use "-I $(TESTSUITE_DIR)"; otherwise, the INCLUDE lines in the test suite will not work.
-NFORT := nagfor
+NFORT := $(shell command -v nagfor)
NFORT := $(NFORT) -colour=error:red,warn:magenta,info:cyan
NFORT := $(NFORT) -f$(FSTD) -info -gline -u -C -C=alias -C=dangling -C=intovf -kind=unique \
-Warn=constant_coindexing -Warn=subnormal #-Warn=allocation
@@ -363,9 +355,9 @@ ntest_i2_r16_d1_tst ntest_i4_r16_d1_tst ntest_i8_r16_d1_tst ntest_i2_r16_d0_tst
ntest_i2_r16_d1_tst ntest_i4_r16_d1_tst ntest_i8_r16_d1_tst ntest_i2_r16_d0_tst ntest_i4_r16_d0_tst ntest_i8_r16_d0_tst: \
FC2 := $(NFORT) $(FFLAGS2) -nan -ieee=stop
-# NVIDIA nvfortran (aka, pgfortran)
-VFORT := $(shell find -L /opt/nvidia -type f -executable -name nvfortran -print 2>/dev/null | sort | tail -n 1)
-VFORT := $(VFORT) -Werror
+# NVIDIA nvfortran
+VFORT := $(shell command -v nvfortran)
+VFORT := $(VFORT) #-Werror # -Werror will lead to failures due to nonstandard use of data type length specifier in the MEX API.
VFORT := $(VFORT) -C -Wall -Wextra -Minform=warn -Mstandard -Mbounds -Mchkstk -Mchkptr
vtest_i2_r4_d1_tst vtest_i4_r4_d1_tst vtest_i8_r4_d1_tst vtest_i2_r4_d0_tst vtest_i4_r4_d0_tst vtest_i8_r4_d0_tst: \
@@ -384,7 +376,7 @@ vtest_i2_r16_d1_tst vtest_i4_r16_d1_tst vtest_i8_r16_d1_tst vtest_i2_r16_d0_tst
FC2 := $(VFORT) $(FFLAGS2) -Kieee -Ktrap=divz,ovf,inv#,unf,denorm
# Oracle sunf95
-SFORT := sunf95
+SFORT := $(shell command -v sunf95)
SFORT := $(SFORT) -w3 -u -U -ansi -xcheck=%all -C
stest_i2_r4_d1_tst stest_i4_r4_d1_tst stest_i8_r4_d1_tst stest_i2_r4_d0_tst stest_i4_r4_d0_tst stest_i8_r4_d0_tst: \
@@ -403,37 +395,28 @@ stest_i2_r16_d1_tst stest_i4_r16_d1_tst stest_i8_r16_d1_tst stest_i2_r16_d0_tst
FC2 := $(SFORT) $(FFLAGS2) -fnonstd -ftrap=overflow,division,invalid#,underflow
# Intel ifx
-XFORT := ifx
+XFORT := $(shell command -v ifx)
#XFORT := $(XFORT) -diag-error-limit 1
-XFORT := $(XFORT) -stand f$(FS) -warn all -debug extended -fimplicit-none \
- -traceback -debug-parameters all
+XFORT := $(XFORT) -stand f$(FS) -warn all -debug full -traceback -debug-parameters all
xtest_i2_r4_d1_tst xtest_i4_r4_d1_tst xtest_i8_r4_d1_tst xtest_i2_r4_d0_tst xtest_i4_r4_d0_tst xtest_i8_r4_d0_tst: \
- FC1 := $(XFORT) $(FFLAGS1) -fp-trap=divzero
+ FC1 := $(XFORT) $(FFLAGS1)
xtest_i2_r4_d1_tst xtest_i4_r4_d1_tst xtest_i8_r4_d1_tst xtest_i2_r4_d0_tst xtest_i4_r4_d0_tst xtest_i8_r4_d0_tst: \
- FC2 := $(XFORT) $(FFLAGS2) -check all -fp-trap=divzero
+ FC2 := $(XFORT) $(FFLAGS2) -check all
xtest_i2_r8_d1_tst xtest_i4_r8_d1_tst xtest_i8_r8_d1_tst xtest_i2_r8_d0_tst xtest_i4_r8_d0_tst xtest_i8_r8_d0_tst: \
- FC1 := $(XFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC1 := $(XFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r8_d1_tst xtest_i4_r8_d1_tst xtest_i8_r8_d1_tst xtest_i2_r8_d0_tst xtest_i4_r8_d0_tst xtest_i8_r8_d0_tst: \
- FC2 := $(XFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC2 := $(XFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r16_d1_tst xtest_i4_r16_d1_tst xtest_i8_r16_d1_tst xtest_i2_r16_d0_tst xtest_i4_r16_d0_tst xtest_i8_r16_d0_tst: \
- FC1 := $(XFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC1 := $(XFORT) $(FFLAGS1) -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
xtest_i2_r16_d1_tst xtest_i4_r16_d1_tst xtest_i8_r16_d1_tst xtest_i2_r16_d0_tst xtest_i4_r16_d0_tst xtest_i8_r16_d0_tst: \
- FC2 := $(XFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags \
- -fp-trap=divzero,invalid,overflow#,underflow,denormal
- #-no-ftz -fp-model strict
+ FC2 := $(XFORT) $(FFLAGS2) -check all -ftrapuv -fpe0 -fpe-all=0 -assume ieee_fpe_flags
# G95
# The compilation of MEX gateways will fail if -std is imposed.
-9FORT := g95
+9FORT := $(shell command -v g95)
9FORT := $(9FORT) -pedantic -Wall -Wextra \
-Werror=100,113,115,137,146,147,159,163 \
-Wimplicit-none -Wline-truncation -Wprecision-loss -Wunused-module-vars -Wunused-vars -Wunset-vars \
@@ -491,10 +474,13 @@ $(TESTS) $(TESTS_INT):
$(eval RP := $(shell expr 8 \* $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//")))
$(eval QPAVLB := $(shell expr $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//") / 16))
$(eval EXTRA_FLAGS := -DPRIMA_DEBUGGING=$(DBG) -DPRIMA_INTEGER_KIND=$(IK) -DPRIMA_REAL_PRECISION=$(RP) -DPRIMA_QP_AVAILABLE=$(QPAVLB))
- @($(FC1) $(EXTRA_FLAGS) -c $(SRC) && $(FC2) $(EXTRA_FLAGS) -c $(SRC)) 2>&1 \
+ $(eval FC := $(shell cut -d' ' -f1 <<< "$(FC1)"))
+ @echo "Compiler: $(FC)"
+ @$(FC) --version 2>&1 | grep -v "\-\-version\|usage" || true # Print the compiler version, but do not fail if the compiler does not support `--version`
+ @echo ""
+ ($(FC1) $(EXTRA_FLAGS) -c $(SRC) && $(FC2) $(EXTRA_FLAGS) -c $(SRC)) 2>&1 \
| grep -v "^[[:blank:]]*$$" \
- | grep -v "Absoft ANSI 1610:" \
- | grep -v "Absoft Pro Fortran.*: 0 Errors, 0 Warnings, 0 Other messages," \
+ | grep -v "Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking" \
| grep -v "after the END INTERFACE keywords is only legal in Fortran 95 and beyond" \
| grep -v "NAG Fortran Compiler Release [0-9]*.* Build [0-9]*" \
| grep -v "Questionable: ./lincoa/geometry.f90, line [0-9]*: Variable RSTAT set but never referenced" \
@@ -503,12 +489,12 @@ $(TESTS) $(TESTS_INT):
| grep -v "NAG Fortran Compiler normal termination, [0-9]* info message" \
| grep -v "Warning: Change of value in conversion from .INTEGER(8). to .REAL(8). at (1) \[-Wconversion\]" \
| grep -v "Warning: Change of value in conversion from .REAL(16). to .REAL(8). at (1) \[-Wconversion\]" \
- | grep -v "Extension: fmxapi\.F90, line [0-9]*: Byte count on numeric data type" \
- | grep -v "Extension: cbfun\.F90, line [0-9]*: Byte count on numeric data type" \
- | grep -v "Extension: \./classical/fmxcl\.F90, line [0-9]*: Byte count on numeric data type" \
- | grep -v "Extension: \./classical/fmxcl\.F90, line [0-9]*: Line longer than 132 characters" \
- | grep -v "Extension: .*_mex\.F90, line [0-9]*: Byte count on numeric data type" \
- | grep -v "Extension: .*_mex\.F90, line [0-9]*: Line longer than 132 characters" \
+ | grep -v "Non-standard(Obsolete): fmxapi.F90, line 824: Byte count on numeric data type" \
+ | grep -v "cbfun\.F90, line [0-9]*: Byte count on numeric data type" \
+ | grep -v "\./classical/fmxcl\.F90, line [0-9]*: Byte count on numeric data type" \
+ | grep -v "\./classical/fmxcl\.F90, line [0-9]*: Line longer than 132 characters" \
+ | grep -v "\.F90, line [0-9]*: Byte count on numeric data type" \
+ | grep -v "\.F90, line [0-9]*: Line longer than 132 characters" \
| grep -v "detected at \*@[0-9]*" \
| grep -v "Info: fmxapi\.F90, line [0-9]*: Possibly discontiguous POUT passed to old style dummy" \
| grep -v "Info: No licences currently available for product" \
@@ -528,6 +514,7 @@ $(TESTS) $(TESTS_INT):
| grep -v "WARNING -- When --chk x is specified" \
| grep -v "Warning (165): Implicit interface 'calfun' called at (1)" \
| grep -v "Warning (165): Implicit interface 'calcfc' called at (1)" \
+ | grep -v "Warning (165): Implicit interface 'callback_fcn' called at (1)" \
| grep -v "Warning (102): MODULE PROCEDURE 'inv' USEd at (1) is not referenced" \
| grep -v "Warning (102): MODULE PROCEDURE 'calfun' USEd at (1) is not referenced" \
| grep -v "Warning (102): MODULE PROCEDURE 'calcfc' USEd at (1) is not referenced" \
@@ -535,9 +522,9 @@ $(TESTS) $(TESTS_INT):
| grep -v "Warning (102): MODULE PROCEDURE 'redrho' USEd at (1) is not referenced" \
| grep -v "common/linalg.f90:[0-9]*:.*pivot.*may be used uninitialized" \
| grep -v "common/linalg.f90:[0-9]*:.*rank.*may be used uninitialized" \
- | grep -v "common/preproc.f90:22:.*min_maxfun.*may be used uninitialized" \
- | grep -v "common/preproc.f90:22:.*unit_memo.*may be used uninitialized" \
- | grep -v "W-0173-PGI Fortran extension: nonstandard use of data type length specifier" \
+ | grep -v "common/preproc.f90:[0-9]*:.*min_maxfun.*may be used uninitialized" \
+ | grep -v "common/preproc.f90:[0-9]*:.*unit_memo.*may be used uninitialized" \
+ | grep -v "Fortran extension: nonstandard use of data type length specifier" \
| grep -v "[0-9]\s*inform,\s*[0-9]*\s*warnings,\s*[0-9]\s*severes,\s*[0-9]\s*fatal for" \
| grep -v "Warning: GNU Extension: Nonstandard type declaration INTEGER\*[1-9].*" \
| grep -v "Warning: GNU Extension: Nonstandard type declaration REAL\*[1-9].*" \
@@ -570,10 +557,10 @@ $(TESTS) $(TESTS_INT):
| grep -v "Warning: ‘\.fname_loc’ may be used uninitialized.*\[-Wmaybe-uninitialized\]" \
| grep -v "Warning: ‘\.position’ may be used uninitialized.*\[-Wmaybe-uninitialized\]" \
| grep -v "Warning: ‘\.sformat’ may be used uninitialized.*\[-Wmaybe-uninitialized\]" \
- | grep -v "msg\s*=.*fmxIsDoubleVector: An invalid shape type" \
- | grep -v "msg\s*=.*fmxCallMATLAB: MEX fails to call a MATLAB function" \
- | grep -v "wid\s*=.*LargeConversionError" \
- | grep -v "msg\s*=.*Large error occurs when converting " \
+ | grep -v "msg\s*=.*fmxIsDoubleVector: An invalid shape type" \
+ | grep -v "msg\s*=.*fmxCallMATLAB: MEX fails to call a MATLAB function" \
+ | grep -v "wid\s*=.*LargeConversionError" \
+ | grep -v "msg\s*=.*Large error occurs when converting " \
| grep -v ": warning #8236: Deferred character length in a data object or component declaration is an extension of Standard F95." \
| grep -v ": warning #6655: The f95 standard requires that the ALLOCATABLE attribute only be given to array objects." \
| grep -v "Fortran 95 does not allow this intrinsic procedure.\s*\[NEW_LINE\]" \
@@ -581,6 +568,8 @@ $(TESTS) $(TESTS_INT):
| grep -v "This intrinsic function in an initialization expression is not standard Fortran 95" \
| grep -v "Fortran 95 does not allow type specification in an allocate statement" \
| grep -v "ipo: warning #11021: unresolved __ehdr_start" \
+ | grep -v "warning: future releases of the clang compiler will prefer GCC installations containing libstdc" \
+ | grep -v "\[-Wgcc-install-dir-libstdcxx\]" \
| tee -a "$(LOG_DIR)/$@.log"
@bash $(CHCKTST) --error "$(LOG_DIR)/$@.log"
@bash $(CHCKTST) --warning "$(LOG_DIR)/$@.log"
@@ -598,7 +587,7 @@ source_%: $(SRC_DIRS)
@rm -rf ./$(COMMON)/$(CMN_DEBUGF90) ./$(COMMON)/$(CMN_FPRINTF90) # Remove the common version of debug.*90 and fprint.*90.
@cp $(MEXGATE_DIR)/$(MEX_DEBUGF90) $(MEXGATE_DIR)/$(MEX_FPRINTF90) ./$(COMMON) # Use the MEX version of debug.*90 and fprint.*90 instead of the common version.
@cp $(FINTRFH) ./ && chmod 777 ./fintrf.h 2>/dev/null || : # The MathWorks header file for Fortran MEX. chmod is necessary.
- @find ./ -type f \( \
+ @find ./ \( \
-name "*.mod" \
-o -name "*.o" \
-o -name "*.dbg" \
@@ -608,41 +597,21 @@ source_%: $(SRC_DIRS)
-o -name "*.stb" \
-o -name "*.out" \
-o -name "*__genmod.f90" \
+ -o -name "flint" \
+ -o -name "mlint" \
\) -exec rm {} \; # Cleaning up; important!!!
+ @printf "\nPreparing the source code for $@.\n"
@if echo $@ | grep -q "stest"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../sunsrc ./ ; \
+ bash $(TOOLS_DIR)/sunsrc ./ ; \
fi
@if echo $@ | grep -q "9test"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../9src ./ ; \
- fi
- @if echo $@ | grep -q "atest\|9test"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../a9src ./ ; \
+ bash $(TOOLS_DIR)/9src ./ ; \
fi
@if echo $@ | grep -q "stest\|9test"; then \
- printf "\nPreparing the source code for $@.\n" ; \
- bash ../s9src ./ ; \
+ bash $(TOOLS_DIR)/s9src ./ ; \
fi
@printf "Done.\n"
-# Adapt the header file for the test.
-# Zaikun 20230411: Instead of modifying the header file, we should use the -D option of the
-# preprocessors (cpp or fpp). See the definition of $(EXTRA_FLAGS) for details.
-#header_%: IK = $(shell expr 8 \* $$(echo $@ | sed "s/.*_i//" | sed "s/_.*//"))
-#header_%: RP = $(shell expr 8 \* $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//"))
-#header_%: DBG = $(shell echo $@ | sed "s/.*_d//" | sed "s/_.*//")
-#header_%: QPAVLB = $(shell expr $$(echo $@ | sed "s/.*_r//" | sed "s/_.*//") / 16)
-#header_%: source_%
-# @if [[ -z "$(FINTRFH)" ]] ; then printf "\nfintrf.h is not available. Skip the test.\n"; exit 0; fi
-# @printf "\nPreparing the header file for the test.\n"
-# $(SEDI) "0,/^#define PRIMA_QP_AVAILABLE [0-9]*/s//#define PRIMA_QP_AVAILABLE $(QPAVLB)/" $(HEADERS)
-# $(SEDI) "0,/^#define PRIMA_INTEGER_KIND [0-9]*/s//#define PRIMA_INTEGER_KIND $(IK)/" $(HEADERS)
-# $(SEDI) "0,/^#define PRIMA_REAL_PRECISION [0-9]*/s//#define PRIMA_REAL_PRECISION $(RP)/" $(HEADERS)
-# $(SEDI) "0,/^#define PRIMA_DEBUGGING [0-9]*/s//#define PRIMA_DEBUGGING $(DBG)/" $(HEADERS)
-# @printf "Done.\n"
-
####################################################################################################
# Cleaning up.
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.lincoa b/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
index 8f7d38f463..044720f947 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.lincoa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.newuoa b/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
index 21b997723f..590e94296e 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.newuoa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa b/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
index ea6d432fb6..76af8ad682 100644
--- a/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
+++ b/matlab/mex_gateways/tests/makefiles/Makefile.uobyqa
@@ -2,7 +2,6 @@
# as possible.
#
# The following compilers are tested:
-# a: Absoft af95
# d: AOCC flang
# f: LLVM flang
# g: GNU gfortran
@@ -12,7 +11,7 @@
# v: NVIDIA nvfortran
# x: Intel ifx
#
-# The following tests are available, where C = a, d, f, g, n, i, s, v, x, and N = 2, 4, 8:
+# The following tests are available, where C = d, f, g, n, i, s, v, x, and N = 2, 4, 8:
# Ctest: test with compiler C
# Ctest_c: test with compiler C; compilation only (do not run the binary)
# Ctest_iN: test with compiler C and integer kind INT(N*8)
diff --git a/matlab/mex_gateways/tests/sunsrc b/matlab/mex_gateways/tests/sunsrc
deleted file mode 120000
index e37f85ee82..0000000000
--- a/matlab/mex_gateways/tests/sunsrc
+++ /dev/null
@@ -1 +0,0 @@
-../../../fortran/tests/sunsrc
\ No newline at end of file
diff --git a/matlab/mex_gateways/tests/tools b/matlab/mex_gateways/tests/tools
new file mode 120000
index 0000000000..bda4b859e5
--- /dev/null
+++ b/matlab/mex_gateways/tests/tools
@@ -0,0 +1 @@
+../../../fortran/tests/tools/
\ No newline at end of file
diff --git a/matlab/mex_gateways/uobyqa_mex.F90 b/matlab/mex_gateways/uobyqa_mex.F90
index 26566c5901..2ee0bdba2e 100644
--- a/matlab/mex_gateways/uobyqa_mex.F90
+++ b/matlab/mex_gateways/uobyqa_mex.F90
@@ -2,16 +2,14 @@
! The MEX gateway for UOBYQA
!
! Authors:
-! Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-! and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-! Department of Applied Mathematics,
-! The Hong Kong Polytechnic University
+! Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+! and Zaikun ZHANG (www.zhangzk.net)
!
! Dedicated to the late Professor M. J. D. Powell FRS (1936--2015)
!
! Started in July 2020
!
-! Last Modified: Saturday, February 12, 2022 PM02:35:07
+! Last Modified: Tue 12 May 2026 04:49:36 PM CST
!--------------------------------------------------------------------------------------------------!
#include "fintrf.h"
@@ -107,14 +105,13 @@ subroutine mexFunction(nargout, poutput, nargin, pinput)
call fmxWriteMPtr(f, poutput(2))
call fmxWriteMPtr(info, poutput(3))
call fmxWriteMPtr(nf, poutput(4))
-call fmxWriteMPtr(xhist(:, 1:min(int(nf), size(xhist, 2))), poutput(5))
-call fmxWriteMPtr(fhist(1:min(int(nf), size(fhist))), poutput(6), 'row')
+call fmxWriteMPtr(xhist(:, 1:min(nf, int(size(xhist, 2), IK))), poutput(5))
+call fmxWriteMPtr(fhist(1:min(nf, int(size(fhist), IK))), poutput(6), 'row')
! N.B.:
-! 1. INT(NF) converts NF to the default integer type; if not, MIN may complain.
-! 2. It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
+! It can happen that 0 < SIZE(XHIST, 2) < MAXHIST or 0 < SIZE(FHIST) < MAXHIST due to the memory
! limit in the Fortran code.
-! Free memory. Indeed, automatic deallocation would take place.
+! Free memory. We prefer explicit deallocation to the automatic one.
deallocate (x) ! Allocated by fmxReadMPtr.
deallocate (xhist) ! Allocated by the solver
deallocate (fhist) ! Allocated by the solver
diff --git a/matlab/setup_tools/README.txt b/matlab/setup_tools/README.txt
index 9276533d5c..39d737935b 100644
--- a/matlab/setup_tools/README.txt
+++ b/matlab/setup_tools/README.txt
@@ -2,11 +2,10 @@ This directory contains some tools for setting up the MATLAB version of the pack
N.B.: This directory is only needed for the setup and NOT needed by the package at runtime.
-Authors:
- Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
- and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
- Department of Applied Mathematics,
- The Hong Kong Polytechnic University
+Author:
+ Zaikun ZHANG (www.zhangzk.net)
+ School of Mathematics,
+ Sun Yat-sen University, China.
Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
diff --git a/matlab/setup_tools/all_precisions_possible.m b/matlab/setup_tools/all_precisions_possible.m
index 7a04db833a..da1f746ead 100644
--- a/matlab/setup_tools/all_precisions_possible.m
+++ b/matlab/setup_tools/all_precisions_possible.m
@@ -3,7 +3,7 @@
% the Fortran solvers in this package; `default_precision`, if requested, is the name of the default
% precision.
-precision_list = {'double', 'single', 'quadruple'};
+precision_list = {'half', 'single', 'double', 'quadruple'};
default_precision = 'double';
diff --git a/matlab/setup_tools/clean_mex.m b/matlab/setup_tools/clean_mex.m
index 370db83745..8f85e1cc78 100644
--- a/matlab/setup_tools/clean_mex.m
+++ b/matlab/setup_tools/clean_mex.m
@@ -35,8 +35,8 @@ function clean_mex(directory, is_verbose)
% Note that `mex_files` contains full path, but we only need the `mexname`, without the path or
% extension. Thus we need to use `fileparts` to retrieve the `mexname` first. Otherwise, `clear`
% will do nothing (it does not raise a warning when requested to clear something nonexistent).
- [~, mexename] = fileparts(mex_files{imf});
- clear(mexename);
+ [~, mexname] = fileparts(mex_files{imf});
+ clear(mexname);
end
% Remove the compiled MEX files
diff --git a/matlab/setup_tools/compile.m b/matlab/setup_tools/compile.m
index a0f1e2f41d..d8f28912b5 100644
--- a/matlab/setup_tools/compile.m
+++ b/matlab/setup_tools/compile.m
@@ -27,6 +27,7 @@ function compile(solvers, mexdir, fortd, gateways, options)
% Bizarrely, if we write a short Fortran program to evaluate only COS(0.59843577329095299_DP),
% then the result is always 0.82621783366991364, regardless of -O or -g. No idea why.
+
% COMPILE starts
% Directories
@@ -59,34 +60,155 @@ function compile(solvers, mexdir, fortd, gateways, options)
verbose_option = '-silent';
end
+
% Modify the compiler options by revising FFLAGS or COMPFLAGS.
% See https://www.mathworks.com/help/matlab/ref/mex.html
-% We force the Fortran compiler to allocate arrays on the heap instead of the stack. Otherwise,
+% 1. We force the Fortran compiler to allocate arrays on the heap instead of the stack. Otherwise,
% the solvers will encounter stack overflow when the problem size is large. As of gfortran 12.0,
% `-fno-stack-arrays` is indeed the default, and we specify it for safety; as of Intel oneAPI
% 2023.1.0, `-no-heap-arrays` is the default, so we must specify `-heap-arrays`.
% N.B.: We assume that the function evaluation is much more expensive than the memory allocation,
% so the performance loss due to the heap allocation is negligible. This is true for derivative-free
% optimization, but may not be true for optimization with derivatives.
+% 2. We require the Fortran compiler to compile the solvers so that they can be called recursively.
+% Otherwise, the solvers will not work properly in recursive invocations. See
+% https://fortran-lang.discourse.group/t/frecursive-assume-recursion-and-recursion-thread-safety
compiler_configurations = mex.getCompilerConfigurations('fortran', 'selected');
-if contains(compiler_configurations.Manufacturer, 'gnu', 'IgnoreCase', true) % gfortran
- extra_compiler_options = '-fno-stack-arrays';
-elseif contains(compiler_configurations.Manufacturer, 'intel', 'IgnoreCase', true) % Intel compiler
+compiler_manufacturer = lower(compiler_configurations.Manufacturer);
+% Get the major version of the compiler. `sscanf(..., '%d')` stops at the first non-digit, so it
+% works for ‘14’, ‘14.1’, ‘14.1.2’, etc.
+compiler_major_version = sscanf(compiler_configurations.Version, '%d');
+if isempty(compiler_major_version)
+ compiler_major_version = NaN; % Failed to get the version number
+end
+
+% First, set `extra_compiler_options` to the extra compiler options needed by the Fortran compiler.
+extra_compiler_options = '';
+if contains(compiler_manufacturer, 'gnu') % gfortran
+ % 1. -Wno-missing-include-dirs is needed to suppress the warning about missing include directories
+ % when Simulink is not installed. Note the space before the new options.
+ % 2. Due to a bug of MinGW 8 on Windows, `-g` causes "internal compiler error: in based_loc_descr, at dwarf2out.c:14264"
+ if ispc && compiler_major_version ~= 8
+ extra_compiler_options = [extra_compiler_options, ' -g'];
+ end
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %extra_compiler_options = [extra_compiler_options, ' -Wno-missing-include-dirs -fno-stack-arrays -frecursive'];
+ extra_compiler_options = [extra_compiler_options, ' -Wno-missing-include-dirs -fno-stack-arrays -frecursive -fbacktrace'];
+ extra_compiler_options = [extra_compiler_options, ' -fno-stack-protector'];
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ % -ftrampoline-impl=heap instructs the compiler to put the trampolines on the heap instead of the
+ % stack. This option is available since gcc/gfortran 14. Without this option, executable stacks
+ % will be generated by the internal subroutines passed as actual arguments. This is related to
+ % an issue with MATLAB R2025a+ on Linux, which segfaults when handling Fortran MEX files with internal
+ % procedures. For details, see the `support_internal_procedures` part below and
+ % https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines/9919?u=zaikunzhang
+ % First, get the major version of GCC corresponding to the libgcc used by the Fortran MEX
+ % (it is $MATLABROOT/sys/os/glnxa64/libgcc_s.so.1 by default, but we will not rely on this path).
+ try
+ gcc_version = getMexLibgcc().latestGccVersion; % Latest gcc version string embedded in libgcc
+ catch exception
+ gcc_version = '';
+ % As of 20250902, getMexLibgcc supports only Linux; indeed, if MEX is configured according
+ % to the documentation of MathWorks, compiler_manufacturer contains 'gnu' only on Linux
+ % and Windows with MinGW, but getMexLibgcc uses ldd, which is not included in MinGW.
+ if verbose && isunix && ~ismac
+ warning('prima:FailToGetGccVersion', 'Fail to get the version of libgcc: %s', exception.message);
+ end
+ end
+ gcc_major_version = sscanf(gcc_version, '%d');
+ if isempty(gcc_major_version)
+ gcc_major_version = NaN; % Failed to get the version number
+ end
+ % Now add -ftrampoline-impl=heap if both the Fortran compiler and libgcc are of version 14+.
+ if compiler_major_version >= 14 && gcc_major_version >= 14
+ extra_compiler_options = [extra_compiler_options, ' -ftrampoline-impl=heap']; % Note the space
+ end
+elseif contains(compiler_manufacturer, 'intel') % Intel compiler
if ispc
- extra_compiler_options = '/heap-arrays';
+ extra_compiler_options = [extra_compiler_options, ' /Z7 /heap-arrays /assume:recursion'];
else
- extra_compiler_options = '-heap-arrays';
+ extra_compiler_options = [extra_compiler_options, ' -g -heap-arrays -assume recursion'];
end
+elseif contains(compiler_manufacturer, 'nag') % NAG compiler
+ extra_compiler_options = [extra_compiler_options, ' -g'];
else
warning('prima:UnrecognizedCompiler', 'Unrecognized compiler %s. The package may not work.', ...
compiler_configurations.Name);
- extra_compiler_options = '';
end
-if ispc % Windows
- compiler_options = ['COMPFLAGS="$COMPFLAGS ', extra_compiler_options, '"'];
-else
- compiler_options = ['FFLAGS="$FFLAGS ', extra_compiler_options, '"'];
+
+% Finally, set `compiler_options` so that `extra_compiler_options` is appended to the compiler flags
+% when MEX is called with `compiler_options`. See Append Compiler Options in
+% https://www.mathworks.com/help/matlab/ref/mex.html , particularly for what `flags_name` should be.
+% N.B.: on Windows with MinGW, the compiler is gfortran, and the option should be passed via
+% FCFLAGS rather than COMPFLAGS. This seems to be undocumented.
+if ispc && contains(compiler_manufacturer, 'intel') % on Windows with Microsoft Visual Studio compilers
+ flags_name = 'COMPFLAGS';
+elseif ispc && contains(compiler_manufacturer, 'gnu') % on Windows with MinGW
+ flags_name = 'FCFLAGS';
+else % with macOS, and Linux compilers
+ flags_name = 'FFLAGS';
+end
+compiler_options = append_flags(flags_name, extra_compiler_options);
+
+% Zaikun 20240216: The following is a workaround for https://github.com/libprima/prima/issues/161,
+% where MEX fails due to incompatibility between the new linker of Xcode 15 on macOS and Intel oneAPI 2023.
+% The fix is to replace the linker option "-undefined error" with "-undefined dynamic_lookup".
+% See also https://github.com/libprima/prima/issues/158.
+% Note that the name of the flag to modify depends on the platform. On macOS with Intel compiler, we
+% have to modify `LDFLAGSVER`; on Windows with MinGW, it is `FFLAGS`. Setting `LDFLAGS` or `LINKFLAGS`
+% does not work, although the latter is suggested at https://www.mathworks.com/help/matlab/ref/mex.html.
+linker_options = '';
+if ismac && contains(compiler_manufacturer, 'intel') % macOS with Intel compiler
+ linker_options = append_flags('LDFLAGSVER', '-undefined dynamic_lookup');
+end
+
+% MEX options shared by all compiling processes below.
+common_mex_options = {verbose_option, compiler_options, linker_options};
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Zaikun 20250720:
+% The following code is to circumvent a problem in MATLAB R2025a+, which segfaults on Linux when the
+% Fortran files contain internal procedures that are passed as actual arguments to other procedures,
+% provided that the Fortran compiler is gfortran and the option -ftrampoline-impl=heap is not used.
+% To avoid this, if gfortran is being used without -ftrampoline-impl=heap (effectively means we are
+% on Linux and either gfortran or libgcc is below 14; see the definition of extra_compiler_options),
+% we replace gateways/*_mex.F90 with gateways/R2025a/*_mex.F90,
+% and fortd/classical/cobyla/cobyla.f90 with gateways/R2025a/classical_cobyla.f90,
+% the latter of which use module variables instead of internal procedures. The price is that PRIMA
+% becomes thread-unsafe and recursion-unsafe. See MathWorks Technical Support Case 07931486 and
+% https://www.mathworks.com/matlabcentral/answers/2179869-why-does-my-fortran-mex-file-with-internal-subroutines-crash-on-linux-with-matlab-r2025a
+% https://www.mathworks.com/matlabcentral/answers/2178414-bug-matlab-2025a-segfaults-on-ubuntu-when-handling-fortran-mex-files-with-internal-subroutines
+% https://stackoverflow.com/questions/79699706/matlab-2025a-vs-fortran-mex-files-with-internal-subroutines
+% https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines
+% https://stackoverflow.com/questions/79705107/fortran-implementating-a-parametrized-objective-function-without-using-module-v
+% When both gfortran and libgcc being used are of version 14+ (the latter depends on MATLAB),
+% -ftrampoline-impl=heap will be included in extra_compiler_options, and the problem will disappear.
+matlab_supports_ip = isMATLABReleaseOlderThan('R2025a');
+compiler_supports_ip = (~contains(compiler_manufacturer, 'gnu') || contains(compiler_options, '-ftrampoline-impl=heap'));
+support_internal_procedures = (matlab_supports_ip || compiler_supports_ip);
+if ~support_internal_procedures
+ if verbose
+ warning('prima:ThreadRecursionUnsafe', ...
+ ['MATLAB R2025a and above encounters segmentation faults when handling Fortran MEX files with internal procedures.\n', ...
+ ' PRIMA is adapted to circumvent this but it becomes thread-unsafe and recursion-unsafe.']);
+ end
+ % Replace the files. N.B.: The .*90 files have become .* after the code refactoring in setup.m.
+ replacement_dir = fullfile(gateways, 'R2025a');
+ for isol = 1 : length(solvers)
+ solver = solvers{isol};
+ copyfile(fullfile(replacement_dir, [solver, '_mex.F']), fullfile(gateways, [solver, '_mex.F']));
+ if strcmp(solver, 'cobyla')
+ % For COBYLA, we also need to replace the classical cobyla.f.
+ copyfile(fullfile(replacement_dir, 'classical_cobyla.f'), fullfile(fortd, 'classical', 'cobyla', 'cobyla.f'));
+ end
+ end
end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
% Name of the file that contains the list of Fortran files. There should be such a file in each
% Fortran source code directory, and the list should indicate the dependence among the files.
@@ -106,7 +228,7 @@ function compile(solvers, mexdir, fortd, gateways, options)
delete(fullfile(common, 'fprint.f'));
copyfile(fullfile(gateways, 'fprint.F'), common);
% Replace "fprint.f" with "fprint.F" in `common_files`.
-common_files = replace(common_files, 'fprint.f', 'fprint.F'); % `replace` is available since R2016b.
+common_files = replace(common_files, 'fprint.f', 'fprint.F');
% common/ppf.h contains preprocessing directives. It is needed only when compiling the common files.
header_file = fullfile(common, 'ppf.h');
@@ -115,7 +237,7 @@ function compile(solvers, mexdir, fortd, gateways, options)
fprintf('Compiling the common files ... ');
for idbg = 1 : length(debug_flags)
- mex_options = {verbose_option, ['-', dbgstr(debug_flags{idbg})], compiler_options};
+ mex_options = [common_mex_options, {['-', dbgstr(debug_flags{idbg})]}];
for iprc = 1 : length(precisions)
prepare_header(header_file, precisions{iprc}, debug_flags{idbg});
work_dir = fullfile(common, pdstr(precisions{iprc}, debug_flags{idbg}));
@@ -155,7 +277,7 @@ function compile(solvers, mexdir, fortd, gateways, options)
% The support for the classical variant is limited. No debugging version.
continue
end
- mex_options = {verbose_option, ['-', dbgstr(debug_flags{idbg})], compiler_options};
+ mex_options = [common_mex_options, {['-', dbgstr(debug_flags{idbg})]}];
for iprc = 1 : length(precisions)
work_dir = fullfile(soldir, pdstr(precisions{iprc}, debug_flags{idbg}));
prepare_work_dir(work_dir);
@@ -180,6 +302,8 @@ function compile(solvers, mexdir, fortd, gateways, options)
else
evalc('mex(mex_options{:}, obj_files{:}, gateway, ''-output'', mexname, ''-outdir'', mexdir)'); % Suppress the output.
end
+ % On macOS, .o files are produced in `mexdir`. Remove them.
+ cellfun(@(filename) delete(filename), list_modo_files(mexdir));
end
end
end
@@ -218,17 +342,29 @@ function prepare_header(header_file, precision, debug_flag)
%PREPARE_HEADER prepares `header_file` for the compilation according to `precision` and `debug_flag`.
switch precision
+case {'h', 'half'}
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 32', '#define PRIMA_REAL_PRECISION 16');
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 64', '#define PRIMA_REAL_PRECISION 16');
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 128', '#define PRIMA_REAL_PRECISION 16');
+ rep_str(header_file, '#define PRIMA_HP_AVAILABLE 0', '#define PRIMA_HP_AVAILABLE 1');
+ rep_str(header_file, '#define PRIMA_QP_AVAILABLE 1', '#define PRIMA_QP_AVAILABLE 0');
case {'s', 'single'}
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 16', '#define PRIMA_REAL_PRECISION 32');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 64', '#define PRIMA_REAL_PRECISION 32');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 128', '#define PRIMA_REAL_PRECISION 32');
+ rep_str(header_file, '#define PRIMA_HP_AVAILABLE 1', '#define PRIMA_HP_AVAILABLE 0');
rep_str(header_file, '#define PRIMA_QP_AVAILABLE 1', '#define PRIMA_QP_AVAILABLE 0');
case {'q', 'quadruple'}
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 16', '#define PRIMA_REAL_PRECISION 128');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 32', '#define PRIMA_REAL_PRECISION 128');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 64', '#define PRIMA_REAL_PRECISION 128');
+ rep_str(header_file, '#define PRIMA_HP_AVAILABLE 1', '#define PRIMA_HP_AVAILABLE 0');
rep_str(header_file, '#define PRIMA_QP_AVAILABLE 0', '#define PRIMA_QP_AVAILABLE 1');
otherwise
+ rep_str(header_file, '#define PRIMA_REAL_PRECISION 16', '#define PRIMA_REAL_PRECISION 64');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 32', '#define PRIMA_REAL_PRECISION 64');
rep_str(header_file, '#define PRIMA_REAL_PRECISION 128', '#define PRIMA_REAL_PRECISION 64');
+ rep_str(header_file, '#define PRIMA_HP_AVAILABLE 1', '#define PRIMA_HP_AVAILABLE 0');
rep_str(header_file, '#define PRIMA_QP_AVAILABLE 1', '#define PRIMA_QP_AVAILABLE 0');
end
@@ -285,3 +421,12 @@ function prepare_work_dir(directory)
modo_files = [list_mod_files(dir_name), list_obj_files(dir_name)];
return
+
+
+function flags = append_flags(flags_name, extra_flags)
+%APPEND_FLAGS returns a string that can be passed to MEX to append `extra_flags` to the existing
+% flags specified by `flags_name`.
+
+flags = [flags_name, '="$', flags_name, ' ', extra_flags, '"'];
+
+return
diff --git a/matlab/setup_tools/create_all_precisions.m b/matlab/setup_tools/create_all_precisions.m
index c36195a7cd..828e193b1f 100644
--- a/matlab/setup_tools/create_all_precisions.m
+++ b/matlab/setup_tools/create_all_precisions.m
@@ -1,7 +1,7 @@
function create_all_precisions(options_or_directory)
%CREATE_ALL_PRECISIONS creates `all_precisions.m` under the directory containing this script
% according to `options_or_directory`. `all_precisions.m` should return a cell array containing
-% the names of all the precisions ('double', 'single', 'quadruple') available for the Fortran
+% the names of all the precisions ('half', 'single', 'double', 'quadruple') available for the Fortran
% solvers in this package. It is created in the following way.
%
% 0. We assume that 'double' is always available.
@@ -14,8 +14,9 @@ function create_all_precisions(options_or_directory)
% 2. If `options_or_directory` is a structure (or empty), then it will be interpreted as compilation
% options. The return of `all_precisions.m` will reflect the precisions available after the compilation.
-% Default values for the availability of 'single' and 'quadruple'. They are used only if
+% Default values for the availability of 'half', 'single' and 'quadruple'. They are used only if
% `options_or_directory` is a structure (i.e., it is indeed the compilation options).
+half_precision = false;
single_precision = true;
quadruple_precision = false;
@@ -45,12 +46,16 @@ function create_all_precisions(options_or_directory)
elseif ischarstr(options_or_directory)
directory = options_or_directory;
+ half_precision = isavailable(directory, 'half');
single_precision = isavailable(directory, 'single');
quadruple_precision = isavailable(directory, 'quadruple');
elseif isa(options_or_directory, 'struct')
options = options_or_directory;
+ if isfield(options, 'half') && islogicalscalar(options.half)
+ half_precision = options.half;
+ end
if isfield(options, 'single') && islogicalscalar(options.single)
single_precision = options.single;
end
@@ -64,6 +69,9 @@ function create_all_precisions(options_or_directory)
% Decide the precision list string.
precision_list_string = '''double''';
+ if half_precision
+ precision_list_string = [precision_list_string, ', ''half'''];
+ end
if single_precision
precision_list_string = [precision_list_string, ', ''single'''];
end
@@ -79,10 +87,10 @@ function create_all_precisions(options_or_directory)
end
fprintf(fid, 'function precision_list = %s()\n', allprec);
fprintf(fid, '%%%s ', upper(allprec));
- fprintf(fid, ' returns a cell array containing the names of all the precisions available for the\n');
+ fprintf(fid, 'returns a cell array containing the names of all the precisions available for the\n');
fprintf(fid, '%% Fortran solvers in this package.\n');
fprintf(fid, '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n');
- fprintf(fid, '%% This file is created automatically by \n%% %s.m at %s.\n', mfilename, datestr(datetime(), 'yyyymmdd.HH:MM:SS'));
+ fprintf(fid, '%% This file is created automatically\n%% by %s.m\n%% on %s.\n', mfilename, char(datetime()));
fprintf(fid, '%% NEVER EDIT IT, OR THE EARTH WILL EXPLODE.\n');
fprintf(fid, '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n');
fprintf(fid, '%s', precision_list_string);
diff --git a/matlab/setup_tools/create_all_variants.m b/matlab/setup_tools/create_all_variants.m
index 3a438382c3..cc2e66f2e0 100644
--- a/matlab/setup_tools/create_all_variants.m
+++ b/matlab/setup_tools/create_all_variants.m
@@ -81,7 +81,7 @@ function create_all_variants(options_or_directory)
fprintf(fid, ' returns a cell array containing the names of all the variants available for the\n');
fprintf(fid, '%% Fortran solvers in this package.\n');
fprintf(fid, '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n');
- fprintf(fid, '%% This file is created automatically by \n%% %s.m at %s.\n', mfilename, datestr(datetime(), 'yyyymmdd.HH:MM:SS'));
+ fprintf(fid, '%% This file is created automatically\n%% by %s.m\n%% on %s.\n', mfilename, char(datetime()));
fprintf(fid, '%% NEVER EDIT IT, OR THE EARTH WILL EXPLODE.\n');
fprintf(fid, '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n');
fprintf(fid, '%s', variant_list_string);
diff --git a/matlab/setup_tools/getMexLibgcc.m b/matlab/setup_tools/getMexLibgcc.m
new file mode 100644
index 0000000000..7b1544f98b
--- /dev/null
+++ b/matlab/setup_tools/getMexLibgcc.m
@@ -0,0 +1,102 @@
+function info = getMexLibgcc()
+%GETMEXLIBGCC Resolve libgcc_s used by the current configuration of Fortran MEX and its GCC version.
+% Output struct fields:
+% libgccPath: full path to the resolved libgcc_s.so.1; empty if not detectable
+% libgccStrings: strings embedded in libgcc_s.so.1 (a string separated by line breaks); empty if not detectable
+% latestGccVersion: latest GCC version string embedded in libgcc_s.so.1 (e.g., '14.0.0'); empty if not detectable
+%
+% Requirements: Linux, ldd, strings, grep, tail. No root needed.
+
+ % Get name of the current function
+ callstack = dbstack;
+ funName = callstack(1).name;
+
+ % Preconditions
+ assert(isunix && ~ismac, sprintf('%s: This function targets Linux.', funName));
+ assert(isCommandAvailable('ldd'), sprintf('%s: ldd command not found.', funName));
+ assert(isCommandAvailable('strings'), sprintf('%s: strings command not found.', funName));
+ assert(isCommandAvailable('grep'), sprintf('%s: grep command not found.', funName));
+ assert(isCommandAvailable('tail'), sprintf('%s: tail command not found.', funName));
+
+ % exampleFile is an example provided by MATLAB for trying MEX.
+ % mexName is the name of the MEX file without extension.
+ [exampleFile, mexName] = official_mex_example('Fortran');
+
+ % Build MEX into a temp directory
+ % NOTE: We do not try setting up MEX here, because we want to probe the current MEX setup.
+ outDir = tempname();
+ mkdir(outDir);
+ c = onCleanup(@() safeCleanup(outDir)); % Cleanup temp dir on function exit
+ try
+ clear(mexName);
+ evalc('mex(''-outdir'', outDir, ''-output'', mexName, exampleFile)');
+ catch exception
+ error('%s: Failed to build MEX from %s.\nThe error message is:\n\n%s\nMake sure that MEX is properly set up.', funName, exampleFile, exception.message);
+ end
+
+ % Find produced MEX
+ mexFile = fullfile(outDir, [mexName, '.', mexext()]);
+ if ~exist(mexFile, 'file')
+ error('%s: Could not find built MEX: %s', funName, mexFile);
+ end
+
+ % Resolve which libgcc_s this MEX will use
+ [st, lddOut] = system(sprintf('ldd "%s"', mexFile));
+ if st ~= 0
+ error('%s, ldd failed: %s', funName, lddOut);
+ end
+ libgccPath = parseLibPath(lddOut, 'libgcc_s.so.1');
+
+ libgccStrings = '';
+ latestGccVersion = '';
+ if ~isempty(libgccPath)
+ % Extract strings from libgcc
+ [~, libgccStrings] = system(sprintf('strings "%s"', libgccPath));
+
+ % Find the latest GCC version mentioned in the strings, supposing that the latest is mentioned in the last line
+ [~, latestGccString] = system(sprintf('strings "%s" | grep -E "^GCC_[0-9]+" | tail -n1', libgccPath));
+ latestGccString = strtrim(latestGccString);
+ % Try to parse a version like 14.0.0 from the latestGccString
+ tok = regexp(latestGccString, '([0-9]+(\.[0-9]+)?+(\.[0-9]+)?)', 'tokens', 'once');
+ if ~isempty(tok)
+ latestGccVersion = tok{1};
+ end
+ end
+
+ info = struct( ...
+ 'libgccPath', libgccPath, ...
+ 'libgccStrings', libgccStrings, ...
+ 'latestGccVersion', latestGccVersion);
+end
+
+
+function p = parseLibPath(lddOut, soname)
+ p = '';
+ lines = regexp(lddOut, '\r?\n', 'split');
+ for i = 1:numel(lines)
+ L = strtrim(lines{i});
+ if startsWith(L, soname)
+ % Format: libgcc_s.so.1 => /path/to/libgcc_s.so.1 (0x....)
+ tok = regexp(L, '=>\s+(\S+)\s+\(', 'tokens', 'once');
+ if ~isempty(tok)
+ p = tok{1};
+ end
+ break;
+ end
+ end
+end
+
+function safeCleanup(p)
+ if ~exist(p, 'dir')
+ return;
+ end
+ try
+ rmdir(p, 's');
+ catch
+ end
+end
+
+function isAvailable = isCommandAvailable(cmd)
+ [status, location] = system(['type ', cmd]);
+ isAvailable = (status == 0 && ~isempty(strtrim(location)));
+end
diff --git a/matlab/setup_tools/getfintrf.m b/matlab/setup_tools/getfintrf.m
index 4f11c7b76e..abfaa871ca 100644
--- a/matlab/setup_tools/getfintrf.m
+++ b/matlab/setup_tools/getfintrf.m
@@ -17,10 +17,10 @@
fileattrib(fintrf, '+w')
% Add time stamp to fintrf.
-time = datestr(datetime(), 'HH.MM.SS, yyyy-mm-dd');
+time = char(datetime());
matv = [version, ', ', computer];
S = fileread(fintrf);
-S = ['/* MATLAB version: ', matv, ' */', newline, '/* Retrieved at ', time, ' */', newline, S];
+S = ['/* MATLAB version: ', matv, ' */', newline, '/* Retrieved on ', time, ' */', newline, S];
fid = fopen(fintrf, 'w'); % Open/create file for writing. Discard existing contents.
if fid == -1
error('Cannot open file %s', fintrf);
diff --git a/matlab/setup_tools/interform.m b/matlab/setup_tools/interform.m
index 3bb8ed2123..4c273dfd46 100644
--- a/matlab/setup_tools/interform.m
+++ b/matlab/setup_tools/interform.m
@@ -11,7 +11,7 @@ function interform(directory)
outputdir = '.interform'; % The leading dot makes the directory hidden on Linux systems.
% Time stamp
-time_stamp = datestr(datetime(), 'yymmdd.HH:MM:SS');
+time_stamp = char(datetime());
% Do not perform refactoring in these subdirectories (if exist)
ignoredir = {'original', 'backup', '.interform', 'trash', 'example', 'examples', 'test', 'tests', 'results', 'test_data', 'bak', 'bakf90'};
@@ -139,7 +139,7 @@ function refactor_file(filename)
% the new file has the same name as the original one, then the original
% file will be backed up in "ORIGINAL_FILE_NAME.bak".
-time_stamp = datestr(datetime(), 'yymmdd.HH:MM:SS');
+time_stamp = char(datetime());
fid = fopen(filename, 'r'); % Open file for reading.
if fid == -1
@@ -204,7 +204,7 @@ function refactor_file(filename)
fprintf(fid, '!\n');
fprintf(fid, '! See http://fortranwiki.org/fortran/show/Continuation+lines for details.\n');
fprintf(fid, '!\n');
-fprintf(fid, '! Generated using the %s.m script by Zaikun ZHANG (www.zhangzk.net)\n! at %s.\n', mfilename, time_stamp);
+fprintf(fid, '! Generated using the %s.m script by Zaikun ZHANG (www.zhangzk.net)\n! on %s.\n', mfilename, time_stamp);
fprintf(fid, '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n');
for i = 1 : length(strs)
diff --git a/matlab/setup_tools/official_mex_example.m b/matlab/setup_tools/official_mex_example.m
new file mode 100644
index 0000000000..e7aa1ad519
--- /dev/null
+++ b/matlab/setup_tools/official_mex_example.m
@@ -0,0 +1,24 @@
+function [path, mexname] = official_mex_example(language)
+% This function returns the path to the official MEX example and the name of the MEX file without extension.
+% NOTE: MATLAB MAY CHANGE THE LOCATION OF THIS FILE IN THE FUTURE.
+
+callstack = dbstack;
+funname = callstack(1).name; % Name of the current function
+
+switch lower(language)
+case 'fortran'
+ example_file_name = 'timestwo.F';
+case {'c', 'c++', 'cpp'}
+ example_file_name = 'timestwo.c';
+otherwise
+ eid = sprintf('%s:UnsupportedLang', funname);
+ error(eid, '%s: Language ''%s'' is not supported by %s.', funname, language, funname);
+end
+path = fullfile(matlabroot, 'extern', 'examples', 'refbook', example_file_name);
+
+if ~exist(path, 'file')
+ eid = sprintf('%s:ExampleFileNotExist', funname);
+ error(eid, 'We cannot find\n%s,\nwhich is supposed to be a MATLAB built-in example for trying MEX on %s.', path, language);
+end
+
+[~, mexname] = fileparts(path); % File name without extension
diff --git a/matlab/setup_tools/package_info.m b/matlab/setup_tools/package_info.m
index deea16fa30..7ed09caae5 100644
--- a/matlab/setup_tools/package_info.m
+++ b/matlab/setup_tools/package_info.m
@@ -2,9 +2,9 @@
%PACKAGE_INFO returns information about the package.
%
% ***********************************************************************
-% Author: Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University
+% Author: Zaikun ZHANG (www.zhangzk.net)
+% School of Mathematics,
+% Sun Yat-sen University, China
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
@@ -21,12 +21,12 @@
invoker_list = {'prima', 'preprima', 'parse_input'};
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
-if (length(callstack) == 1 || ~ismember(callstack(2).name, invoker_list))
+if length(callstack) > 1 && ismember(callstack(2).name, invoker_list)
+ invoker = callstack(2).name; % Name of the function who calls this function
+else
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s should only be called by %s.', funname, funname, strjoin(invoker_list, ', '));
-else
- invoker = callstack(2).name; % Name of the function who calls this function
end
name = 'PRIMA';
@@ -35,7 +35,7 @@
author = 'Zaikun Zhang';
-email = 'zaikun.zhang@polyu.edu.hk';
+email = 'zaikunzhang@gmail.com';
url = 'www.libprima.net';
diff --git a/matlab/setup_tools/try_mex_setup.m b/matlab/setup_tools/try_mex_setup.m
index e45bc07f3d..ca7251c5b2 100644
--- a/matlab/setup_tools/try_mex_setup.m
+++ b/matlab/setup_tools/try_mex_setup.m
@@ -3,13 +3,13 @@
% At return,
% success = 1 means MEX is correctly set up.
% success = 0 means MEX setup fails.
-% success = -1 means MEX setup runs successfully, but either we cannot try MEX on the example
-% file because such a file is not found, or the MEX file of the example file works but the result
-% is incorrect.
+% success = -1 means MEX setup runs successfully, but we cannot try MEX on the example file
+% because such a file is not found, or the example is not `timestwo`, or the MEX file of the example
+% file works but the result is incorrect.
verbose = (nargin >=2 && verbose);
-% Return if MEX is already well configured. This is important, because MEX is usable if it was set
+% Return silently if MEX is already well configured. This is important, because MEX is usable if it was set
% up before, and because MEX setup may fail even if it succeeded before due to change of environment.
success = mex_well_configured(language); % verbose = false
if success == 1
@@ -17,7 +17,7 @@
end
orig_warning_state = warning;
-warning('off','all'); % We do not want to see warnings
+warning('off', 'all'); % We do not want to see warnings
% Try `mex('-setup', language)`
mex_setup = -1;
@@ -44,11 +44,28 @@
if strcmpi(language, 'fortran') && (ismac || ispc) && (~isempty(exception) || mex_setup ~= 0)
if ismac
oneapi_root = '/opt/intel/oneapi/';
- compiler_dir = [oneapi_root, 'compiler/latest/mac/'];
+ system_string = 'mac';
elseif ispc % Windows
oneapi_root = 'C:\Program Files (x86)\Intel\oneAPI\';
- compiler_dir = [oneapi_root, 'compiler\latest\windows\'];
+ system_string = 'windows';
end
+ % 1. Intel oneAPI does not support macOS any more starting from oneAPI 2024.
+ % 2. For oneAPI 2024 on Windows, the compiler directory is "compiler/latest", and the compiler
+ % (ifort or ifx) is located in "compiler/latest/bin/"; In previous versions, the paths were
+ % "compiler/latest/" and "compiler/latest//bin/intel64".
+ % The first version of MATLAB that supports oneAPI 2024 on Windows is R2024a. Earlier versions
+ % cannot locate the compiler due to the change of the directory structure, even if ONEAPI_ROOT
+ % is set correctly.
+ % 3. Starting from oneAPI 2025, ifort is removed and replaced by ifx. MATLAB R2024a/b looks for
+ % ifort during mex setup and hence would fail with oneAPI 2025, even though it actually uses
+ % ifx to compile Fortran code when the oneAPI version is 2024.
+ compiler_dir = fullfile(oneapi_root, 'compiler', 'latest', system_string);
+ if ~exist(compiler_dir, 'dir')
+ compiler_dir = fullfile(oneapi_root, 'compiler', 'latest');
+ end
+ % Zaikun 20250731: Note that the above two definitions of compiler_dir cannot be exchanged, as
+ % fullfile(oneapi_root, 'compiler', 'latest') is always a valid directory regardless of the
+ % version of Intel oneAPI.
% Set PATH.
compiler_bin = fullfile(compiler_dir, 'bin');
@@ -111,7 +128,10 @@
if ~isempty(exception) || mex_setup ~= 0
fprintf('\nYour MATLAB failed to run mex(''-setup'', ''%s'').', language);
- fprintf('\nTo see the detailed error message, execute the following command:\n');
+ if ~isempty(exception)
+ fprintf('\nThe error message is:\n\n%s\n', exception.message);
+ end
+ fprintf('\nFor more details, execute the following command:\n');
fprintf('\n mex(''-v'', ''-setup'', ''%s'')\n', language);
success = 0;
else
@@ -133,30 +153,22 @@
% At return,
% success = 1 means MEX compiles the example successfully and the resultant MEX file works well.
% success = 0 means MEX cannot compile the example or the resultant MEX file does not work.
-% success = -1 means either we cannot try MEX on the example file because such a file is not found,
-% or the MEX file of the example file works but the result is incorrect.
+% success = -1 means we cannot try MEX on the example file because such a file is not found,
+% or the example is not `timestwo`, or the MEX file of the example file works but the result is incorrect.
verbose = (nargin >=2 && verbose);
success = 1;
orig_warning_state = warning;
-warning('off','all'); % We do not want to see warnings
+warning('off', 'all'); % We do not want to see warnings
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
% Locate example_file, which is an example provided by MATLAB for trying MEX.
-% NOTE: MATLAB MAY CHANGE THE LOCATION OF THIS FILE IN THE FUTURE.
-switch lower(language)
-case 'fortran'
- example_file_name = 'timestwo.F';
-case {'c', 'c++', 'cpp'}
- example_file_name = 'timestwo.c';
-otherwise
- error(sprintf('%s:UnsupportedLang', funname), '%s: Language ''%s'' is not supported by %s.', funname, language, funname);
-end
-example_file = fullfile(matlabroot, 'extern', 'examples', 'refbook', example_file_name);
+% mexname is the name of the MEX file without extension.
+[example_file, mexname] = official_mex_example('Fortran');
% Check whether example_file exists
if ~exist(example_file, 'file')
@@ -164,7 +176,7 @@
fprintf('\n');
wid = sprintf('%s:ExampleFileNotExist', funname);
warning('on', wid);
- warning(wid, 'We cannot find\n%s,\nwhich is a MATLAB built-in example for trying MEX on %s. It will be ignored.\n', example_file, language);
+ warning(wid, 'We cannot find\n%s,\nwhich is supposed to be a MATLAB built-in example for trying MEX on %s. It will be ignored.\n', example_file, language);
end
success = -1;
return
@@ -175,51 +187,65 @@
% In general, we should clear a MEX function before compiling it. Otherwise, it may lead to a
% failure of even crash. See https://github.com/equipez/test_matlab/tree/master/crash
% Without the next line, `mex(example_file)` fails on Windows if we run this script two times.
-clear('timestwo');
+clear(mexname);
%!------------------------------------------------------------------------------------------------!%
-temp_mexdir = tempdir(); % The directory to output the MEX file of `timestwo`.
+temp_mexdir = tempdir(); % The directory to output the compiled MEX file
mex_status = -1;
exception = [];
try
- [~, mex_status] = evalc('mex(example_file, ''-outdir'', temp_mexdir)'); % Use evalc so that no output will be displayed
+ [~, mex_status] = evalc('mex(''-outdir'', temp_mexdir, ''-output'', mexname, example_file)'); % Use evalc so that no output will be displayed
catch exception
% Do nothing
end
if ~isempty(exception) || mex_status ~= 0
- delete(fullfile(temp_mexdir, 'timestwo.*')); % Remove the trash before returning
+ delete(fullfile(temp_mexdir, [mexname, '.*'])); % Remove the trash before returning
if verbose
- fprintf('\nThe MEX of your MATLAB failed to compile\n%s,\nwhich is a MATLAB built-in example for trying MEX on %s.\n', example_file, language);
- fprintf('\nTo see the detailed error message, execute the following command:\n');
- fprintf('\n mex(''-v'', fullfile(matlabroot, ''extern'', ''examples'', ''refbook'', ''%s''));\n', example_file_name);
+ fprintf('\nThe MEX of your MATLAB failed to compile\n%s,\nwhich is supposed to be a MATLAB built-in example for trying MEX on %s.\n', example_file, language);
+ if ~isempty(exception)
+ fprintf('\nThe error message is:\n\n%s\n', exception.message);
+ end
+ fprintf('\nFor more details, execute the following command:\n');
+ fprintf('\n mex(''-v'', ''%s'')\n', example_file);
end
success = 0;
return
end
% Check whether the mexified example_file works
-addpath(temp_mexdir); % Make `timestwo` available on path
+% N.B.: We assume that the MEX function is `timestwo`, which multiplies a given number by two.
+% If it is not, we raise a warning (even if verbose = false) and return with success = -1.
+if ~strcmpi(mexname, 'timestwo')
+ fprintf('\n');
+ wid = sprintf('%s:MexExampleIsNotTimestwo', funname);
+ warning('on', wid);
+ warning(wid, 'The MEX example is not timestwo but %s; we will not test whether it works correctly.\n', mexname);
+ success = -1;
+ return
+end
+
+addpath(temp_mexdir); % Make the MEX function available on path
exception = [];
try
- [~, timestwo_out] = evalc('timestwo(1)'); % Try whether timestwo works correctly
+ [~, mexfun_out] = evalc([mexname, '(1)']);
catch exception
% Do nothing
end
rmpath(temp_mexdir); % Clean up the path before returning.
-delete(fullfile(temp_mexdir, 'timestwo.*')); % Remove the trash before returning
+delete(fullfile(temp_mexdir, [mexname, '.*'])); % Remove the trash before returning
if ~isempty(exception)
if verbose
- fprintf('\nThe MEX of your MATLAB compiled\n%s,\nbut the resultant MEX file does not work.\n', example_file);
+ fprintf('\nThe MEX of your MATLAB compiled\n%s,\nbut the resultant MEX file does not work:\n%s\n', example_file, exception.message);
end
success = 0;
-elseif abs(timestwo_out - 2) >= 20*eps
+elseif abs(mexfun_out - 2) >= 20*eps
if verbose
fprintf('\n');
wid = sprintf('%s:ExampleFileWorksIncorrectly', funname);
warning('on', wid);
- warning(wid, 'The MEX of your MATLAB compiled\n%s,\nbut the resultant MEX file returns %.16f when calculating 2 times 1.', example_file, timestwo_out);
+ warning(wid, 'The MEX of your MATLAB compiled\n%s,\nbut the resultant MEX file returns %.16f when calculating 2 times 1.', example_file, mexfun_out);
end
success = -1;
end
diff --git a/matlab/setup_tools/uninstall_prima.m b/matlab/setup_tools/uninstall_prima.m
index 1fbcd5cad4..9cab1b9bb5 100644
--- a/matlab/setup_tools/uninstall_prima.m
+++ b/matlab/setup_tools/uninstall_prima.m
@@ -9,7 +9,6 @@ function uninstall_prima(path_string_stamp)
interfaces = fullfile(matd, 'interfaces'); % Directory of the interfaces
mexdir = fullfile(interfaces, 'private'); % The private subdirectory of the interfaces
tests = fullfile(matd, 'tests'); % Directory containing some tests
-tools = fullfile(matd, 'setup_tools'); % Directory containing some tools
% Remove the compiled MEX files.
clean_mex(mexdir);
@@ -18,7 +17,7 @@ function uninstall_prima(path_string_stamp)
orig_warning_state = warning;
warning('off', 'MATLAB:rmpath:DirNotFound'); % Maybe the paths were not added. We do not want to see this warning.
warning('off', 'MATLAB:SavePath:PathNotSaved'); % Maybe we do not have the permission to save path.
-rmpath(interfaces, tests, tools);
+rmpath(interfaces, tests);
savepath;
warning(orig_warning_state); % Restore the behavior of displaying warnings
@@ -26,7 +25,8 @@ function uninstall_prima(path_string_stamp)
user_startup = fullfile(userpath,'startup.m');
if exist(user_startup, 'file')
add_path_string = sprintf('addpath(''%s'');', interfaces);
- full_add_path_string = sprintf('%s\t%s %s', add_path_string, '%', path_string_stamp);
+ % full_add_path_string must match the string with the same name defined in the setup script.
+ full_add_path_string = sprintf('%s %s %s', add_path_string, '%', path_string_stamp);
try
del_str_ln(user_startup, full_add_path_string);
catch
diff --git a/matlab/tests/copy_crash_dump_files.m b/matlab/tests/copy_crash_dump_files.m
new file mode 100644
index 0000000000..ad06b66483
--- /dev/null
+++ b/matlab/tests/copy_crash_dump_files.m
@@ -0,0 +1,27 @@
+function cfiles = copy_crash_dump_files(directory, verbose)
+%This function copies the crash dump files to a specified directory.
+
+if nargin < 2
+ verbose = false; % Default to not displaying the content of the crash dump files
+end
+
+pc = parcluster();
+pc_dir = pc.JobStorageLocation;
+
+% Below are possible locations of the crash dump files. The last three are for Windows.
+files = dir(fullfile(pc_dir, '*', 'matlab_crash_dump*'));
+files = [files; dir(fullfile(homedir(), 'matlab_crash_dump*'))];
+files = [files; dir(fullfile(tempdir(), 'matlab_crash_dump*'))];
+files = [files; dir(fullfile(getenv('temp'), 'matlab_crash_dump*'))];
+files = [files; dir(fullfile('C:\Users\*\AppData\Local\Temp', 'matlab_crash_dump*'))];
+
+cfiles = cell(length(files), 1);
+for ifiles = 1 : length(files)
+ cfiles{ifiles} = fullfile(files(ifiles).folder, files(ifiles).name);
+ copyfile(cfiles{ifiles}, directory);
+ if verbose
+ type(cfiles{ifiles}); % Display the content of the crash dump file
+ end
+end
+
+return
diff --git a/matlab/tests/parallel.m b/matlab/tests/parallel.m
new file mode 100644
index 0000000000..95632617b0
--- /dev/null
+++ b/matlab/tests/parallel.m
@@ -0,0 +1,107 @@
+function parallel(solver, options)
+%PARALLEL verifies that the solvers can be called in parallel.
+
+% Turn off the warning about the debug mode.
+orig_warning_state = warning;
+warning('off', [solver, ':Debug']);
+
+if nargin < 2
+ options = struct();
+end
+
+% Set the random seed. We ALTER THE SEED WEEKLY to test the solvers as much as possible.
+if isfield(options, 'yw')
+ yw = options.yw;
+elseif isfield(options, 'seed')
+ yw = options.seed;
+else
+ yw = year_week('Asia/Shanghai');
+end
+fprintf('\nThe seed is\t\t%d\n\n', yw);
+% Define the random seed by yw
+random_seed = yw;
+orig_rng_state = rng(); % Save the current random number generator settings
+rng(random_seed);
+
+% Set the dimension of the problem
+if isfield(options, 'n')
+ n = options.n;
+else
+ if ismember(solver, {'uobyqa', 'cobyla'})
+ n = 20;
+ else
+ n = 50;
+ end
+end
+
+% Set the number of parallel runs
+if isfield(options, 'np')
+ np = options.np;
+else
+ np = 32;
+end
+
+% Set up the solver
+if ~isfield(options, 'compile') || options.compile
+ old_directory = pwd();
+ cd(fileparts(fileparts(fileparts(mfilename('fullpath')))));
+ compile_options = struct();
+ compile_options.verbose = false;
+ compile_options.debug = (rand() < 0.5);
+ compile_options.classical = false;
+ compile_options.single = false;
+ setup(solver, compile_options);
+ cd(old_directory);
+end
+solver_name = solver;
+solver = str2func(solver);
+
+% Conduct the test
+tic;
+fprintf('\n>>>>>> Parallel test for %s starts <<<<<<\n', solver_name);
+
+% Call the solver
+solver_options = struct();
+solver_options.iprint = 2;
+solver_options.debug = (rand() < 0.5);
+solver_options.rhoend = 1.0e-5;
+
+% We conduct two parallel tests, in case something does not finish correctly during the first run.
+for i = 1 : 2
+
+ %ticBytes(gcp)
+
+ parfor ip = 1:np
+ fprintf('\n>>>>>> Parallel test for %s, %d-th run <<<<<<\n', solver_name, i);
+ test(solver, n, solver_options, random_seed + i);
+ end
+
+ %tocBytes(gcp)
+
+end
+
+fprintf('\n>>>>>> Parallel test for %s ends <<<<<<\n', solver_name);
+toc;
+
+% Restore the random number generator state
+rng(orig_rng_state);
+% Restore the warning state
+warning(orig_warning_state);
+
+return
+
+
+function test(solver, n, solver_options, random_seed)
+rng(random_seed);
+shift = randn(n, 1);
+fun = @(x) chrosen(x + shift);
+x0 = randn(n, 1);
+[~, g0] = chrosen(x0 + shift);
+[x, fx, exitflag, output] = solver(fun, x0, solver_options)
+[~, gx] = chrosen(x + shift);
+
+whos
+
+norm(gx) / norm(g0)
+assert(norm(gx) < 1.0e-3 * norm(g0), 'X is close to stationary');
+return
diff --git a/matlab/tests/pdv.m b/matlab/tests/pdv.m
index a4cb01fafb..e4c588985b 100644
--- a/matlab/tests/pdv.m
+++ b/matlab/tests/pdv.m
@@ -1,5 +1,23 @@
-function pdv()
-% PDV tests the Fortran solvers for all precision, debugging flags, and variants.
+function pdv(options)
+% PDV tests the Fortran solvers for all Precision, Debugging flags, and Variants.
+
+if nargin < 1
+ options = struct();
+end
+
+% Set the random seed. We ALTER THE SEED WEEKLY to test the solvers as much as possible.
+if isfield(options, 'yw')
+ yw = options.yw;
+elseif isfield(options, 'seed')
+ yw = options.seed;
+else
+ yw = year_week('Asia/Shanghai');
+end
+fprintf('\nThe seed is\t\t%d\n\n', yw);
+% Define the random seed by yw
+random_seed = yw;
+orig_rng_state = rng(); % Save the current random number generator settings
+rng(random_seed);
oldpath = path(); % Record the current path.
restoredefaultpath; % Restore the "right out of the box" path of MATLAB
@@ -19,9 +37,9 @@ function pdv()
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
fake_solver_name = root_dir_name;
-options.competitor = root_dir_name;
-options.compile = true;
-test_dir = prepare_test_dir(fake_solver_name, funname, options);
+opt.competitor = root_dir_name;
+opt.compile = true;
+test_dir = prepare_test_dir(fake_solver_name, funname, opt);
exception = [];
@@ -35,53 +53,69 @@ function pdv()
clear('setup');
opt=struct();
opt.verbose = true;
- opt.single=true;
- opt.quadruple=true;
- opt.debug=true;
- opt.classical=true;
+ opt.half = half_precision_available();
+ opt.single = true;
+ opt.double = true;
+ opt.quadruple = true;
+ opt.debug = true;
+ %opt.classical = true;
+ opt.classical = false;
+
tic
+
setup(opt);
- testprima
- toc
solvers = {'cobyla', 'uobyqa', 'newuoa', 'bobyqa', 'lincoa'};
- precisions = {'double', 'single', 'quadruple'};
+ precisions = {'half', 'single', 'double', 'quadruple'};
+ precisions = precisions([opt.half, opt.single, opt.double, opt.quadruple]);
debug_flags = {true, false};
- variants = {'modern', 'classical'};
+ %variants = {'modern', 'classical'};
+ variants = {'modern'};
% Show current path information.
showpath(solvers);
% Test the solvers.
- fun = @sin;
- x0 = 1;
+ %fun = @sin; x0 = -1;
+ fun = @chrosen; x0 = [-1, -1];
+ x0 = x0 + 0.1*randn(size(x0));
for isol = 1 : length(solvers)
solver = str2func(solvers{isol});
solver
- options = struct();
+ opt = struct();
for iprc = 1 : length(precisions)
- options.precision = precisions{iprc};
+ opt.precision = precisions{iprc};
for idbg = 1 : length(debug_flags)
- options.debug = debug_flags{idbg};
+ opt.debug = debug_flags{idbg};
for ivar = 1 : length(variants)
- for iprint = -4 : 4
- options.classical = strcmp(variants{ivar}, 'classical');
- options.output_xhist = true;
- options.iprint = iprint;
- options
- format long
- [x, f, exitflag, output] = solver(fun, x0, options)
- if (ismember(solvers{isol}, matlab_implemented))
- options_mat = options;
- options_mat.fortran = false;
- [x, f, exitflag, output] = solver(fun, x0, options_mat)
- end
+ opt.classical = strcmp(variants{ivar}, 'classical');
+ if ismac && strcmp(func2str(solver), 'cobyla') && strcmp(opt.precision, 'half') && opt.classical
+ % Skip the classical cobyla in half precision on macOS, as it will encounter an infinite cycling.
+ continue;
+ end
+ if ismac && strcmp(func2str(solver), 'bobyqa') && strcmp(opt.precision, 'quadruple') && opt.classical
+ % Skip the classical bobyqa in quadruple precision on macOS, as it will encounter a segmentation fault.
+ continue;
+ end
+ opt.output_xhist = true;
+ opt.maxfun = 200*length(x0);
+ opt.rhoend = 1.0e-4;
+ opt.iprint = randi([-4, 4]);
+ format long
+ opt
+ [x, f, exitflag, output] = solver(fun, x0, opt)
+ if (ismember(solvers{isol}, matlab_implemented))
+ opt_mat = opt;
+ opt_mat.fortran = false;
+ [x, f, exitflag, output] = solver(fun, x0, opt_mat)
end
end
end
end
end
+ toc
+
% Show current path information again at the end of test.
showpath(solvers);
@@ -91,7 +125,9 @@ function pdv()
end
-setpath(oldpath); % Restore the path to oldpath.
+% Restore the random number generator state
+rng(orig_rng_state);
+path(oldpath); % Restore the path to oldpath.
cd(olddir); % Go back to olddir.
fprintf('\nCurrently in %s\n\n', pwd());
diff --git a/matlab/tests/private/chrosen.m b/matlab/tests/private/chrosen.m
new file mode 100644
index 0000000000..1f596e030e
--- /dev/null
+++ b/matlab/tests/private/chrosen.m
@@ -0,0 +1,25 @@
+function [f, g, H] = chrosen(x) % Chained Rosenbrock function
+
+alpha = 4.0;
+
+f = sum((x(1:end-1)-1).^2 + alpha*(x(2:end)-x(1:end-1).^2).^2); % Function value
+
+n = length(x);
+
+if nargout >= 2
+ g=zeros(n,1); % Gradient
+ for i=1:n-1
+ g(i) = g(i) + 2*(x(i)-1)+alpha*2*(x(i)^2-x(i+1))*2*x(i);
+ g(i+1) = g(i+1) - alpha*2*(x(i)^2-x(i+1));
+ end
+end
+
+if nargout >= 3
+ H=zeros(n,n); % Hessian
+ for i=1:n-1
+ H(i,i) = H(i,i)+2+alpha*2*2*(3*x(i)^2-x(i+1));
+ H(i,i+1) = H(i,i+1)-alpha*2*2*x(i);
+ H(i+1,i) = H(i+1,i) -alpha*2*2*x(i);
+ H(i+1,i+1)= H(i+1,i+1)+alpha*2;
+ end
+end
diff --git a/matlab/tests/private/compdf b/matlab/tests/private/compdf
index e7eae93ee8..392fe35429 100755
--- a/matlab/tests/private/compdf
+++ b/matlab/tests/private/compdf
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# Usage: compdf [LIST OF INPUT FILES].
# The purpose is to combine the input files into a pdf file.
# The output will be written to output-\`date +%s\`.pdf."
diff --git a/matlab/tests/private/dataprof.m b/matlab/tests/private/dataprof.m
index 1f54101397..c3e2500abc 100644
--- a/matlab/tests/private/dataprof.m
+++ b/matlab/tests/private/dataprof.m
@@ -1,4 +1,5 @@
-function T = dataprof(frec, fmin, pdim, options)
+%function T = dataprof(frec, fmin, pdim, options)
+function T = dataprof(frec, fmin)
% To be implemented.
delsame = 0;
@@ -44,7 +45,10 @@
T = mean(T, 3);
-sol = textread('solvers', '%s');
+fid = fopen('solvers','r');
+C = textscan(fid, '%s', 'Delimiter', '\n');
+fclose(fid);
+sol = C{1};
for is = 1:ns
sol{is} = regexprep(sol{is}, '_4test', '');
%sol{is} = regexprep(sol{is}, 'newuoa', 'NEWUOA');
@@ -116,7 +120,7 @@
xlabel('$\log_2(\alpha), \quad \alpha = \mathrm{NF}/\mathrm{NF}_{\min}$', 'fontsize', fontsize, 'interpreter', 'latex');
ylabel('$\pi_s(\alpha)$', 'fontsize', fontsize, 'interpreter', 'latex');
-xlabh = get(gca,'XLabel');
+%xlabh = get(gca,'XLabel');
%set(xlabh,'Position',get(xlabh,'Position') - [0 .0175 0])
set(gca,'FontSize',fontsize);
@@ -146,7 +150,7 @@
clf;
hfig=figure(2);
for is = 1:ns
- [xs, ys] = stairs([1:1.2*M]/(n+1), D(is, :));
+ [xs, ys] = stairs((1:1.2*M)/(n+1), D(is, :));
plot(xs, ys, lines{is}, 'Color', colors{is}, 'Linewidth',linewidth);
hold on;
end
@@ -164,7 +168,7 @@
xlabel('$\beta = \mathrm{NF}/(n+1)$', 'fontsize',fontsize, 'interpreter', 'latex');
ylabel('$\delta_s(\beta)$', 'fontsize', fontsize, 'interpreter', 'latex');
-xlabh = get(gca, 'XLabel');
+%xlabh = get(gca, 'XLabel');
%set(xlabh,'Position',get(xlabh,'Position') - [0 .0175 0])
set(gca, 'FontSize', fontsize);
diff --git a/matlab/tests/private/get_solvers.m b/matlab/tests/private/get_solvers.m
index 96970e0e72..fd9959da03 100644
--- a/matlab/tests/private/get_solvers.m
+++ b/matlab/tests/private/get_solvers.m
@@ -4,7 +4,7 @@
% Possible members of `solvers`:
% SOLVER, a member of {'cobyla', 'uobyqa', 'newuoa', 'bobyqa', 'lincoa'}.
% SOLVER_norma, the version SOLVER in the ".development/norma" directory.
-% SOLVER_classical|_single|_quadruple, the classical/single-precision/quadruple-precision version of SOLVER.
+% SOLVER_classical|_default|_half|_single|_quadruple, the classical/default/single-precision/quadruple-precision version of SOLVER.
% SOLVER_archiva, the version of SOLVER in the "norma" directory under `dev_arch`, which is equivalent
% to the latest archiva version of SOLVER.
@@ -48,45 +48,62 @@
for is = 1 : length(solvers)
mexopts{is} = struct();
if compile_flag
- % Do we compile the debugged version?
+ % Do we compile the debug version?
% Yes if we are in verification or if options.debug is true.
mexopts{is}.debug = isverify || (isfield(options, 'debug') && options.debug);
- % Do we compile ONLY the debugging version?
- % Yes if we are profiling and options.debug is true.
- %mexopts{is}.debug_only = isprofile && (isfield(options, 'debug') && options.debug);
-
% Do we compile the classical version?
% Yes if we are in verification (unless options.no_classical = true) or if the solver name
% ends with '_classical' or SOLVER_classical is requested.
- mexopts{is}.classical = (isverify && ~(isfield(options, 'no_classical') && options.no_classical)) ...
- || endsWith(solvers{is}, '_classical') || ismember([solvers{is}, '_classical'], solvers);
+ mexopts{is}.classical = (isverify && ~(isfield(options, 'no_classical') && options.no_classical)) || ...
+ endsWith(solvers{is}, '_classical') || ismember([solvers{is}, '_classical'], solvers);
+ % Do we compile ONLY the debugging version?
+ % Yes if we are profiling and options.debug is true but the classical version is not requested.
+ % Zaikun 20250805: Since the debugging version is not available for the classical solvers,
+ % we must set `mexopts{is}.debug_only` to false if mexopts{is}.classical is true.
+ mexopts{is}.debug_only = isprofile && (isfield(options, 'debug') && options.debug) && ~mexopts{is}.classical;
+
+ % Do we compile the half-precision version?
+ % Yes if we are in verification or if the solver name ends with '_half' or
+ % SOLVER_half is requested or options.precision='half'.
+ mexopts{is}.half = (isverify || endsWith(solvers{is}, '_half') || ismember([solvers{is}, '_half'], solvers)) || ...
+ (isfield(options, 'precision') && (isa(options.precision, 'char') || isa(options.precision, 'string')) && strcmpi(options.precision, 'half'));
+ mexopts{is}.half = mexopts{is}.half && half_precision_available();
% Do we compile the single-precision version?
- % Yes if we are in verification or if the solver name ends with '_single' or SOLVER_single is requested.
- mexopts{is}.single = (isverify || endsWith(solvers{is}, '_single') || ismember([solvers{is}, '_single'], solvers));
+ % Yes if we are in verification or if the solver name ends with '_single' or
+ % SOLVER_single is requested or options.precision='single'.
+ mexopts{is}.single = (isverify || endsWith(solvers{is}, '_single') || ismember([solvers{is}, '_single'], solvers)) || ...
+ (isfield(options, 'precision') && (isa(options.precision, 'char') || isa(options.precision, 'string')) && strcmpi(options.precision, 'single'));
% Do we compile the quadruple-precision version?
- % Yes if we are in verification or if the solver name ends with '_quadruple' or SOLVER_quadruple is requested.
- mexopts{is}.quadruple = (isverify || endsWith(solvers{is}, '_quadruple') || ismember([solvers{is}, '_quadruple'], solvers));
+ % Yes if we are in verification or if the solver name ends with '_quadruple' or
+ % SOLVER_quadruple is requested or options.precision is 'quadruple'.
+ mexopts{is}.quadruple = (isverify || endsWith(solvers{is}, '_quadruple') || ismember([solvers{is}, '_quadruple'], solvers)) || ...
+ (isfield(options, 'precision') && (isa(options.precision, 'char') || isa(options.precision, 'string')) && strcmpi(options.precision, 'quadruple'));
% Should we be verbose?
- mexopts{is}.verbose = (isfield(options, 'verbose') && options.verbose || with_compiler_options);
+ mexopts{is}.verbose = (isfield(options, 'verbose') && options.verbose || ...
+ (with_compiler_options && ~(isfield(options, 'verbose') && ~options.verbose && ~isprofile)));
end
end
% SOLVER_classical is obtained by preparing SOLVER. Thus we remove solvers ending with '_classical'.
-% The same for _single and _quadruple.
-[solvers, ind] = unique(regexprep(solvers, '(_classical|_single|_quadruple)', ''));
+% The same for _default, _half, _single, and _quadruple.
+[solvers, ind] = unique(regexprep(solvers, '(_classical|_default|_half|_single|_quadruple)', ''));
mexopts = mexopts(ind);
% Compile the solvers.
exception = [];
+sco_success = false;
try
if with_compiler_options
- set_compiler_options(options.compiler_options);
+ sco_success = set_compiler_options(options.compiler_options);
+ if ~sco_success
+ warning('Failed to set compiler options!!!');
+ end
end
for is = 1 : length(solvers)
@@ -101,7 +118,7 @@
% The archiva solver name is SOLVER_norma. See the comments on archiva_dir in
% prepare_test_dir.m for details.
solver = regexprep(solver, '_archiva', '_norma');
- else % SOLVER or SOLVER_classical|_single|_quadruple
+ else % SOLVER or SOLVER_classical|_default|_half|_single|_quadruple
solver_dir = fullfile(test_dir, root_dir_name);
end
@@ -112,7 +129,24 @@
cd(solver_dir);
fprintf('\nDirectory changed to: %s\n', pwd());
+
if compile_flag % Compilation requested.
+
+ % Revise the header file to use the intended integer kind.
+ header_file = fullfile(cd(), 'fortran', 'common', 'ppf.h');
+ integer_kind = 0;
+ if isfield(options, 'integer_kind')
+ integer_kind = options.integer_kind;
+ end
+ if integer_kind ~= 0
+ % Do not change the integer kind if it is 0!!! This enables us to test two solvers
+ % with different integer kinds by modifying their respective header files manually.
+ rep_str(header_file, '#define PRIMA_INTEGER_KIND 0', ['#define PRIMA_INTEGER_KIND ', num2str(integer_kind)]);
+ rep_str(header_file, '#define PRIMA_INTEGER_KIND 16', ['#define PRIMA_INTEGER_KIND ', num2str(integer_kind)]);
+ rep_str(header_file, '#define PRIMA_INTEGER_KIND 32', ['#define PRIMA_INTEGER_KIND ', num2str(integer_kind)]);
+ rep_str(header_file, '#define PRIMA_INTEGER_KIND 64', ['#define PRIMA_INTEGER_KIND ', num2str(integer_kind)]);
+ end
+
setup(solver, mexopts{is});
else % No compilation. Set up the path only.
setup('path');
@@ -125,14 +159,17 @@
end
% Restore the compiler options.
-if with_compiler_options
- restore_compiler_options();
+if with_compiler_options && sco_success
+ rco_success = restore_compiler_options();
+ if ~rco_success
+ warning('Failed to restore compiler options!!!');
+ end
end
% Go back to the old directory.
cd(olddir);
% If there is an exception, restore the path and rethrow the exception.
if ~isempty(exception)
- setpath(oldpath);
+ path(oldpath);
rethrow(exception);
end
diff --git a/matlab/tests/private/half_precision_available.m b/matlab/tests/private/half_precision_available.m
new file mode 100644
index 0000000000..ff23af2ae8
--- /dev/null
+++ b/matlab/tests/private/half_precision_available.m
@@ -0,0 +1,4 @@
+function hpa = half_precision_available()
+%HALF_PRECISION_SUPPORTED checks whether half precision is supported
+
+hpa = ismac_silicon();
diff --git a/matlab/tests/private/isequiv.m b/matlab/tests/private/isequiv.m
index 264428f226..0da302a7a1 100644
--- a/matlab/tests/private/isequiv.m
+++ b/matlab/tests/private/isequiv.m
@@ -30,39 +30,30 @@ function isequiv(solvers, options)
options = struct();
end
-if isfield(options, 'prec')
- prec = options.prec;
-else
- prec = 0;
-end
if isfield(options, 'nr')
nr = options.nr;
else
nr = 10;
end
-% ir is the index of the random experiment to be conducted. If it is negative, then experiments
-% 1, ..., nr, ..., nr + 20 will be conducted. nr + 20 is because there are fixed experiments
-% that will always be run.
+
+% ir is the index of the random experiment to be conducted. If it is not provided, then experiments
+% minir to maxir will be conducted. By default, minir = 1 and maxir = max(nr, 10). max(nr, 10) is
+% because there are fixed experiments that will always be run.
if isfield(options, 'ir')
ir = options.ir;
+ minir = ir;
+ maxir = ir;
else
- ir = -1;
-end
-
-if ir < 0
if isfield(options, 'minir')
minir = options.minir;
else
- minir = 0;
+ minir = 1;
end
if isfield(options, 'maxir')
maxir = options.maxir;
else
- maxir = nr + 20;
+ maxir = max([minir, nr, 10]);
end
-else
- minir = ir;
- maxir = ir;
end
if isfield(options, 'minip')
@@ -77,6 +68,12 @@ function isequiv(solvers, options)
maxip = 2^32 - 1;
end
+if isfield(options, 'verbose')
+ verbose = options.verbose;
+else
+ verbose = false;
+end
+
requirements = struct();
if isfield(options, 'list')
requirements.list = options.list; % Only test problems in this list
@@ -164,26 +161,35 @@ function isequiv(solvers, options)
for ip = minip : maxip
% Turn off unwanted warnings
- orig_warning_state = warnoff(solvers);
+ orig_warning_state = warnoff(solvers, verbose);
pname = upper(plist{ip});
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_start_dir, pname)]);
system(['touch ', fullfile(prob_start_time_dir, [pname, '.', strtrim(time)])]);
system(['touch ', fullfile(prob_start_runs_dir, pname)]);
- fprintf('\n%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ else
+ fprintf('%3d. \t%s\n', ip, pname);
+ end
prob = macup(pname);
for ir = minir : maxir
- % The following line compares the solvers on `prob`; ir is needed for the random seed, and
- % `prec` is the precision of the comparison (should be 0). The function will raise an error
- % if the solvers behave differently.
+ % The following line compares the solvers on `prob`; ir is needed for the random seed.
+ % The function will raise an error if the solvers behave differently.
system(['touch ', fullfile(prob_start_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
- compare(solvers, prob, ir, prec, single_test, options);
+ if verbose
+ fprintf(' \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ end
+
+ compare(solvers, prob, ir, single_test, options);
+
system(['touch ', fullfile(prob_end_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ end
end
decup(prob);
@@ -191,7 +197,9 @@ function isequiv(solvers, options)
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_end_dir, pname)]);
system(['touch ', fullfile(prob_end_time_dir, [pname, '.', strtrim(time)])]);
- fprintf('\n%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ end
% Restore the behavior of displaying warnings
warning(orig_warning_state);
@@ -200,25 +208,34 @@ function isequiv(solvers, options)
parfor ip = minip : maxip
% Turn off unwanted warnings
- orig_warning_state = warnoff(solvers);
+ orig_warning_state = warnoff(solvers, verbose);
pname = upper(plist{ip});
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_start_dir, pname)]);
system(['touch ', fullfile(prob_start_time_dir, [pname, '.', strtrim(time)])]);
- fprintf('\n%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ else
+ fprintf('%3d. \t%s\n', ip, pname);
+ end
prob = macup(pname);
for ir = minir : maxir
- % The following line compares the solvers on `prob`; ir is needed for the random seed, and
- % `prec` is the precision of the comparison (should be 0). The function will raise an error
- % if the solvers behave differently.
+ % The following line compares the solvers on `prob`; ir is needed for the random seed.
+ % The function will raise an error if the solvers behave differently.
system(['touch ', fullfile(prob_start_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
- compare(solvers, prob, ir, prec, single_test, options);
+ if verbose
+ fprintf(' \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ end
+
+ compare(solvers, prob, ir, single_test, options);
+
system(['touch ', fullfile(prob_end_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ end
end
decup(prob);
@@ -226,7 +243,9 @@ function isequiv(solvers, options)
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_end_dir, pname)]);
system(['touch ', fullfile(prob_end_time_dir, [pname, '.', strtrim(time)])]);
- fprintf('\n%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ end
% Restore the behavior of displaying warnings
warning(orig_warning_state);
@@ -239,43 +258,40 @@ function isequiv(solvers, options)
disp(['prob_end_dir = ', prob_end_dir]);
disp(['prob_end_time_dir = ', prob_end_time_dir]);
disp(['prob_end_runs_dir = ', prob_end_runs_dir]);
-
-fprintf('\n\nSucceed!\n\n'); % Declare success if we arrive here without an error.
+fprintf('\nThe seed is\t\t%d\n', options.yw);
+fprintf('\nSucceed!\n\n'); % Declare success if we arrive here without an error.
return
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-function equiv = compare(solvers, prob, ir, prec, single_test, options)
+function equiv = compare(solvers, prob, ir, single_test, options)
pname = prob.name;
objective = prob.objective;
-nonlcon = prob.nonlcon;
x0 = prob.x0;
n = length(x0);
-% Some randomization
-% Set seed using pname, n, and ir. We ALTER THE SEED weekly to test the solvers as much as possible.
-% N.B.: The weeknum function considers the week containing January 1 to be the first week of the
-% year, and increments the number every SUNDAY.
-if isfield(options, 'yw')
- yw = options.yw;
-elseif isfield(options, 'seed')
- yw = options.seed;
-else
- yw = year_week('Asia/Shanghai');
-end
-fprintf('\nYW = %d\n', yw);
+% Set seed using pname, n, and ir. We ALTER THE RANDOM SEED weekly to test the solvers as much as possible.
+yw = options.yw;
rseed = max(0, min(2^32 - 1, sum(pname) + n + ir + yw)); % A random seed defined by the current test and yw
orig_rng_state = rng(); % Save the current random number generator settings
rng(rseed); % Set the random seed for reproducibility
+
+% Some randomization
prob.x0 = x0 + 0.5*randn(size(x0));
test_options = struct();
-test_options.rhobeg = 1 + 0.5*(2*rand-1);
-test_options.rhoend = 1e-3*(1 + 0.5*(2*rand-1));
-test_options.npt = max(min(floor(6*rand*n), (n+2)*(n+1)/2), n+2);
-test_options.maxfun = max(ceil(20*n*(1+rand)), n+3); % For reproducibility, do not remove this even if `options` contains `maxfun`.
+test_options.rhobeg = randn;
+test_options.rhoend = 1e-3*randn;
+test_options.eta1 = randn;
+test_options.eta2 = randn;
+test_options.gamma1 = randn;
+test_options.gamma2 = randn;
+test_options.ctol = 1e-3*randn;
+test_options.cweight = 1e3*randn;
+test_options.npt = floor(10*randn*n);
+test_options.maxfun = floor(10*randn*n); % For reproducibility, do not remove this even if `options` contains `maxfun`.
if isfield(options, 'maxfun')
- test_options.maxfun = options.maxfun;
+ test_options.maxfun = options.maxfun; % Useful for debugging, when we may set `maxfun` to a small value.
end
test_options.ftarget = objective(x0) - 10*abs(randn)*max(1, objective(x0));
%test_options.fortran = (rand > 0.5);
@@ -283,7 +299,7 @@ function isequiv(solvers, options)
test_options.output_xhist = (rand > 0.5);
%test_options.output_xhist = 1;
test_options.output_nlchist = (rand > 0.5);
-test_options.maxhist = ceil(randn*1.5*test_options.maxfun);
+test_options.maxhist = floor((randn+8)*100*n);
%test_options.maxhist = test_options.maxfun;
if single_test
% DO NOT INVOKE ANY RANDOMIZATION WITHIN THIS IF. Otherwise, a single test cannot reproduce the
@@ -292,11 +308,11 @@ function isequiv(solvers, options)
test_options.output_xhist = true;
test_options.output_nlchist = true;
end
-test_options.maxfilt = ceil(randn*500);
-test_options.iprint = floor(3*rand);
+test_options.maxfilt = floor(randn*500);
+test_options.iprint = floor(0.5*abs(randn));
test_options.quiet = (rand < 0.9);
% Test all precisions. For unavailable precisions, the double-precision version will be called.
-if rand < 0.7 % Prob = 0.6
+if rand < 0.7 % Prob = 0.7
test_options.precision = 'double';
elseif rand < 0.9 % Prob = 0.27
test_options.precision = 'single';
@@ -327,55 +343,38 @@ function isequiv(solvers, options)
call_by_package = (rand < 0.5); % Call by the package instead of the solver
call_by_structure = (rand < 0.5); % Pass the problem by a structure
-if mod(ir, 50) == 0 && ~isempty(dir('*_output.txt'))
- delete('*_output.txt');
+
+% The TOUGH tests
+if rand < 0.5
+ prob = tough(prob, rseed); % We must pass the random seed `rseed` to `tough` to ensure reproducibility.
+ test_options.chkfunval = false; % The checking would fail due to noise.
end
-if ir == 1
+
+% Fixed tests that will always be run
+switch ir
+case 1
test_options.npt = (n+2)*(n+1)/2;
-end
-if ir == 2
+case 2
test_options.npt = n + 2;
-end
-if ir == 3
+case 3
+ test_options.npt = 2*n;
+case 4
+ test_options.npt = ceil(rand*n^2);
+case 5
test_options.maxfun = test_options.npt + 1;
-end
-if ir == 4
+case 6
test_options.maxfun = 1000*n;
-end
-if ir == 5
- test_options.maxfun = 1;
-end
-if ir == 6
- test_options.maxfun = ceil(n/2);
-end
-if ir == 7
+case 7
test_options.ftarget = inf;
-end
-if ir == 8
+case 8
test_options.rhoend = test_options.rhobeg;
end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-if ir == 9
- test_options.npt = 2*n;
-end
-if 10 <= ir && ir <= 12
- test_options.npt = ceil(rand*n^2);
-end
-if 13 <= ir && ir <= 15
- test_options.npt = floor(2*rand*n);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-if 1 <= ir && ir <= 20
- % The TOUGH tests
- % We must pass the random seed `rseed` to `tough` to ensure reproducibility.
- test_options.chkfunval = false; % The checking would fail due to noise.
- prob = tough(prob, rseed);
-else
- prob.objective = objective;
- prob.nonlcon = nonlcon;
-end
+
prob.options = test_options;
+if mod(ir, 5) == 0 && ~isempty(dir('*_output.txt'))
+ delete('*_output.txt');
+end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% BEGIN: Call the solvers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% N.B.: In some tests, we may invoke this function with solvers{1} == solvers{2}. So do NOT assume
@@ -543,12 +542,25 @@ function isequiv(solvers, options)
equiv = true;
return
else
+ fprintf('\n\n>>> !%s encounters an error during test %d of %s with the following data! <<<\n\n', regexprep(solvers{1}, '_norma', ''), ir, pname);
+ format long;
+ prob1
+ prob1.x0
+ prob1.lb
+ prob1.ub
+ prob1.Aineq
+ prob1.bineq
+ prob1.Aeq
+ prob1.beq
+ test_options
+ options
+ fprintf('\nThe seed is\t\t%d\n\n', yw);
rethrow(exception)
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END: Call the solvers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-equiv = iseq(x1(:), fx1, exitflag1, output1, x2(:), fx2, exitflag2, output2, prec);
+equiv = iseq(x1(:), fx1, exitflag1, output1, x2(:), fx2, exitflag2, output2);
if ~equiv
format long;
@@ -576,76 +588,88 @@ function isequiv(solvers, options)
chist2 = output2.chist(end-nhist+1:end);
chist1 == chist2
end
+ fprintf('\nThe solvers produce different results on %s at the %dth run with the following data.\n\n', pname, ir);
+ prob1
+ prob1.x0
+ prob1.lb
+ prob1.ub
+ prob1.Aineq
+ prob1.bineq
+ prob1.Aeq
+ prob1.beq
+ test_options
+ options
+ fprintf('\nThe seed is\t\t%d\n\n', yw);
if single_test && options.sequential
- %if options.sequential
- fprintf('\nThe solvers produce different results on %s at the %dth run.\n\n', pname, ir);
cd(options.olddir);
keyboard
end
- error('\nThe solvers produce different results on %s at the %dth run.\n', pname, ir);
+ error('PRIMA:VerificationFailure', 'Verification failed on %s at the %dth run with seed %d!', pname, ir, yw);
end
return
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-function eq = iseq(x, f, exitflag, output, xx, ff, ee, oo, prec)
+function eq = iseq(x, f, exitflag, output, xx, ff, ee, oo)
eq = true;
-if ~isempty(setdiff(fieldnames(output), [fieldnames(oo); 'fhist'; 'xhist'; 'chist'; 'nlcihist'; 'nlcehist'])) ...
- || ~isempty(setdiff(fieldnames(oo), [fieldnames(output); 'fhist'; 'xhist'; 'chist'; 'nlcihist', 'nlcehist']))
- eq = false;
+%if ~isempty(setdiff(fieldnames(output), [fieldnames(oo); 'fhist'; 'xhist'; 'chist'; 'nlcihist'; 'nlcehist'])) ...
+% || ~isempty(setdiff(fieldnames(oo), [fieldnames(output); 'fhist'; 'xhist'; 'chist'; 'nlcihist', 'nlcehist']))
+% eq = false;
+%end
+
+eq = eq && isempty(setxor(fieldnames(output), fieldnames(oo)));
+
+if ~isfield(output, 'xhist')
+ output.xhist = zeros(length(x), 0);
+end
+if ~isfield(oo, 'xhist')
+ oo.xhist = zeros(length(x), 0);
end
-if ~isfield(output,'constrviolation')
- output.constrviolation = 0;
+if ~isfield(output, 'fhist')
+ output.fhist = zeros(1, 0);
end
-if ~isfield(oo,'constrviolation')
- oo.constrviolation = 0;
+if ~isfield(oo, 'fhist')
+ oo.fhist = zeros(1, 0);
end
if ~isfield(output, 'chist')
- output.chist = zeros(output.funcCount, 1);
+ output.chist = zeros(1, 0);
end
if ~isfield(oo, 'chist')
- oo.chist = zeros(oo.funcCount, 1);
-end
-
-if (norm(xx-x)/(1+norm(x)) > prec || abs(ff-f)/(1+abs(f)) > prec ...
- || abs(oo.constrviolation-output.constrviolation)/(1+abs(output.constrviolation)) > prec)
- eq = false;
+ oo.chist = zeros(1, 0);
end
-if isfield(output, 'fhist')
- output.fhist = output.fhist(:);
-else
- output.fhist = [];
+if ~isfield(output,'constrviolation')
+ output.constrviolation = 0;
end
-if isfield(oo, 'fhist')
- oo.fhist = oo.fhist(:);
-else
- oo.fhist = [];
+if ~isfield(oo,'constrviolation')
+ oo.constrviolation = 0;
end
-nhist = min(length(output.fhist), length(oo.fhist));
-output.fhist = output.fhist(end - nhist + 1: end);
-oo.fhist = oo.fhist(end - nhist + 1: end);
-minfhist = min(length(output.fhist), length(oo.fhist));
-if norm(output.fhist(end-minfhist+1:end) - oo.fhist(end-minfhist+1:end))/(1+norm(output.fhist(end-minfhist+1:end))) > prec
- eq = false;
+if ~isfield(output, 'nlcihist')
+ output.nlcihist = zeros(length(x), 0);
end
-
-minchist = min(length(output.chist), length(oo.chist));
-if norm(output.chist(end-minchist+1:end) - oo.chist(end-minchist+1:end))/(1+norm(output.chist(end-minchist+1:end))) > prec
- eq = false;
+if ~isfield(oo, 'nlcihist')
+ oo.nlcihist = zeros(length(x), 0);
end
-if (prec == 0 && (exitflag ~= ee|| oo.funcCount ~= output.funcCount))
- eq = false;
+if ~isfield(output, 'nlcehist')
+ output.nlcehist = zeros(length(x), 0);
+end
+if ~isfield(oo, 'nlcehist')
+ oo.nlcehist = zeros(length(x), 0);
end
-%diff = max([abs(ff-f)/(1+abs(f)), norm(xx-x)/(1+norm(x)), ...
-% abs(oo.constrviolation-output.constrviolation)/(1+abs(output.constrviolation))]);
+eq = (eq && exitflag == ee && output.funcCount == oo.funcCount);
+eq = (eq && all(xx == x) && ff == f && (oo.constrviolation == output.constrviolation || (isnan(oo.constrviolation) && isnan(output.constrviolation))));
+eq = (eq && size(output.xhist, 2) == size(oo.xhist, 2) && all(output.xhist == oo.xhist, 'all'));
+eq = (eq && size(output.fhist, 2) == size(oo.fhist, 2) && all(output.fhist == oo.fhist | (isnan(output.fhist) & isnan(oo.fhist))));
+eq = (eq && size(output.chist, 2) == size(oo.chist, 2) && all(output.chist == oo.chist | (isnan(output.chist) & isnan(oo.chist))));
+eq = (eq && size(output.nlcehist, 2) == size(oo.nlcehist, 2) && all(output.nlcehist == oo.nlcehist, 'all'));
+eq = (eq && size(output.nlcihist, 2) == size(oo.nlcihist, 2) && all(output.nlcihist == oo.nlcihist, 'all'));
return
@@ -666,227 +690,124 @@ function isequiv(solvers, options)
case 'uobyqa'
blacklist = [blacklist, { ...
'BENNETT5LS', ...
+ 'CHNROSNB', ...
+ 'CHNRSNBM', ...
+ 'ERRINROS', ...
+ 'ERRINRSM', ...
'HATFLDFL', ...
'HIELOW', ...
- 'VARDIM', ...
+ 'TOINTPSP', ...
'YFITU', ...
}];
case 'newuoa'
blacklist = [blacklist, { ...
- 'ARGLINB', ...
- 'ARGLINC', ...
- 'ARGTRIGLS', ...
'BA-L1LS', ...
'BA-L1SPLS', ...
- 'BROWNAL', ...
- 'COATING', ...
+ 'CHNROSNB', ...
+ 'CHNRSNBM', ...
+ 'DIAMON2DLS', ...
'DIAMON3DLS', ...
- 'DMN37143LS', ...
- 'HYDC20LS', ...
- 'HYDC20LS', ...
- 'LUKSAN11LS', ...
- 'LUKSAN13LS', ...
- 'LUKSAN14LS', ...
- 'LUKSAN15LS', ...
- 'LUKSAN16LS', ...
- 'LUKSAN17LS', ...
- 'LUKSAN21LS', ...
- 'LUKSAN22LS', ...
- 'MANCINO', ...
- 'PENALTY2', ...
- 'PENALTY3', ...
- 'QING', ...
- 'VARDIM', ...
+ 'DMN15102LS', ...
+ 'DMN15103LS', ...
+ 'DMN15332LS', ...
+ 'DMN15333LS', ...
+ 'ERRINROS', ...
+ 'ERRINRSM', ...
+ 'HYDCAR6LS', ...
+ 'METHANB8LS', ...
+ 'METHANL8LS', ...
+ 'TOINTGOR', ...
+ 'TOINTPSP', ...
+ 'TOINTQOR', ...
+ 'VANDANMSLS', ...
}];
case 'bobyqa'
blacklist = [blacklist, { ...
- 'ARGLINA', ...
- 'ARGLINB', ...
- 'ARGLINC', ...
- 'ARGTRIGLS', ...
'BA-L1SPLS', ...
- 'BROWNAL', ...
- 'CHEBYQAD', ...
- 'COATING', ...
+ 'BQPGABIM', ...
+ 'BQPGASIM', ...
+ 'CHNROSNB', ...
+ 'CHNRSNBM', ...
'DIAMON3DLS', ...
'DMN15102LS', ...
- 'DMN37143LS', ...
- 'HOLMES', ...
- 'LRA9A', ...
+ 'DMN15103LS', ...
+ 'ERRINROS', ...
+ 'ERRINRSM', ...
+ 'HATFLDGLS', ...
+ 'HYDCAR6LS', ...
+ 'LRCOVTYPE', ...
'LUKSAN11LS', ...
'LUKSAN12LS', ...
- 'LUKSAN13LS', ...
- 'LUKSAN14LS', ...
'LUKSAN15LS', ...
- 'LUKSAN16LS', ...
- 'LUKSAN17LS', ...
- 'LUKSAN21LS', ...
- 'LUKSAN22LS', ...
- 'LUKSAN23LS', ...
- 'MANCINO', ...
- 'PENALTY2', ...
- 'PENALTY3', ...
- 'QING', ...
- 'VARDIM', ...
+ 'METHANB8LS', ...
+ 'METHANL8LS', ...
+ 'TOINTGOR', ...
+ 'TOINTPSP', ...
+ 'TOINTQOR', ...
+ 'VANDANMSLS', ...
}];
case 'lincoa'
%blacklist = [blacklist, {'LSNNODOC', 'HS55', 'HEART6', 'AVGASA', 'AVGASB', 'CHEBYQAD', 'HS54'}]; % Classical lincoa encounters SEGFAULT
blacklist = [blacklist, { ...
- 'ARGTRIGLS', ...
+ 'ANTWERP', ...
+ 'AVION2', ...
'BA-L1SPLS', ...
'BQP1VAR', ...
- 'BROWNAL', ...
+ 'BQPGABIM', ...
'CHEBYQAD', ...
- 'COATING', ...
- 'CVXQP1', ...
- 'DECONVU', ...
- 'DIAMON3DLS', ...
+ 'CHNROSNB', ...
+ 'CHNRSNBM', ...
+ 'CHWIRUT2LS', ...
+ 'DALLASS', ...
+ 'DMN15102LS', ...
'DMN15103LS', ...
- 'DMN15332LS', ...
+ 'DMN37142LS', ...
'DMN37143LS', ...
- 'DUAL1', ...
- 'DUAL2', ...
- 'DUAL3', ...
- 'HIMMELBI', ...
- 'HYDC20LS', ...
- 'LINSPANH', ...
+ 'ERRINROS', ...
+ 'ERRINRSM', ...
+ 'GOULDQP1', ...
+ 'HATFLDGLS', ...
+ 'HYDCAR6LS', ...
'LRCOVTYPE', ...
'LSQFIT', ...
'LUKSAN11LS', ...
- 'LUKSAN12LS', ...
- 'LUKSAN13LS', ...
- 'LUKSAN14LS', ...
- 'LUKSAN15LS', ...
- 'LUKSAN16LS', ...
- 'LUKSAN17LS', ...
- 'LUKSAN21LS', ...
- 'LUKSAN22LS', ...
- 'MANCINO', ...
- 'MINSURF', ...
- 'PENALTY3', ...
- 'QING', ...
- 'QPCBLEND', ...
- 'QPCBOEI2', ...
- 'QPNBLEND', ...
- 'QPNBOEI2', ...
+ 'METHANB8LS', ...
+ 'METHANL8LS', ...
'SIM2BQP', ...
- 'SPANHYD', ...
- 'VARDIM', ...
+ 'TOINTGOR', ...
+ 'TOINTPSP', ...
+ 'TOINTQOR', ...
+ 'VANDANMSLS', ...
+ 'WAYSEA2', ...
+ 'WEEDS', ...
+ 'ZANGWIL3', ...
}];
case 'cobyla'
%blacklist = [blacklist, {'LAUNCH', 'MINMAXRB', 'MAKELA1', 'HS75', 'GAUSS3','HATFLDG'}]; % Classical cobyla encounters SEGFAULT
- %blacklist = [blacklist, {'EXTRASIM', 'POLAK2', 'POLAK6', 'SPIRAL'}]; % Assertion failed: B = A^{-1}
- blacklist = [blacklist, {'HS80'}]; % QRADD_RDIAG: Assertion failed: C^T*Q(:, N) == Rdiag(N).
- blacklist = [blacklist, {'DEGENLPA'}]; % Classical cobyla encounters infinite cycling
+ %blacklist = [blacklist, {'DEGENLPA'}]; % Classical cobyla encounters infinite cycling
+ %blacklist = [blacklist, {'EXTRASIM', 'POLAK2', 'POLAK6', 'SPIRAL'}]; % Assertion fails: B = A^{-1}
+ blacklist = [blacklist, {'HS80'}]; % QRADD_RDIAG: Assertion fails: C^T*Q(:, N) == Rdiag(N).
blacklist = [blacklist, { ...
- 'ACOPP30', ...
- 'ACOPR14', ...
- 'ACOPR30', ...
- 'AIRPORT', ...
- 'ANTWERP', ...
- 'AVION2', ...
- 'BA-L1SPLS', ...
- 'BATCH', ...
- 'BQPGABIM', ...
- 'BQPGASIM', ...
- 'CHANDHEQ', ...
- 'CHEBYQAD', ...
- 'CHEBYQADNE', ...
'CHNROSNB', ...
- 'CHNRSBNE', ...
'CHNRSNBM', ...
'CHNRSNBMNE', ...
- 'CORE1', ...
- 'CRESC132', ...
- 'DALLASS', ...
- 'DECONVB', ...
- 'DECONVBNE', ...
- 'DECONVC', ...
- 'DECONVU', ...
'DEGENLPB', ...
- 'DEGENQPC', ...
- 'DIAMON2D', ...
- 'DIAMON3DLS', ...
- 'DISCS', ...
- 'DISC2', ...
- 'DMN15102', ...
- 'DMN15103', ...
- 'DMN15332', ...
- 'DMN15332LS', ...
- 'DMN15333', ...
- 'DMN37142', ...
- 'DMN37142LS', ...
- 'DMN37143', ...
- 'DNIEPER', ...
- 'DUAL1', ...
- 'DUAL2', ...
- 'DUAL4', ...
'DUALC5', ...
'ERRINROS', ...
- 'ERRINRSM', ...
- 'ERRINRSMNE', ...
- 'FBRAIN3', ...
- 'FEEDLOC', ...
- 'GROUPING', ...
- 'HAIFAM', ...
- 'HIMMELBI', ...
'HS55', ...
- 'HYDC20LS', ...
- 'HYDCAR20', ...
- 'KISSING2', ...
- 'LAKES', ...
- 'LINSPANH', ...
- 'LOADBAL', ...
- 'LRCOVTYPE', ...
- 'LUKSAN11', ...
- 'LUKSAN11LS', ...
- 'LUKSAN12', ...
- 'LUKSAN12LS', ...
- 'LUKSAN13', ...
- 'LUKSAN13LS', ...
- 'LUKSAN14', ...
- 'LUKSAN14LS', ...
- 'LUKSAN15', ...
- 'LUKSAN16', ...
- 'LUKSAN17', ...
- 'LUKSAN17LS', ...
- 'LUKSAN21', ...
- 'LUKSAN21LS', ...
- 'LUKSAN22', ...
- 'LUKSAN22LS', ...
- 'MANCINONE', ...
- 'MESH', ...
- 'METHANL8', ...
- 'METHANL8LS', ...
'MIFFLIN1', ...
- 'MSS1', ...
- 'NET1', ...
'PALMER1NE', ...
'PALMER4ANE', ...
'PALMER5BNE', ...
'PALMER7ANE', ...
'PALMER8ENE', ...
'POLAK2', ...
- 'PRODPL0', ...
- 'PRODPL1', ...
- 'QPCBLEND', ...
- 'SANTA', ...
'SIPOW3', ...
- 'SPANHYD', ...
- 'SWOPF', ...
- 'TAX13322', ...
- 'TAXR13322', ...
'TOINTGOR', ...
- 'TOINTQOR', ...
- 'TOINTPSP', ...
- 'TRO4X4', ...
+ 'TRO3X3', ...
'TRO6X2', ...
- 'VANDERM1', ...
- 'VANDERM2', ...
- 'VANDERM3', ...
- 'VANDERM4', ...
'VESUVIOU', ...
- 'WATER', ...
}];
end
+
return
diff --git a/matlab/tests/private/isintnum.m b/matlab/tests/private/isintnum.m
index 8913dd9051..aca3cfa863 100644
--- a/matlab/tests/private/isintnum.m
+++ b/matlab/tests/private/isintnum.m
@@ -1,2 +1,2 @@
function is_intnum = isintnum(x)
-is_intnum = isnumeric(x) && numel(x) == 1 && abs(x-round(x)) <= eps;
+is_intnum = isnumeric(x) && isscalar(x) && abs(x-round(x)) <= eps;
diff --git a/matlab/tests/private/ismac_silicon.m b/matlab/tests/private/ismac_silicon.m
new file mode 100644
index 0000000000..8a8c2017f3
--- /dev/null
+++ b/matlab/tests/private/ismac_silicon.m
@@ -0,0 +1,7 @@
+function isms = ismac_silicon()
+% ISMAC_SILICON checks whether the current machine is an Apple Silicon Mac.
+isms = false;
+if ismac
+ [~, result] = system('uname -v');
+ isms = contains(result, 'arm64', 'IgnoreCase', true);
+end
diff --git a/matlab/tests/private/iswritable.m b/matlab/tests/private/iswritable.m
new file mode 100644
index 0000000000..81bb0ba255
--- /dev/null
+++ b/matlab/tests/private/iswritable.m
@@ -0,0 +1,11 @@
+function iw = iswritable(fdname)
+%ISWRITABLE returns true or false to indicate whether the file or directory is writable.
+[status, attributes] = fileattrib(fdname);
+
+iw = false;
+if status == 1
+ iw = attributes.UserWrite;
+else
+ errid = 'IsWritable:FDNotExists';
+ error(errid, 'IsWritable:Error checking the attribute of %s. The file or directory does not exist', fdname);
+end
diff --git a/matlab/tests/private/locate_matcutest.m b/matlab/tests/private/locate_matcutest.m
index 22101322d1..36f4e76e63 100644
--- a/matlab/tests/private/locate_matcutest.m
+++ b/matlab/tests/private/locate_matcutest.m
@@ -1,8 +1,8 @@
function cmpaths = locate_matcutest(directory)
%This function finds where MatCUTEst (https://github.com/matcutest/matcutest) is installed, adds the
% paths needed for using MatCUTEst, and returns these paths in a cell array.
-% We search at most 3 levels below the given directory, whose default value is the home directory.
-% N.B.: As of 20230512, MatCUTEst supports only Linux.
+% We search at most 10 levels below the given directory, whose default value is the home directory.
+% N.B.: As of 202508, MatCUTEst supports only Linux.
% We use the following path as the signature to identify MatCUTEst.
signature_path = fullfile('matcutest', 'mtools', 'src');
@@ -23,14 +23,29 @@
if isempty(cmtools)
% In the following line, the "*/" before signature_path cannot be removed.
name_str = ['"*/', signature_path, '"'];
- [~, cmtools] = system(['find ', directory, ' -maxdepth 6 -wholename ', name_str, ' -type d -print -quit']);
+ % Use `find` to locate the directory containing the signature path. If multiple such directories
+ % exist, print a warning, list all of them, and use the first one. We only search at most 13
+ % levels below the given directory to avoid long searching time.
+ [status, cmtools] = system(['find ', directory, ' -maxdepth 13 -wholename ', name_str, ' -type d -print']);
- if isempty(cmtools)
+ if status ~= 0 || isempty(cmtools)
error('locate_matcutest:MatCUTEstNotFound', 'MatCUTEst is not found under %s.', directory);
end
-end
-cmtools = strtrim(cmtools); % Remove the leading and trailing white-space characters, including '\n'.
+ % Remove the leading and trailing white-space characters, including '\n'. Without doing this,
+ % the following `strsplit` will create an extra empty string at the end of the cell array.
+ cmtools = strtrim(cmtools);
+ cmtools = strsplit(cmtools, '\n');
+ if length(cmtools) > 1
+ warning('locate_matcutest:MultipleMatCUTEstFound', 'Multiple MatCUTEst installations are found under %s.', directory);
+ end
+ fprintf('\nThe MatCUTEst installations found under %s:\n\n', directory);
+ for i = 1 : length(cmtools)
+ fprintf('\t%s\n', cmtools{i});
+ end
+ cmtools = cmtools{1}; % Use the first one if multiple are found.
+ fprintf('\nThe MatCUTEst to be used:\n\n\t%s\n\n', cmtools);
+end
cmpaths = {cmtools}; % There may be other paths to include in the future.
diff --git a/matlab/tests/private/parse_input.m b/matlab/tests/private/parse_input.m
index f66ce8db05..ee70687290 100644
--- a/matlab/tests/private/parse_input.m
+++ b/matlab/tests/private/parse_input.m
@@ -12,17 +12,22 @@
% - `reverse_flag` (optional) is either 'reverse' or 'rev', which means to test the solvers in the reverse order
% - `problem_type` can be any of {'u', 'b', 'l', 'n', 'ub', 'ubl', 'ubln', 'bl', 'bln', 'ln'},
% indicating the problem type to test
-% - `competitor` (optional) can be any of {'classical', 'archiva', 'norma', 'single', 'quadruple'},
+% - `competitor` (optional) can be any of {'classical', 'archiva', 'norma', 'ultima', 'half', single', 'quadruple'},
% indicating the name of a competitor solver to test (only for profiling)
% - 'classical' means to test the classical solvers
+% - 'default' means to test the solvers with the default settings for npt, gamma1/2, eta1/2,
+% scaling, honour_x0
% - 'archiva' means to compare with the "archiva" version of the solver, located under the
% .development/archiva/dev_arch/ directory
% - 'norma' means to compare with the "norma" version of the solver, located under the
% .development/norma/ directory
+% - 'ultima' means to compare with the "ultima" (latest) version of the solver pushed to GitHub
+% - 'half' means to compare with the half precision version of the solver, namely the solver
+% invoked with precision = 'half'
% - 'single' means to compare with the single precision version of the solver, namely the solver
-% invoked with the 'single' flag set to true
+% invoked with precision = 'single'
% - 'quadruple' means to compare with the quadruple precision version of the solver, namely the solver
-% invoked with the 'quadruple' flag set to true
+% invoked with precision = 'quadruple'
% - `options` (optional) is a structure containing options to pass to `isequiv`, `perfdata`, etc.
%
% If the testing function is `verify`, then the following signatures are also supported:
@@ -59,7 +64,7 @@
reverse_flags = {'reverse', 'rev'};
reload_flags = {'reload', 'load'};
problem_types = {'u', 'b', 'l', 'n', 'ub', 'ubl', 'ubln', 'bl', 'bln', 'ln'};
-competitors = {'classical', 'archiva', 'norma', 'single', 'quadruple'};
+competitors = {'classical', 'default', 'archiva', 'norma', 'ultima', 'half', 'single', 'quadruple'};
% Default values.
solver = '';
@@ -132,15 +137,18 @@
end
end
+user_sets_dim = false; % Whether the user sets the dimension range explicitly.
if length(argin) == 2
if ischarstr(argin{1}) && isnumvec(argin{2}) && length(argin{2}) == 2
solver = argin{1};
mindim = min(argin{2});
maxdim = max(argin{2});
+ user_sets_dim = true;
elseif ischarstr(argin{2}) && isnumvec(argin{1}) && length(argin{1}) == 2
solver = argin{2};
mindim = min(argin{1});
maxdim = max(argin{1});
+ user_sets_dim = true;
elseif ischarstr(argin{1}) && ischarstr(argin{2})
argin = lower(argin);
if length(intersect(argin, known_solvers)) == 1
@@ -197,7 +205,7 @@
options.maxdim = maxdim;
% Revise the dimension range for COBYLA, UOBYQA, and PRIMA.
- if strcmpi(solver, 'cobyla') || strcmpi(solver, 'uobyqa') || strcmpi(solver, 'prima')
+ if ~user_sets_dim && (strcmpi(solver, 'cobyla') || strcmpi(solver, 'uobyqa') || strcmpi(solver, 'prima'))
if options.maxdim == 50
options.maxdim = 20;
end
@@ -218,7 +226,7 @@
options.maxcon = 100*options.maxdim;
% Revise the dimension range for verification to avoid overtime of GitHub Actions.
- if strcmpi(invoker, 'verify')
+ if ~user_sets_dim && strcmpi(invoker, 'verify')
if strcmpi(solver, 'cobyla')
options.maxdim = min(options.maxdim, 80); %!!!
elseif strcmpi(solver, 'newuoa')
diff --git a/matlab/tests/private/perfdata.m b/matlab/tests/private/perfdata.m
index 1432cd4554..ad9126d5e4 100644
--- a/matlab/tests/private/perfdata.m
+++ b/matlab/tests/private/perfdata.m
@@ -18,6 +18,8 @@
if ~exist(outdir, 'dir')
mkdir(outdir);
end
+options.outdir = outdir;
+disp(['outdir = ', outdir]);
% Check whether to reload data or calculate everything from scratch.
matfile = fullfile(data_dir, strcat(stamp, '.perfdata.', test_feature, '.mat'));
@@ -54,26 +56,24 @@
save(matfile, 'frec', 'fmin', 'pdim', 'plist', '-v7.3');
end
-% Record the tested problems in `problems.txt`.
-fprob = fullfile(outdir, strcat(stamp, '.', feature_and_time, '.', 'problems.txt'));
+% Record the tested problems in a text file.
+fprob = fullfile(outdir, strcat(stamp, '.', feature_and_time, '.', 'tested_problems.txt'));
fid = fopen(fprob, 'w');
-if fid >= 3
- for ip = 1 : length(plist)
- fprintf(fid, "%s\n", plist{ip});
- end
- fclose(fid);
-else
- error('\nFail to open file %s.\n', fprob);
-end
+assert(fid >= 3); % 0, 1, 2 are reserved for stdin, stdout, stderr.
+fprintf(fid, '%s\n', plist{:});
+fclose(fid);
% Plot the profiles.
ns = length(solvers);
for is = 1:ns
solvers{is} = regexprep(solvers{is}, '_4test', '');
solvers{is} = regexprep(solvers{is}, '_classical$', ' (classical)');
+ solvers{is} = regexprep(solvers{is}, '_default$', ' (default)');
+ solvers{is} = regexprep(solvers{is}, '_half$', ' (half)');
solvers{is} = regexprep(solvers{is}, '_single$', ' (single)');
solvers{is} = regexprep(solvers{is}, '_quadruple$', ' (quadruple)');
solvers{is} = regexprep(solvers{is}, '_archiva$', ' (archiva)');
+ solvers{is} = regexprep(solvers{is}, '_ultima$', ' (ultima)');
solvers{is} = regexprep(solvers{is}, '_norma$', ' (norma)');
%solvers{is} = regexprep(solvers{is}, 'newuoa', 'NEWUOA');
end
@@ -99,6 +99,12 @@
%dataprof(frec, fmin, pdim, prof_options);
end
+% Clear variables to release the memory associated with them. This is important if memory is
+% limited, e.g., when we run the tests on GitHub Actions with GitHub-hosted runners. Without doing
+% this, the hosted runners may shut down due to memory exhaustion, as it happened on 20250731 with
+% MATLAB R2025a.
+clear('frec', 'fmin', 'pdim', 'plist');
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Appearance of the plots.
%fontsize = 12;
@@ -152,6 +158,12 @@
set(gcf,'position',[0, 0, 4600, 920]);
saveas(hfig, epsname, 'epsc2');
+% Close the figure to release the memory associated with it. This is important if memory is limited,
+% e.g., when we run the tests on GitHub Actions with GitHub-hosted runners. Without doing this, the
+% hosted runners may shut down due to memory exhaustion, as it happened on 20250731 with MATLAB
+% R2025a.
+close(hfig);
+
% Try converting the eps to pdf.
try
system(['epstopdf ',epsname]);
diff --git a/matlab/tests/private/perfprof.m b/matlab/tests/private/perfprof.m
index e2693a00c2..afa2134cf5 100644
--- a/matlab/tests/private/perfprof.m
+++ b/matlab/tests/private/perfprof.m
@@ -24,6 +24,8 @@
lines = {'-', '-.', '--', ':', '-', '-.', '--', ':', '-', '-.'};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+assert(numel(frec) > 0, 'frec is empty.');
+
[np, ns, nr, maxfun] = size(frec);
% nf_return(ip, is, ir) is the number of function evaluations that the is-th solver uses when it
@@ -97,6 +99,11 @@
end
end
+% Clear variables to release the memory associated with them. This is important if memory is
+% limited, e.g., when we run the tests on GitHub Actions with GitHub-hosted runners. Without doing
+% this, the hosted runners may shut down due to memory exhaustion, as it happened on 20250731 with
+% MATLAB R2025a.
+clear('frec', 'fmin');
% pp{is, ir} is the performance profile of the is-th solver during the ir-th run.
pp = cell(ns, nr);
@@ -145,6 +152,12 @@
end
end
+% Clear variables to release the memory associated with them. This is important if memory is
+% limited, e.g., when we run the tests on GitHub Actions with GitHub-hosted runners. Without doing
+% this, the hosted runners may shut down due to memory exhaustion, as it happened on 20250731 with
+% MATLAB R2025a.
+clear('T');
+
% For each solver, we average the performance profiles across the random runs. The resultant profile
% for the is-th solver is recorded in perf_prof{is}.
perf_prof = cell(1, ns);
@@ -203,6 +216,7 @@
for is = 1:ns
solvers{is} = regexprep(solvers{is}, '_4test', '');
solvers{is} = regexprep(solvers{is}, '_classical$', ' (classical)');
+ solvers{is} = regexprep(solvers{is}, '_half$', ' (half)');
solvers{is} = regexprep(solvers{is}, '_single$', ' (single)');
solvers{is} = regexprep(solvers{is}, '_quadruple$', ' (quadruple)');
%solvers{is} = regexprep(solvers{is}, 'newuoa', 'NEWUOA');
@@ -230,6 +244,12 @@
epsname = fullfile(options.outdir, strcat(figname,'.eps'));
saveas(hfig, epsname, 'epsc2');
+% Close the figure to release the memory associated with it. This is important if memory is limited,
+% e.g., when we run the tests on GitHub Actions with GitHub-hosted runners. Without doing this, the
+% hosted runners may shut down due to memory exhaustion, as it happened on 20250731 with MATLAB
+% R2025a.
+close(hfig);
+
% Try converting the eps to pdf.
try
system(['epstopdf ',epsname]);
diff --git a/matlab/tests/private/prepare_test_dir.m b/matlab/tests/private/prepare_test_dir.m
index 4dd53ca5ce..93bfa170ff 100644
--- a/matlab/tests/private/prepare_test_dir.m
+++ b/matlab/tests/private/prepare_test_dir.m
@@ -2,7 +2,7 @@
%PREPARE_TEST_DIR prepares a copy of the package in `test_dir` for the solver named `solver and test
% named `test_name`, with options in `test_options`, which specifies the competitor etc.
-if verLessThan('matlab', '9.10') && ispc
+if isMATLABReleaseOlderThan('R2020b') && ispc
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
warning('%s may FAIL because ''copyfile'' of MATLAB R2020b or earlier may raise an error when handling unix symbolic links under Windows.', funname);
@@ -12,7 +12,7 @@
fprintf('\nThe solver: %s\n', solver);
% competitor: the competitor solver to be tested against. It can be 'norma', 'archiva', 'classical',
-% 'single', or 'quadruple'.
+% 'default', 'single', or 'quadruple'.
competitor = '';
if isfield(test_options, 'competitor')
competitor = test_options.competitor;
@@ -62,7 +62,7 @@
% fullfile(root_dir, '.development', 'archiva', 'dev_arch'), and an equivalent version is in
% fullfile(root_dir, '.development', 'archiva', 'dev_arch', 'norma'), which is the "norma" version
% of the "archiva" version. We use the latter because the name of the solver there is SOLVER_norma,
-% which is convenient for the test. Consequently, when we compiler the archiva version, the solver
+% which is convenient for the test. Consequently, when we compile the archiva version, the solver
% name is SOLVER_norma. See get_solvers.m for details.
archiva_dir = fullfile(root_dir, '.development', 'archiva', 'dev_arch', 'norma');
diff --git a/matlab/tests/private/rep_str.m b/matlab/tests/private/rep_str.m
new file mode 120000
index 0000000000..984ff28ee0
--- /dev/null
+++ b/matlab/tests/private/rep_str.m
@@ -0,0 +1 @@
+../../setup_tools/rep_str.m
\ No newline at end of file
diff --git a/matlab/tests/private/restore_compiler_options.m b/matlab/tests/private/restore_compiler_options.m
index ae502a949e..b5657700a8 100644
--- a/matlab/tests/private/restore_compiler_options.m
+++ b/matlab/tests/private/restore_compiler_options.m
@@ -1,11 +1,24 @@
-function restore_compiler_options()
+function success = restore_compiler_options()
+%Restore the compiler options by restoring the mexopts files. It is hacky!!!
if ~isunix || ismac
error('Configuration of compiler options supports only Linux.')
end
+success = false;
+
config_dir = fullfile(matlabroot,'bin', 'glnxa64', 'mexopts');
config_files = {dir(fullfile(config_dir, 'gfortran*.xml')).name};
+
+try
+ fileattrib(config_dir, '+w');
+catch
+end
+if ~iswritable(config_dir)
+ warning('The directory %s is not writable. Compiler options not restored.', config_dir);
+ return;
+end
+
for ifile = 1 : length(config_files)
cfile = fullfile(config_dir, config_files{ifile});
cfile_orig = fullfile(config_dir, [config_files{ifile}, '.orig']);
@@ -36,11 +49,27 @@ function restore_compiler_options()
% was deleted.
mex_setup_file = fullfile(prefdir, ['mex_FORTRAN_', computer('arch'), '.xml']);
if exist(mex_setup_file, 'file')
- fileattrib(prefdir, '+w');
- fileattrib(mex_setup_file, '+w');
+
+ try
+ fileattrib(prefdir, '+w');
+ catch
+ end
+ if ~iswritable(prefdir)
+ warning('The directory %s is not writable. The restored compiler options may not take effect.', prefdir);
+ end
+
+ try
+ fileattrib(mex_setup_file, '+w');
+ catch
+ end
+ if ~iswritable(mex_setup_file)
+ warning('The file %s is not writable. The restored compiler options may not take effect.', mex_setup_file);
+ end
+
delete(mex_setup_file);
end
fprintf('\nCompiler options restored successfully.\n\n');
+success = true;
return
diff --git a/matlab/tests/private/set_compiler_options.m b/matlab/tests/private/set_compiler_options.m
index eb3710c6c3..330c3e5f13 100644
--- a/matlab/tests/private/set_compiler_options.m
+++ b/matlab/tests/private/set_compiler_options.m
@@ -1,21 +1,38 @@
-function set_compiler_options(compiler_options)
+function success = set_compiler_options(compiler_options)
%Configure the compiler options by editing the mexopts files. It is hacky!!!
if ~isunix || ismac
error('Configuration of compiler options supports only Linux.')
end
+success = false;
+
mfilepath = fileparts(mfilename('fullpath')); % The directory containing this setup script
-setup_tools = fullfile(fileparts(fileparts(mfilepath)), 'setup_tools');
-addpath(setup_tools); % We use `rep_str` from `setup_tools`.
% Modify mexopts files in `config_dir` after making backups.
config_dir = fullfile(matlabroot, 'bin', 'glnxa64', 'mexopts');
config_files = {dir(fullfile(config_dir, 'gfortran*.xml')).name};
-fileattrib(config_dir, '+w');
+
+try
+ fileattrib(config_dir, '+w');
+catch
+end
+if ~iswritable(config_dir)
+ warning('The directory %s is not writable. Compiler options not set.', config_dir);
+ return;
+end
+
for ifile = 1 : length(config_files)
cfile = fullfile(config_dir, config_files{ifile});
- fileattrib(cfile, '+w')
+
+ try
+ fileattrib(cfile, '+w')
+ catch
+ end
+ if ~iswritable(cfile)
+ warning('The file %s is not writable. Compiler options not set.', cfile);
+ return;
+ end
cfile_orig = fullfile(config_dir, [config_files{ifile}, '.orig']);
if ~exist(cfile_orig, 'file')
@@ -39,21 +56,35 @@ function set_compiler_options(compiler_options)
rep_str(cfile, 'LDDEBUGFLAGS="-g"', ['LDDEBUGFLAGS="', compiler_options, '"']);
end
-rmpath(setup_tools); % Remove `setup_tools` from path since it has finishes its job.
-
% If MEX has been set up before, then the configuration is already written in the following file.
% We delete it so that MEX will be reconfigured according to `config_files`.
% Why not making a backup for it? Because the existing version may be generated using the modified
% `config_files` when this script was called the last time (N.B.: `mex_setup_file` does not exist
-% in a fresh installation of MATLAB), in which case it would be wrong to store % this copy and
+% in a fresh installation of MATLAB), in which case it would be wrong to store this copy and
% restore `mex_setup_file` using it.
mex_setup_file = fullfile(prefdir, ['mex_FORTRAN_', computer('arch'), '.xml']);
if exist(mex_setup_file, 'file')
- fileattrib(prefdir, '+w');
- fileattrib(mex_setup_file, '+w');
+
+ try
+ fileattrib(prefdir, '+w');
+ catch
+ end
+ if ~iswritable(prefdir)
+ warning('The directory %s is not writable. The modified compiler options may not take effect.', prefdir);
+ end
+
+ try
+ fileattrib(mex_setup_file, '+w');
+ catch
+ end
+ if ~iswritable(mex_setup_file)
+ warning('The file %s is not writable. The modified compiler options may not take effect.', mex_setup_file);
+ end
+
delete(mex_setup_file);
end
fprintf('\nCompiler options set to \n\n%s\n\n', compiler_options);
+success = true;
return
diff --git a/matlab/tests/private/setpath.m b/matlab/tests/private/setpath.m
deleted file mode 100644
index 38751be4a5..0000000000
--- a/matlab/tests/private/setpath.m
+++ /dev/null
@@ -1,14 +0,0 @@
-function setpath(path_string)
-
-path(path_string);
-orig_warning_state = warning;
-warning('off', 'MATLAB:SavePath:PathNotSaved'); % Maybe we do not have the permission to save path.
-
-try
- sys_pathdef = fullfile(matlabroot(), 'toolbox', 'local', 'pathdef.m');
- savepath(sys_pathdef);
-catch
- % Do nothing.
-end
-
-warning(orig_warning_state); % Restore the behavior of displaying warnings.
diff --git a/matlab/tests/private/showpath.m b/matlab/tests/private/showpath.m
index 4bb5183c95..eccfc9fe7f 100644
--- a/matlab/tests/private/showpath.m
+++ b/matlab/tests/private/showpath.m
@@ -13,14 +13,20 @@
fprintf('\n%s', ndftpath{ic});
end
+fprintf('\n\nMatCUTEst path:\n');
+fprintf('\n%s', which('matcutest'));
+
fprintf('\n\nSolver paths:\n');
for isol = 1 : length(solvers)
solver = solvers{isol};
% `regexprep` removes '_classical' in case 'solver' ends with it.
solver = regexprep(solver, '_classical$', '');
+ solver = regexprep(solver, '_default$', '');
+ solver = regexprep(solver, '_half$', '');
solver = regexprep(solver, '_single$', '');
solver = regexprep(solver, '_quadruple$', '');
solver = regexprep(solver, '_archiva$', '_norma');
fprintf('\n%s: %s', solvers{isol}, which(solver));
end
+
fprintf('\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n');
diff --git a/matlab/tests/private/testcu.m b/matlab/tests/private/testcu.m
index b8c0d65dfb..ebac627d59 100644
--- a/matlab/tests/private/testcu.m
+++ b/matlab/tests/private/testcu.m
@@ -6,8 +6,10 @@
solvers = lower(solvers);
% Default options
+precision = 'double';
rhobeg = 1;
rhoend = 1e-6;
+npt = @(n) 2*n+1;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% MAXN is the maximal possible dimension of problems in our test.
@@ -15,8 +17,8 @@
% problems to be tested in the current experiment.
maxn = 200;
%maxfun_dim = 100;
-%maxfun_dim = 500;
-maxfun_dim = 200;
+%maxfun_dim = 200;
+maxfun_dim = 500;
maxfun = maxfun_dim*maxn;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -32,7 +34,8 @@
ctol_multiple = 1; % The real ctol to use is ctol*ctol_multiple.
cpenalty = 1e10; % The penalty to use when the constraint violation is greater than ctol.
type = 'ubln'; % The default types of problems to test
-mindim = 1; % The default minimal dimension of problems to test
+mindim = 2; % The default minimal dimension of problems to test
+% We do not test 1D problems, because they are not interesting, and because the classical uobyqa cannot handle them.
if any(startsWith(solvers, 'cobyla'))
maxdim = 20; % The default maximal dimension of problems to test
else
@@ -51,11 +54,13 @@
% the returned X, there may be a slight difference between this X and the one that is used to
% evaluate the function value by the Fortran code. Consequently, the function values may differ.
debug = true;
+debug = debug && ~(isfield(options, 'compiler_options') && contains(options.compiler_options, 'fast'));
chkfunval = ~(isfield(options, 'eval_options') && ~isempty(options.eval_options) && ...
(isfield(options.eval_options, 'noise') && ~isempty(options.eval_options.noise) && ...
isnumeric(options.eval_options.noise) && abs(options.eval_options.noise) > 0 || ...
isfield(options.eval_options, 'dnoise') && ~isempty(options.eval_options.dnoise) && ...
isnumeric(options.eval_options.dnoise) && abs(options.eval_options.dnoise) > 0));
+%chkfunval = chkfunval && ~(isfield(options, 'compiler_options') && contains(options.compiler_options, 'fast'));
output_xhist = true;
output_nlchist = true;
thorough_test = 0;
@@ -64,6 +69,9 @@
minip = 1;
maxip = 2^32 - 1;
strict = 2;
+verbose = false;
+% Zaikun 20250908: It turns out that CUTEst contains many feasibility problems. We do not test them by default.
+test_feasibility_problems = false;
% Directories for recording the starting/ending of problems (tic/toc are unavailable in parfor).
stamp = options.stamp;
@@ -87,9 +95,9 @@
disp(['prob_end_runs_dir = ', prob_end_runs_dir]);
% Set options
-options = setopt(options, rhobeg, rhoend, maxfun_dim, maxfun, maxit, ftarget, perm, randomizex0, ...
+options = setopt(options, precision, rhobeg, rhoend, npt, maxfun_dim, maxfun, maxit, ftarget, perm, randomizex0, ...
eval_options, nr, ctol, ctol_multiple, cpenalty, type, mindim, maxdim, mincon, maxcon, ...
- sequential, debug, chkfunval, output_xhist, output_nlchist, thorough_test, minip, maxip, strict);
+ sequential, debug, chkfunval, output_xhist, output_nlchist, thorough_test, minip, maxip, strict, verbose, test_feasibility_problems);
assert(options.maxdim <= maxn);
@@ -107,6 +115,13 @@
requirements.maxcon = options.maxcon;
requirements.type = options.type;
+ if ~options.test_feasibility_problems
+ % If requirements does not contain `is_feasibility`, then we test all problems, including feasibility problems.
+ % If requirements.is_feasibility = false, then we test only the problems that are not feasibility problems.
+ % If requirements.is_feasibility = true, then we test only feasibility problems.
+ requirements.is_feasibility = false;
+ end
+
if isfield(options, 'blacklist')
requirements.blacklist = options.blacklist;
else
@@ -120,6 +135,13 @@
end
assert(~isempty(plist));
+% Record the problems to test in problems.txt.
+fprob = fullfile(options.outdir, strcat(stamp, '.', 'problems_to_test.txt'));
+fid = fopen(fprob, 'w');
+assert(fid >= 3); % 0, 1, 2 are reserved for stdin, stdout, stderr.
+fprintf(fid, '%s\n', plist{:});
+fclose(fid);
+
np = length(plist);
ns = length(solvers);
nr = options.nr;
@@ -128,6 +150,7 @@
minip = options.minip;
maxip = min(np, options.maxip);
strict = options.strict;
+verbose = options.verbose;
% These arrays will record the function values and constraint values during the tests.
pdim = NaN(np, 1); % Data profile needs the dimension of the problem.
@@ -174,13 +197,17 @@
for ip = minip : maxip
% Turn off unwanted warnings
- orig_warning_state = warnoff(solvers);
+ orig_warning_state = warnoff(solvers, verbose);
pname = plist{ip};
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_start_time_dir, [pname, '.', strtrim(time)])]);
system(['touch ', fullfile(prob_start_dir, pname)]);
- fprintf('\n%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ else
+ fprintf('%3d. \t%s\n', ip, pname);
+ end
prob = macup(pname);
orig_prob = prob;
@@ -200,7 +227,9 @@
for ir = 1 : nr
system(['touch ', fullfile(prob_start_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ end
if have_eval_options
prob.objective = @(x) evalfun(prob.orig_objective, x, eval_options, ir);
@@ -221,11 +250,13 @@
end
for is = 1 : ns
- [frec(ip, is, ir, :), crec(ip, is, ir, :)] = testsolv(solvers{is}, prob, options);
+ [frec(ip, is, ir, :), crec(ip, is, ir, :)] = testsolv(solvers{is}, prob, options, ir);
end
system(['touch ', fullfile(prob_end_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ end
end
decup(prob);
@@ -233,7 +264,9 @@
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_end_time_dir, [pname, '.', strtrim(time)])]);
system(['touch ', fullfile(prob_end_dir, pname)]);
- fprintf('\n%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ end
% Restore the behavior of displaying warnings
warning(orig_warning_state);
@@ -243,13 +276,17 @@
parfor ip = minip : maxip
% Turn off unwanted warnings
- orig_warning_state = warnoff(solvers);
+ orig_warning_state = warnoff(solvers, verbose);
pname = plist{ip};
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_start_time_dir, [pname, '.', strtrim(time)])]);
system(['touch ', fullfile(prob_start_dir, pname)]);
- fprintf('\n%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s starts at %s\n', ip, pname, char(datetime()));
+ else
+ fprintf('%3d. \t%s\n', ip, pname);
+ end
prob = macup(pname);
orig_prob = prob;
@@ -269,7 +306,9 @@
for ir = 1 : nr
system(['touch ', fullfile(prob_start_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d starts at %s\n', pname, ir, char(datetime()));
+ end
if have_eval_options
prob.objective = @(x) evalfun(prob.orig_objective, x, eval_options, ir);
@@ -290,11 +329,13 @@
end
for is = 1 : ns
- [frec(ip, is, ir, :), crec(ip, is, ir, :)] = testsolv(solvers{is}, prob, options);
+ [frec(ip, is, ir, :), crec(ip, is, ir, :)] = testsolv(solvers{is}, prob, options, ir);
end
system(['touch ', fullfile(prob_end_runs_dir, [pname, '.', num2str(ir, '%02d')])]);
- fprintf('\n \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ if verbose
+ fprintf(' \t%s Run No. %3d ends at %s\n', pname, ir, char(datetime()));
+ end
end
decup(prob);
@@ -302,7 +343,9 @@
[~, time] = system('date +%y%m%d_%H%M%S');
system(['touch ', fullfile(prob_end_time_dir, [pname, '.', strtrim(time)])]);
system(['touch ', fullfile(prob_end_dir, pname)]);
- fprintf('\n%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ if verbose
+ fprintf('%3d. \t%s ends at %s\n', ip, pname, char(datetime()));
+ end
% Restore the behavior of displaying warnings
warning(orig_warning_state);
@@ -401,23 +444,38 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-function [fval_history, cv_history, output] = testsolv(solver, prob, options)
+function [fval_history, cv_history] = testsolv(solver, prob, options, ir)
prob.options = setsolvopt(solver, length(prob.x0), options); % Set the options for the solver
+solver_name = solver;
+if isa(solver, 'function_handle')
+ solver_name = func2str(solver);
+end
+
if ischarstr(solver) && ~strcmp(solver, 'fmincon') && ~strcmpi(solver, 'fminunc') && ~strcmpi(solver, 'fminsearch')
prob.options.classical = endsWith(solver, '_classical');
+ if endsWith(solver, '_default')
+ prob.options = rmfield(prob.options, intersect(fieldnames(prob.options), ...
+ {'npt', 'eta1', 'eta2', 'gamma1', 'gamma2', 'scale', 'honour_x0'}));
+ end
+ if endsWith(solver, '_half')
+ prob.options.precision = 'half';
+ end
if endsWith(solver, '_single')
prob.options.precision = 'single';
end
if endsWith(solver, '_quadruple')
prob.options.precision = 'quadruple';
end
- % `regexprep` removes '_classical' in case 'solver' ends with it. Similar for '_single', '_quadruple'.
+ % `regexprep` removes '_classical' in case 'solver' ends with it. Similar for '_default', '_half', '_single', '_quadruple'.
solver = regexprep(solver, '_classical$', '');
+ solver = regexprep(solver, '_default$', '');
+ solver = regexprep(solver, '_half$', '');
solver = regexprep(solver, '_single$', '');
solver = regexprep(solver, '_quadruple$', '');
solver = regexprep(solver, '_archiva$', '_norma');
+ solver = regexprep(solver, '_ultima$', '_norma');
solver = str2func(solver);
end
@@ -432,7 +490,23 @@
%have_eval_options = isfield(options, 'eval_options') && isstruct(options.eval_options) && ~isempty(fieldnames(options.eval_options));
prob.options.output_xhist = true; % We always need xhist to recover the history of the computation.
-[x, ~, ~, output] = solver(prob);
+exception = [];
+try
+ [x, ~, ~, output] = solver(prob);
+catch exception
+ % Do nothing for the moment
+end
+
+if ~isempty(exception)
+ fprintf('\n\n>>> !%s encounters an error during test %d of %s with the following options! <<<\n\n', solver_name, ir, prob.name);
+ format long;
+ options
+ prob
+ prob.options
+ options
+ rethrow(exception)
+end
+
% Solvers (e.g., fmincon) may not respect maxfun. Indeed, PRIMA solvers may also increase maxfun
% if it is too small (e.g., <= npt for NEWUOA). In addition, nhist may be smaller than maxfun due to
% memory limitation.
@@ -445,6 +519,8 @@
xhist(:, k) = prob.x0;
end
xhist(:, nf - nhist + 1 : nf) = output.xhist(:, 1 : nhist);
+% output can be large, particularly when n and maxfun are large. Clear it to save memory.
+clear('output');
if (nf <= 0)
% Sometimes PRIMA may return nf = 0, e.g., when it detects infeasibility.
@@ -456,9 +532,13 @@
% may modify the right-hand side of linear constraints when x0 is infeasible; in addition, it
% scales the constraints so that their gradients have norm 1), making results not comparable.
xhist_cell = num2cell(xhist(:, 1:nf), 1);
+ % xhist can be large, particularly when n and maxfun are large. Clear it to save memory.
+ clear('xhist');
fval_history(1:nf) = cellfun(prob.orig_objective, xhist_cell);
orig_cstrv = @(x) get_cstrv(x, prob.Aineq, prob.bineq, prob.Aeq, prob.beq, prob.lb, prob.ub, prob.orig_nonlcon);
cv_history(1:nf) = cellfun(orig_cstrv, xhist_cell);
+ % xhist_cell can be large, particularly when n and maxfun are large. Clear it to save memory.
+ clear('xhist_cell');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Set the (nf+1)-th entries of fval_history and cv_history to the values returned by the solver.
@@ -482,9 +562,13 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-function options = setopt(options, rhobeg, rhoend, maxfun_dim, maxfun, maxit, ftarget, perm, ...
+function options = setopt(options, precision, rhobeg, rhoend, npt, maxfun_dim, maxfun, maxit, ftarget, perm, ...
randomizex0, eval_options, nr, ctol, ctol_multiple, cpenalty, type, mindim, maxdim, mincon, maxcon, ...
- sequential, debug, chkfunval, output_xhist, output_nlchist, thorough_test, minip, maxip, strict) % Set options
+ sequential, debug, chkfunval, output_xhist, output_nlchist, thorough_test, minip, maxip, strict, verbose, test_feasibility_problems) % Set options
+
+if (~isfield(options, 'precision'))
+ options.precision = precision;
+end
if (~isfield(options, 'rhoend'))
options.rhoend = rhoend;
@@ -492,6 +576,9 @@
if (~isfield(options, 'rhobeg'))
options.rhobeg = rhobeg;
end
+if (~isfield(options, 'npt'))
+ options.npt = npt;
+end
if (~isfield(options, 'maxit'))
options.maxit = maxit;
end
@@ -570,6 +657,12 @@
if (~isfield(options, 'strict'))
options.strict = strict;
end
+if (~isfield(options, 'verbose'))
+ options.verbose = verbose;
+end
+if (~isfield(options, 'test_feasibility_problems'))
+ options.test_feasibility_problems = test_feasibility_problems;
+end
% Set eval_options
have_eval_options = isfield(options, 'eval_options') && isstruct(options.eval_options) && ~isempty(fieldnames(options.eval_options));
@@ -681,14 +774,17 @@
function solv_options = setsolvopt(solv, n, options)
solv_options = struct();
+solv_options.precision = options.precision;
solv_options.rhobeg = options.rhobeg;
solv_options.rhoend = options.rhoend;
+solv_options.npt = min(max(n+2, fix(options.npt(n))), (n+1)*(n+2)/2);
solv_options.maxfun = min(options.maxfun_dim*n, options.maxfun); % may differ from options.maxfun
solv_options.ftarget = options.ftarget;
solv_options.output_xhist = options.output_xhist;
solv_options.output_nlchist = options.output_nlchist;
solv_options.iprint = 0;
-solv_options.quiet = true;
+%solv_options.quiet = true;
+solv_options.quiet = false;
solv_options.debug = options.debug;
solv_options.chkfunval = options.chkfunval;
%solv_options.scale = true;
@@ -737,7 +833,8 @@
case {'absolute', 'additive', 'add', 'a', '+'}
f = f + noise.level*r;
otherwise
- f = f * (1 + noise.level*r);
+ %f = f * (1 + noise.level*r);
+ f = f + max(abs(f), 1)*noise.level*r; % abs(f) is reasonable because the distribution of r is symmetric w.r.t. 0.
end
end
end
@@ -751,7 +848,8 @@
case {'absolute', 'additive', 'add', 'a', '+'}
f = f + dnoise.level*r;
otherwise
- f = f * (1 + dnoise.level*r);
+ %f = f * (1 + dnoise.level*r);
+ f = f + max(abs(f), 1)*dnoise.level*r;
end
end
end
@@ -908,6 +1006,9 @@
'PENALTY3', ...
'VARDIM', ...
}];
+case 'bobyqa'
+ % For the following problems, the classical bobyqa (single-precision) encounters SEGFAULT.
+ blacklist = [blacklist, {'MGH17LS'}];
case 'lincoa'
blacklist = [blacklist, { ...
'DALLASM', ...
@@ -940,6 +1041,7 @@
'DECONVBNE', ...
'DECONVC', ... % In a test on 20230328, the classical cobyla encountered infinite cycling.
'DECONVNE', ...
+ 'DEGENQP', ...
'DIAMON2D', ... % 1415
'DIAMON3D', ... % 3703
'DMN15102', ... % 887
@@ -956,6 +1058,8 @@
'FBRAIN2NE', ...
'FBRAIN3', ...
'FEEDLOC', ...
+ 'GOULDQP1', ...
+ 'GROUPING', ...
'HAIFAM', ... % 173
'HIMMELBI', ... % 100
'HIMMELBJ', ...
@@ -966,6 +1070,7 @@
'LEVYMONE', ... % 15
'LHAIFAM', ...
'LINSPANH', ... % 3 (it takes a long time on GitHub Actions)
+ 'LSNNODOC', ...
'LUKSAN11', ...
'LUKSAN12', ... % 563
'LUKSAN13', ... % 508
@@ -981,7 +1086,8 @@
'OET6', ...
'OET7', ...
'QINGNE', ...
- 'QPCBLEND' , ...
+ 'QPCBLEND', ...
+ 'QPNBLEND', ...
'SPANHYD', ... % 15
'SWOPF', ... % 10
'TAX13322', ... % 5
@@ -994,6 +1100,8 @@
'VESUVIOU', ...
}];
% For the following problems, the classical cobyla encounters SEGFAULT.
- blacklist = [blacklist, {'ERRINBAR', 'HS118', 'LAKES', 'TENBARS1', 'TENBARS2', 'TENBARS3', 'TENBARS4', 'VANDERM4', 'VANDANIUMS'}];
+ blacklist = [blacklist, {'ERRINBAR', 'HS118', 'LAKES', 'ODFITS', 'TENBARS1', 'TENBARS2', 'TENBARS3', 'TENBARS4', 'TRUSPYR2', 'VANDERM4', 'VANDANIUMS'}];
+ % For the following problems, the classical cobyla encounters infinite cycling.
+ blacklist = [blacklist, {'HS101', 'HS112', 'MESH', 'POLYGON', 'ZAMB2-8', 'ZAMB2-9'}];
end
return
diff --git a/matlab/tests/private/warnoff.m b/matlab/tests/private/warnoff.m
index d7bd11d458..fd670be6fa 100644
--- a/matlab/tests/private/warnoff.m
+++ b/matlab/tests/private/warnoff.m
@@ -1,6 +1,14 @@
-function orig_warning_state = warnoff(solvers)
+function orig_warning_state = warnoff(solvers, verbose)
orig_warning_state = warning;
+if nargin < 2
+ verbose = false;
+end
+
+if verbose
+ return % Do nothing bu return `orig_warning_state`
+end
+
for isol = 1 : length(solvers)
if endsWith(solvers{isol}, '_classical')
solvers{isol} = regexprep(solvers{isol}, '_classical', '');
@@ -9,36 +17,56 @@
end
end
+solvers = regexprep(solvers, '_default', '');
+solvers = regexprep(solvers, '_half', '');
solvers = regexprep(solvers, '_single', '');
solvers = regexprep(solvers, '_quadruple', '');
solvers = regexprep(solvers, '_archiva', '_norma');
solvers = [solvers, 'prima', 'prima_norma'];
-cellfun(@(solver) warning('off', [solver, ':Debug']), solvers);
cellfun(@(solver) warning('off', [solver, ':ChkFunval']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ReviseX0']), solvers);
+cellfun(@(solver) warning('off', [solver, ':Debug']), solvers);
cellfun(@(solver) warning('off', [solver, ':UnknownProbField']), solvers);
-cellfun(@(solver) warning('off', [solver, ':UnknownOption']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidMaxfun']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ExtremeBarrier']), solvers);
-cellfun(@(solver) warning('off', [solver, ':IprintContradictFortran']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidMaxhist']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidNpt']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ObjectiveAbnormalReturn']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ConstraintAbnormalReturn']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidChkfunval']), solvers);
-cellfun(@(solver) warning('off', [solver, ':FortranContradictPrecision']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidMaxfun']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidMaxfilt']), solvers);
-cellfun(@(solver) warning('off', [solver, ':InvalidPrecision']), solvers);
-cellfun(@(solver) warning('off', [solver, ':IprintContradictQuiet']), solvers);
-cellfun(@(solver) warning('off', [solver, ':FortranContradictClassical']), solvers);
-cellfun(@(solver) warning('off', [solver, ':FortranContradictPrecision']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ObjectiveFailure']), solvers);
-cellfun(@(solver) warning('off', [solver, ':ConstraintFailure']), solvers);
-cellfun(@(solver) warning('off', [solver, ':X0IsRow']), solvers);
-cellfun(@(solver) warning('off', [solver, ':BeqIsRow']), solvers);
-cellfun(@(solver) warning('off', [solver, ':BineqIsRow']), solvers);
%cellfun(@(solver) warning('off', ['FMXAPI:', upper(solver)]), solvers);
+if ~strcmpi(getenv('CI'), 'true') % Don't turn off these warnings in CI
+ cellfun(@(solver) warning('off', [solver, ':BeqIsRow']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':BineqIsRow']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ConstraintAbnormalReturn']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ConstraintFailure']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ExtremeBarrier']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':FortranContradictClassical']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':FortranContradictPrecision']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':HugeNegativeF']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InfEquality']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InfInequality']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InfeasibleX0']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidChkfunval']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidCtol']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidCweight']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidEta1']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidEta2']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidGamma1']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidGamma2']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidIprint']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidMaxfilt']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidMaxfun']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidMaxhist']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidNpt']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidPrecision']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidRhobeg']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':InvalidRhoend']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':IprintContradictFortran']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':IprintContradictQuiet']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':NaNEquality']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':NaNInLB']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':NaNInUB']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':NaNInequality']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ObjectiveAbnormalReturn']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ObjectiveFailure']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':ReviseX0']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':UnknownOption']), solvers);
+ cellfun(@(solver) warning('off', [solver, ':X0IsRow']), solvers);
+end
+
return
diff --git a/matlab/tests/prof.m b/matlab/tests/prof.m
index 8eb98228b8..f28ed90d22 100644
--- a/matlab/tests/prof.m
+++ b/matlab/tests/prof.m
@@ -1,6 +1,6 @@
function outputfiles = prof(varargin)
-time = datestr(datetime(), 'yymmdd_HHMM');
+time = char(datetime('now','Format','yyMMdd_HHmm'));
% Set up the directory to save the testing data, i.e., `data_dir`.
mfiledir = fileparts(mfilename('fullpath')); % Directory where this .m file resides.
@@ -71,6 +71,12 @@
output{7} = profile(argin{:});
options = rmfield(options, {'eval_options'});
+% Precision of function evaluation ~ 1.0e-3
+options.eval_options = struct('signif', 3);
+argin = [varargin, {options}];
+output{8} = profile(argin{:});
+options = rmfield(options, {'eval_options'});
+
outputfiles = struct();
prob_types = fieldnames(output{1});
diff --git a/matlab/tests/profile.m b/matlab/tests/profile.m
index fe5d14730e..5a15363d92 100644
--- a/matlab/tests/profile.m
+++ b/matlab/tests/profile.m
@@ -16,17 +16,20 @@
% - `reverse_flag` (optional) is either 'reverse' or 'rev', which means to test the solvers in the reverse order
% - `problem_type` can be any of {'u', 'b', 'l', 'n', 'ub', 'ubl', 'ubln', 'bl', 'bln', 'ln'},
% indicating the problem type to test
-% - `competitor` (optional) can be any of {'classical', 'archiva', 'norma', 'single', 'quadruple'},
-% indicating the name of a competitor solver to test (only for profiling)
+% - `competitor` (optional) can be any of {'classical', 'default', 'archiva', 'norma', 'ultima', 'half', 'single', 'quadruple'},
+% indicating the name of a competitor solver to test
% - 'classical' means to test the classical solvers
+% - 'default' means to test the solvers with the default settings for npt, gamma1/2, eta1/2,
+% scaling, honour_x0
% - 'archiva' means to compare with the "archiva" version of the solver, located under the
% .development/archiva/dev_arch/ directory
-% - 'norma' means to compare with the "norma" version of the solver, located under the
-% .development/norma/ directory
+% - 'ultima' means to compare with the "ultima" (latest) version of the solver pushed to GitHub
+% - 'half' means to compare with the half precision version of the solver, namely the solver
+% invoked with precision = 'half'
% - 'single' means to compare with the single precision version of the solver, namely the solver
-% invoked with the 'single' flag set to true
+% invoked with precision = 'single'
% - 'quadruple' means to compare with the quadruple precision version of the solver, namely the solver
-% invoked with the 'quadruple' flag set to true
+% invoked with precision = 'quadruple'
%
% Coded by Zaikun ZHANG (www.zhangzk.net).
%
@@ -80,10 +83,15 @@
if isfield(options, 'randomizex0') && isnumeric(options.randomizex0) && isscalar(options.randomizex0) && abs(options.randomizex0) > 0
test_feature = [test_feature, '.', 'randomizex0', sprintf('%g', options.randomizex0)];
end
+ if isfield(options, 'precision') && (isa(options.precision, 'char') || ...
+ isa(options.precision, 'string')) && ~isempty(options.precision)
+ test_feature = [test_feature, '.', 'precision-', options.precision];
+ end
+
if isfield(options, 'eval_options') && isstruct(options.eval_options) && ~isempty(fieldnames(options.eval_options))
test_feature = [test_feature, '.', strjoin(fieldnames(options.eval_options), '_')];
if isfield(options.eval_options, 'dnoise')
- if isnumeric(options.eval_options.dnoise) && isscalar(options.eval_options)
+ if isnumeric(options.eval_options.dnoise) && isscalar(options.eval_options.dnoise)
dnoise_level = abs(options.eval_options.dnoise);
elseif isstruct(options.eval_options.dnoise) && isfield(options.eval_options.dnoise, 'level')
dnoise_level = abs(options.eval_options.dnoise.level);
@@ -95,7 +103,7 @@
end
end
if isfield(options.eval_options, 'noise')
- if isnumeric(options.eval_options.noise) && isscalar(options.eval_options)
+ if isnumeric(options.eval_options.noise) && isscalar(options.eval_options.noise)
noise_level = abs(options.eval_options.noise);
elseif isstruct(options.eval_options.noise) && isfield(options.eval_options.noise, 'level')
noise_level = abs(options.eval_options.noise.level);
@@ -113,11 +121,13 @@
test_feature = regexprep(test_feature, '^\.', '');
if isempty(test_feature)
test_feature = 'plain';
+ elseif isempty(regexprep(test_feature, 'precision-single|precision-double|precision-quadruple', ''))
+ test_feature = [test_feature, '.plain'];
end
options.test_feature = test_feature;
if ~isfield(options, 'time')
- options.time = datestr(datetime(), 'yymmdd_HHMM');
+ options.time = char(datetime('now','Format','yyMMdd_HHmm'));
end
% Define the solvers to test.
@@ -126,9 +136,8 @@
solvers = solvers(end:-1:1); % Reverse order
end
- % The following line can be used for testing the single-precision version. If such a test is
- % intended, remember to set mexopt.single = true in `get_solvers`.
- %solvers = {solver, [solver, '_single']};
+ % Be verbose if required or in CI.
+ options.verbose = ((isfield(options, 'verbose') && options.verbose) || strcmpi(getenv('CI'), 'true'));
% Make the solvers available. Note that the solvers are under `test_dir`.
get_solvers(solvers, test_dir, options);
@@ -177,15 +186,21 @@
end
-setpath(oldpath); % Restore the path to oldpath.
-cd(olddir); % Go back to olddir.
+% Restore the path to oldpath.
+% N.B.: Do NOT try uninstalling the solvers, because we may call `verify` again with 'ncp', which
+% means not to compile the solvers and reuse the existing MEX files, which would fail if we
+% uninstall the solvers here.
+path(oldpath);
+
+% Go back to olddir.
+cd(olddir);
fprintf('\nCurrently in %s\n\n', pwd());
+% `dev_arch` a subdirectory of fullfile(s_root_dir, 'archiva'). It contains the "archiva" version of
+% solvers used as a benchmark for the development of the current version of the solvers.
+% It may not be the latest archiva version. To make sure that it is the one desired, we print the
+% path of `dev_arch` here if the competitor is 'archiva'
if strcmpi(options.competitor, 'archiva')
- % `dev_arch` a subdirectory of fullfile(s_root_dir, 'archiva'). It contains the "archiva" version of
- % solvers used as a benchmark for the development of the current version of the solvers.
- % It may not be the latest archiva version. To make sure that it is the one desired, we print the
- % path of `dev_arch` here if the competitor is 'archiva'
archiva_dir_name = 'dev_arch';
mfilepath = fileparts(mfilename('fullpath')); % Directory where this .m file resides.
root_dir = fileparts(fileparts(mfilepath)); % root directory of the project
diff --git a/matlab/tests/recursive.m b/matlab/tests/recursive.m
new file mode 100644
index 0000000000..2f8852b7fb
--- /dev/null
+++ b/matlab/tests/recursive.m
@@ -0,0 +1,97 @@
+function recursive(solver, options)
+%RECURSIVE verifies that the solvers can be called recursively.
+
+% Turn off the warning about the debug mode.
+orig_warning_state = warning;
+warning('off', [solver, ':Debug']);
+
+if nargin < 2
+ options = struct();
+end
+
+% Set the random seed. We ALTER THE SEED WEEKLY to test the solvers as much as possible.
+if isfield(options, 'yw')
+ yw = options.yw;
+elseif isfield(options, 'seed')
+ yw = options.seed;
+else
+ yw = year_week('Asia/Shanghai');
+end
+fprintf('\nThe seed is\t\t%d\n\n', yw);
+% Define the random seed by yw
+random_seed = yw;
+orig_rng_state = rng(); % Save the current random number generator settings
+rng(random_seed);
+
+% Set the dimension of the problem
+if isfield(options, 'n')
+ n = options.n;
+else
+ if ismember(solver, {'uobyqa'})
+ n = 5;
+ else
+ n = 10;
+ end
+end
+
+% Set the recursion depth
+if isfield(options, 'depth')
+ depth = options.depth;
+else
+ depth = 3;
+end
+
+% Set up the solver
+if ~isfield(options, 'compile') || options.compile
+ old_directory = pwd();
+ cd(fileparts(fileparts(fileparts(mfilename('fullpath')))));
+ compile_options = struct();
+ compile_options.verbose = false;
+ compile_options.debug = (rand() < 0.5);
+ compile_options.classical = false;
+ compile_options.single = false;
+ setup(solver, compile_options);
+ cd(old_directory);
+end
+solver_name = solver;
+solver = str2func(solver);
+
+% Define the objective function, which is based on the Rosenbrock function and recursive calls of
+% the solver.
+solver_options = struct();
+solver_options.debug = (rand() < 0.5);
+solver_options.rhoend = 1.0e-3;
+solver_options.maxfun = min(100*n, 5e3);
+fun = @chrosen;
+for i = 1 : depth
+ fun = @(x) rfun(x, fun, solver, solver_options);
+end
+
+% Conduct the test
+tic;
+fprintf('\n>>>>>> Recursive test for %s starts <<<<<<\n', solver_name);
+
+% Call the solver
+% We call the solver two times, in case something does not finish correctly during the first run.
+solver_options.iprint = 3;
+[x, fx, exitflag, output] = solver(fun, randn(n, 1), solver_options)
+[x, fx, exitflag, output] = solver(fun, randn(n, 1), solver_options)
+
+fprintf('\n>>>>>> Recursive test for %s ends <<<<<<\n', solver_name);
+toc;
+
+% Restore the random number generator state
+rng(orig_rng_state);
+% Restore the warning state
+warning(orig_warning_state);
+
+return
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+function f = rfun(x, fun, solver, solver_options)
+%RFUN defines a function of x by minimizing fun([x; y]) with respect to y in R^2 using a solver.
+solver_options.iprint = 0;
+solver_options.rhoend = 1.0e-2;
+[~, f] = solver(@(y) fun([x; y]), randn(2, 1), solver_options);
+return
diff --git a/matlab/tests/stress.m b/matlab/tests/stress.m
index f5083ca02c..1c0a0f6fd8 100644
--- a/matlab/tests/stress.m
+++ b/matlab/tests/stress.m
@@ -1,9 +1,9 @@
function stress(solver, options)
-%STRESS Stress test for the solver on problems large dimensions.
-% N.B.: When the dimension beyond some limit, the MEX function will crash due to memory violations.
-% In the official version, the limit for each solver is much higher than the size of problems that
-% it is designed to solve. To achieve this, we have to force the MEX function to use the heap
-% instead of the stack for automatic arrays. This is done by the following compilation options:
+%STRESS test of a solver on excessively large problems that the solver is NOT designed for.
+% N.B.: When the dimension is beyond some limit, the MEX function will crash due to memory violations.
+% However, the limit for each solver is much higher than the size of problems that it is designed to
+% solve. To achieve this, we have to force the MEX function to use the heap instead of the stack for
+% automatic arrays. This is done by the following compilation options:
% gfortran on Linux: -fno-stack-arrays
% Intel compiler on macOS: -heap-arrays
% Intel compiler on Windows: /heap-arrays
@@ -14,7 +14,6 @@ function stress(solver, options)
% optimization, but may not be true for optimization with derivatives.
% See matlab/setup_tools/compile.m for details.
-
% Turn off unwanted warnings
orig_warning_state = warnoff({solver});
@@ -22,40 +21,68 @@ function stress(solver, options)
options = struct();
end
+% Set the random seed. We ALTER THE SEED WEEKLY to test the solvers as much as possible.
+if isfield(options, 'yw')
+ yw = options.yw;
+elseif isfield(options, 'seed')
+ yw = options.seed;
+else
+ yw = year_week('Asia/Shanghai');
+end
+fprintf('\nThe seed is\t\t%d\n\n', yw);
+% Define the random seed by yw
+random_seed = yw;
+orig_rng_state = rng(); % Save the current random number generator settings
+rng(random_seed);
+
% Whether to conduct a TOUGH test
tough_test = isfield(options, 'tough') && options.tough;
% Which precision to test
+precision = 'double';
if isfield(options, 'precision') && ischarstr(options.precision)
precision = options.precision;
-else
- precision = 'double';
+end
+if strcmpi(precision, 'date') % Use the date to determine the precision
+ daynum = day(datetime('now', 'TimeZone', 'Asia/Shanghai'));
+ if half_precision_available()
+ if mod(daynum, 4) == 0
+ options.precision = 'half';
+ elseif mod(daynum, 4) == 1
+ options.precision = 'single';
+ elseif mod(daynum, 4) == 2
+ options.precision = 'double';
+ else
+ options.precision = 'quadruple';
+ end
+ else
+ if mod(daynum, 3) == 0
+ options.precision = 'single';
+ elseif mod(daynum, 3) == 1
+ options.precision = 'double';
+ else
+ options.precision = 'quadruple';
+ end
+ end
end
% Set up the solver
-old_directory = pwd();
-cd(fileparts(fileparts(fileparts(mfilename('fullpath')))));
-opt.verbose = true;
-opt.debug = true;
-opt.single = strcmpi(precision, 'single');
-opt.quadruple = strcmpi(precision, 'quadruple');
-setup(solver, opt);
-cd(old_directory);
+if ~isfield(options, 'compile') || options.compile
+ old_directory = pwd();
+ cd(fileparts(fileparts(fileparts(mfilename('fullpath')))));
+ compile_options = struct();
+ compile_options.verbose = false;
+ compile_options.debug = (rand() < 0.5);
+ compile_options.half = strcmpi(precision, 'half');
+ compile_options.single = strcmpi(precision, 'single');
+ compile_options.quadruple = strcmpi(precision, 'quadruple');
+ compile_options.classical = false;
+ setup(solver, compile_options);
+ cd(old_directory);
+end
solver_name = solver;
solver = str2func(solver);
-% Set the random seed. We ALTER THE SEED WEEKLY to test the solvers as much as possible.
-if isfield(options, 'yw')
- yw = options.yw;
-elseif isfield(options, 'seed')
- yw = options.seed;
-else
- yw = year_week('Asia/Shanghai');
-end
-fprintf('\nYW = %d\n', yw);
-% Define the random seed by yw
-random_seed = yw;
-
% Set the dimension of the problem
if isfield(options, 'n')
n = options.n;
@@ -101,20 +128,20 @@ function stress(solver, options)
problem_type = 'n';
end
-% Set the options for the test
-test_options = struct();
-test_options.precision = precision;
-test_options.maxfun = 500 * n;
-test_options.rhobeg = 1;
-test_options.rhoend = 1.0e-7;
-test_options.iprint = 2;
-test_options.debug = true;
-fprintf('\n>>>>>> test_options =');
-test_options
+% Set the options for the solver
+solver_options = struct();
+solver_options.precision = precision;
+solver_options.maxfun = 500 * n;
+solver_options.rhobeg = 1;
+solver_options.rhoend = 1.0e-7;
+solver_options.iprint = 2;
+solver_options.debug = (rand() < 0.5);
+fprintf('\n>>>>>> solver_options =');
+solver_options
% Generate the problem
problem = stress_problem(n, problem_type, random_seed);
-problem.options = test_options;
+problem.options = solver_options;
original_problem = problem;
if tough_test
problem = tough(original_problem, random_seed);
@@ -122,9 +149,9 @@ function stress(solver, options)
% Conduct the test
if tough_test
- fprintf('\n>>>>>> TOUGH test starts <<<<<<\n');
+ fprintf('\n>>>>>> TOUGH test for %s starts <<<<<<\n', solver_name);
else
- fprintf('\n>>>>>> Test starts <<<<<<\n');
+ fprintf('\n>>>>>> Test for %s starts <<<<<<\n', solver_name);
end
tic;
@@ -134,7 +161,7 @@ function stress(solver, options)
while redo
exception = [];
try
- [a, b, c, d] = solver(problem)
+ [x, fx, exitflag, output] = solver(problem)
catch exception
end
if isempty(exception) || ~tough_test || ~strcmp(solver_name, 'cobyla')
@@ -149,6 +176,8 @@ function stress(solver, options)
end
toc;
+% Restore the random number generator state
+rng(orig_rng_state);
% Restore the behavior of displaying warnings
warning(orig_warning_state);
@@ -157,9 +186,9 @@ function stress(solver, options)
end
if tough_test
- fprintf('\n>>>>>> TOUGH test ends <<<<<<\n\n');
+ fprintf('\n>>>>>> TOUGH test for %s ends <<<<<<\n', solver_name);
else
- fprintf('\n>>>>>> Test ends <<<<<<\n\n');
+ fprintf('\n>>>>>> Test for %s ends <<<<<<\n', solver_name);
end
diff --git a/matlab/tests/testprima.m b/matlab/tests/testprima.m
index 7191d57713..b807f00687 100644
--- a/matlab/tests/testprima.m
+++ b/matlab/tests/testprima.m
@@ -1,5 +1,14 @@
-function testprima(release, precision, nrun)
+function testprima(release, precision, nrun, test_classical, verbose)
%TESTPRIMA tests prima on a few VERY simple problems.
+% release is a boolean that controls whether to test prima in release mode (true) or not (false).
+% In release mode, the testing precision is relaxed and the testing results are not printed.
+% precision is a positive scalar that specifies the required precision.
+% nrun is a positive integer that specifies the number of runs with quasi-randomly perturbed x0.
+% The default value is 1, meaning that no perturbation will be added to x0. If nrun > 1, a tiny
+% quasi-random perturbation will be added to x0 in each run.
+% test_classical is a boolean that controls whether to test the classical version of prima.
+% verbose is a boolean that controls whether the solvers print messages. In release mode, it will
+% be ignored and the solvers will not print messages.
%
% Note: Do NOT follow the syntax here when you use prima. This file is
% written for testing purpose, and it uses quite atypical syntax. See
@@ -35,11 +44,18 @@ function testprima(release, precision, nrun)
precision = 1e-6; % Default testing precision
end
if nargin < 3
- nrun = 1; % Number of runs with randomly perturbed x0
+ nrun = 1; % Number of runs with quasi-randomly perturbed x0
perturb = 0; % Magnitude of perturbation on x0
else
perturb = eps;
end
+if nargin < 4
+ test_classical = false;
+end
+if nargin < 5
+ verbose = false;
+end
+verbose = verbose && ~release; % In release mode, the solvers will not print messages regardless of the value of `verbose`.
options.debug = true;
options.chkfunval = true;
@@ -80,7 +96,7 @@ function testprima(release, precision, nrun)
% 2. Himmelblau's function (hmlb) has multiple minima when
% unconstrained. They are [3; 2], [-2.805118; 3.131312], [-3.779310; -3.283186], [3.584428; -1.848126]
-if release
+if release || ~test_classical
clflag_list = {false}; % clflag: call the solvers in classical mode (true) or not (false)
else
clflag_list = {true, false}; % clflag: call the solvers in classical mode (true) or not (false)
@@ -103,7 +119,7 @@ function testprima(release, precision, nrun)
x0 = x0_list{ifun};
if ~strcmp(solver, 'cobyla') || ~strcmp(func2str(fun), 'chebquad')
% The result of cobyla on chebquad is sensitive to x0, so we do not perturb x0
- r = abs(sin(1e3*sum(double([solver, func2str(fun), type]))*irun*(1:length(x0))'));
+ r = abs(sin(1e4*sum(double([solver, func2str(fun), type]))*irun*(1:length(x0))'));
% Introduce a tiny perturbation to the experiments.
% We use a deterministic permutation so that
% experiments can be easily repeated when necessary.
@@ -113,15 +129,15 @@ function testprima(release, precision, nrun)
fopt = fopt_list{ifun}{itype};
n = length(x0);
+ % `problem` will be used below when we test `prima(problem)`.
problem = struct();
problem.objective = fun;
problem.x0 = x0;
options.solver = solver;
options.classical = clflag;
+ % Generate options.iprint using a linear congruential generator.
+ options.iprint = verbose * (mod(mod(2147483647 * sum(double([solver, func2str(fun), type]))*irun, 48271), 9) - 4);
problem.options = options;
- if ~release % 20230508: Test the newly implemented iprint.
- problem.options.iprint = round(4*(2*rand() - 1));
- end
switch type
case 'unconstrained'
@@ -130,6 +146,7 @@ function testprima(release, precision, nrun)
lb = -0.5*ones(n,1);
ub = 0.5*ones(n,1);
[x, fx] = prima(fun, x0, [], [], [], [], lb, ub, options);
+
problem.lb = lb;
problem.ub = ub;
case 'linearly-constrained' % simplex
@@ -137,6 +154,7 @@ function testprima(release, precision, nrun)
bineq = 1;
lb = zeros(n,1);
[x, fx] = prima(fun, x0, Aineq, bineq, [], [], lb, [], options);
+
problem.Aineq = Aineq;
problem.bineq = bineq;
problem.lb = lb;
@@ -144,12 +162,18 @@ function testprima(release, precision, nrun)
nonlcon = @(x) ballcon(x, zeros(n,1), 1);
lb = zeros(n,1);
[x, fx] = prima(fun, x0, [], [], [], [], lb, [], nonlcon, options);
+
problem.lb = lb;
problem.nonlcon = nonlcon;
end
xs = prima(problem);
+ % Remove the *_output.txt files produced when options.iprint < 0.
+ if ~isempty(dir('*_output.txt'))
+ delete('*_output.txt');
+ end
+
if strcmp(solver, 'cobyla') % The precision of cobyla is lower
prec = max(1e3*precision, 1e-2);
else
@@ -187,35 +211,16 @@ function testprima(release, precision, nrun)
return
-function [f, g, H]=chrosen(x)
-%CHROSEN calculates the function value, gradient, and Hessian of the
-% Chained Rosenbrock function.
-% See
-% [1] Toint (1978), 'Some numerical results using a sparse matrix
-% updating formula in unconstrained optimization'
-% [2] Powell (2006), 'The NEWUOA software for unconstrained
-% optimization without derivatives'
-n=length(x);
+function f = chrosen(x)
+%CHROSEN calculates the function value of the Chained Rosenbrock function.
+% See
+% [1] Toint (1978), 'Some numerical results using a sparse matrix updating formula in
+% unconstrained optimization'
+% [2] Powell (2006), 'The NEWUOA software for unconstrained optimization without derivatives'
alpha = 4;
-
-f=0; % Function value
-g=zeros(n,1); % Gradient
-H=zeros(n,n); % Hessian
-
-for i=1:n-1
- f = f + (x(i)-1)^2+alpha*(x(i)^2-x(i+1))^2;
-
- g(i) = g(i) + 2*(x(i)-1)+alpha*2*(x(i)^2-x(i+1))*2*x(i);
- g(i+1) = g(i+1) - alpha*2*(x(i)^2-x(i+1));
-
- H(i,i) = H(i,i)+2+alpha*2*2*(3*x(i)^2-x(i+1));
- H(i,i+1) = H(i,i+1)-alpha*2*2*x(i);
- H(i+1,i) = H(i+1,i) -alpha*2*2*x(i);
- H(i+1,i+1)= H(i+1,i+1)+alpha*2;
-end
-
+f = sum((x(1:end-1)-1).^2 + alpha*(x(2:end)-x(1:end-1).^2).^2);
return
function f = chebquad(x)
@@ -241,14 +246,13 @@ function testprima(release, precision, nrun)
return
-function [f, g] = hmlb(x)
-%HMLB evaluates the Himmelblau's function and its gradient
+function f = hmlb(x)
+%HMLB evaluates the Himmelblau's function.
%
% See
% [1] Himmelblau (1972), 'Applied Nonlinear Programming'
f = (x(1)^2+x(2)-11)^2 + (x(1)+x(2)^2-7)^2;
-g = 2*[-7 + x(1) + x(2)^2 + 2*x(1)*(-11 + x(1)^2 + x(2)); -11 + x(1)^2 + x(2) + 2*x(2)*(-7 + x(1) + x(2)^2)];
return
@@ -256,7 +260,8 @@ function testprima(release, precision, nrun)
%GOLDP evaluates the Goldstein-Price function
%
% See
-% [1] Dixon, L. C. W., & Szego, G. P. (1978). The global optimization problem: an introduction. Towards global optimization, 2, 1-15.
+% [1] Dixon, L. C. W., & Szego, G. P. (1978). The global optimization problem: an introduction.
+% Towards global optimization, 2, 1-15.
f1a = (x(1) + x(2) + 1)^2;
f1b = 19 - 14*x(1) + 3*x(1)^2 - 14*x(2) + 6*x(1)*x(2) + 3*x(2)^2;
diff --git a/matlab/tests/testprima_ex.m b/matlab/tests/testprima_ex.m
new file mode 100644
index 0000000000..791c7600d7
--- /dev/null
+++ b/matlab/tests/testprima_ex.m
@@ -0,0 +1,40 @@
+function testprima_ex(test_classical, verbose)
+%TESTPRIMA tests prima extensively on a few VERY simple problems, combining testprima and pdv.
+% test_classical is a boolean that controls whether to test the classical version of prima.
+% verbose is a boolean that controls whether the solvers print messages.
+
+ver;
+
+root_dir = fileparts(fileparts(pwd()));
+cd(root_dir);
+options = struct();
+options.debug = true;
+options.verbose = true;
+setup(options);
+prima('info')
+testprima(false, 1.0e-10, 1500, test_classical, verbose);
+setup
+setup path
+prima('info')
+testprima(false, 1.0e-10, 1500, test_classical, verbose);
+setup cobyla
+setup uobyqa
+setup newuoa
+setup bobyqa
+setup lincoa
+setup prima
+setup path
+setup clean
+setup path
+setup uninstall
+setup path
+setup uninstall
+cd(fullfile(root_dir, 'matlab', 'tests'));
+pdv
+cd(root_dir);
+setup
+prima('info')
+cd(fullfile(root_dir, 'matlab', 'examples'));
+rosenbrock_example
+
+end
diff --git a/matlab/tests/verify.m b/matlab/tests/verify.m
index 2ffbe17e21..43e69e6e95 100644
--- a/matlab/tests/verify.m
+++ b/matlab/tests/verify.m
@@ -61,15 +61,33 @@ function verify(varargin)
% with `-fcheck=all`, the classical version often crashes due to memory access violation.
options.no_classical = true;
+ % Be verbose if required or in CI.
+ options.verbose = ((isfield(options, 'verbose') && options.verbose) || strcmpi(getenv('CI'), 'true'));
+
+ % Determine the integer kind to use: 0, 16, 32, or 64, alternating every 4 weeks.
+ if isfield(options, 'yw')
+ yw = options.yw;
+ elseif isfield(options, 'seed')
+ yw = options.seed;
+ else
+ yw = year_week('Asia/Shanghai');
+ end
+ options.yw = yw; % options.yw is needed in `isequiv`.
+ options.integer_kind = 2^(mod(yw, 4) + 3); % 0, 16, 32, or 64, used in `get_solvers`.
+ if options.integer_kind == 8
+ options.integer_kind = 0;
+ end
+ fprintf('\n\n!!! Using integer kind %d (yw = %d). Watch out for segfaults when using non-default integers !!!\n\n', options.integer_kind, yw);
+
% Make the solvers available. Note that the solvers are under `test_dir`.
get_solvers(solvers, test_dir, options);
- % Show current path information.
- showpath(solvers);
-
% Tell MATLAB where to find MatCUTEst.
locate_matcutest();
+ % Show current path information.
+ showpath(solvers);
+
% Record `olddir` in `options` so that we can come back to `olddir` during `isequiv` if
% necessary (for example, when a single test fails).
options.olddir = olddir;
@@ -90,10 +108,17 @@ function verify(varargin)
end
-setpath(oldpath); % Restore the path to oldpath.
-cd(olddir); % Go back to olddir.
+% Restore the path to oldpath.
+% N.B.: Do NOT try uninstalling the solvers, because we may call `verify` again with 'ncp', which
+% means not to compile the solvers and reuse the existing MEX files, which would fail if we
+% uninstall the solvers here.
+path(oldpath);
+
+% Go back to olddir.
+cd(olddir);
fprintf('\nCurrently in %s\n\n', pwd());
-if ~isempty(exception) % Rethrow any exception caught above.
+% Rethrow any exception caught above.
+if ~isempty(exception)
rethrow(exception);
end
diff --git a/prima-config.cmake.in b/prima-config.cmake.in
index e4fd8882ed..79814b9493 100644
--- a/prima-config.cmake.in
+++ b/prima-config.cmake.in
@@ -2,7 +2,3 @@
include("${CMAKE_CURRENT_LIST_DIR}/prima-targets.cmake")
-get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
-if (NOT "Fortran" IN_LIST languages)
- message(SEND_ERROR "Fortran language must be enabled to use PRIMA")
-endif ()
diff --git a/pyprima/.gitignore b/pyprima/.gitignore
new file mode 100644
index 0000000000..fa9567d1b0
--- /dev/null
+++ b/pyprima/.gitignore
@@ -0,0 +1,2 @@
+pycutest_cache_holder
+out
diff --git a/pyprima/README.md b/pyprima/README.md
new file mode 100644
index 0000000000..8f96b209d3
--- /dev/null
+++ b/pyprima/README.md
@@ -0,0 +1,96 @@
+## About
+
+This is a Python translation of [Zaikun Zhang](https://www.zhangzk.net)'s [modern-Fortran reference implementation](https://github.com/libprima/prima/tree/main/fortran)
+for Powell's derivative-free optimization solvers, which is available at `fortran/` under the root directory.
+It is supposed to be a faithful translation, [producing bit-for-bit identical results](https://github.com/scipy/scipy/pull/22350#issue-2795978526).
+as the modern-Fortran reference implementation.
+If you notice a difference, [raise an issue](https://github.com/libprima/prima/issues/new).
+
+Due to [bug-fixes](https://github.com/libprima/prima#bug-fixes) and [improvements](https://github.com/libprima/prima#improvements),
+the modern-Fortran reference implementation by [Zaikun Zhang](https://www.zhangzk.net)
+behaves differently from the original Fortran 77 implementation by [M. J. D. Powell](https://www.zhangzk.net/powell.html),
+even though the algorithms are essentially the same. Therefore, it is important to point out that you are using
+PRIMA rather than the original solvers if you want your results to be reproducible.
+
+Compared to Powell's Fortran 77 implementation, the modern-Fortran implementation and hence any faithful
+translation like this one generally [produce better solutions with fewer function evaluations](https://github.com/libprima/prima#improvements),
+making them preferable for [applications with expensive function evaluations](https://github.com/orgs/libprima/discussions/145).
+However, if function evaluations are not the dominant cost in your application, the Fortran 77
+solvers are likely to be faster, as they are more efficient in terms of memory usage and flops
+thanks to the careful and ingenious (but unmaintained and unmaintainable) implementation by Powell.
+
+As of April 2025, only the COBYLA solver is available in this Python translation
+(many thanks to [Nickolai Belakovski](http://www.nickolai.me/)), and SciPy 1.16.0
+integrates it to replace the original Fortran 77 implementation of [COBYLA underlying the
+`scipy.optimize.minimize` function](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html).
+The other solvers will be translated from the Fortran reference implementation in the future.
+If you are interested in doing so, contact [Zaikun Zhang](https://www.zhangzk.net).
+
+## Development notes
+
+To develop, `cd` into the `src` directory and run
+
+```pip install --editable .```
+
+This will install PRIMA locally in an editable fashion. From there you can run the examples/cobyla/cobyla_example.py (from any directory) and go from there.
+
+### Style notes
+
+- Most of the comments are copied from Fortran verbatim, except in cases where they need to modified due to specifics of the Python language. In these cases a note will be made of the difference between Fortran and Python
+ - Rationale:
+ - The main purpose of this is to keep the Python and Fortran codebases as similar as possible.
+- For determining the dimensions of an array, we exclusively use `np.size` instead of `np.shape` or `some_array.shape` or `len`
+ - Rationale:
+ - Fortran uses `SIZE` so this helps us to be as consistent with the Fortran code as possible.
+
+### A note on Fortran's `maxval`, `maximum`, and `maxval` and their Python equivalents
+
+| Fortran | Python | Return value |
+|-----------|--------------|--------------|
+| `maxval` | `max` | scalar |
+| `maximum` | `np.max` | scalar |
+| `max` | `np.maximum` | vector |
+
+The difference between `maxval` and `maximum` is that `maximum` will return NaN if the input contains NaN. Python's `max`
+and numpy's `np.max` have a similar distinction.
+
+Fortran's `max` and numpy's `mp.maximum` accept two arguments, either of which can be a scalar or an array,
+and returns an elementwise maximum of the two arguments. In the case of a scalar and an array argument it
+returns an elementwise maximum of the scalar and each element of the array.
+
+This note applies to `minval`, `minimum`, and `min` as well.
+
+
+### A note on indices
+
+Consider the following Fortran code
+
+```
+do i=0:5
+ print *, *
+end do
+```
+
+It can be easily and automatically translated to Python as
+
+```
+for i in range(0, 6):
+ print(i)
+```
+
+Now consider the following similar loop
+
+```
+do i=1:5
+ print *, some_array(i)
+end do
+```
+
+This can be translated to Python as
+
+```
+for i in range(1, 6):
+ print(some_array[i-1])
+```
+
+This leads to awkward Python code, since the more pythonic code would range from 0 to 5, and the indexing would be `some_array[i]`. In order to make the Python code more usable, we will attempt to write more "pythonic" code, even though that makes the translation a little bit more difficult.
diff --git a/pyprima/profiles_vs_matlab/.gitignore b/pyprima/profiles_vs_matlab/.gitignore
new file mode 100644
index 0000000000..fa9567d1b0
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/.gitignore
@@ -0,0 +1,2 @@
+pycutest_cache_holder
+out
diff --git a/pyprima/profiles_vs_matlab/README.md b/pyprima/profiles_vs_matlab/README.md
new file mode 100644
index 0000000000..d7960735da
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/README.md
@@ -0,0 +1,7 @@
+This folder contains scripts and utilities to run performance profiles of the PRIMA Python bindings against the PRIMA MATLAB bindings.
+
+In general we expect the performance profiles to show a straight line for both the output based and history based profiles since the underlying code is the same. However since there are bound to be some differences in how MATLAB and Python handle floating point arithmetic we expect to see a few cases where one might perform slightly "better" than the other.
+
+In order to use this you will need Python and MATLAB and in the MATLAB GUI you should have run setup.m from the root of the repo. If that has been completed you should be able to run the profiles with simply `python profiles.py`. By default no profiles will be run, so you need to specify which algorithm you'd like to run profiles for using any combination of `-b` for BOBYQA, `-c` for COBYLA, `-l` for LINCOA, `-n` for NEWUOA, and `-u` for UOBYQA. You can also use `-j#` to change the number of jobs used to run the problems. For example to run both UOBYQA and NEWUOA with 4 jobs, you would run `python profiles.py -nuj4`.
+
+It is not necessary to have a MATLAB window open to run the profiles. Each job will create an instance of the MATLAB engine for Python. Please note that this instance takes up about 500MB of memory so you may want to limit the number of jobs accordingly.
\ No newline at end of file
diff --git a/pyprima/profiles_vs_matlab/bobyqa.txt b/pyprima/profiles_vs_matlab/bobyqa.txt
new file mode 100644
index 0000000000..95e470a484
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/bobyqa.txt
@@ -0,0 +1,220 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EXP2
+EXP2B
+EXPFIT
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HS1
+HS110
+HS2
+HS25
+HS3
+HS38
+HS3MOD
+HS4
+HS45
+HS5
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LOGHAIRY
+LOGROS
+LSC1LS
+LSC2LS
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+S368
+SIM2BQP
+SIMBQP
+SINEVAL
+SISSER
+SNAIL
+SPECAN
+SSI
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
diff --git a/pyprima/profiles_vs_matlab/cobyla.txt b/pyprima/profiles_vs_matlab/cobyla.txt
new file mode 100644
index 0000000000..40cb534f87
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/cobyla.txt
@@ -0,0 +1,654 @@
+AIRCRFTA
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITA
+ALLINITC
+ALLINITU
+ALSOTAME
+ARGAUSS
+AVGASA
+AVGASB
+BARD
+BARDNE
+BEALE
+BEALENE
+BENNETT5
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGS6NE
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOX3NE
+BOXBOD
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNBSNE
+BROWNDEN
+BROWNDENE
+BT1
+BT10
+BT11
+BT12
+BT13
+BT2
+BT3
+BT4
+BT5
+BT6
+BT7
+BT8
+BT9
+BYRDSPHR
+CAMEL6
+CANTILVR
+CB2
+CB3
+CERI651A
+CERI651ALS
+CERI651B
+CERI651BLS
+CERI651C
+CERI651CLS
+CERI651D
+CERI651DLS
+CERI651E
+CERI651ELS
+CHACONN1
+CHACONN2
+CHWIRUT1
+CHWIRUT1LS
+CHWIRUT2
+CHWIRUT2LS
+CLIFF
+CLUSTER
+CLUSTERLS
+CONCON
+CONGIGMZ
+COOLHANS
+COOLHANSLS
+CRESC100
+CRESC132
+CRESC4
+CRESC50
+CSFI1
+CSFI2
+CUBE
+CUBENE
+DANIWOOD
+DANIWOODLS
+DANWOOD
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DEMBO7
+DEMYMALO
+DENSCHNA
+DENSCHNB
+DENSCHNBNE
+DENSCHNC
+DENSCHNCNE
+DENSCHND
+DENSCHNDNE
+DENSCHNE
+DENSCHNENE
+DENSCHNF
+DENSCHNFNE
+DEVGLA1
+DEVGLA1B
+DEVGLA1NE
+DEVGLA2
+DEVGLA2B
+DEVGLA2NE
+DGOSPEC
+DIPIGRI
+DIXCHLNG
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+EGGCRATENE
+ELATTAR
+ELATVIDU
+ELATVIDUB
+ELATVIDUNE
+ENGVAL2
+ENGVAL2NE
+ENSO
+ENSOLS
+EQC
+ERRINBAR
+EXP2
+EXP2B
+EXP2NE
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXPFITNE
+EXTRASIM
+FBRAIN
+FBRAIN2
+FBRAIN2LS
+FBRAIN2NE
+FBRAIN3
+FBRAIN3LS
+FBRAINLS
+FBRAINNE
+FCCU
+FLETCHER
+FLT
+FREURONE
+GAUSS1
+GAUSS1LS
+GAUSS2
+GAUSS2LS
+GAUSS3
+GAUSS3LS
+GAUSSIAN
+GBRAIN
+GBRAINLS
+GENHS28
+GIGOMEZ1
+GIGOMEZ2
+GIGOMEZ3
+GOTTFR
+GROWTH
+GROWTHLS
+GULF
+GULFNE
+HAHN1
+HAHN1LS
+HAIFAS
+HAIRY
+HALDMADS
+HART6
+HATFLDA
+HATFLDANE
+HATFLDB
+HATFLDBNE
+HATFLDD
+HATFLDDNE
+HATFLDE
+HATFLDENE
+HATFLDF
+HATFLDFL
+HATFLDFLNE
+HATFLDFLS
+HATFLDH
+HEART6
+HEART6LS
+HEART8
+HEART8LS
+HELIX
+HELIXNE
+HET-Z
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBC
+HIMMELBCLS
+HIMMELBD
+HIMMELBE
+HIMMELBF
+HIMMELBFNE
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HIMMELP2
+HIMMELP3
+HIMMELP4
+HIMMELP5
+HIMMELP6
+HONG
+HS1
+HS10
+HS100
+HS100LNP
+HS100MOD
+HS101
+HS102
+HS103
+HS104
+HS105
+HS106
+HS107
+HS108
+HS109
+HS11
+HS110
+HS111
+HS111LNP
+HS112
+HS113
+HS114
+HS116
+HS117
+HS118
+HS119
+HS12
+HS13
+HS14
+HS15
+HS16
+HS17
+HS18
+HS19
+HS1NE
+HS2
+HS20
+HS21
+HS21MOD
+HS22
+HS23
+HS24
+HS25
+HS25NE
+HS26
+HS268
+HS27
+HS28
+HS29
+HS2NE
+HS3
+HS30
+HS31
+HS32
+HS33
+HS34
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS39
+HS3MOD
+HS4
+HS40
+HS41
+HS42
+HS43
+HS44
+HS44NEW
+HS45
+HS46
+HS47
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS56
+HS57
+HS59
+HS6
+HS60
+HS61
+HS62
+HS63
+HS64
+HS65
+HS66
+HS68
+HS69
+HS7
+HS70
+HS71
+HS72
+HS73
+HS74
+HS75
+HS76
+HS76I
+HS77
+HS78
+HS79
+HS8
+HS80
+HS81
+HS83
+HS84
+HS85
+HS86
+HS87
+HS88
+HS89
+HS9
+HS90
+HS91
+HS92
+HS93
+HS95
+HS96
+HS97
+HS98
+HS99
+HUBFIT
+HUMPS
+HYPCIR
+INTEQNE
+JENSMP
+JENSMPNE
+JUDGE
+JUDGEB
+JUDGENE
+KIRBY2
+KIRBY2LS
+KIWCRESC
+KOEBHELB
+KOEBHELBNE
+KOWOSB
+KOWOSBNE
+KSIP
+LANCZOS1
+LANCZOS1LS
+LANCZOS2
+LANCZOS2LS
+LANCZOS3
+LANCZOS3LS
+LEVYMONE10
+LEVYMONE5
+LEVYMONE6
+LEVYMONE7
+LEVYMONE8
+LEVYMONE9
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LEWISPOL
+LIN
+LOGHAIRY
+LOGROS
+LOOTSMA
+LOTSCHD
+LSC1
+LSC1LS
+LSC2
+LSC2LS
+LSNNODOC
+LSQFIT
+MADSEN
+MAKELA1
+MAKELA2
+MARATOS
+MARATOSB
+MATRIX2
+MAXLIKA
+MCONCON
+MDHOLE
+MEXHAT
+MEYER3
+MEYER3NE
+MGH09
+MGH09LS
+MGH10
+MGH10LS
+MGH10S
+MGH10SLS
+MGH17
+MGH17LS
+MGH17S
+MGH17SLS
+MIFFLIN1
+MIFFLIN2
+MINMAXBD
+MINMAXRB
+MISRA1A
+MISRA1ALS
+MISRA1B
+MISRA1BLS
+MISRA1C
+MISRA1CLS
+MISRA1D
+MISRA1DLS
+MISTAKE
+MOREBVNE
+MWRIGHT
+NELSON
+NELSONLS
+NYSTROM5
+NYSTROM5C
+ODFITS
+OET1
+OET2
+OET3
+OET4
+OET5
+OET6
+OET7
+OSBORNE1
+OSBORNE2
+OSBORNEA
+OSBORNEB
+OSCIPANE
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1ANE
+PALMER1B
+PALMER1BNE
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER1ENE
+PALMER1NE
+PALMER2
+PALMER2A
+PALMER2ANE
+PALMER2B
+PALMER2BNE
+PALMER2C
+PALMER2E
+PALMER2ENE
+PALMER2NE
+PALMER3
+PALMER3A
+PALMER3ANE
+PALMER3B
+PALMER3BNE
+PALMER3C
+PALMER3E
+PALMER3ENE
+PALMER3NE
+PALMER4
+PALMER4A
+PALMER4ANE
+PALMER4B
+PALMER4BNE
+PALMER4C
+PALMER4E
+PALMER4ENE
+PALMER4NE
+PALMER5A
+PALMER5ANE
+PALMER5B
+PALMER5BNE
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER5ENE
+PALMER6A
+PALMER6ANE
+PALMER6C
+PALMER6E
+PALMER6ENE
+PALMER7A
+PALMER7ANE
+PALMER7C
+PALMER7E
+PALMER7ENE
+PALMER8A
+PALMER8ANE
+PALMER8C
+PALMER8E
+PALMER8ENE
+PARKCH
+PENLT1NE
+PENLT2NE
+PENTAGON
+PFIT1
+PFIT1LS
+PFIT2
+PFIT2LS
+PFIT3
+PFIT3LS
+PFIT4
+PFIT4LS
+POLAK1
+POLAK2
+POLAK3
+POLAK4
+POLAK5
+POLAK6
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBS
+POWELLBSLS
+POWELLSE
+POWELLSQ
+POWELLSQLS
+POWERSUM
+POWERSUMB
+POWERSUMNE
+PRICE3
+PRICE3B
+PRICE3NE
+PRICE4
+PRICE4B
+PRICE4NE
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42
+RAT42LS
+RAT43
+RAT43LS
+RECIPE
+RECIPELS
+RES
+RK23
+ROBOT
+ROSENBR
+ROSENBRTU
+ROSENMMX
+ROSZMAN1
+ROSZMAN1LS
+RSNBRNE
+S268
+S277-280
+S308
+S308NE
+S316-322
+S365
+S365MOD
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SINVALNE
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SNAKE
+SPECAN
+SPECANNE
+SPIRAL
+SSI
+SSINE
+STANCMIN
+STRATEC
+STREG
+STREGNE
+STRTCHDV
+STRTCHDVB
+STRTCHDVNE
+SUPERSIM
+SYNTHES1
+SYNTHES2
+SYNTHES3
+TAME
+TENBARS1
+TENBARS2
+TENBARS3
+TENBARS4
+TFI1
+TFI2
+TFI3
+THURBER
+THURBERLS
+TRIGGER
+TRIGON1
+TRIGON1B
+TRIGON1NE
+TRIGON2
+TRIGON2B
+TRIGON2NE
+TRUSPYR1
+TRUSPYR2
+TRY-B
+TWOBARS
+VARDIMNE
+VESUVIA
+VESUVIALS
+VESUVIO
+VESUVIOLS
+VESUVIOU
+VESUVIOULS
+VIBRBEAM
+VIBRBEAMNE
+WACHBIEG
+WATSON
+WATSONNE
+WAYSEA1
+WAYSEA1B
+WAYSEA1NE
+WAYSEA2
+WAYSEA2B
+WAYSEA2NE
+WEEDS
+WEEDSNE
+WOMFLET
+YFIT
+YFITNE
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
+ZECEVIC3
+ZECEVIC4
+ZY2
diff --git a/pyprima/profiles_vs_matlab/excludelist.py b/pyprima/profiles_vs_matlab/excludelist.py
new file mode 100644
index 0000000000..d996a6ead4
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/excludelist.py
@@ -0,0 +1,133 @@
+def excludelist(problem_type):
+ # As of 20230426, the objective function of HS67 takes infinite time to be evaluated at some
+ # points, e.g., [88.1351318; 12829.9219; 1.0e-5], maybe due to an infinite cycling.
+ excludelist = ['HS76']
+ # ARGLALE results in a segfault from slsqp when trying to project x0. We disable it for now
+ excludelist += ['ARGLALE', 'ARGLBLE', 'ARGLCLE']
+
+ if problem_type == 'unconstrained':
+ # With the following excludelist, there is no unconstrained problem in CUTEst (as of 20230130) with
+ # dimension between 51 and 100.
+ excludelist += [
+ 'ARGTRIGLS',
+ 'BA-L1LS',
+ 'BA-L1SPLS',
+ 'BROWNAL',
+ 'CHNROSNB',
+ 'CHNRSNBM',
+ 'DIAMON2DLS',
+ 'DIAMON3DLS',
+ 'DMN15102LS',
+ 'DMN15103LS',
+ 'DMN15332LS',
+ 'DMN15333LS',
+ 'DMN37142LS',
+ 'DMN37143LS',
+ 'ERRINROS',
+ 'ERRINRSM',
+ 'HYDC20LS',
+ 'LUKSAN11LS',
+ 'LUKSAN12LS',
+ 'LUKSAN13LS',
+ 'LUKSAN14LS',
+ 'LUKSAN15LS',
+ 'LUKSAN16LS',
+ 'LUKSAN17LS',
+ 'LUKSAN22LS',
+ 'LUKSAN21LS',
+ 'LRCOVTYPE',
+ 'MANCINO',
+ 'QING',
+ 'SENSORS',
+ 'TOINTGOR',
+ 'TOINTPSP',
+ 'VARDIM',
+ ]
+ elif problem_type == 'bobyqa':
+ # For the following problems, the classical bobyqa (single-precision) encounters SEGFAULT.
+ excludelist += ['MGH17LS']
+ elif problem_type == 'lincoa':
+ excludelist += [
+ 'DALLASM',
+ 'TARGUS',
+ ]
+ elif problem_type == 'cobyla':
+ # The following problems were observed to take excessive time during tests GitHub Actions and
+ # make the tests run overtime. Some of them may not be very time-consuming during a "plain"
+ # test but become more challenging with some perturbations or variations. The excessive time may
+ # be also a result of infinite cycling encountered by the classical version of cobyla.
+ # The number following the problem is the time in seconds taken by cobyla in a "plain" test on
+ # 20230130, which tested all linearly and nonlinearly constrained problems with at most 100
+ # variables and 10000 constraints. Bound-constrained or unconstrained problems were not tested.
+ excludelist += [
+ 'ACOPP30' ,
+ 'ACOPR30',
+ 'AIRPORT', # 73
+ 'BATCH', # 20
+ 'CHANDHEQ', # 17
+ 'CHEBYQADNE', # 546
+ 'CHNRSBNE', # 18
+ 'CHNRSNBMNE', # 32
+ 'CORE1', # 64
+ 'CRESC100',
+ 'CRESC132',
+ 'CVXQP1', # 54
+ 'DALLASS', # 3 (it takes a long time on GitHub Actions)
+ 'DECONVBNE',
+ 'DECONVC', # In a test on 20230328, the classical cobyla encountered infinite cycling.
+ 'DECONVNE',
+ 'DIAMON2D', # 1415
+ 'DIAMON3D', # 3703
+ 'DMN15102', # 887
+ 'DMN15103', # 3205
+ 'DMN15332', # 838
+ 'DMN15333', # 1441
+ 'DMN37142', # 857
+ 'DMN37143', # 2406
+ 'DUAL1', # 73
+ 'DUAL2', # 30
+ 'DUAL4',
+ 'ERRINRSMNE', # 16
+ 'FBRAIN2',
+ 'FBRAIN2NE',
+ 'FBRAIN3',
+ 'FEEDLOC',
+ 'GOULDQP1',
+ 'HAIFAM', # 173
+ 'HIMMELBI', # 100
+ 'HIMMELBJ',
+ 'HYDCAR20', # 648
+ 'HYDCAR6',
+ 'KISSING2',
+ 'LAKES', # 65
+ 'LEVYMONE', # 15
+ 'LHAIFAM',
+ 'LINSPANH', # 3 (it takes a long time on GitHub Actions)
+ 'LUKSAN11',
+ 'LUKSAN12', # 563
+ 'LUKSAN13', # 508
+ 'LUKSAN14', # 23
+ 'LUKSAN15', # 19
+ 'LUKSAN16', # 17
+ 'LUKSAN17', # 25
+ 'LUKSAN21', # 13
+ 'LUKSAN22', # 19
+ 'MANCINONE',
+ 'MSS1', # 39
+ 'OET5',
+ 'OET6',
+ 'OET7',
+ 'QINGNE',
+ 'QPCBLEND' ,
+ 'SPANHYD', # 15
+ 'SWOPF', # 10
+ 'TAX13322', # 5
+ 'TAXR13322', # 5
+ 'TRO4X4', # 30
+ 'VANDERM1', # 72
+ 'VANDERM2', # 72
+ 'VANDERM3', # 76
+ 'VESUVIO',
+ 'VESUVIOU',
+ ]
+ return excludelist
\ No newline at end of file
diff --git a/pyprima/profiles_vs_matlab/lincoa.txt b/pyprima/profiles_vs_matlab/lincoa.txt
new file mode 100644
index 0000000000..6388432a47
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/lincoa.txt
@@ -0,0 +1,308 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+AVGASA
+AVGASB
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+BT3
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EQC
+EXP2
+EXP2B
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXTRASIM
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+FCCU
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GENHS28
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HATFLDH
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HONG
+HS1
+HS105
+HS110
+HS112
+HS118
+HS119
+HS2
+HS21
+HS21MOD
+HS24
+HS25
+HS268
+HS28
+HS3
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS3MOD
+HS4
+HS41
+HS44
+HS44NEW
+HS45
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS62
+HS76
+HS76I
+HS86
+HS9
+HUBFIT
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+KSIP
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LIN
+LOGHAIRY
+LOGROS
+LOTSCHD
+LSC1LS
+LSC2LS
+LSNNODOC
+LSQFIT
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+ODFITS
+OET1
+OET3
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PENTAGON
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+RES
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S268
+S277-280
+S308
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SPECAN
+SSI
+STANCMIN
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+SUPERSIM
+TAME
+TFI2
+TFI3
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
diff --git a/pyprima/profiles_vs_matlab/matlab_wrapper.m b/pyprima/profiles_vs_matlab/matlab_wrapper.m
new file mode 100644
index 0000000000..310e8d626a
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/matlab_wrapper.m
@@ -0,0 +1,61 @@
+function [x, fx, flags, details] = matlab_wrapper(port, algorithm, options, x0, lb, ub, Aineq, bineq, Aeq, beq, ...
+ num_nl_ineq_con, num_nl_eq_con, port_nonlcon)
+arguments
+ port (1, 1) {mustBeGreaterThan(port, 1024), mustBeInteger} % Ports below 1024 are privileged.
+ algorithm (1, 1) string {mustBeMember(algorithm, {'bobyqa', 'cobyla', 'lincoa', 'newuoa', 'uobyqa'})}
+ options (1, 1) struct
+ x0 (:, 1) double
+ lb (:, 1) double = []
+ ub (:, 1) double = []
+ Aineq (:, :) double = []
+ bineq (:, 1) double = []
+ Aeq (:, :) double = []
+ beq (:, 1) double = []
+ num_nl_ineq_con (1, 1) {mustBeNonnegative} = 0
+ num_nl_eq_con (1, 1) {mustBeNonnegative} = 0
+ port_nonlcon (1, 1) {mustBeGreaterThan(port_nonlcon, 1024), mustBeInteger} = 8602 % Ports below 1024 are privileged.
+end
+
+obj_fun_conn = tcpclient('127.0.0.1', port);
+if algorithm == "bobyqa"
+ [x, fx, flags, details] = bobyqa(@obj_fun, x0, lb, ub, options);
+elseif algorithm == "cobyla"
+ nonlcon_fun_conn = tcpclient('127.0.0.1', port_nonlcon);
+ [x, fx, flags, details] = cobyla(@obj_fun, x0, Aineq, bineq, Aeq, beq, lb, ub, @nonlcon, options);
+ clear nonlcon_fun_conn; % Close the connection
+elseif algorithm == "lincoa"
+ [x, fx, flags, details] = lincoa(@obj_fun, x0, Aineq, bineq, Aeq, beq, lb, ub, options);
+elseif algorithm == "newuoa"
+ [x, fx, flags, details] = newuoa(@obj_fun, x0, options);
+elseif algorithm == "uobyqa"
+ [x, fx, flags, details] = uobyqa(@obj_fun, x0, options);
+end
+
+clear obj_fun_conn; % Close the connection
+
+function f = obj_fun(x)
+ % Write x to the socket
+ write(obj_fun_conn, x, 'double');
+ % Now Python should pick this up and run the function and write the result back to the socket
+ % We read the socket in a blocking manner
+ f = read(obj_fun_conn, 1, 'double');
+end
+
+function [cineq, ceq] = nonlcon(x)
+ % Write x to the socket
+ write(nonlcon_fun_conn, x, 'double');
+ % Now Python should pick this up and run the function and write the result back to the socket
+ % We read the socket in a blocking manner
+ if num_nl_ineq_con == 0
+ cineq = [];
+ else
+ cineq = read(nonlcon_fun_conn, num_nl_ineq_con, 'double');
+ end
+ if num_nl_eq_con == 0
+ ceq = [];
+ else
+ ceq = read(nonlcon_fun_conn, num_nl_eq_con, 'double');
+ end
+end
+
+end
diff --git a/pyprima/profiles_vs_matlab/profiles.py b/pyprima/profiles_vs_matlab/profiles.py
new file mode 100644
index 0000000000..a886a4c0c8
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/profiles.py
@@ -0,0 +1,328 @@
+import numpy as np
+import sys
+import os
+from excludelist import excludelist
+from optiprofiler import set_cutest_problem_options, find_cutest_problems, run_benchmark
+import argparse
+from time import time
+import matlab.engine
+import threading
+import socket
+import struct
+
+
+nondefault_options = lambda n, f0: {
+ 'ftarget' : f0 - 314, # if this is doable, 3.14 otherwise
+ 'maxfev' : 271*n,
+ 'npt' : int(min(3.14*n, n**1.23)),
+ 'rhobeg' : 2.718,
+ 'rhoend' : 3.14*1.0e-4,
+}
+
+
+def get_matlab_options(n, f0):
+ if os.environ.get('NONDEFAULT_MATLAB') == 'True':
+ options = nondefault_options(n, f0)
+ # Change the option name
+ options['maxfun'] = matlab.int32(options.pop('maxfev'))
+ return options
+ return {}
+
+
+def get_python_options(n, f0):
+ if os.environ.get('NONDEFAULT_PYTHON') == 'True':
+ return nondefault_options(n, f0)
+ return {}
+
+
+def get_matlab_engine():
+ '''
+ This function is essentially following the singleton design pattern
+ (https://gameprogrammingpatterns.com/singleton.html).
+
+ Despite the above link recommending against this pattern we use it for
+ two reasons.
+
+ 1) The "lazy-loading" means that the MATLAB engine won't be started until
+ after OptiProfiler has created separate processes via the multiprocessing
+ module. If OptiProfiler stops using multiprocessing this approach may need
+ to be reviewed.
+
+ 2) Starting a MATLAB engine takes as much as 30s, so we only want to do it
+ once per process and keep the resulting engine. If future versions can
+ start up much more quickly then this might not be necessary.
+
+ '''
+ if not hasattr(get_matlab_engine, 'eng'):
+ get_matlab_engine.eng = matlab.engine.start_matlab()
+ return get_matlab_engine.eng
+
+
+def fun_wrapper(server, obj_fun, num_vars):
+ server.listen(1)
+ conn, _ = server.accept()
+ with conn:
+ while True:
+ bufsize = num_vars * 8 # 8 being sizeof(double)
+ flags = socket.MSG_WAITALL # This helps with large (n ~ 5000) problems, i.e. TESTQUAD
+ data = conn.recv(bufsize, flags)
+ if not data: break # This indicates the connection was closed
+ if len(data) < bufsize: raise ValueError('Received incomplete data')
+ x = struct.unpack(f'{num_vars}d', data)
+ x = np.array(x, dtype=np.float64)
+ fx = obj_fun(x)
+ conn.sendall(struct.pack('d', fx))
+
+
+def nl_constraints_wrapper(server, cineq_fun, ceq_fun, num_vars):
+ server.listen(1)
+ conn, _ = server.accept()
+ with conn:
+ while True:
+ bufsize = num_vars * 8 # 8 being sizeof(double)
+ flags = socket.MSG_WAITALL # This helps with large (n ~ 5000) problems
+ data = conn.recv(bufsize, flags)
+ if not data: break # This indicates the connection was closed
+ x = struct.unpack(f'{num_vars}d', data)
+ x = np.array(x, dtype=np.float64)
+ cineq = cineq_fun(x)
+ ceq = ceq_fun(x)
+ if len(cineq) > 0:
+ conn.sendall(struct.pack(f'{len(cineq)}d', *cineq))
+ if len(ceq) > 0:
+ conn.sendall(struct.pack(f'{len(ceq)}d', *ceq))
+
+
+def matlab_uobyqa(fun, x0):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ result = get_matlab_engine().matlab_wrapper(port, 'uobyqa', get_matlab_options(len(x0), f0), x0_m, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_uobyqa(fun, x0):
+ from prima import minimize
+ f0 = fun.__self__._fun(x0)
+ res = minimize(fun, x0, method='uobyqa', options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_newuoa(fun, x0):
+ # We bind to port 0 so that the OS automatically assigns an available port
+ # Then we extract the port number so that we can pass it to the MATLAB engine
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ # We need to reach into the internals of fun in order to call the original function object
+ # so that we don't add to the number of function evaluations. Otherwise OptiProfiler will
+ # end up throwing an exception as we hit max_eval. In the future when OptiProfiler offers
+ # the signature where it provides the problem itself we'll be able to do this without reaching
+ # into the internals like this.
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ result = get_matlab_engine().matlab_wrapper(port, 'newuoa', get_matlab_options(len(x0), f0), x0_m, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_newuoa(fun, x0):
+ from prima import minimize
+ f0 = fun.__self__._fun(x0)
+ res = minimize(fun, x0, method='newuoa', options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_bobyqa(fun, x0, lb, ub):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ result = get_matlab_engine().matlab_wrapper(port, 'bobyqa', get_matlab_options(len(x0), f0), x0_m, lb, ub, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_bobyqa(fun, x0, lb, ub):
+ from prima import minimize
+ from scipy.optimize import Bounds
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ res = minimize(fun, x0, method='bobyqa', bounds=bounds, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_lincoa(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ a_ub = matlab.double(a_ub)
+ b_ub = matlab.double(b_ub)
+ a_eq = matlab.double(a_eq)
+ b_eq = matlab.double(b_eq)
+ result = get_matlab_engine().matlab_wrapper(port, 'lincoa', get_matlab_options(len(x0), f0), x0_m, lb, ub, a_ub, b_ub, a_eq, b_eq, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_lincoa(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq):
+ from prima import minimize, Bounds, LinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ res = minimize(fun, x0, method='lincoa', bounds=bounds, constraints=constraints, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ineq, c_eq):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ with socket.create_server(('localhost', 0)) as server_nonlcon:
+ port_nonlcon = server_nonlcon.getsockname()[1]
+ threading.Thread(target=nl_constraints_wrapper, args=(server_nonlcon, c_ineq, c_eq, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ a_ub = matlab.double(a_ub)
+ b_ub = matlab.double(b_ub)
+ a_eq = matlab.double(a_eq)
+ b_eq = matlab.double(b_eq)
+ c_ineq_x0 = c_ineq(x0)
+ m_c_ineq = c_ineq_x0.size
+ c_eq_x0 = c_eq(x0)
+ m_c_eq = c_eq_x0.size
+ result = get_matlab_engine().matlab_wrapper(port, 'cobyla', get_matlab_options(len(x0), f0), x0_m, lb, ub, a_ub, b_ub, a_eq, b_eq, m_c_ineq, m_c_eq, port_nonlcon, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ineq, c_eq):
+ from prima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ c_ineq_x0 = c_ineq(x0)
+ if c_ineq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_ineq, -np.inf, np.zeros_like(c_ineq_x0)))
+ c_eq_x0 = c_eq(x0)
+ if c_eq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_eq, np.zeros_like(c_eq_x0), np.zeros_like(c_eq_x0)))
+ res = minimize(fun, x0, method='cobyla', bounds=bounds, constraints=constraints, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def get_problems(description):
+ cutest_problem_names = find_cutest_problems(description)
+ return list(filter(lambda x: x not in excludelist(description), cutest_problem_names))
+
+
+if __name__ == '__main__':
+ # If we run this script from a directory other than the one that contains it, pycutest's call to importlib will fail,
+ # unless we insert the current working directory into the path.
+ sys.path.insert(0, os.getcwd())
+ os.environ['PYCUTEST_CACHE'] = os.getcwd()
+
+ parser = argparse.ArgumentParser(description='Generate performance profiles comparing PRIMA MATLAB to PRIMA Python. '
+ 'By default no profiles are run. To run one or more profiles use the flags '
+ 'specified below. Multiple flags may be specified to run multiple profiles, '
+ 'but please note they will be run sequentially.')
+ parser.add_argument('-j', '--n_jobs', type=int, default=None, help='Number of jobs to run in parallel')
+ parser.add_argument('-n', '--newuoa', action='store_true', help='Run the NEWUOA benchmark')
+ parser.add_argument('-u', '--uobyqa', action='store_true', help='Run the UOBYQA benchmark')
+ parser.add_argument('-b', '--bobyqa', action='store_true', help='Run the BOBYQA benchmark')
+ parser.add_argument('-l', '--lincoa', action='store_true', help='Run the LINCOA benchmark')
+ parser.add_argument('-c', '--cobyla', action='store_true', help='Run the COBYLA benchmark')
+ parser.add_argument('--default_only', action='store_true', help='Run only the default options for both MATLAB and Python')
+ args = parser.parse_args()
+
+
+ def run_three_benchmarks(matlab_fun, python_fun, algorithm, cutest_problem_names, default_only, n_jobs):
+ '''
+ Proper validation of both default and nondefault options requires 3 runs: Both default, both nondefault, and
+ one default one nondefault. The first two should look identical, and so the third run confirms that our
+ experiment setup is valid (i.e. it rules out a scenario where even though options are provided, both algorithms
+ end up using default options anyway).
+ '''
+ # Sharing state with multiprocessing is hard when we can't control the function signature,
+ # so we resort to using the environment to pass options.
+ os.environ['NONDEFAULT_MATLAB'] = "False"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ algorithm = algorithm.lower()
+ ALGORITHM = algorithm.upper()
+ project_x0 = algorithm == 'lincoa'
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_default_options', n_jobs=n_jobs, project_x0=project_x0)
+ if not default_only:
+ os.environ['NONDEFAULT_MATLAB'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "True"
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_nondefault_options', n_jobs=n_jobs, project_x0=project_x0)
+ os.environ['NONDEFAULT_MATLAB'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_different_options', n_jobs=n_jobs, project_x0=project_x0)
+
+ if args.newuoa:
+ start = time()
+ print("Running profiles for NEWUOA")
+ with open('uobyqa_newuoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('unconstrained'), cutest_problem_names))
+ run_three_benchmarks(matlab_newuoa, python_newuoa, 'newuoa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed NEWUOA profile in {time() - start:.2f} seconds')
+
+ if args.uobyqa:
+ start = time()
+ print("Running profiles for UOBYQA")
+ with open('uobyqa_newuoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('unconstrained'), cutest_problem_names))
+ run_three_benchmarks(matlab_uobyqa, python_uobyqa, 'uobyqa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed UOBYQA profile in {time() - start:.2f} seconds')
+
+ if args.bobyqa:
+ start = time()
+ print("Running profiles for BOBYQA")
+ with open('bobyqa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('bobyqa'), cutest_problem_names))
+ run_three_benchmarks(matlab_bobyqa, python_bobyqa, 'bobyqa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed BOBYQA profile in {time() - start:.2f} seconds')
+
+ if args.lincoa:
+ start = time()
+ print("Running profiles for LINCOA")
+ with open('lincoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('lincoa'), cutest_problem_names))
+ run_three_benchmarks(matlab_lincoa, python_lincoa, 'lincoa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed LINCOA profile in {time() - start:.2f} seconds')
+
+ if args.cobyla:
+ start = time()
+ print("Running profiles for COBYLA")
+ with open('cobyla.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('cobyla'), cutest_problem_names))
+ run_three_benchmarks(matlab_cobyla, python_cobyla, 'cobyla', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed COBYLA profile in {time() - start:.2f} seconds')
diff --git a/pyprima/profiles_vs_matlab/uobyqa_newuoa.txt b/pyprima/profiles_vs_matlab/uobyqa_newuoa.txt
new file mode 100644
index 0000000000..8a3e876b3e
--- /dev/null
+++ b/pyprima/profiles_vs_matlab/uobyqa_newuoa.txt
@@ -0,0 +1,133 @@
+AKIVA
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS6
+BOX3
+BOXBODLS
+BRKMCC
+BROWNBS
+BROWNDEN
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA2
+DJTL
+ECKERLE4LS
+EGGCRATE
+ELATVIDU
+ENGVAL2
+ENSOLS
+EXP2
+EXPFIT
+FBRAIN3LS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HUMPS
+JENSMP
+JUDGE
+KIRBY2LS
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LOGHAIRY
+LSC1LS
+LSC2LS
+MARATOSB
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+PALMER1C
+PALMER1D
+PALMER2C
+PALMER3C
+PALMER4C
+PALMER5C
+PALMER5D
+PALMER6C
+PALMER7C
+PALMER8C
+PARKCH
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+PRICE3
+PRICE4
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+SINEVAL
+SISSER
+SNAIL
+SSI
+STRATEC
+STREG
+STRTCHDV
+THURBERLS
+TRIGON1
+TRIGON2
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA2
+YFITU
+ZANGWIL2
diff --git a/pyprima/profiles_vs_python_bindings/.gitignore b/pyprima/profiles_vs_python_bindings/.gitignore
new file mode 100644
index 0000000000..fa9567d1b0
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/.gitignore
@@ -0,0 +1,2 @@
+pycutest_cache_holder
+out
diff --git a/pyprima/profiles_vs_python_bindings/README.md b/pyprima/profiles_vs_python_bindings/README.md
new file mode 100644
index 0000000000..3325d336ea
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/README.md
@@ -0,0 +1,5 @@
+This folder contains scripts and utilities to run performance profiles of pyPRIMA Python bindings against the PRIMA Python bindings.
+
+In general we expect the performance profiles to show a straight line for both the output based and history based profiles since the underlying code is the same. However since there are bound to be some differences in how Python and Fortran handle floating point arithmetic we expect to see a few cases where one might perform slightly "better" than the other.
+
+In order to use this you will need both pyPRIMA and the Python bindings available in your environment. See the respective folders for installation instructions. Once they are both available you can simple run `python profiles.py`.
diff --git a/pyprima/profiles_vs_python_bindings/bobyqa.txt b/pyprima/profiles_vs_python_bindings/bobyqa.txt
new file mode 100644
index 0000000000..95e470a484
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/bobyqa.txt
@@ -0,0 +1,220 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EXP2
+EXP2B
+EXPFIT
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HS1
+HS110
+HS2
+HS25
+HS3
+HS38
+HS3MOD
+HS4
+HS45
+HS5
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LOGHAIRY
+LOGROS
+LSC1LS
+LSC2LS
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+S368
+SIM2BQP
+SIMBQP
+SINEVAL
+SISSER
+SNAIL
+SPECAN
+SSI
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
diff --git a/pyprima/profiles_vs_python_bindings/cobyla.txt b/pyprima/profiles_vs_python_bindings/cobyla.txt
new file mode 100644
index 0000000000..40cb534f87
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/cobyla.txt
@@ -0,0 +1,654 @@
+AIRCRFTA
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITA
+ALLINITC
+ALLINITU
+ALSOTAME
+ARGAUSS
+AVGASA
+AVGASB
+BARD
+BARDNE
+BEALE
+BEALENE
+BENNETT5
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGS6NE
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOX3NE
+BOXBOD
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNBSNE
+BROWNDEN
+BROWNDENE
+BT1
+BT10
+BT11
+BT12
+BT13
+BT2
+BT3
+BT4
+BT5
+BT6
+BT7
+BT8
+BT9
+BYRDSPHR
+CAMEL6
+CANTILVR
+CB2
+CB3
+CERI651A
+CERI651ALS
+CERI651B
+CERI651BLS
+CERI651C
+CERI651CLS
+CERI651D
+CERI651DLS
+CERI651E
+CERI651ELS
+CHACONN1
+CHACONN2
+CHWIRUT1
+CHWIRUT1LS
+CHWIRUT2
+CHWIRUT2LS
+CLIFF
+CLUSTER
+CLUSTERLS
+CONCON
+CONGIGMZ
+COOLHANS
+COOLHANSLS
+CRESC100
+CRESC132
+CRESC4
+CRESC50
+CSFI1
+CSFI2
+CUBE
+CUBENE
+DANIWOOD
+DANIWOODLS
+DANWOOD
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DEMBO7
+DEMYMALO
+DENSCHNA
+DENSCHNB
+DENSCHNBNE
+DENSCHNC
+DENSCHNCNE
+DENSCHND
+DENSCHNDNE
+DENSCHNE
+DENSCHNENE
+DENSCHNF
+DENSCHNFNE
+DEVGLA1
+DEVGLA1B
+DEVGLA1NE
+DEVGLA2
+DEVGLA2B
+DEVGLA2NE
+DGOSPEC
+DIPIGRI
+DIXCHLNG
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+EGGCRATENE
+ELATTAR
+ELATVIDU
+ELATVIDUB
+ELATVIDUNE
+ENGVAL2
+ENGVAL2NE
+ENSO
+ENSOLS
+EQC
+ERRINBAR
+EXP2
+EXP2B
+EXP2NE
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXPFITNE
+EXTRASIM
+FBRAIN
+FBRAIN2
+FBRAIN2LS
+FBRAIN2NE
+FBRAIN3
+FBRAIN3LS
+FBRAINLS
+FBRAINNE
+FCCU
+FLETCHER
+FLT
+FREURONE
+GAUSS1
+GAUSS1LS
+GAUSS2
+GAUSS2LS
+GAUSS3
+GAUSS3LS
+GAUSSIAN
+GBRAIN
+GBRAINLS
+GENHS28
+GIGOMEZ1
+GIGOMEZ2
+GIGOMEZ3
+GOTTFR
+GROWTH
+GROWTHLS
+GULF
+GULFNE
+HAHN1
+HAHN1LS
+HAIFAS
+HAIRY
+HALDMADS
+HART6
+HATFLDA
+HATFLDANE
+HATFLDB
+HATFLDBNE
+HATFLDD
+HATFLDDNE
+HATFLDE
+HATFLDENE
+HATFLDF
+HATFLDFL
+HATFLDFLNE
+HATFLDFLS
+HATFLDH
+HEART6
+HEART6LS
+HEART8
+HEART8LS
+HELIX
+HELIXNE
+HET-Z
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBC
+HIMMELBCLS
+HIMMELBD
+HIMMELBE
+HIMMELBF
+HIMMELBFNE
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HIMMELP2
+HIMMELP3
+HIMMELP4
+HIMMELP5
+HIMMELP6
+HONG
+HS1
+HS10
+HS100
+HS100LNP
+HS100MOD
+HS101
+HS102
+HS103
+HS104
+HS105
+HS106
+HS107
+HS108
+HS109
+HS11
+HS110
+HS111
+HS111LNP
+HS112
+HS113
+HS114
+HS116
+HS117
+HS118
+HS119
+HS12
+HS13
+HS14
+HS15
+HS16
+HS17
+HS18
+HS19
+HS1NE
+HS2
+HS20
+HS21
+HS21MOD
+HS22
+HS23
+HS24
+HS25
+HS25NE
+HS26
+HS268
+HS27
+HS28
+HS29
+HS2NE
+HS3
+HS30
+HS31
+HS32
+HS33
+HS34
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS39
+HS3MOD
+HS4
+HS40
+HS41
+HS42
+HS43
+HS44
+HS44NEW
+HS45
+HS46
+HS47
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS56
+HS57
+HS59
+HS6
+HS60
+HS61
+HS62
+HS63
+HS64
+HS65
+HS66
+HS68
+HS69
+HS7
+HS70
+HS71
+HS72
+HS73
+HS74
+HS75
+HS76
+HS76I
+HS77
+HS78
+HS79
+HS8
+HS80
+HS81
+HS83
+HS84
+HS85
+HS86
+HS87
+HS88
+HS89
+HS9
+HS90
+HS91
+HS92
+HS93
+HS95
+HS96
+HS97
+HS98
+HS99
+HUBFIT
+HUMPS
+HYPCIR
+INTEQNE
+JENSMP
+JENSMPNE
+JUDGE
+JUDGEB
+JUDGENE
+KIRBY2
+KIRBY2LS
+KIWCRESC
+KOEBHELB
+KOEBHELBNE
+KOWOSB
+KOWOSBNE
+KSIP
+LANCZOS1
+LANCZOS1LS
+LANCZOS2
+LANCZOS2LS
+LANCZOS3
+LANCZOS3LS
+LEVYMONE10
+LEVYMONE5
+LEVYMONE6
+LEVYMONE7
+LEVYMONE8
+LEVYMONE9
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LEWISPOL
+LIN
+LOGHAIRY
+LOGROS
+LOOTSMA
+LOTSCHD
+LSC1
+LSC1LS
+LSC2
+LSC2LS
+LSNNODOC
+LSQFIT
+MADSEN
+MAKELA1
+MAKELA2
+MARATOS
+MARATOSB
+MATRIX2
+MAXLIKA
+MCONCON
+MDHOLE
+MEXHAT
+MEYER3
+MEYER3NE
+MGH09
+MGH09LS
+MGH10
+MGH10LS
+MGH10S
+MGH10SLS
+MGH17
+MGH17LS
+MGH17S
+MGH17SLS
+MIFFLIN1
+MIFFLIN2
+MINMAXBD
+MINMAXRB
+MISRA1A
+MISRA1ALS
+MISRA1B
+MISRA1BLS
+MISRA1C
+MISRA1CLS
+MISRA1D
+MISRA1DLS
+MISTAKE
+MOREBVNE
+MWRIGHT
+NELSON
+NELSONLS
+NYSTROM5
+NYSTROM5C
+ODFITS
+OET1
+OET2
+OET3
+OET4
+OET5
+OET6
+OET7
+OSBORNE1
+OSBORNE2
+OSBORNEA
+OSBORNEB
+OSCIPANE
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1ANE
+PALMER1B
+PALMER1BNE
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER1ENE
+PALMER1NE
+PALMER2
+PALMER2A
+PALMER2ANE
+PALMER2B
+PALMER2BNE
+PALMER2C
+PALMER2E
+PALMER2ENE
+PALMER2NE
+PALMER3
+PALMER3A
+PALMER3ANE
+PALMER3B
+PALMER3BNE
+PALMER3C
+PALMER3E
+PALMER3ENE
+PALMER3NE
+PALMER4
+PALMER4A
+PALMER4ANE
+PALMER4B
+PALMER4BNE
+PALMER4C
+PALMER4E
+PALMER4ENE
+PALMER4NE
+PALMER5A
+PALMER5ANE
+PALMER5B
+PALMER5BNE
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER5ENE
+PALMER6A
+PALMER6ANE
+PALMER6C
+PALMER6E
+PALMER6ENE
+PALMER7A
+PALMER7ANE
+PALMER7C
+PALMER7E
+PALMER7ENE
+PALMER8A
+PALMER8ANE
+PALMER8C
+PALMER8E
+PALMER8ENE
+PARKCH
+PENLT1NE
+PENLT2NE
+PENTAGON
+PFIT1
+PFIT1LS
+PFIT2
+PFIT2LS
+PFIT3
+PFIT3LS
+PFIT4
+PFIT4LS
+POLAK1
+POLAK2
+POLAK3
+POLAK4
+POLAK5
+POLAK6
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBS
+POWELLBSLS
+POWELLSE
+POWELLSQ
+POWELLSQLS
+POWERSUM
+POWERSUMB
+POWERSUMNE
+PRICE3
+PRICE3B
+PRICE3NE
+PRICE4
+PRICE4B
+PRICE4NE
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42
+RAT42LS
+RAT43
+RAT43LS
+RECIPE
+RECIPELS
+RES
+RK23
+ROBOT
+ROSENBR
+ROSENBRTU
+ROSENMMX
+ROSZMAN1
+ROSZMAN1LS
+RSNBRNE
+S268
+S277-280
+S308
+S308NE
+S316-322
+S365
+S365MOD
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SINVALNE
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SNAKE
+SPECAN
+SPECANNE
+SPIRAL
+SSI
+SSINE
+STANCMIN
+STRATEC
+STREG
+STREGNE
+STRTCHDV
+STRTCHDVB
+STRTCHDVNE
+SUPERSIM
+SYNTHES1
+SYNTHES2
+SYNTHES3
+TAME
+TENBARS1
+TENBARS2
+TENBARS3
+TENBARS4
+TFI1
+TFI2
+TFI3
+THURBER
+THURBERLS
+TRIGGER
+TRIGON1
+TRIGON1B
+TRIGON1NE
+TRIGON2
+TRIGON2B
+TRIGON2NE
+TRUSPYR1
+TRUSPYR2
+TRY-B
+TWOBARS
+VARDIMNE
+VESUVIA
+VESUVIALS
+VESUVIO
+VESUVIOLS
+VESUVIOU
+VESUVIOULS
+VIBRBEAM
+VIBRBEAMNE
+WACHBIEG
+WATSON
+WATSONNE
+WAYSEA1
+WAYSEA1B
+WAYSEA1NE
+WAYSEA2
+WAYSEA2B
+WAYSEA2NE
+WEEDS
+WEEDSNE
+WOMFLET
+YFIT
+YFITNE
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
+ZECEVIC3
+ZECEVIC4
+ZY2
diff --git a/pyprima/profiles_vs_python_bindings/excludelist.py b/pyprima/profiles_vs_python_bindings/excludelist.py
new file mode 100644
index 0000000000..d996a6ead4
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/excludelist.py
@@ -0,0 +1,133 @@
+def excludelist(problem_type):
+ # As of 20230426, the objective function of HS67 takes infinite time to be evaluated at some
+ # points, e.g., [88.1351318; 12829.9219; 1.0e-5], maybe due to an infinite cycling.
+ excludelist = ['HS76']
+ # ARGLALE results in a segfault from slsqp when trying to project x0. We disable it for now
+ excludelist += ['ARGLALE', 'ARGLBLE', 'ARGLCLE']
+
+ if problem_type == 'unconstrained':
+ # With the following excludelist, there is no unconstrained problem in CUTEst (as of 20230130) with
+ # dimension between 51 and 100.
+ excludelist += [
+ 'ARGTRIGLS',
+ 'BA-L1LS',
+ 'BA-L1SPLS',
+ 'BROWNAL',
+ 'CHNROSNB',
+ 'CHNRSNBM',
+ 'DIAMON2DLS',
+ 'DIAMON3DLS',
+ 'DMN15102LS',
+ 'DMN15103LS',
+ 'DMN15332LS',
+ 'DMN15333LS',
+ 'DMN37142LS',
+ 'DMN37143LS',
+ 'ERRINROS',
+ 'ERRINRSM',
+ 'HYDC20LS',
+ 'LUKSAN11LS',
+ 'LUKSAN12LS',
+ 'LUKSAN13LS',
+ 'LUKSAN14LS',
+ 'LUKSAN15LS',
+ 'LUKSAN16LS',
+ 'LUKSAN17LS',
+ 'LUKSAN22LS',
+ 'LUKSAN21LS',
+ 'LRCOVTYPE',
+ 'MANCINO',
+ 'QING',
+ 'SENSORS',
+ 'TOINTGOR',
+ 'TOINTPSP',
+ 'VARDIM',
+ ]
+ elif problem_type == 'bobyqa':
+ # For the following problems, the classical bobyqa (single-precision) encounters SEGFAULT.
+ excludelist += ['MGH17LS']
+ elif problem_type == 'lincoa':
+ excludelist += [
+ 'DALLASM',
+ 'TARGUS',
+ ]
+ elif problem_type == 'cobyla':
+ # The following problems were observed to take excessive time during tests GitHub Actions and
+ # make the tests run overtime. Some of them may not be very time-consuming during a "plain"
+ # test but become more challenging with some perturbations or variations. The excessive time may
+ # be also a result of infinite cycling encountered by the classical version of cobyla.
+ # The number following the problem is the time in seconds taken by cobyla in a "plain" test on
+ # 20230130, which tested all linearly and nonlinearly constrained problems with at most 100
+ # variables and 10000 constraints. Bound-constrained or unconstrained problems were not tested.
+ excludelist += [
+ 'ACOPP30' ,
+ 'ACOPR30',
+ 'AIRPORT', # 73
+ 'BATCH', # 20
+ 'CHANDHEQ', # 17
+ 'CHEBYQADNE', # 546
+ 'CHNRSBNE', # 18
+ 'CHNRSNBMNE', # 32
+ 'CORE1', # 64
+ 'CRESC100',
+ 'CRESC132',
+ 'CVXQP1', # 54
+ 'DALLASS', # 3 (it takes a long time on GitHub Actions)
+ 'DECONVBNE',
+ 'DECONVC', # In a test on 20230328, the classical cobyla encountered infinite cycling.
+ 'DECONVNE',
+ 'DIAMON2D', # 1415
+ 'DIAMON3D', # 3703
+ 'DMN15102', # 887
+ 'DMN15103', # 3205
+ 'DMN15332', # 838
+ 'DMN15333', # 1441
+ 'DMN37142', # 857
+ 'DMN37143', # 2406
+ 'DUAL1', # 73
+ 'DUAL2', # 30
+ 'DUAL4',
+ 'ERRINRSMNE', # 16
+ 'FBRAIN2',
+ 'FBRAIN2NE',
+ 'FBRAIN3',
+ 'FEEDLOC',
+ 'GOULDQP1',
+ 'HAIFAM', # 173
+ 'HIMMELBI', # 100
+ 'HIMMELBJ',
+ 'HYDCAR20', # 648
+ 'HYDCAR6',
+ 'KISSING2',
+ 'LAKES', # 65
+ 'LEVYMONE', # 15
+ 'LHAIFAM',
+ 'LINSPANH', # 3 (it takes a long time on GitHub Actions)
+ 'LUKSAN11',
+ 'LUKSAN12', # 563
+ 'LUKSAN13', # 508
+ 'LUKSAN14', # 23
+ 'LUKSAN15', # 19
+ 'LUKSAN16', # 17
+ 'LUKSAN17', # 25
+ 'LUKSAN21', # 13
+ 'LUKSAN22', # 19
+ 'MANCINONE',
+ 'MSS1', # 39
+ 'OET5',
+ 'OET6',
+ 'OET7',
+ 'QINGNE',
+ 'QPCBLEND' ,
+ 'SPANHYD', # 15
+ 'SWOPF', # 10
+ 'TAX13322', # 5
+ 'TAXR13322', # 5
+ 'TRO4X4', # 30
+ 'VANDERM1', # 72
+ 'VANDERM2', # 72
+ 'VANDERM3', # 76
+ 'VESUVIO',
+ 'VESUVIOU',
+ ]
+ return excludelist
\ No newline at end of file
diff --git a/pyprima/profiles_vs_python_bindings/explorer.py b/pyprima/profiles_vs_python_bindings/explorer.py
new file mode 100644
index 0000000000..f903bcea36
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/explorer.py
@@ -0,0 +1,85 @@
+import pycutest
+from pyprima import minimize as pyprima_minimize
+from prima import minimize, LinearConstraint, Bounds, NonlinearConstraint
+from optiprofiler.problems import load_cutest_problem
+import numpy as np
+import debugpy
+import sys
+
+np.set_printoptions(precision=53, floatmode='fixed', suppress=False)
+
+debug = input("Type Y to debug, ENTER to continue: ")
+if debug == 'Y':
+ print("Waiting for debugger to attach...")
+ debugpy.listen(5678)
+ debugpy.wait_for_client()
+
+sys.modules['pyprima'].common.linalg.COMPARING = False
+problem_name = 'POLAK3'
+problem = load_cutest_problem(problem_name)
+
+if debug == 'Y':
+ debugpy.breakpoint()
+constraints = []
+if problem.m_linear_ub > 0:
+ print("Adding linear inequality constraints")
+ constraints.append(LinearConstraint(problem.a_ub, -np.inf, problem.b_ub))
+if problem.m_linear_eq > 0:
+ print("Adding linear equality constraints")
+ constraints.append(LinearConstraint(problem.a_eq, problem.b_eq, problem.b_eq))
+if problem.m_nonlinear_ub > 0:
+ print("Adding nonlinear inequality constraints")
+ constraints.append(NonlinearConstraint(problem.c_ub, -np.inf, np.zeros(problem.m_nonlinear_ub)))
+if problem.m_nonlinear_eq > 0:
+ print("Adding nonlinear equality constraints")
+ constraints.append(NonlinearConstraint(problem.c_eq, np.zeros(problem.m_nonlinear_eq), np.zeros(problem.m_nonlinear_eq)))
+bounds = Bounds(problem.lb, problem.ub)
+
+x0 = problem.x0
+f0 = problem.fun(x0)
+
+nondefault_options = lambda n, f0: {
+ 'ftarget' : f0 - 314, # if this is doable, 3.14 otherwise
+ 'maxfev' : 271*n,
+ # 'npt' : int(min(3.14*n, n**1.23)),
+ 'rhobeg' : 2.71828,
+ # 'ctol': 2e-4,
+ 'rhoend' : 3.14e-4,
+ 'iprint' : 1
+}
+
+
+python_options = nondefault_options(len(x0), f0)
+# del python_options['npt']
+
+
+fortran_options = nondefault_options(len(x0), f0)
+
+# def fun(x):
+# return x[0]**2 + abs(x[1])**3
+
+# def con1(x):
+# return x[0]**2 + x[1]**2 - 25
+
+# def con2(x):
+# return -con1(x)
+
+# x0 = [np.sqrt(25 - (2.0/3)**2), 2.0/3 + 1e-4]
+
+# bounds=None
+
+# constraints = [NonlinearConstraint(con1, -np.inf, 0),
+# NonlinearConstraint(con2, -np.inf, 0)]
+
+# print(con1(x0))
+
+fun = problem.fun
+
+if sys.argv[1] == 'p':
+ result = pyprima_minimize(fun, x0, method='cobyla', options=python_options, constraints=constraints, bounds=bounds)
+ print(result.cstrv)
+ from pyprima.common.linalg import matprod
+ print(matprod.counter)
+else:
+ result = minimize(fun, x0, method='cobyla', options=fortran_options, constraints=constraints, bounds=bounds)
+ print(result.maxcv)
diff --git a/pyprima/profiles_vs_python_bindings/jankiprofiler.py b/pyprima/profiles_vs_python_bindings/jankiprofiler.py
new file mode 100644
index 0000000000..33b2f9b8b5
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/jankiprofiler.py
@@ -0,0 +1,76 @@
+from scipy.optimize import minimize as scipy_minimize
+from pyprima import minimize, LinearConstraint, Bounds, NonlinearConstraint
+from optiprofiler.problems import load_cutest_problem
+from optiprofiler.utils import ProblemError
+import numpy as np
+import debugpy
+from time import time
+
+
+
+debugpy.breakpoint()
+def get_constraints(problem):
+ constraints = []
+ if problem.m_linear_ub > 0:
+ constraints.append(LinearConstraint(problem.a_ub, -np.inf, problem.b_ub))
+ if problem.m_linear_eq > 0:
+ constraints.append(LinearConstraint(problem.a_eq, -np.inf, problem.b_eq))
+ constraints.append(LinearConstraint(-problem.a_eq, -np.inf, problem.b_eq))
+ if problem.m_nonlinear_ub > 0:
+ constraints.append(NonlinearConstraint(problem.c_ub, -np.inf, np.zeros(problem.m_nonlinear_ub)))
+ if problem.m_nonlinear_eq > 0:
+ constraints.append(NonlinearConstraint(problem.c_eq, -np.inf, np.zeros(problem.m_nonlinear_eq)))
+ constraints.append(NonlinearConstraint(lambda x: -problem.c_eq(x), -np.inf, np.zeros(problem.m_nonlinear_eq)))
+ return constraints
+
+
+nondefault_options = lambda n, f0: {
+ 'ftarget' : f0 - 314, # if this is doable, 3.14 otherwise
+ 'maxfev' : 271*n,
+ # 'npt' : int(min(3.14*n, n**1.23)),
+ 'rhobeg' : 2.71828,
+ # 'ctol': 2e-4,
+ 'rhoend' : 3.14e-4,
+ # 'iprint' : 1
+}
+
+with open('cobyla.txt') as f:
+ problems = f.read().splitlines()
+
+
+f = open('results.csv', 'w')
+f.write(", ".join(["Problem", "PyPRIMA result", "SciPy result", "Error", "Error != 0", "PyPRIMA cstrv", "SciPy cstrv", "Error", "PyPRIMA nfev", "SciPy nfev", "PyPRIMA s", "SciPy s, Speedup (<1)/Slowdown (>1)"]))
+f.write("\n")
+
+
+for problem_name in problems:
+ try:
+ problem = load_cutest_problem(problem_name)
+ except ProblemError:
+ continue
+ cons = get_constraints(problem)
+ bounds = Bounds(problem.lb, problem.ub)
+ options = {} #nondefault_options(problem.n, problem.fun(problem.x0))
+ print("Solving with bindings")
+ time1 = time()
+ result_1 = minimize(problem.fun, problem.x0, method='cobyla', options=options, constraints=cons, bounds=bounds)
+ time1 = (time() - time1)/result_1.nf
+ print("Solving with scipy")
+ options = {} #nondefault_options(problem.n, problem.fun(problem.x0))
+ time2 = time()
+ result_2 = scipy_minimize(problem.fun, problem.x0, method='cobyla', options=options, constraints=cons, bounds=bounds)
+ time2 = (time() - time2)/result_2.nfev
+
+ # Do some math. The math is designed such that a negative value for error means the
+ # first algorithm tested is better.
+ funerror = (result_1.f - result_2.fun)/abs(result_1.f) if result_1.f != 0 else 0
+ funerror0 = funerror != 0
+ cstrerror = (result_1.cstrv - result_2.maxcv)/abs(result_1.cstrv) if result_1.cstrv != 0 else 0
+ speed_change = time1/time2 # If first one is faster, this is < 1
+ f.write(f'{problem_name: <11}, {result_1.f: <23}, {result_2.fun: <23}, {funerror*100: <8.2f}, {funerror0}, {result_1.cstrv: <23}, {result_2.maxcv: <23}, {cstrerror*100: <8.2f}, {result_1.nf: <5}, {result_2.nfev: <5}, {time1: <5}, {time2: <5}, {speed_change: <5}\n')
+ f.flush()
+
+
+ # for result in results:
+ # problem_name, result_1_fun, presult_f, funerror, result_1_maxcv, presult_cstrv, cstrerror, result_1_nfev, presult_nf = result
+ # f.write(f'{problem_name: <11}, {result_1_fun: <23}, {presult_f: <23}, {funerror*100: <8.2f}, {result_1_maxcv: <23}, {presult_cstrv: <23}, {cstrerror*100: <8.2f}, {result_1_nfev: <5}, {presult_nf: <5}\n')
diff --git a/pyprima/profiles_vs_python_bindings/lincoa.txt b/pyprima/profiles_vs_python_bindings/lincoa.txt
new file mode 100644
index 0000000000..6388432a47
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/lincoa.txt
@@ -0,0 +1,308 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+AVGASA
+AVGASB
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+BT3
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EQC
+EXP2
+EXP2B
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXTRASIM
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+FCCU
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GENHS28
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HATFLDH
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HONG
+HS1
+HS105
+HS110
+HS112
+HS118
+HS119
+HS2
+HS21
+HS21MOD
+HS24
+HS25
+HS268
+HS28
+HS3
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS3MOD
+HS4
+HS41
+HS44
+HS44NEW
+HS45
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS62
+HS76
+HS76I
+HS86
+HS9
+HUBFIT
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+KSIP
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LIN
+LOGHAIRY
+LOGROS
+LOTSCHD
+LSC1LS
+LSC2LS
+LSNNODOC
+LSQFIT
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+ODFITS
+OET1
+OET3
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PENTAGON
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+RES
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S268
+S277-280
+S308
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SPECAN
+SSI
+STANCMIN
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+SUPERSIM
+TAME
+TFI2
+TFI3
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
diff --git a/pyprima/profiles_vs_python_bindings/n1.txt b/pyprima/profiles_vs_python_bindings/n1.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pyprima/profiles_vs_python_bindings/n2.txt b/pyprima/profiles_vs_python_bindings/n2.txt
new file mode 100644
index 0000000000..a6cb26804b
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/n2.txt
@@ -0,0 +1,122 @@
+ AKIVA
+ ALSOTAME
+ BEALE
+ BOOTH
+ BOX2
+ BOXBOD
+ BOXBODLS
+ BRKMCC
+ BROWNBS
+ BT1
+ BT10
+ CAMEL6
+ CLIFF
+ CLUSTER
+ CUBE
+ CUBENE
+ DANWOOD
+ DANWOODLS
+ DENSCHNB
+ DENSCHNC
+ DENSCHNF
+ DJTL
+ EXPFIT
+ EXTRASIM
+ FBRAIN
+ FBRAINLS
+ FLT
+ FREURONE
+ GBRAIN
+ GBRAINLS
+ GOTTFR
+ HAIRY
+ HET-Z
+ HILBERTA
+ HIMMELBA
+ HIMMELBC
+ HIMMELBD
+ HIMMELBG
+ HIMMELBH
+ HIMMELP1
+ HIMMELP2
+ HIMMELP3
+ HIMMELP4
+ HIMMELP5
+ HIMMELP6
+ HS1
+ HS10
+ HS11
+ HS12
+ HS13
+ HS14
+ HS15
+ HS16
+ HS17
+ HS18
+ HS19
+ HS2
+ HS20
+ HS21
+ HS22
+ HS23
+ HS24
+ HS3
+ HS35MOD
+ HS3MOD
+ HS4
+ HS5
+ HS57
+ HS59
+ HS6
+ HS7
+ HS8
+ HS88
+ HS9
+ HUBFIT
+ HUMPS
+ HYPCIR
+ JENSMP
+ JENSMPNE
+ LOGHAIRY
+ LOGROS
+ LSQFIT
+ MARATOS
+ MARATOSB
+ MDHOLE
+ MEXHAT
+ MISRA1A
+ MISRA1ALS
+ MISRA1B
+ MISRA1BLS
+ MISRA1C
+ MISRA1CLS
+ MISRA1D
+ POWELLBS
+ POWELLBSLS
+ POWELLSQ
+ PT
+ ROSENBR
+ ROSENBRTU
+ RSNBRNE
+ S308
+ S316-322
+ SIMBQP
+ SIMPLLPA
+ SIMPLLPB
+ SINEVAL
+ SINVALNE
+ SIPOW1
+ SIPOW1M
+ SIPOW2
+ SIPOW2M
+ SISSER
+ SNAIL
+ SNAKE
+ SUPERSIM
+ TAME
+ TRY-B
+ TWOBARS
+ ZANGWIL2
+ ZECEVIC2
+ ZECEVIC3
+ ZECEVIC4
diff --git a/pyprima/profiles_vs_python_bindings/n3.txt b/pyprima/profiles_vs_python_bindings/n3.txt
new file mode 100644
index 0000000000..8c01c0333c
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/n3.txt
@@ -0,0 +1,118 @@
+ ALLINIT
+ ALLINITA
+ ALLINITC
+ ARGAUSS
+ BARD
+ BARDNE
+ BENNETT5
+ BENNETT5LS
+ BIGGS3
+ BOX3
+ BOX3NE
+ BT2
+ BT4
+ BYRDSPHR
+ CB2
+ CB3
+ CHACONN1
+ CHACONN2
+ CHWIRUT1
+ CHWIRUT1LS
+ CHWIRUT2
+ CHWIRUT2LS
+ CONGIGMZ
+ DEMYMALO
+ DENSCHND
+ DENSCHNE
+ ECKERLE4
+ ECKERLE4LS
+ ENGVAL2
+ GAUSSIAN
+ GIGOMEZ1
+ GIGOMEZ2
+ GIGOMEZ3
+ GROWTH
+ GROWTHLS
+ GULF
+ GULFNE
+ HATFLDD
+ HATFLDE
+ HATFLDF
+ HATFLDFL
+ HELIX
+ HELIXNE
+ HIELOW
+ HIMMELBE
+ HS25
+ HS26
+ HS27
+ HS28
+ HS29
+ HS30
+ HS31
+ HS32
+ HS33
+ HS34
+ HS35
+ HS35I
+ HS36
+ HS37
+ HS60
+ HS61
+ HS62
+ HS63
+ HS64
+ HS65
+ HS66
+ HS89
+ KIWCRESC
+ KOEBHELB
+ LOOTSMA
+ LSC1
+ LSC1LS
+ LSC2
+ LSC2LS
+ MADSEN
+ MAKELA1
+ MAKELA2
+ MEYER3
+ MEYER3NE
+ MGH10
+ MGH10LS
+ MGH10S
+ MIFFLIN1
+ MIFFLIN2
+ MINMAXRB
+ NELSON
+ NELSONLS
+ OET1
+ OET2
+ PFIT1
+ PFIT1LS
+ PFIT2
+ PFIT2LS
+ PFIT3
+ PFIT3LS
+ PFIT4
+ PFIT4LS
+ POLAK1
+ POLAK4
+ POLAK5
+ RAT42
+ RAT42LS
+ RECIPE
+ SPIRAL
+ SSI
+ SSINE
+ STANCMIN
+ TFI1
+ TFI2
+ TFI3
+ WACHBIEG
+ WEEDS
+ WOMFLET
+ YFIT
+ YFITNE
+ YFITU
+ ZANGWIL3
+ ZY2
diff --git a/pyprima/profiles_vs_python_bindings/profiles.py b/pyprima/profiles_vs_python_bindings/profiles.py
new file mode 100644
index 0000000000..4608f28333
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/profiles.py
@@ -0,0 +1,122 @@
+import numpy as np
+import sys
+import os
+from excludelist import excludelist
+from optiprofiler import set_cutest_problem_options, find_cutest_problems, run_benchmark
+import argparse
+from time import time
+
+
+nondefault_options = lambda n, f0: {
+ 'ftarget' : f0 - 314, # if this is doable, 3.14 otherwise
+ 'maxfev' : 271*n,
+ 'npt' : int(min(3.14*n, n**1.23)),
+ 'rhobeg' : 2.71828,
+ 'rhoend' : 3.14159*1.0e-4,
+}
+
+def get_pyprima_options(n, f0):
+ if os.environ.get('NONDEFAULT_PYPRIMA') == 'True':
+ options = nondefault_options(n, f0)
+ # Change the option name
+ options['maxfun'] = options.pop('maxfev')
+ return options
+ return {}
+
+
+def get_python_options(n, f0):
+ if os.environ.get('NONDEFAULT_PYTHON') == 'True':
+ return nondefault_options(n, f0)
+ return {}
+
+
+def pyprima_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ub, c_eq):
+ from pyprima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ c_ub_x0 = c_ub(x0)
+ if c_ub_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_ub, -np.inf, np.zeros_like(c_ub_x0)))
+ c_eq_x0 = c_eq(x0)
+ if c_eq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_eq, np.zeros_like(c_eq_x0), np.zeros_like(c_eq_x0)))
+ options = get_pyprima_options(len(x0), f0)
+ if 'npt' in options:
+ del options['npt']
+ result = minimize(fun, x0, method='cobyla', bounds=bounds, constraints=constraints, options=options)
+ return result.x
+
+
+def python_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ub, c_eq):
+ from prima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ c_ub_x0 = c_ub(x0)
+ if c_ub_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_ub, -np.inf, np.zeros_like(c_ub_x0)))
+ c_eq_x0 = c_eq(x0)
+ if c_eq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_eq, np.zeros_like(c_eq_x0), np.zeros_like(c_eq_x0)))
+ res = minimize(fun, x0, method='cobyla', bounds=bounds, constraints=constraints, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def get_problems(description):
+ cutest_problem_names = find_cutest_problems(description)
+ return list(filter(lambda x: x not in excludelist(description), cutest_problem_names))
+
+
+if __name__ == '__main__':
+ # If we run this script from a directory other than the one that contains it, pycutest's call to importlib will fail,
+ # unless we insert the current working directory into the path.
+ sys.path.insert(0, os.getcwd())
+ os.environ['PYCUTEST_CACHE'] = os.getcwd()
+
+ parser = argparse.ArgumentParser(description='Generate performance profiles comparing PyPRIMA to PRIMA Python (bindings).')
+ parser.add_argument('-j', '--n_jobs', type=int, default=None, help='Number of jobs to run in parallel')
+ parser.add_argument('--default_only', action='store_true', help='Run only the default options for both PyPRIMA and PRIMA')
+ args = parser.parse_args()
+
+
+ def run_three_benchmarks(pyprima_fun, python_fun, algorithm, cutest_problem_names, default_only, n_jobs):
+ '''
+ Proper validation of both default and nondefault options requires 3 runs: Both default, both nondefault, and
+ one default one nondefault. The first two should look identical, and so the third run confirms that our
+ experiment setup is valid (i.e. it rules out a scenario where even though options are provided, both algorithms
+ end up using default options anyway).
+ '''
+ # Sharing state with multiprocessing is hard when we can't control the function signature,
+ # so we resort to using the environment to pass options.
+ algorithm = algorithm.lower()
+ ALGORITHM = algorithm.upper()
+ project_x0 = algorithm == 'lincoa'
+ os.environ['NONDEFAULT_PYPRIMA'] = "False"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ run_benchmark([pyprima_fun, python_fun], [f'PyPRIMA-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_default_options', n_jobs=n_jobs, project_x0=project_x0)
+ if not default_only:
+ os.environ['NONDEFAULT_PYPRIMA'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "True"
+ run_benchmark([pyprima_fun, python_fun], [f'PyPRIMA-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_nondefault_options', n_jobs=n_jobs, project_x0=project_x0)
+ os.environ['NONDEFAULT_PYPRIMA'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ run_benchmark([pyprima_fun, python_fun], [f'PyPRIMA-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_different_options', n_jobs=n_jobs, project_x0=project_x0)
+
+ start = time()
+ print("Running profiles for COBYLA")
+ with open('cobyla.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('cobyla'), cutest_problem_names))
+ run_three_benchmarks(pyprima_cobyla, python_cobyla, 'cobyla', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed COBYLA profile in {time() - start:.2f} seconds')
diff --git a/pyprima/profiles_vs_python_bindings/results.csv b/pyprima/profiles_vs_python_bindings/results.csv
new file mode 100644
index 0000000000..898fb07cc2
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/results.csv
@@ -0,0 +1,517 @@
+Problem, PyPRIMA result, SciPy result, Error, Error != 0, PyPRIMA cstrv, SciPy cstrv, Error, PyPRIMA nfev, SciPy nfev, PyPRIMA s, SciPy s, Speedup (<1)/Slowdown (>1)
+AIRCRFTA , 0.0 , 0.0 , 0.00 , False, 8.619960322787945e-12 , 3.8264573160531745e-06 , -44390560.43, 33 , 34 , 0.002245483976421934, 7.491252001594094e-05, 29.974748893030277
+AIRCRFTB , 0.33200050298580724 , 1.2851059066529078 , -287.08 , True, 0.0 , 0.0 , 0.00 , 2500 , 1000 , 0.0004473069190979004, 6.07609748840332e-06, 73.61746909947028
+AKIVA , 6.169186262477943 , 6.171902302081875 , -0.04 , True, 0.0 , 0.0 , 0.00 , 1000 , 291 , 0.0003472862243652344, 7.5499216715494794e-06, 45.99865263157895
+ALLINIT , 16.705968432931407 , 16.705968541508405 , -0.00 , True, 0.0 , 0.0 , 0.00 , 91 , 74 , 0.00036252723945366156, 9.955586613835515e-06, 36.4144528610548
+ALLINITA , 30.79490611327864 , 30.795768480499337 , -0.00 , True, 9.679811729945698e-09 , 1.6132979618888044e-08 , -66.67 , 72 , 41 , 0.0007522768444485135, 6.090722432950648e-05, 12.351192370525958
+ALLINITC , 30.495419651107884 , 30.496551711922812 , -0.00 , True, 7.427919390678994e-10 , 0.0 , 100.00 , 51 , 46 , 0.0008860008389342065, 3.517192343006963e-05, 25.190571129719203
+ALLINITU , 5.744384910325017 , 5.7443849452775835 , -0.00 , True, 0.0 , 0.0 , 0.00 , 99 , 69 , 0.00033777651160654394, 7.218208865842957e-06, 46.79505925700277
+ALSOTAME , 0.08208499862391111 , 0.08208499862052751 , 0.00 , True, 1.5010215292932116e-13 , 4.107070239456334e-11 , -27261.83, 13 , 16 , 0.001014397694514348, 4.4986605644226074e-05, 22.548882717150356
+ARGAUSS , 0.0 , 0.0 , 0.00 , False, 3.517346390381412e-05 , 3.5181298777553494e-05 , -0.02 , 13 , 20 , 0.002161998015183669, 8.304119110107422e-05, 26.035248128271384
+AVGASA , -4.63192554527031 , -4.63192552437065 , -0.00 , True, 2.4787216730195574e-24 , 2.220446049250313e-16 , -8958028844.59, 146 , 128 , 0.001285698315868639, 1.7311424016952515e-05, 74.26877850196475
+AVGASB , -4.483219364852291 , -4.483216930115568 , -0.00 , True, 2.220446049250313e-16 , 2.1094237467877974e-15 , -850.00 , 144 , 98 , 0.0012631515661875408, 1.7560258203623245e-05, 71.93240278932299
+BARD , 0.01025547679737248 , 0.010851854841086556 , -5.82 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0003935559590657552, 6.738901138305664e-06, 58.40061324370541
+BARDNE , 0.0 , 0.0 , 0.00 , False, 0.05081632653064083 , 0.05081632653061252 , 0.00 , 27 , 41 , 0.0014325954295970776, 7.821292411990282e-05, 18.31660746248107
+BEALE , 9.622000924125749e-10 , 7.254063788950956e-06 , -753803.88, True, 0.0 , 0.0 , 0.00 , 735 , 382 , 0.0004007780633005155, 6.707550967551027e-06, 59.750282217660484
+BENNETT5 , 0.0 , 0.0 , 0.00 , False, 0.007660478825364692 , 0.007700018425062183 , -0.52 , 1500 , 1000 , 0.0030236220359802246, 0.0006283180713653564, 4.812247448827616
+BENNETT5LS , 0.6870228862299468 , 0.6394240555237142 , 6.93 , True, 0.0 , 0.0 , 0.00 , 55 , 39 , 0.0008968006480823864, 2.6018191606570512e-05, 34.46821599453179
+BIGGS3 , 2.8475985877977878e-08 , 0.00032070233175094385 , -1126120.29, True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0006439808209737142, 8.80885124206543e-06, 73.10610694598569
+BIGGS5 , 0.004006433885186791 , 0.03592484485292646 , -796.68 , True, 0.0 , 0.0 , 0.00 , 2500 , 1000 , 0.0005796143531799316, 1.0944128036499023e-05, 52.9612182210313
+BIGGS6 , 0.007953445408235116 , 0.061995068573047776 , -679.47 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0007098393440246582, 1.1803150177001953e-05, 60.139821435785564
+BIGGS6NE , 0.0 , 0.0 , 0.00 , False, 3.0298183406607393e-09 , 5.9713611832989955e-09 , -97.09 , 71 , 67 , 0.005857870612345951, 0.00014217931832840192, 41.20058164026079
+BIGGSC4 , -24.375 , -24.375 , 0.00 , False, 0.0 , 0.0 , 0.00 , 19 , 40 , 0.003274428217034591, 2.567172050476074e-05, 127.5500103870077
+BOOTH , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 9 , 13 , 0.0006742212507459852, 7.790785569411058e-05, 8.654085582757899
+BOX2 , 5.846883652860379e-19 , 8.750757693512076e-09 , -1496653296435.34, True, 0.0 , 0.0 , 0.00 , 34 , 27 , 0.00046967057620777805, 2.8813326800311052e-05, 16.30046330515044
+BOX3 , 5.846883652860379e-19 , 3.685383150790669e-08 , -6303158006110.24, True, 0.0 , 0.0 , 0.00 , 41 , 32 , 0.00042265217478682356, 2.2195279598236084e-05, 19.04243525818944
+BOX3NE , 0.0 , 0.0 , 0.00 , False, 3.174127159027984e-10 , 4.893190342389157e-10 , -54.16 , 14 , 19 , 0.0016563619886125838, 9.079983359888981e-05, 18.241905551607044
+BOXBOD , 0.0 , 0.0 , 0.00 , False, 19.45872044064301 , 19.458720455266118 , -0.00 , 35 , 219 , 0.0012090001787458148, 5.450314038420377e-05, 22.182211340912204
+BOXBODLS , 2661.7278309334333 , 4470.941011729683 , -67.97 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0005618159770965576, 8.357048034667969e-06, 67.22660618509643
+BRKMCC , 0.1690426792228084 , 0.16904612924098295 , -0.00 , True, 0.0 , 0.0 , 0.00 , 195 , 168 , 0.0006421076945769481, 8.977594829740979e-06, 71.52335416717332
+BROWNBS , 996219687329.2258 , 998003023452.4346 , -0.18 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0007287099361419678, 8.907079696655274e-06, 81.81244144650553
+BROWNDEN , 85822.20135316643 , 85822.2062050091 , -0.00 , True, 0.0 , 0.0 , 0.00 , 816 , 652 , 0.0009268627447240493, 1.4797675828992223e-05, 62.63569735107328
+BROWNDENE , 0.0 , 0.0 , 0.00 , False, 115.70643967925746 , 115.70643970236192 , -0.00 , 138 , 118 , 0.0018840060717817666, 0.00012630325252726928, 14.916528546048358
+BT1 , -0.9839255863997494 , -0.9942935850802206 , 1.05 , True, 5.007552775215807e-10 , 1.1714344361202222e-07 , -23293.35, 1000 , 1000 , 0.0013515009880065917, 5.3283214569091794e-05, 25.364479206751206
+BT10 , -1.0000000000001457 , -1.0000007369062227 , 0.00 , True, 2.184918912462308e-13 , 1.9998724392622513e-05 , -9153073855.54, 23 , 23 , 0.002538784690525221, 6.399983945100203e-05, 39.66861030126337
+BT11 , 0.011410073385170229 , 0.0114114377873153 , -0.01 , True, 3.1474822748123188e-12 , 9.275823442678188e-09 , -294606.14, 2500 , 125 , 0.0013911391258239745, 4.16412353515625e-05, 33.407729479662876
+BT12 , 6.1881188113503045 , 6.188118814853112 , -0.00 , True, 5.9220753667355e-09 , 1.5073107004999456e-08 , -154.52 , 282 , 223 , 0.0012481246434204968, 2.9851502901770076e-05, 41.81111575948452
+BT13 , -1.1991823248817916e-08, 2.1247042990143477e-06 , -17817.94, True, 1.3765651071531645e-08 , 1.1050674941673642e-06 , -7927.72, 497 , 688 , 0.0012070345926572619, 2.4105920348056528e-05, 50.07212233465194
+BT2 , 0.032568200393792095 , 0.03256822163257142 , -0.00 , True, 2.360778239562933e-12 , 6.442927080740901e-09 , -272815.39, 149 , 246 , 0.000678052838216692, 2.3369866658032424e-05, 29.013979760284144
+BT3 , 4.093023255814434 , 4.093023279386804 , -0.00 , True, 1.1102230246251565e-16 , 1.6653345369377348e-16 , -50.00 , 104 , 139 , 0.0009015592244955209, 1.4655024027652878e-05, 61.51878173617113
+BT4 , -78.46256728035347 , -78.46256729992808 , 0.00 , True, 7.499886045536641e-09 , 1.9999840450424244e-08 , -166.67 , 87 , 63 , 0.0008183786238747081, 3.7284124465215773e-05, 21.949787895333696
+BT6 , 0.27704478842613023 , 0.2770448076809272 , -0.00 , True, 1.500484181349293e-10 , 2.825830058839074e-08 , -18732.79, 165 , 132 , 0.0008233229319254558, 2.885767907807321e-05, 28.530462539901112
+BT7 , 306.49999755454013 , 360.3797597119592 , -17.58 , True, 5.868026287103589e-09 , 1.9672314333085694e-08 , -235.25 , 159 , 207 , 0.0012135160794048189, 3.28073179088353e-05, 36.989188899163516
+BT8 , 0.9999999994282865 , 1.0000000075665538 , -0.00 , True, 1.4333438052631913e-09 , 6.7805583916710765e-09 , -373.06 , 133 , 93 , 0.0009977225970504875, 3.092776062668011e-05, 32.25977493468419
+BT9 , -1.000000012835143 , -1.0000000058376701 , -0.00 , True, 8.118040530880083e-09 , 1.7800509626324054e-08 , -119.27 , 137 , 115 , 0.0008985770009729984, 3.0001350071119224e-05, 29.951218823249317
+BYRDSPHR , -4.683300132967832 , -4.683300135522073 , 0.00 , True, 1.2500187551722775e-09 , 1.999869603253046e-08 , -1499.87, 80 , 83 , 0.0009495258331298828, 3.0506088072995107e-05, 31.125781544256117
+CAMEL6 , -1.0316284534876325 , -1.031628327120866 , -0.00 , True, 0.0 , 0.0 , 0.00 , 65 , 41 , 0.0003923819615290715, 1.236287558951029e-05, 31.738729285765974
+CANTILVR , 1.3399563589530934 , 1.339956360618622 , -0.00 , True, 3.863192266084781e-09 , 1.6812724962234071e-09 , 56.48 , 191 , 192 , 0.0007449170057686211, 2.1186967690785725e-05, 35.159208086798934
+CB2 , 1.9522244912137179 , 1.9522244658257621 , 0.00 , True, 4.621474203325704e-09 , 4.9276438485179597e-08 , -966.25 , 92 , 70 , 0.0007004997004633365, 2.381120409284319e-05, 29.418911271013044
+CB3 , 1.9999999999998086 , 2.0000015424772917 , -0.00 , True, 3.6770586575585185e-13 , 0.0 , 100.00 , 27 , 31 , 0.000986478946827076, 2.9094757572297127e-05, 33.90572835590017
+CHACONN1 , 1.952224491491724 , 1.952224461331149 , 0.00 , True, 4.6196332315062705e-09 , 4.9279431535431684e-08 , -966.74 , 79 , 64 , 0.0007338886019549793, 2.9318034648895264e-05, 25.031984945233464
+CHACONN2 , 2.000000000803364 , 2.0000000079971954 , -0.00 , True, 0.0 , 0.0 , 0.00 , 28 , 35 , 0.0009675366537911552, 2.8201511928013394e-05, 34.30797101449275
+CHWIRUT1 , 0.0 , 0.0 , 0.00 , False, 13.100003505803315 , 13.167594551695046 , -0.52 , 21 , 23 , 0.002420959018525623, 0.0008120847784954568, 2.9811653692252613
+CHWIRUT1LS , 2654.446390521632 , 2731.739362130793 , -2.91 , True, 0.0 , 0.0 , 0.00 , 1500 , 37 , 0.00041126012802124025, 1.5464988914695947e-05, 26.592979166666666
+CHWIRUT2 , 0.0 , 0.0 , 0.00 , False, 8.550007574529033 , 8.566639187020883 , -0.19 , 31 , 26 , 0.0013978712020381804, 0.00021403569441575272, 6.53101907069244
+CHWIRUT2LS , 619.4317100414929 , 576.3242986213921 , 6.96 , True, 0.0 , 0.0 , 0.00 , 1500 , 117 , 0.00045006926854451496, 9.239229381593883e-06, 48.712857962064405
+CLIFF , 0.20068718382825754 , 0.2005500730195649 , 0.07 , True, 0.0 , 0.0 , 0.00 , 1000 , 34 , 0.00039438796043395996, 1.0139801922966453e-05, 38.89503596127248
+CLUSTER , 0.0 , 0.0 , 0.00 , False, 3.455543131680771e-11 , 6.13539906167419e-07 , -1775423.80, 23 , 22 , 0.0009031399436618971, 5.071813409978693e-05, 17.8070419918246
+CONCON , -13372.743772861397 , -8973.991236291584 , -32.89 , True, 1000.0 , 1000.0 , 0.00 , 7500 , 1000 , 0.0035256218592325844, 4.343318939208984e-05, 81.17345073154308
+CONGIGMZ , 27.999999985652252 , 28.000000006147562 , -0.00 , True, 1.4347747878673545e-08 , 0.0 , 100.00 , 49 , 368 , 0.0008906296321323939, 2.04346750093543e-05, 43.584232767327784
+COOLHANS , 0.0 , 0.0 , 0.00 , False, 5.282988433275638e-13 , 0.003196112210438982 , -604981867796.55, 506 , 458 , 0.002231796268417901, 5.130111910890804e-05, 43.50385151793632
+CRESC100 , 24.8530828557642 , 42.37931441856949 , -70.52 , True, 0.0 , 0.028387069755154926 , 0.00 , 3000 , 1000 , 0.006467500686645508, 0.00032571887969970703, 19.856081700293668
+CRESC132 , 24.085146275637356 , 42.524376275371196 , -76.56 , True, 0.0 , 0.036832014384426515 , 0.00 , 3000 , 1000 , 0.019907488028208416, 0.00614983606338501, 3.237076211955296
+CRESC4 , 6.312035059812189 , 43.09054951849964 , -582.67 , True, 0.0 , 0.026948443869141414 , 0.00 , 3000 , 1000 , 0.002032497008641561, 3.281712532043457e-05, 61.93403562303994
+CRESC50 , 2.8435678558998276 , 45.021130213617965 , -1483.26, True, 2.696424989282677e-21 , 0.007925443697252111 , -293924130237366468608.00, 3000 , 1000 , 0.0016486723423004151, 0.00015887212753295897, 10.377354215001548
+CSFI1 , -49.075198592669196 , -49.075198592203165 , -0.00 , True, 4.416733645484783e-10 , 2.842170943040401e-14 , 99.99 , 104 , 156 , 0.0016044240731459397, 5.534062018761268e-05, 28.991797845898923
+CSFI2 , 55.01760720743236 , 55.017607212066636 , -0.00 , True, 7.93501442331035e-09 , 3.552713678800501e-15 , 100.00 , 126 , 291 , 0.001663071768624442, 7.577941999402653e-05, 21.94621928691902
+CUBE , 0.01040408453680293 , 0.03363884480866413 , -223.32 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0004446690082550049, 7.159948348999023e-06, 62.10505810662316
+CUBENE , 0.0 , 0.0 , 0.00 , False, 8.578146526438957e-09 , 3.1443815642906436e-06 , -36555.72, 13 , 17 , 0.0010568912212665265, 0.00013010642107795268, 8.123282559846103
+DANWOOD , 0.0 , 0.0 , 0.00 , False, 0.03663810054155103 , 0.03663815312645369 , -0.00 , 20 , 19 , 0.0013230562210083008, 7.757387663188733e-05, 17.055435134260758
+DANWOODLS , 0.13298044468124987 , 103.917818 , -78045.19, True, 0.0 , 0.0 , 0.00 , 1000 , 5 , 0.0004126608371734619, 4.5347213745117185e-05, 9.100026288117771
+DEGENLPA , 25.937253679319408 , 3.975443841143263e-05 , 100.00 , True, 2.871232140932989e-10 , 1.3296331024734951e-13 , 99.95 , 81 , 134 , 0.014245295230253243, 0.00013036692320410884, 109.27077881519158
+DEGENLPB , -25.937253679319408 , -30.731245971382965 , 18.48 , True, 2.871232140932989e-10 , 1.399713678296166e-13 , 99.95 , 96 , 135 , 0.014048866927623749, 0.00012067335623281973, 116.42061981369551
+DEMBO7 , 174.78702324560493 , 250.08062886246415 , -43.08 , True, 4.310084253703546e-11 , 0.08064832736812595 , -187115431109.56, 5038 , 1000 , 0.01341738981025093, 8.993577957153321e-05, 149.18856404173374
+DEMYMALO , -2.9999999999995466 , -2.999999930905572 , -0.00 , True, 0.0 , 0.0 , 0.00 , 31 , 37 , 0.002703605159636467, 4.638207925332559e-05, 58.28986546442112
+DENSCHNB , 1.0337444008679068e-12 , 1.312848269121153e-08 , -1269893.11, True, 0.0 , 0.0 , 0.00 , 66 , 38 , 0.000463225624778054, 1.1525656047620272e-05, 40.19082496164695
+DENSCHNC , 4.0218368170370506e-11 , 1.0647085928399082e-07 , -264631.92, True, 0.0 , 0.0 , 0.00 , 130 , 107 , 0.00046228445493257965, 9.184685822959259e-06, 50.332092337550854
+DENSCHND , 5.727556979692036e-07 , 5.247656657188822e-06 , -816.21 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0004156338373819987, 6.9801807403564456e-06, 59.544853183955546
+DENSCHNE , 3.1979240732654555e-13 , 0.9961645018781955 , -311503487592397.69, True, 0.0 , 0.0 , 0.00 , 389 , 1000 , 0.0004394783765307429, 7.122039794921875e-06, 61.7068128212507
+DENSCHNF , 3.500480714730578e-11 , 4.997194536316827e-07 , -1427473.79, True, 0.0 , 0.0 , 0.00 , 54 , 33 , 0.0003926886452568902, 1.3004649769176137e-05, 30.19601851851852
+DIPIGRI , 680.6300573744224 , 680.6300574802378 , -0.00 , True, 2.964384293591138e-11 , 2.8019341868912306e-07 , -945099.38, 484 , 280 , 0.0012241355643784705, 2.573984009878976e-05, 47.55800967217458
+DIXCHLNG , 2471.89781021019 , 2471.897808814896 , 0.00 , True, 2.322262604437242e-09 , 1.5123994168142474e-08 , -551.26 , 618 , 311 , 0.0025599662540028395, 4.246610537219277e-05, 60.28257669419176
+DJTL , -8949.186909833958 , -8405.69915440774 , -6.07 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0003911421298980713, 6.4239501953125e-06, 60.88810124703088
+DUALC1 , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 37 , 55 , 0.00466127653379698, 6.758082996715198e-05, 68.97335436783801
+DUALC2 , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 29 , 43 , 0.0036921336733061693, 5.9044638345407884e-05, 62.53122682719116
+DUALC5 , 0.0 , -3.194059669687556e-15 , 0.00 , False, 0.0 , 6.445568270989904e-19 , 0.00 , 33 , 53 , 0.005317789135557233, 7.300106984264446e-05, 72.84535893816151
+DUALC8 , 0.0 , -6.223324862719115e-13 , 0.00 , False, 0.0 , 7.820783852606752e-17 , 0.00 , 33 , 59 , 0.006891879168423739, 9.549270241947497e-05, 72.17178898288458
+ECKERLE4 , 0.0 , 0.0 , 0.00 , False, 0.01371280165694913 , 0.013712932431785285 , -0.00 , 61 , 192 , 0.0015177531320540632, 0.00012106324235598247, 12.536861746946775
+ECKERLE4LS , 0.6996958558194455 , 0.6996958853494009 , -0.00 , True, 0.0 , 0.0 , 0.00 , 1500 , 39 , 0.000400143305460612, 0.00011629936022636217, 3.440632043734231
+ELATTAR , 37.11044569241935 , 9.44564497994434 , 74.55 , True, 9.302495974916614e-09 , 44.24015939996587 , -475573002234.60, 3139 , 1000 , 0.0017234603095563517, 0.00013340401649475098, 12.919103598534939
+ENGVAL2 , 0.012209503868603113 , 0.42094476286231636 , -3347.68, True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00034576193491617837, 5.788803100585938e-06, 59.729434376716085
+ENSO , 0.0 , 0.0 , 0.00 , False, 4.872796761247474 , 4.873581527582592 , -0.02 , 156 , 131 , 0.0033991611920870268, 0.00047930688348435266, 7.091826362635568
+ENSOLS , 788.5397866708956 , 788.5449362549716 , -0.00 , True, 0.0 , 0.0 , 0.00 , 2484 , 1000 , 0.0004780990298051573, 2.280688285827637e-05, 20.962927409944598
+EQC , -829.5477053187723 , -923.5725949795307 , 11.33 , True, 8.326672684688674e-17 , 4.440892098500626e-16 , -433.33 , 30 , 57 , 0.002082339922587077, 1.929935656095806e-05, 107.89685739055051
+ERRINBAR , -142.05539019911558 , -160.3409562105142 , 12.87 , True, 589.884 , 13.975320515922874 , 97.63 , 147 , 1000 , 0.0009451026008242653, 9.729719161987305e-05, 9.713565058657121
+EXPFIT , 0.24051059621230178 , 0.24056408546152047 , -0.02 , True, 0.0 , 0.0 , 0.00 , 308 , 201 , 0.00040144115299373475, 7.651931610866565e-06, 52.4627209714793
+EXPFITA , 0.001136611706167554 , 3.9469229722360537 , -347153.42, True, 0.0 , 47.323081300000005 , 0.00 , 134 , 6 , 0.001188753256157263, 6.552537282307942e-05, 18.141877030855426
+EXPFITB , 0.005019365520467761 , 17.894823948578278 , -356415.66, True, 7.105427357601002e-15 , 47.64597255 , -670557450693390208.00, 109 , 6 , 0.0018519064702025247, 6.882349650065105e-05, 26.90805559675403
+EXPFITC , 0.023302572606785345 , 87.62130046808227 , -375915.57, True, 0.0 , 47.6470374 , 0.00 , 130 , 6 , 0.004245429772597092, 0.0001817941665649414, 23.352948297604033
+EXTRASIM , 1.0 , 1.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 9 , 13 , 0.0008920033772786459, 5.0929876474233774e-05, 17.51434401632457
+FBRAIN , 0.0 , 0.0 , 0.00 , False, 0.04787576461951498 , 0.04787578024195599 , -0.00 , 16 , 24 , 0.028877899050712585, 0.010295748710632324, 2.804837206340385
+FBRAIN2 , 0.0 , 0.0 , 0.00 , False, 0.03112854734644654 , 0.031151137510952255 , -0.07 , 522 , 1000 , 0.024895478482447365, 0.007931392908096314, 3.1388532595622927
+FBRAIN2LS , 0.39313171871271146 , 0.39376721513515583 , -0.16 , True, 0.0 , 0.0 , 0.00 , 2000 , 129 , 0.0005638840198516846, 0.00020468881887982982, 2.7548354762979685
+FBRAIN3 , 0.0 , 0.0 , 0.00 , False, 0.027823096811724568 , 0.029651028498527854 , -6.57 , 3000 , 1000 , 0.02923461977640788, 0.009890447854995727, 2.9558438813912042
+FBRAIN3LS , 0.3652343985442748 , 0.42739328217708317 , -17.02 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0014612080256144206, 0.00040909409523010254, 3.5718140218878913
+FBRAINLS , 0.5060838591440499 , 0.6976938018250041 , -37.86 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0005404770374298096, 0.00013646793365478516, 3.9604691223435853
+FCCU , 11.149109141486754 , 11.149109224644832 , -0.00 , True, 7.105427357601002e-15 , 7.105427357601002e-15 , 0.00 , 618 , 578 , 0.004430223437188898, 3.227684324587918e-05, 137.25702366369148
+FLETCHER , 11.656854202186993 , 11.656854236571824 , -0.00 , True, 9.999197914112301e-09 , 3.333233244973144e-09 , 66.66 , 129 , 155 , 0.0008463175721870836, 2.8676371420583418e-05, 29.512714833216698
+FLT , 5.378712120506237e-05 , 0.00019638162345871505 , -265.11 , True, 1.5209614932242006e-14 , 9.31012098283342e-11 , -612020.76, 63 , 39 , 0.0008451408810085721, 3.131230672200521e-05, 26.990693739424703
+FREURONE , 0.0 , 0.0 , 0.00 , False, 4.948952095103347 , 4.9489520957570035 , -0.00 , 79 , 80 , 0.0005564387840560719, 2.9474496841430664e-05, 18.8786525194865
+GAUSS1 , 0.0 , 0.0 , 0.00 , False, 6.395560694378488 , 10.647320574207033 , -66.48 , 1249 , 1000 , 0.004166560329562478, 0.0007147459983825684, 5.829427991190127
+GAUSS1LS , 3552.649289380294 , 4055.7162369098705 , -14.16 , True, 0.0 , 0.0 , 0.00 , 92 , 10 , 0.0004368035689644192, 3.7217140197753905e-05, 11.736623680472386
+GAUSS2 , 0.0 , 0.0 , 0.00 , False, 5.5395263232415415 , 13.731937358906464 , -147.89 , 190 , 1000 , 0.00495530555122777, 0.0008897941112518311, 5.569047365638624
+GAUSS2LS , 5156.1948148125775 , 5756.849054357412 , -11.65 , True, 0.0 , 0.0 , 0.00 , 100 , 11 , 0.0009638714790344238, 4.0552832863547586e-05, 23.76828968466061
+GAUSS3 , 0.0 , 0.0 , 0.00 , False, 5.420551209386602 , 17.421989797848823 , -221.41 , 433 , 1000 , 0.005132821635891604, 0.0006716537475585937, 7.6420650588923085
+GAUSS3LS , 14725.134251776495 , 15280.84279431247 , -3.77 , True, 0.0 , 0.0 , 0.00 , 4000 , 10 , 0.0004968634843826294, 3.921985626220703e-05, 12.668671732522798
+GAUSSIAN , 1.1336670930726987e-08 , 2.4788513400717508e-08 , -118.66 , True, 0.0 , 0.0 , 0.00 , 198 , 37 , 0.0004235662595190183, 1.851288047996727e-05, 22.87954378452116
+GBRAIN , 0.0 , 0.0 , 0.00 , False, 0.3285258575483354 , 0.3285258592172833 , -0.00 , 17 , 16 , 0.02392735200769761, 0.009956687688827515, 2.4031437718536353
+GBRAINLS , 31.166329428798402 , 35.44131496314514 , -13.72 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0005173192024230957, 0.00013014793395996094, 3.9748552795486187
+GENHS28 , 3.2568363276182606e-11 , 5.55777447913074e-07 , -1706394.87, True, 0.0 , 0.0 , 0.00 , 1450 , 750 , 0.0005994227836871969, 1.595465342203776e-05, 37.5704045604168
+GIGOMEZ1 , -2.999999999999996 , -2.999999997573364 , -0.00 , True, 0.0 , 0.0 , 0.00 , 24 , 34 , 0.0010816256205240886, 3.426916459027459e-05, 31.562649205374804
+GIGOMEZ2 , 1.9522244909044881 , 1.9522244852083008 , 0.00 , True, 4.621070193167043e-09 , 4.926685215345117e-08 , -966.14 , 97 , 63 , 0.0007551158826375745, 2.541239299471416e-05, 29.714473674110124
+GIGOMEZ3 , 1.9999999999999987 , 1.999999828821951 , 0.00 , True, 2.6645352591003757e-15 , 2.7141195113600247e-07 , -10186089583.33, 25 , 31 , 0.001059122085571289, 3.084059684507308e-05, 34.341815461346634
+GOTTFR , 0.0 , 0.0 , 0.00 , False, 4.27467727881492e-10 , 2.6967234852959265e-05 , -6308501.35, 14 , 21 , 0.0011487177440098353, 5.652790977841332e-05, 20.321249246836715
+GROWTH , 0.0 , 0.0 , 0.00 , False, 0.44094130354844197 , 1.3949791199835566 , -216.36 , 541 , 1000 , 0.0009481849599898192, 5.86700439453125e-05, 16.161313273834274
+GROWTHLS , 13.322116557546796 , 181.62077882048226 , -1263.30, True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0004216354688008626, 7.557153701782226e-06, 55.79289312342914
+GULF , 6.21412247743578 , 6.501497325883342 , -4.62 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.000441400686899821, 1.5545129776000975e-05, 28.394789445969646
+GULFNE , 0.0 , 0.0 , 0.00 , False, 1.1092515794786095e-13 , 0.04134414520967966 , -37272108486879.11, 89 , 431 , 0.01262672831503193, 0.00035641752097003695, 35.42678901044661
+HAHN1 , 0.0 , 0.0 , 0.00 , False, 0.7809017486741325 , 8.56824335194635 , -997.22 , 500 , 77 , 0.004157715797424316, 0.0007654970342462713, 5.4313937369046945
+HAHN1LS , 5271.868589886371 , 85286.67344375506 , -1517.77, True, 0.0 , 0.0 , 0.00 , 67 , 91 , 0.00037538827355228254, 1.3922597025776957e-05, 26.96251804582657
+HAIFAS , -0.4500000131807079 , -0.4500000074988339 , -0.00 , True, 1.3450682048721574e-08 , 4.1187254273822305e-08 , -206.21 , 287 , 265 , 0.0016470379114981728, 3.324904531802771e-05, 49.536397082810225
+HAIRY , 20.00000000161213 , 251.30580051764042 , -1156.53, True, 0.0 , 0.0 , 0.00 , 84 , 1000 , 0.00039306141081310456, 6.242036819458008e-06, 62.970056438602334
+HALDMADS , 0.0001223709968637849 , 0.0002916104355905428 , -138.30 , True, 3.61577434659921e-12 , 4.996403735191279e-09 , -138083.51, 62 , 221 , 0.002732949872170725, 7.622705865229956e-05, 35.85275255912396
+HART6 , -3.322886891570115 , -3.3228867218749034 , -0.00 , True, 0.0 , 0.0 , 0.00 , 203 , 118 , 0.0004830055048900285, 1.0609626770019531e-05, 45.525211711961035
+HATFLDA , 2.494489994050722e-07 , 7.285208048767928e-06 , -2820.52, True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.00043604302406311034, 7.794857025146485e-06, 55.939836055545356
+HATFLDB , 0.005572809002647818 , 0.005572874623415028 , -0.00 , True, 0.0 , 0.0 , 0.00 , 388 , 174 , 0.0006030232635969968, 1.043560861170977e-05, 57.78515523477432
+HATFLDD , 0.0017122455222537462 , 0.004890529393961784 , -185.62 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0003882781664530436, 6.721973419189453e-06, 57.76252630583339
+HATFLDE , 0.004049535992833022 , 0.031405692251434275 , -675.54 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00038916333516438804, 7.644176483154296e-06, 50.909778969912466
+HATFLDF , 0.0 , 0.0 , 0.00 , False, 9.124645483638005e-14 , 7.890691318818144e-07 , -864766792.37, 159 , 191 , 0.0008564970028475396, 3.1644761250281206e-05, 27.065996677093857
+HATFLDFL , 6.656992069304824e-05 , 7.244685317496869e-05 , -8.83 , True, 0.0 , 0.0 , 0.00 , 215 , 89 , 0.0003837152969005496, 1.0527921526619557e-05, 36.44739333688384
+HATFLDH , -24.375 , -24.375 , 0.00 , False, 0.0 , 0.0 , 0.00 , 20 , 32 , 0.001858055591583252, 2.368539571762085e-05, 78.44731047499214
+HEART6 , 0.0 , 0.0 , 0.00 , False, 2.1671553440683056e-13 , 0.22262166486794607 , -102725291694969.81, 1669 , 1000 , 0.001976826292372807, 7.904624938964843e-05, 25.008476779566013
+HEART6LS , 7.197773453057586 , 23.743571322631414 , -229.87 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0005915706157684326, 1.4957904815673828e-05, 39.54902929643916
+HEART8 , 0.0 , 0.0 , 0.00 , False, 0.6900000000149926 , 0.6900360359932733 , -0.01 , 652 , 670 , 0.0020069384867428272, 5.586574326700239e-05, 35.92431370958316
+HEART8LS , 1.4739420035255246 , 12.871721180799655 , -773.29 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0005941292047500611, 8.085966110229492e-06, 73.4765885301489
+HELIX , 3.900559579423757e-06 , 0.002271381555571896 , -58132.20, True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00043378734588623045, 6.973028182983398e-06, 62.20932061408008
+HELIXNE , 0.0 , 0.0 , 0.00 , False, 1.0426623004458758e-11 , 9.057696135262056 , -86870851006872.19, 34 , 1000 , 0.0011310577392578125, 4.068803787231445e-05, 27.79828663174302
+HET-Z , 0.9999980000553024 , 0.9999847951680927 , 0.00 , True, 0.0 , 1.3204831907307699e-05 , 0.00 , 14 , 19 , 0.010687862123761858, 0.0020331458041542455, 5.256810456940066
+HIELOW , 933.42472985683 , 934.6264228572329 , -0.13 , True, 0.0 , 0.0 , 0.00 , 1500 , 47 , 0.0006074873606363932, 0.00018564183661278258, 3.27236237111524
+HILBERTA , 2.3594076954326413e-13 , 1.0396446791292815e-08 , -4406279.96, True, 0.0 , 0.0 , 0.00 , 157 , 136 , 0.0004643756113234599, 8.036108577952665e-06, 57.78612954502507
+HILBERTB , 2.84270498004006e-12 , 7.915523753802663e-08 , -2784404.13, True, 0.0 , 0.0 , 0.00 , 278 , 188 , 0.0007291663464882391, 9.06371055765355e-06, 80.44898850752894
+HIMMELBA , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 9 , 19 , 0.0007881588406032986, 3.4056211772717927e-05, 23.142880537132566
+HIMMELBC , 0.0 , 0.0 , 0.00 , False, 1.7763568394002505e-15 , 2.836890383051127e-06 , -159702730800.00, 18 , 17 , 0.0009702046712239584, 5.640703089096967e-05, 17.200066302005634
+HIMMELBD , 0.0 , 0.0 , 0.00 , False, 2.421306136054515 , 2.421306141377451 , -0.00 , 63 , 43 , 0.0007033764369904049, 4.444011422090752e-05, 15.827511907237424
+HIMMELBE , 0.0 , 0.0 , 0.00 , False, 1.0 , 1.0 , 0.00 , 15 , 26 , 0.0010210196177164713, 7.23416988666241e-05, 14.113846283855157
+HIMMELBF , 890.5284098490333 , 1070.3671292953572 , -20.19 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.000457810640335083, 7.806301116943359e-06, 58.646295278235904
+HIMMELBG , 5.361100772323128e-13 , 2.7549246520931086e-08 , -5138629.47, True, 0.0 , 0.0 , 0.00 , 60 , 40 , 0.00044314861297607423, 1.271963119506836e-05, 34.839737582005625
+HIMMELBH , -0.9999999999995057 , -0.9999999973448206 , -0.00 , True, 0.0 , 0.0 , 0.00 , 68 , 38 , 0.000538938185747932, 1.1280963295384458e-05, 47.77412811620755
+HIMMELP1 , -51.7378463582927 , -62.05393553329657 , 19.94 , True, 0.0 , 0.0 , 0.00 , 61 , 106 , 0.0005262601571004899, 1.0593882146871315e-05, 49.675855347882084
+HIMMELP2 , -62.05393553382555 , -62.05393553329657 , -0.00 , True, 0.0 , 0.0 , 0.00 , 83 , 106 , 0.0004562412399843515, 2.6867074786492112e-05, 16.98142591294437
+HIMMELP3 , -59.01317776912174 , -59.01317776912174 , 0.00 , False, 0.0 , 0.0 , 0.00 , 19 , 62 , 0.0011469439456337376, 3.5678186724262855e-05, 32.146923679105065
+HIMMELP4 , -59.01317776912174 , -59.01317776912174 , 0.00 , False, 0.0 , 0.0 , 0.00 , 20 , 62 , 0.0009227514266967774, 3.9808211788054436e-05, 23.17992658423493
+HIMMELP5 , -59.01317776912174 , -59.01317776912174 , 0.00 , False, 0.0 , 0.0 , 0.00 , 22 , 66 , 0.0010445334694602273, 3.0076864993933476e-05, 34.72880134518376
+HIMMELP6 , -59.01317776912174 , -59.01317776912174 , 0.00 , False, 0.0 , 0.0 , 0.00 , 24 , 66 , 0.0009252528349558512, 4.830143668434837e-05, 19.15580360481639
+HONG , 4.061062663274332 , 4.061062663274335 , -0.00 , True, 5.551115123125783e-17 , 0.0 , 100.00 , 23 , 27 , 0.0017893003380816915, 5.032398082591869e-05, 35.55561997909626
+HS1 , 0.01788269994453199 , 2.1103027689347647 , -11700.81, True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.00044731998443603514, 8.108854293823243e-06, 55.16438799212019
+HS10 , -1.0000000028914051 , -1.00000000465231 , 0.00 , True, 7.50245354730339e-09 , 2.000140697511199e-08 , -166.60 , 83 , 61 , 0.0006692553141030921, 2.3341569744172643e-05, 28.672249614667646
+HS100 , 680.6300573744683 , 680.6300574802386 , -0.00 , True, 1.3244516594568267e-11 , 2.801930065743363e-07 , -2115439.70, 431 , 280 , 0.0009507955918455898, 2.5528669357299805e-05, 37.24422838253864
+HS100LNP , 680.6300573743858 , 680.6300572845221 , 0.00 , True, 7.01803060110251e-10 , 1.826371942570404e-07 , -25924.00, 374 , 224 , 0.0010812888170946091, 3.165113074438912e-05, 34.16272315283055
+HS100MOD , 678.6796378890597 , 678.6796386861209 , -0.00 , True, 5.5294435696850996e-11 , 1.0783050186091714e-07 , -194911.49, 280 , 188 , 0.0007285782269069127, 2.285013807580826e-05, 31.885068899354614
+HS101 , 3000.0099000928994 , 236.17190388534164 , 92.13 , True, 0.009900092898921927 , 1.3451487192871672 , -13487.23, 3500 , 46 , 0.0015952905246189662, 3.1087709509808086e-05, 51.31579488400895
+HS102 , 911.8805725815748 , 202.18592831246204 , 77.83 , True, 3.806752670110556e-12 , 2.1733919973269047 , -57093070805148.42, 1771 , 55 , 0.0010692113405698306, 3.056526184082031e-05, 34.98125899061937
+HS103 , 3000.018922673851 , 174.7474889441776 , 94.18 , True, 0.0189226738507831 , 1.2393723415240554 , -6449.67, 3500 , 75 , 0.0017110674721854075, 2.8362274169921874e-05, 60.32899413968681
+HS104 , 3.951163371650058 , 3.9513715136736876 , -0.01 , True, 1.080468535118787e-08 , 4.852925915330567e-09 , 55.08 , 3482 , 1000 , 0.0012260883584370359, 2.6413917541503905e-05, 46.4182700847194
+HS105 , 1061.9983946607667 , nan , nan , True, 0.0 , 3.9998999999999967 , 0.00 , 4000 , 93 , 0.0008839464783668518, 3.399900210800991e-05, 25.99918890438848
+HS106 , 6408.986098247231 , 13299.236465270089 , -107.51 , True, 0.07173311174847186 , 0.03986072936095297 , 44.43 , 4000 , 1000 , 0.00339767050743103, 2.816319465637207e-05, 120.64222645502645
+HS107 , 5055.011797818983 , 5055.0118386894 , -0.00 , True, 2.0781103504674547e-09 , 2.077757521590229e-09 , 0.02 , 132 , 151 , 0.002598054481275154, 4.8343708973057224e-05, 53.741314774236585
+HS108 , -0.8660254053927573 , -0.8660254098736172 , 0.00 , True, 5.1851221094523225e-09 , 9.825623425996355e-09 , -89.50 , 357 , 164 , 0.002542537801405963, 3.9464089928603755e-05, 64.42661685612873
+HS109 , -6.075630920181735 , -0.791481842181902 , -86.97 , True, 39391.286883127956 , 44244.14310000495 , -12.32 , 4500 , 120 , 0.0012251831160651314, 8.616646130879721e-05, 14.218793454618122
+HS11 , -8.498464230058168 , -8.498464217757624 , -0.00 , True, 2.8172515431634793e-09 , 2.8175441979527704e-09 , -0.01 , 71 , 53 , 0.0008025068632313903, 4.3905006264740565e-05, 18.278254156084046
+HS110 , -45.77847553188483 , -43.13434222210946 , -5.78 , True, 0.0 , 0.0 , 0.00 , 266 , 11 , 0.0007715458260442977, 2.4015253240411933e-05, 32.12732417686816
+HS111 , -47.761090839123376 , -47.76095726980981 , -0.00 , True, 5.0930287764927584e-11 , 4.121700483694646e-08 , -80828.28, 5000 , 1000 , 0.0019273446559906005, 5.43212890625e-05, 35.480466116573034
+HS111LNP , -47.761090839123376 , -47.76095726980981 , -0.00 , True, 5.0930287764927584e-11 , 4.121700483694646e-08 , -80828.28, 5000 , 1000 , 0.0021519619941711425, 4.67989444732666e-05, 45.9831309956238
+HS112 , -47.76109081799816 , -47.3693863732629 , -0.82 , True, 0.0 , 6.661338147750939e-16 , 0.00 , 2883 , 15 , 0.0018186750322673373, 9.320576985677083e-05, 19.51247261904593
+HS113 , 24.306209068180017 , 24.306209066730947 , 0.00 , True, 1.4779288903810084e-12 , 1.9612997448348324e-08 , -1326959.62, 275 , 232 , 0.0021234989166259766, 3.793630106695767e-05, 55.97538128132196
+HS114 , -1600.274441285408 , -1577.7086066071042 , -1.41 , True, 4.574118861455645e-14 , 5.3681611689171405e-06 , -11735945841.75, 2316 , 1000 , 0.00436077628522751, 6.786513328552246e-05, 64.25650513174173
+HS116 , 169.48739323552041 , 249.65375977500202 , -47.30 , True, 4.739358888386432e-09 , 0.0034180440139655317 , -72120287.89, 6500 , 1000 , 0.006797220340141883, 4.018282890319824e-05, 169.1573372426469
+HS117 , 32.3486789657388 , 32.34867917533312 , -0.00 , True, 2.8554936193359026e-13 , 1.1005747351664328e-21 , 100.00 , 1362 , 1000 , 0.0030357590856005968, 3.605198860168457e-05, 84.20503842772067
+HS118 , 664.8204499999998 , 664.8204499999999 , -0.00 , True, 8.881784197001252e-16 , 7.105427357601002e-15 , -700.00 , 83 , 176 , 0.005454158208456384, 4.105134443803267e-05, 132.86186562511926
+HS119 , 27.00887068664121 , 27.00887077263801 , -0.00 , True, 3.5 , 3.5 , 0.00 , 497 , 635 , 0.0014590603005238461, 3.410399429441437e-05, 42.78268075956177
+HS12 , -30.00000000487068 , -30.000000009661655 , 0.00 , True, 1.0277179995910046e-08 , 2.7395921975426063e-08 , -166.57 , 58 , 42 , 0.0006992077005320582, 2.6078451247442336e-05, 26.811703421254112
+HS13 , 1.0000009773373966 , 1.0003666065301662 , -0.04 , True, 1.2564716695224914e-22 , 4.7000194648919074e-27 , 100.00 , 75 , 46 , 0.0008267752329508463, 3.20465668388035e-05, 25.79918270526713
+HS14 , 0.0 , 1.0000000000013602e-08 , 0.00 , False, 1.0 , 1.0 , 0.00 , 14 , 20 , 0.001044562884739467, 5.093812942504883e-05, 20.50650262445254
+HS15 , 306.4999999128419 , 360.3797664046955 , -17.58 , True, 1.2451162323401377e-10 , 9.003391365780544e-09 , -7130.96, 21 , 124 , 0.0010863826388404483, 2.5493483389577557e-05, 42.614130922751485
+HS16 , 23.144660975101118 , 23.1444209366138 , 0.00 , True, 0.0 , 3.7126701416534402e-06 , 0.00 , 14 , 17 , 0.001250811985560826, 4.435988033519072e-05, 28.196919741655755
+HS17 , 0.9999999999786774 , 0.9999471889441803 , 0.01 , True, 4.774957587311912e-09 , 2.6405879495512675e-05 , -552907.62, 35 , 30 , 0.0008222852434430803, 3.522237141927083e-05, 23.3455389375967
+HS18 , 4.999999999563107 , 4.9999999999803055 , -0.00 , True, 3.4652991587336146e-09 , 1.9802257611445384e-09 , 42.86 , 67 , 59 , 0.0007077153049298186, 2.8404138856014965e-05, 24.9159218843894
+HS19 , -6961.813875580151 , -6961.81465008001 , 0.00 , True, 2.4868995751603507e-14 , 5.8486743625962845e-06 , -23517935328.57, 38 , 28 , 0.0009570059023405376, 4.075254712785993e-05, 23.483339564959202
+HS2 , 4.941229318024095 , 4.941240175325019 , -0.00 , True, 0.0 , 0.0 , 0.00 , 42 , 36 , 0.0005515529995872861, 1.5305148230658637e-05, 36.03708969524634
+HS20 , 40.19872981077604 , 40.20502312072939 , -0.02 , True, 2.853273173286652e-14 , 0.0 , 100.00 , 15 , 17 , 0.00112152099609375, 4.776786355411305e-05, 23.478567234292427
+HS21 , -99.96 , -99.95999999 , -0.00 , True, 0.0 , 0.0 , 0.00 , 34 , 30 , 0.0006002327975104837, 2.3436546325683593e-05, 25.610974807013346
+HS21MOD , -95.96 , -95.95999993356425 , -0.00 , True, 0.0 , 2.5721554667454302e-21 , 0.00 , 83 , 453 , 0.000983266945344856, 1.607930686563319e-05, 61.15107781458064
+HS22 , 1.0000000000000036 , 1.0000012598347676 , -0.00 , True, 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 19 , 17 , 0.000879626525075812, 4.671601688160616e-05, 18.82922782790058
+HS23 , 2.0000000017670727 , 2.0000000414432746 , -0.00 , True, 0.0 , 0.0 , 0.00 , 22 , 18 , 0.0009432814337990501, 5.393558078342014e-05, 17.489038221110913
+HS24 , -1.0000000826919648 , -1.000000082691965 , 0.00 , True, 0.0 , 0.0 , 0.00 , 10 , 14 , 0.0010336875915527345, 3.036430903843471e-05, 34.04284913067863
+HS25 , 1.3837549393683133 , 2.092099908445872 , -51.19 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.000521976629892985, 1.4825105667114257e-05, 35.208965225175966
+HS26 , 1.8793965576815343e-08 , 1.2835596034629835e-06 , -6729.64, True, 1.1628193963275635e-09 , 8.136795215385462e-08 , -6897.47, 1500 , 192 , 0.0010811306635538738, 2.8432657321294148e-05, 38.024256802201165
+HS268 , 2.2637881498121715 , 2.5071222611513804 , -10.75 , True, 0.0 , 0.0 , 0.00 , 2500 , 972 , 0.0006303243637084961, 1.1791172341554743e-05, 53.457310727881676
+HS27 , 0.04000000000194822 , 0.045069006451661266 , -12.67 , True, 3.866647900436256e-12 , 4.09149025930311e-06 , -105814816.81, 822 , 1000 , 0.0007454858789188728, 2.628779411315918e-05, 28.358631983719643
+HS28 , 5.676213641791447e-14 , 7.066937017007986e-09 , -12449989.91, True, 0.0 , 0.0 , 0.00 , 99 , 49 , 0.0004481330062403823, 1.949193526287468e-05, 22.990688210109077
+HS29 , -22.6274169990359 , -22.627416916754395 , -0.00 , True, 1.587153519722051e-09 , 6.20848545906938e-08 , -3811.71, 97 , 69 , 0.0005990500302658868, 2.3292458575704824e-05, 25.718625980115526
+HS3 , 3.4052170504073325e-13 , 2.106046023506225e-14 , 93.82 , True, 0.0 , 0.0 , 0.00 , 27 , 40 , 0.0006480040373625579, 1.3470649719238281e-05, 48.10488364470665
+HS30 , 1.000000000000247 , 1.0000000131512348 , -0.00 , True, 0.0 , 0.0 , 0.00 , 82 , 64 , 0.0008902288064724062, 3.731250762939453e-05, 23.858723603210475
+HS31 , 5.999999990445379 , 6.000000006108164 , -0.00 , True, 5.2100325165227446e-09 , 8.156090025579488e-09 , -56.55 , 83 , 62 , 0.0006398178008665521, 2.6337562068816154e-05, 24.29297742876895
+HS32 , 0.6688834125791887 , 0.66888342705263 , -0.00 , True, 1.0 , 1.0 , 0.00 , 59 , 57 , 0.000972557876069667, 3.9385076154742326e-05, 24.693563426119262
+HS33 , -4.585786437626904 , -4.585786433534783 , -0.00 , True, 0.0 , 0.0 , 0.00 , 32 , 25 , 0.001994185149669647, 3.631591796875e-05, 54.91215040703782
+HS34 , -0.8340324452479582 , -0.8340165056052742 , -0.00 , True, 5.329070518200751e-15 , 0.0 , 100.00 , 28 , 33 , 0.0010270306042262486, 3.345084912849195e-05, 30.702676643011415
+HS35 , 0.11111111111171246 , 0.1111111155756026 , -0.00 , True, 0.0 , 0.0 , 0.00 , 72 , 50 , 0.0005552636252509223, 1.7981529235839844e-05, 30.87966646041427
+HS35I , 0.11111111111171246 , 0.1111111155756026 , -0.00 , True, 0.0 , 0.0 , 0.00 , 72 , 50 , 0.0005350444051954481, 1.7681121826171874e-05, 30.26077250389548
+HS35MOD , 0.25 , 0.25000002500000007 , -0.00 , True, 0.0 , 0.0 , 0.00 , 34 , 30 , 0.00037397356594310086, 2.5733311971028647e-05, 14.532663590451621
+HS36 , -3299.9999999999995 , -3300.0000000000014 , 0.00 , True, 0.0 , 1.4210854715202004e-14 , 0.00 , 20 , 31 , 0.0010371088981628418, 2.9710031324817288e-05, 34.907701268444214
+HS37 , -3455.9999999999986 , -3455.9999999638426 , -0.00 , True, 0.0 , 0.0 , 0.00 , 98 , 73 , 0.0005423365807046696, 1.557559183199112e-05, 34.81964515728707
+HS38 , 7.5476248033592785 , 7.874123872468778 , -4.33 , True, 0.0 , 0.0 , 0.00 , 2000 , 357 , 0.0004899615049362183, 2.046459529246269e-05, 23.941910305779462
+HS39 , -1.000000012835143 , -1.0000000058376701 , -0.00 , True, 8.118040530880083e-09 , 1.7800509626324054e-08 , -119.27 , 137 , 115 , 0.0011027242145399108, 3.407934437627378e-05, 32.35755366548757
+HS3MOD , 7.69905444614579e-15 , 9.387483074291592e-09 , -121930237.55, True, 5.1640636690727773e-26 , 9.123820083056552e-25 , -1666.79, 76 , 77 , 0.0005037627722087659, 9.960942454152293e-06, 50.57380609590498
+HS4 , 2.666666664 , 2.666666664 , 0.00 , False, 0.0 , 0.0 , 0.00 , 10 , 13 , 0.0010376930236816405, 1.9238545344426082e-05, 53.93822688274547
+HS40 , -0.25000000310672466 , -0.2500000112476113 , 0.00 , True, 7.60447804726283e-09 , 2.0260675581695864e-08 , -166.43 , 76 , 76 , 0.0013092348450108578, 4.0330384906969573e-05, 32.46274113254511
+HS41 , 1.9259259259259351 , 2.0 , -3.85 , True, 0.0 , 1.1102230246251565e-16 , 0.00 , 83 , 44 , 0.0009465303765722068, 2.4562532251531426e-05, 38.53553725158743
+HS42 , 12.857864369257461 , 12.857864362253792 , 0.00 , True, 3.0286253505096283e-09 , 9.117252952606236e-09 , -201.04 , 112 , 80 , 0.0008638820477894374, 4.929900169372559e-05, 17.523317270324885
+HS43 , -44.00000001180716 , -44.00000004253842 , 0.00 , True, 4.025152655628972e-09 , 2.902606199839397e-08 , -621.12 , 109 , 94 , 0.0009566228324120198, 2.6253943747662485e-05, 36.43730030072882
+HS44 , -15.0 , -15.0 , 0.00 , False, 2.5544872326980644e-17 , 5.551115123125783e-17 , -117.31 , 23 , 33 , 0.0012792608012323794, 2.1688865892814867e-05, 58.98237406946094
+HS44NEW , -15.0 , -14.999999999999998 , -0.00 , True, 0.0 , 0.0 , 0.00 , 19 , 32 , 0.0014921614998265316, 2.2292137145996094e-05, 66.93667323388685
+HS45 , 1.0000000004 , 1.0000000003999994 , 0.00 , True, 0.0 , 8.881784197001252e-16 , 0.00 , 26 , 37 , 0.0015603854106022762, 1.523945782635663e-05, 102.39113677020654
+HS46 , 9.370651493355147e-07 , 4.660020949307742e-06 , -397.30 , True, 8.230502390738081e-11 , 8.617643154629206e-09 , -10370.37, 2500 , 114 , 0.0012634352684020996, 3.8694917109974646e-05, 32.6511945951789
+HS47 , 1.262452556451271e-09 , 2.4931387134398777e-07 , -19648.38, True, 7.983613770079501e-13 , 5.200653241388409e-09 , -651315.94, 1484 , 267 , 0.0015157364770729909, 3.294730454348446e-05, 46.00487044615422
+HS48 , 1.3668380310505785e-12 , 2.3896504076590145e-08 , -1748205.47, True, 3.0 , 3.0 , 0.00 , 126 , 97 , 0.000975270119924394, 2.107177813028552e-05, 46.28323788786871
+HS49 , 4.060968732970158e-06 , 4.208809976512229e-05 , -936.41 , True, 0.0 , 0.0 , 0.00 , 2500 , 1000 , 0.0006804152488708496, 1.4566898345947265e-05, 46.70968607810403
+HS5 , -1.9132229549805313 , -1.9132229482506355 , -0.00 , True, 0.0 , 0.0 , 0.00 , 57 , 38 , 0.00041175306889048793, 1.2786764847604851e-05, 32.201504743212304
+HS50 , 3.394568194263641e-07 , 5.329841050980891e-06 , -1470.11, True, 0.0 , 0.0 , 0.00 , 2500 , 857 , 0.0004937252044677734, 1.5659399043720827e-05, 31.529000767481524
+HS51 , 1.2524005178970177e-13 , 1.790358943006667e-08 , -14295318.42, True, 2.220446049250313e-16 , 1.1102230246251565e-16 , 50.00 , 111 , 98 , 0.0010970798698631493, 1.7246421502560984e-05, 63.61202929548254
+HS52 , 5.326647564487172 , 5.3266481128480265 , -0.00 , True, 0.0 , 8.326672684688674e-17 , 0.00 , 155 , 117 , 0.001097936014975271, 1.6899190397344082e-05, 64.96974051181914
+HS53 , 4.093023255813972 , 4.093023284924769 , -0.00 , True, 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 84 , 88 , 0.0009581758862450009, 2.052296291698109e-05, 46.68798994185133
+HS54 , -0.8350653717953426 , -0.8346917264256679 , -0.04 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0004626483917236328, 1.4852046966552734e-05, 31.150479982020745
+HS55 , 0.9999999999999998 , 1.0 , -0.00 , True, 4.398722839364572e-17 , 7.323949384273595e-21 , 99.98 , 27 , 82 , 0.0016306594566062645, 1.7767999230361568e-05, 91.77507469832784
+HS56 , -3.455999996784974 , -3.455999770215785 , -0.00 , True, 6.962839194102344e-10 , 1.0788117066340419e-07 , -15393.85, 425 , 280 , 0.001703639871933881, 3.2857486179896764e-05, 51.84936737419131
+HS57 , 0.030646319844124605 , 0.03064653795175385 , -0.00 , True, 0.0 , 0.0 , 0.00 , 41 , 30 , 0.0003853716501375524, 2.693335215250651e-05, 14.308343348998552
+HS59 , -7.802789403951061 , -6.74950471925318 , -13.50 , True, 4.908088158117607e-09 , 0.0 , 100.00 , 62 , 107 , 0.000540583364425167, 2.2963942768417787e-05, 23.540529162467212
+HS6 , 2.659399629742652e-16 , 3.430240577916082e-10 , -128985425.14, True, 3.997913111675189e-12 , 4.0028778069967075e-08 , -1001141.82, 51 , 40 , 0.0006423744500852099, 3.2448768615722655e-05, 19.796574030052877
+HS60 , 0.03256820028662839 , 0.03256820403967027 , -0.00 , True, 2.5243585000112034e-11 , 4.5365016188014806e-08 , -179609.09, 82 , 50 , 0.0007014390898913872, 3.386020660400391e-05, 20.71573567446701
+HS61 , -81.9190960946562 , -81.91909603830148 , -0.00 , True, 7.051248473999294e-11 , 7.051226269538802e-09 , -9899.97, 77 , 73 , 0.000797024020900974, 3.163128683011826e-05, 25.197331527532867
+HS62 , -26272.514646270065 , nan , nan , True, 0.0 , 1.533407839658385 , 0.00 , 385 , 59 , 0.0005814162167635831, 2.0572694681458552e-05, 28.261548900912484
+HS63 , 961.7151721285715 , 961.7151721061806 , 0.00 , True, 1.2500507295953867e-09 , 1.9999431444261973e-08 , -1499.89, 76 , 66 , 0.0010064338382921722, 4.3572801532167375e-05, 23.0977537110892
+HS64 , 6299.842422132616 , 6299.84242792121 , -0.00 , True, 5.776867061102475e-09 , 4.1320419308377154e-13 , 99.99 , 176 , 371 , 0.0006296878511255437, 2.0644092816869524e-05, 30.502083899321946
+HS65 , 0.953528855884636 , 0.9535288959683764 , -0.00 , True, 6.471939428820406e-09 , 2.3011377692228052e-08 , -255.56 , 98 , 70 , 0.0005856338812380421, 2.948556627546038e-05, 19.861713889668145
+HS66 , 0.5181632731531938 , 0.5181632732274452 , -0.00 , True, 4.561963695692839e-09 , 2.60709320798469e-09 , 42.85 , 69 , 58 , 0.0007716192715409874, 2.6739876845787313e-05, 28.856500573694706
+HS68 , -0.9204250023983066 , -0.9203940315688056 , -0.00 , True, 1.6521228829446954e-12 , 2.16818663023588e-09 , -131136.40, 1919 , 1000 , 0.0009502293605118632, 3.0305147171020507e-05, 31.355378515387187
+HS7 , -1.7320508076751209 , -1.7320508111268387 , 0.00 , True, 4.0000580625587645e-10 , 4.0000162737641176e-08 , -9899.90, 71 , 54 , 0.0007170690617091219, 3.0446935583043983e-05, 23.55143622757426
+HS70 , 0.2690861852831081 , 0.26908618894490766 , -0.00 , True, 7.929190637412376e-10 , 0.0 , 100.00 , 71 , 64 , 0.0011711825787181585, 3.343820571899414e-05, 35.02528181567121
+HS71 , 17.014017287253317 , 17.01401728831689 , -0.00 , True, 7.500043253116928e-09 , 2.000000343116426e-08 , -166.67 , 137 , 70 , 0.0010568695346804431, 4.554476056780134e-05, 23.205073898832072
+HS72 , 727.67899310589 , 727.6793577895986 , -0.00 , True, 1.2488123892840167e-08 , 5.121120541518032e-15 , 100.00 , 320 , 944 , 0.0011094622313976288, 2.1704425246028577e-05, 51.11686758904779
+HS73 , 21.70034024455217 , 21.70034043817458 , -0.00 , True, 5.808574387409816e-12 , 0.0 , 100.00 , 24 , 28 , 0.001163711150487264, 5.1294054303850445e-05, 22.687057326250553
+HS74 , 5126.498109901266 , 436.89511259075994 , 91.48 , True, 1.159658880034442e-08 , 479.93301445999 , -4138570597882.54, 157 , 1000 , 0.0010929745473679464, 3.654408454895019e-05, 29.908384923527777
+HS75 , 5174.412695642915 , 436.89511259075994 , 91.56 , True, 1.353841483364704e-10 , 479.93301445999 , -354497199529649.88, 83 , 1000 , 0.0013568688587970044, 3.610801696777344e-05, 37.57804977238201
+HS76 , -4.681818181818033 , -4.681818171849587 , -0.00 , True, 8.881784197001252e-16 , 1.2688556221783827e-16 , 85.71 , 78 , 72 , 0.0006519494912563226, 1.4291869269476996e-05, 45.61681043701433
+HS76I , -4.681818181818033 , -4.681818171849587 , -0.00 , True, 8.881784197001252e-16 , 1.2688556221783827e-16 , 85.71 , 78 , 72 , 0.0006382067998250326, 1.5599860085381402e-05, 40.91105922309489
+HS77 , 0.24150512830947857 , 0.2415051297117471 , -0.00 , True, 2.630118345336996e-13 , 3.381773971966595e-08 , -12857777.59, 201 , 132 , 0.0009368497933914412, 3.094564784656871e-05, 30.274040409056106
+HS78 , -2.919700409047368 , -2.9197004157844497 , 0.00 , True, 1.240882951947242e-10 , 2.0006278744766348e-08 , -16022.62, 124 , 110 , 0.0011742884112942603, 3.3055652271617545e-05, 35.524587493998276
+HS79 , 0.07877682098723651 , 0.07877682718903371 , -0.00 , True, 4.805045250577678e-13 , 6.055925760861669e-09 , -1260226.48, 120 , 91 , 0.0013684988021850585, 3.44737545474545e-05, 39.696830825353395
+HS8 , -1.0 , -1.0 , 0.00 , False, 1.1235457009206584e-13 , 0.0002806925282650141 , -249827423951.38, 15 , 17 , 0.0007341225941975911, 4.736114950741039e-05, 15.500523146777217
+HS80 , 0.05394984776144163 , 0.05394984783191497 , -0.00 , True, 4.405702358489805e-09 , 2.065755155822302e-08 , -368.88 , 131 , 106 , 0.0011649386573383826, 3.648029183441738e-05, 31.933370013211345
+HS83 , -30665.53867178331 , -30665.538289526732 , -0.00 , True, 0.0 , 3.268360524278465e-08 , 0.00 , 36 , 67 , 0.0016066100862291125, 3.061009876763643e-05, 52.486275801493186
+HS84 , -5280335.079198177 , -5280334.2186224535 , -0.00 , True, 0.0 , 0.0 , 0.00 , 32 , 54 , 0.0014945939183235168, 3.1329967357494216e-05, 47.70493059470124
+HS85 , -2.2156046884766307 , -2.215604689605889 , 0.00 , True, 6.295408638834488e-12 , 8.624069209872687e-07 , -13698882.39, 49 , 266 , 0.0018364701952253068, 7.541645738415252e-05, 24.351053588619102
+HS86 , -32.34867896572225 , -32.34867871945164 , -0.00 , True, 1.1102230246251565e-16 , 0.0 , 100.00 , 81 , 76 , 0.0008820751566945771, 1.5315256620708266e-05, 57.594539780802236
+HS87 , 8996.881026598478 , 9196.88105110364 , -2.22 , True, 1.4873345610677546e-13 , 1.971844909576248e-11 , -13157.57, 146 , 142 , 0.0024563694653445727, 4.1541918902329994e-05, 59.12989891294599
+HS88 , 1.3626551998868794 , 1.3626533804334495 , 0.00 , True, 1.5249093925383045e-09 , 2.2630245737767312e-08 , -1384.04, 121 , 132 , 0.0006946532194279442, 5.406863761670662e-05, 12.847618324551679
+HS89 , 1.3626510087114894 , 0.6534571821001889 , 52.05 , True, 5.62993057938953e-09 , 0.05121323369363297 , -909660027.62, 295 , 368 , 0.0008284415228892181, 7.655115231223728e-05, 10.822064696167683
+HS9 , -0.4999999999999995 , -0.4999999997539461 , -0.00 , True, 1.7763568394002505e-15 , 1.7763568394002505e-15 , 0.00 , 41 , 33 , 0.0005403669869027487, 3.151460127397017e-05, 17.146559532995603
+HS90 , 1.362648090657046 , 1.3630340071803333 , -0.03 , True, 8.82065914600339e-09 , 1.529846251095083e-08 , -73.44 , 2000 , 164 , 0.000781682014465332, 0.00010245747682524891, 7.629331100926543
+HS91 , 1.3627003313032782 , 0.8818616472766194 , 35.29 , True, 9.618945080470129e-09 , 0.0066511102162089 , -69145842.31, 2500 , 1000 , 0.0008310148239135742, 0.0001330878734588623, 6.244106260894178
+HS92 , 1.362648315862452 , 1.367642925113423 , -0.37 , True, 8.823653232590124e-09 , 3.131879766495678e-08 , -254.94 , 2200 , 504 , 0.0010079858519814232, 0.00020546667159549773, 4.905836280668648
+HS93 , 135.07596849134077 , 135.08418262044773 , -0.01 , True, 8.19484991407271e-10 , 9.17840560556979e-07 , -111902.12, 3000 , 1000 , 0.0009676140149434408, 2.3874998092651366e-05, 40.528338942203675
+HS95 , 0.015619525242393854 , 0.015619525242383853 , 0.00 , True, 9.74976069359163e-21 , 5.55208328359702e-17 , -569358.42, 27 , 38 , 0.0018240699061640986, 3.705526653089022e-05, 49.22565877763981
+HS96 , 0.015619525242393854 , 0.015619525242383853 , 0.00 , True, 9.74976069359163e-21 , 5.55208328359702e-17 , -569358.42, 27 , 38 , 0.0017926339749936705, 3.7161927474172494e-05, 48.2384552372734
+HS97 , 4.071246356513221 , 4.071246356513634 , -0.00 , True, 2.6496772428321986e-15 , 4.7477714944161886e-14 , -1691.83, 90 , 44 , 0.00126546753777398, 3.5746531053022906e-05, 35.401128459063884
+HS98 , 4.071246356513219 , 4.07124635651389 , -0.00 , True, 6.277010138248813e-15 , 0.0 , 100.00 , 47 , 44 , 0.0014956809104757105, 3.919818184592507e-05, 38.156895040559064
+HS99 , -831079891.4454019 , -831079253.8696586 , -0.00 , True, 8.119968697428703e-09 , 0.003438641535467468 , -42347865.41, 1063 , 870 , 0.0015174817724936656, 2.9898785996711118e-05, 50.75395946379192
+HUBFIT , 0.016893493939398714 , 0.01689349718586012 , -0.00 , True, 0.0 , 1.1102230246251565e-16 , 0.00 , 42 , 38 , 0.0005108628954206194, 1.968208112214741e-05, 25.955735689239038
+HUMPS , 0.07402558850596634 , 2.0602960599452964 , -2683.22, True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.00036452412605285643, 6.0830116271972655e-06, 59.92494316845654
+HYPCIR , 0.0 , 0.0 , 0.00 , False, 6.257883100602157e-11 , 1.2569433910147865e-05 , -20085660.17, 14 , 16 , 0.0009254898343767438, 5.869567394256592e-05, 15.767598737895767
+INTEQNE , 0.0 , 0.0 , 0.00 , False, 1.1899801123065486e-16 , 5.390476856057033e-06 , -4529888189046.81, 63 , 74 , 0.0036056230938623824, 7.734749768231366e-05, 46.615898405293166
+JENSMP , 244.6723932383504 , 256.16633854572797 , -4.70 , True, 0.0 , 0.0 , 0.00 , 1000 , 31 , 0.0003684151172637939, 1.045196287093624e-05, 35.24841427520236
+JENSMPNE , 0.0 , 0.0 , 0.00 , False, 4.6933761371746625 , 4.693388772381194 , -0.00 , 57 , 63 , 0.0008112296723482901, 5.666793338836185e-05, 14.315497739942217
+KIRBY2 , 0.0 , 0.0 , 0.00 , False, 0.3353220492242386 , 8.704211509383981 , -2495.78, 453 , 1000 , 0.0037359007146974274, 0.00048044490814208986, 7.775919052081093
+KIRBY2LS , 1134.542293442038 , 200082.05825988215 , -17535.49, True, 0.0 , 0.0 , 0.00 , 39 , 57 , 0.00042369426825107675, 1.2820227104320861e-05, 33.0488894466056
+KIWCRESC , -4.3039668897216e-09 , -2.5269589045371094e-09, -41.29 , True, 9.77613667796362e-09 , 1.9999999767428278e-08 , -104.58 , 65 , 65 , 0.0013820758232703577, 2.5228353647085337e-05, 54.7826403024135
+KOEBHELB , 112.2324652731155 , 113.28664365934536 , -0.94 , True, 0.0 , 0.0 , 0.00 , 1500 , 14 , 0.0004128459294637044, 2.4931771414620535e-05, 16.559029143897995
+KOWOSB , 0.0003539600568591833 , 0.00038688806415937014 , -9.30 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.0004183650016784668, 6.96706771850586e-06, 60.048935733351584
+KOWOSBNE , 0.0 , 0.0 , 0.00 , False, 0.008084368386043338 , 0.00808583455662032 , -0.02 , 39 , 38 , 0.0015308184501452323, 6.61862523932206e-05, 23.128948909970806
+KSIP , 0.5757979234546846 , 0.5757979255464901 , -0.00 , True, 1.3807686216615878e-08 , 6.661338147750939e-16 , 100.00 , 750 , 634 , 0.027528508186340332, 0.0008133755121321332, 33.84477129656728
+LANCZOS1 , 0.0 , 0.0 , 0.00 , False, 2.624619657026761e-09 , 4.521541613344837e-09 , -72.27 , 507 , 689 , 0.003046384224524865, 0.00011666454320722118, 26.11234005445712
+LANCZOS1LS , 0.0015028766437297843 , 0.0023299171692234373 , -55.03 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0006532660325368246, 1.0816097259521484e-05, 60.39757380710959
+LANCZOS2 , 0.0 , 0.0 , 0.00 , False, 2.5616923711169193e-06 , 3.0022247374805744e-06 , -17.20 , 741 , 1000 , 0.0028394983525861775, 9.669089317321777e-05, 29.366761019565022
+LANCZOS2LS , 0.0014905484473592546 , 0.0023493445221330583 , -57.62 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0005183592637379964, 8.569955825805664e-06, 60.485640098298326
+LANCZOS3 , 0.0 , 0.0 , 0.00 , False, 3.8872465890252283e-05 , 3.9353375935691215e-05 , -1.24 , 654 , 77 , 0.0022951266087523296, 0.00011855905706232244, 19.358509300102313
+LANCZOS3LS , 0.0014414244649921589 , 0.002248338414042431 , -55.98 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.00047188337643941245, 8.73398780822754e-06, 54.02839880253687
+LEWISPOL , 3.8200966003246027e-13 , 6.037172370249287e-09 , -1580271.65, True, 30.0 , 30.0 , 0.00 , 169 , 135 , 0.001029722789335533, 8.529027303059896e-05, 12.073156208165813
+LIN , -0.01960627871535296 , -0.019122267920497843 , -2.47 , True, 1.1102230246251565e-16 , 0.029258730348960538 , -26353921419382400.00, 324 , 60 , 0.0010510491736141251, 0.0003184676170349121, 3.3003329613224177
+LOGHAIRY , 5.936265653044021 , 6.204964390857263 , -4.53 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0004649970531463623, 7.606029510498047e-06, 61.135320669550495
+LOGROS , 0.0 , 0.5738932043439839 , 0.00 , False, 0.0 , 0.0 , 0.00 , 34 , 882 , 0.00042635553023394416, 8.441153026762463e-06, 50.509157798963564
+LOOTSMA , -6.639986497366268 , 4.900175445692261 , -173.80 , True, 1.0000000000003528 , 1.0000000200000276 , -0.00 , 84 , 67 , 0.0009571285474868048, 3.1044234090776585e-05, 30.83112131831183
+LOTSCHD , 0.0 , 6.642249999999998e-09 , 0.00 , False, 0.0 , 6.487214466105119e-21 , 0.00 , 57 , 127 , 0.0021796477468390215, 2.7889341819943407e-05, 78.15343083071167
+LSC1 , 0.0 , 0.0 , 0.00 , False, 20.59128191228956 , 20.634217308633765 , -0.21 , 124 , 1000 , 0.001209572438270815, 5.10711669921875e-05, 23.684057159998844
+LSC1LS , 1094.5009122473384 , 1569.0090759854781 , -43.35 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00044538021087646483, 7.580041885375976e-06, 58.75695907904256
+LSC2 , 0.0 , 0.0 , 0.00 , False, 1.8579296004027128 , 1.9223791146362714 , -3.47 , 156 , 1000 , 0.0010485068345681215, 4.978203773498535e-05, 21.061950901846306
+LSC2LS , 16.04542573077069 , 36.74697943684713 , -129.02 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0005188700358072916, 1.0124683380126953e-05, 51.24802587167773
+LSNNODOC , 0.0013164087313451043 , 0.00248921836647701 , -89.09 , True, 10.0 , 10.0 , 0.00 , 2500 , 1000 , 0.001014249610900879, 1.9684076309204103e-05, 51.52640108525817
+LSQFIT , 0.03378698787879743 , 0.03378699437172024 , -0.00 , True, 0.0 , 1.1102230246251565e-16 , 0.00 , 42 , 38 , 0.000628329458690825, 1.910485719379626e-05, 32.888466651028224
+MADSEN , 0.616432435072257 , 0.616432429017703 , 0.00 , True, 1.8759019893010986e-09 , 2.0002580702893624e-08 , -966.29 , 84 , 63 , 0.0010018462226504372, 3.4112778920975945e-05, 29.368648768582204
+MAKELA1 , -1.4142135674087453 , -1.414213574056223 , 0.00 , True, 7.50028950058379e-09 , 1.9999412681492856e-08 , -166.65 , 89 , 64 , 0.0008727084384875352, 3.415718674659729e-05, 25.54977507257601
+MAKELA2 , 7.199999994681275 , 7.199999998946289 , -0.00 , True, 7.50009743200053e-09 , 1.9999744083065707e-08 , -166.66 , 93 , 85 , 0.0008986124428369666, 2.7011422549977023e-05, 33.26786810929108
+MARATOS , -1.0000000030881628 , -1.0000000065320287 , 0.00 , True, 7.499812483947891e-09 , 1.999789887698231e-08 , -166.65 , 54 , 42 , 0.0008073515362209744, 3.869192940848214e-05, 20.866148278560253
+MARATOSB , 1.0000001135252934 , 1.0280420792410259 , -2.80 , True, 0.0 , 0.0 , 0.00 , 46 , 31 , 0.0004007608994193699, 1.125181874921245e-05, 35.61743291033908
+MATRIX2 , 2.4886787886708407e-13 , 5.5903814276132715e-09 , -2246225.02, True, 2.989196465904595e-12 , 1.3090055599121024e-09 , -43691.22, 133 , 117 , 0.0013546298321028402, 3.181971036470853e-05, 42.57203527550866
+MAXLIKA , 1149.311569730486 , nan , nan , True, 0.0 , 3.9998999999999967 , 0.00 , 4000 , 93 , 0.001142895758152008, 4.257950731503066e-05, 26.841450975374798
+MCONCON , -13372.956913035729 , -8970.549732816078 , -32.92 , True, 1000.0 , 1000.0000000000001 , -0.00 , 4947 , 1000 , 0.003936196279593229, 4.70731258392334e-05, 83.61875718719706
+MDHOLE , 5.817221177434099 , 7.93810479325739 , -36.46 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0003581387996673584, 6.824016571044922e-06, 52.48211166235763
+MEXHAT , 0.16058334003669944 , 0.13397947544143426 , 16.57 , True, 0.0 , 0.0 , 0.00 , 1000 , 28 , 0.0003308241367340088, 9.715557098388672e-06, 34.050969325153375
+MEYER3 , 7081397.716410512 , 7180936.942682075 , -1.41 , True, 0.0 , 0.0 , 0.00 , 115 , 49 , 0.0003773938054623811, 7.999186613121812e-06, 47.17902253252935
+MEYER3NE , 0.0 , 0.0 , 0.00 , False, 31.733493240528333 , 98.61035270974753 , -210.75 , 1500 , 1000 , 0.0008506460189819336, 5.4048061370849607e-05, 15.738696216044536
+MGH09 , 0.0 , 0.0 , 0.00 , False, 0.013514448366290482 , 0.013865007226279924 , -2.59 , 2000 , 1000 , 0.0011714234352111815, 5.446600914001465e-05, 21.5074218527711
+MGH09LS , 0.006806104091232839 , 0.007712599744335959 , -13.32 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.00040782296657562256, 7.098197937011718e-06, 57.454437054950965
+MGH10 , 0.0 , 0.0 , 0.00 , False, 15435.174910484824 , 15492.164395288964 , -0.37 , 1500 , 1000 , 0.0008472480773925781, 6.514906883239746e-05, 13.00476112056504
+MGH10LS , 1366860355.936367 , 1381312551.0101686 , -1.06 , True, 0.0 , 0.0 , 0.00 , 47 , 42 , 0.0006435272541451962, 9.854634602864584e-06, 65.30199039121483
+MGH10S , 0.0 , 0.0 , 0.00 , False, 5.065742562474043 , 144.26391731940384 , -2747.83, 1500 , 1000 , 0.001045417308807373, 7.08160400390625e-05, 14.76243670545141
+MGH17 , 0.0 , 0.0 , 0.00 , False, 0.2649999983691491 , 0.2649958584298719 , 0.00 , 57 , 683 , 0.0015917409930312843, 0.00013908774402445268, 11.444149908358886
+MGH17LS , 1.106036218750212 , 1.1072477047543667 , -0.11 , True, 0.0 , 0.0 , 0.00 , 289 , 1000 , 0.0005194258112395923, 1.1410951614379882e-05, 45.51993810796822
+MGH17S , 0.0 , 0.0 , 0.00 , False, 0.2649999991164428 , 0.2649948678691345 , 0.00 , 55 , 1000 , 0.0017652728340842507, 0.00013646197319030763, 12.936005487934944
+MIFFLIN1 , -1.0000000025491724 , -1.0000000092602752 , 0.00 , True, 7.499656378582602e-09 , 2.000000003387451e-08 , -166.68 , 63 , 53 , 0.0009465104057675316, 5.0247840161593456e-05, 18.836837617768683
+MIFFLIN2 , -1.0000000009121601 , -1.000000007202976 , 0.00 , True, 7.03149435283479e-09 , 7.50080113444859e-08 , -966.74 , 82 , 59 , 0.001173818983682772, 5.5187839572712525e-05, 21.269522285542838
+MINMAXBD , 822.559 , 115.7064396668632 , 85.93 , True, 0.0 , 2.4966753525701435e-08 , 0.00 , 2500 , 824 , 0.0011209948539733887, 7.295868929150034e-05, 15.364788825831937
+MINMAXRB , 0.0 , 3.608224830031759e-16 , 0.00 , False, 1.7763568394002505e-15 , 2.789139829673104e-05 , -1570146137100.00, 28 , 60 , 0.0013152446065630233, 4.5482317606608076e-05, 28.91771298769377
+MISRA1A , 0.0 , 0.0 , 0.00 , False, 0.12611092265655088 , 1.6508123118882736 , -1209.02, 91 , 1000 , 0.0011217987144386376, 9.257912635803223e-05, 12.117188383267884
+MISRA1ALS , 19.594518355352196 , 10747.849573197622 , -54751.31, True, 0.0 , 0.0 , 0.00 , 39 , 5 , 0.0007184101985051081, 8.645057678222657e-05, 8.31006831006831
+MISRA1B , 0.0 , 0.0 , 0.00 , False, 0.09916875804037772 , 1.0159510957075781 , -924.47 , 80 , 1000 , 0.0014340877532958984, 8.237099647521972e-05, 17.410105676302287
+MISRA1BLS , 7.424831134022499 , 8765.497950447194 , -117956.53, True, 0.0 , 0.0 , 0.00 , 35 , 28 , 0.00046482086181640623, 1.7651489802769254e-05, 26.333236854799804
+MISRA1C , 0.0 , 0.0 , 0.00 , False, 0.07504991856171728 , 0.0 , 100.00 , 96 , 14 , 0.0013123949368794758, 0.00012409687042236328, 10.5755683637528
+MISRA1CLS , 4.692559563212987 , 11571.116798248933 , -246484.34, True, 0.0 , 0.0 , 0.00 , 38 , 4 , 0.0005683459733661852, 6.80685043334961e-05, 8.349617476265093
+MISRA1D , 0.0 , 0.0 , 0.00 , False, 0.08579603296624327 , 0.40622020325264074 , -373.47 , 96 , 1000 , 0.0012605711817741394, 7.324004173278808e-05, 17.211502778401712
+MISTAKE , -1.0000000026472402 , -1.000000042821479 , 0.00 , True, 1.181797504301585e-08 , 6.943817887350434e-08 , -487.56 , 172 , 183 , 0.0017522088316983956, 4.672743583637508e-05, 37.49850169040058
+MOREBVNE , 0.0 , 0.0 , 0.00 , False, 1.2981837047421567e-12 , 4.78898302959316e-10 , -36789.87, 52 , 63 , 0.003994423609513503, 8.036219884478856e-05, 49.70525529332922
+MWRIGHT , 24.978809538615934 , 24.97880985026962 , -0.00 , True, 8.565215203759635e-11 , 3.381369184651817e-09 , -3847.79, 152 , 102 , 0.0012754343057933607, 3.734289431104473e-05, 34.15467197506786
+NELSON , 0.0 , 0.0 , 0.00 , False, 0.5181440320956119 , 0.7479573287922272 , -44.35 , 1500 , 238 , 0.002233206590016683, 0.00036642831914565144, 6.094525104455713
+NELSONLS , 55.484298826377845 , 58.91568283195533 , -6.18 , True, 0.0 , 0.0 , 0.00 , 1500 , 41 , 0.00039609607060750327, 1.4997110134217798e-05, 26.41149308517513
+NYSTROM5 , 0.0 , 0.0 , 0.00 , False, 0.009163814503268558 , 0.011944507269092297 , -30.34 , 7500 , 1000 , 0.0026516372044881184, 9.052014350891113e-05, 29.293338495724786
+ODFITS , -2380.026775403082 , -2344.0120879448864 , -1.51 , True, 0.0 , 37.47020816688695 , 0.00 , 313 , 1000 , 0.0030240921166758187, 1.8482208251953124e-05, 163.62179645693826
+OET1 , 0.5382431193122431 , 0.5386936158944566 , -0.08 , True, 4.440892098500626e-16 , 0.0 , 100.00 , 17 , 35 , 0.0073705280528349034, 7.70296369280134e-05, 95.684315112674
+OET2 , 0.0871596358543518 , 0.32072088945283955 , -267.97 , True, 1.7763568394002505e-15 , 1.9944508267144556e-09 , -112277500.00, 24 , 76 , 0.03513017296791077, 0.0017238949474535491, 20.37837225510945
+OET3 , 0.004505052872585125 , 0.009967839872030435 , -121.26 , True, 2.7755575615628914e-17 , 3.3306690738754696e-16 , -1100.00, 30 , 52 , 0.11287006537119547, 0.0010773081045884353, 104.77045971385809
+OET4 , 0.00429542968155437 , 0.005443423951409177 , -26.73 , True, 3.1086244689504383e-15 , 0.0 , 100.00 , 34 , 43 , 0.04509129243738511, 0.0018917238989541697, 23.83608541516741
+OET5 , 0.0026500864864651566 , 0.003196907389169015 , -20.63 , True, 7.947938307495406e-09 , 1.0361844993145297e-08 , -30.37 , 804 , 197 , 0.0248510033930119, 0.0018073507977016082, 13.749961227568383
+OET6 , 0.0020697326322287866 , 0.08715963581538654 , -4111.15, True, 1.0628072533158672e-08 , 3.534472048372095e-09 , 66.74 , 434 , 88 , 0.13978921213457662, 0.002643815495751121, 52.8740422163468
+OET7 , 0.00028428994336747866 , 0.001096817998305305 , -285.81 , True, 7.086220499274987e-11 , 1.6931354471783067e-06 , -2389234.97, 3500 , 1000 , 0.012406636306217739, 0.0017832670211791993, 6.957251022347598
+OSBORNE1 , 0.0 , 0.0 , 0.00 , False, 0.0027817898995155994 , 0.0027818884248117367 , -0.00 , 69 , 222 , 0.0037099002064138217, 0.00012616638664726738, 29.404822512559242
+OSBORNE2 , 0.0 , 0.0 , 0.00 , False, 0.048027412887798715 , 0.04803248935990123 , -0.01 , 5500 , 1000 , 0.0044582757949829105, 0.00020226502418518066, 22.041753451655605
+OSBORNEA , 0.004328709474377522 , 0.059537620695548056 , -1275.41, True, 0.0 , 0.0 , 0.00 , 2500 , 287 , 0.00040103569030761716, 7.620256536929034e-06, 52.62758390929903
+OSBORNEB , 0.2551399831307883 , 0.29084371628595873 , -13.99 , True, 0.0 , 0.0 , 0.00 , 5500 , 1000 , 0.0005395476601340554, 1.3611078262329101e-05, 39.64033192192733
+OSCIPANE , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 41 , 61 , 0.0003996069838361042, 7.011460476234311e-05, 5.6993401758534
+OSLBQP , 6.2500000000000755 , 6.2500000066152035 , -0.00 , True, 3.85924847427735e-22 , 1.7313673500272e-18 , -448528.11, 111 , 118 , 0.0010668479644500458, 1.119759123204118e-05, 95.27477314918673
+PALMER1 , 18426.97730380164 , 17576.650914898073 , 4.61 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.0003655660152435303, 7.077932357788086e-06, 51.64870145181393
+PALMER1A , 15595.886845369452 , 16067.707440698048 , -3.03 , True, 0.0 , 0.0 , 0.00 , 3000 , 832 , 0.00040282265345255536, 7.4640489541567286e-06, 53.968383102340646
+PALMER1B , 20458.740170932877 , 20305.23345647078 , 0.75 , True, 0.0 , 0.0 , 0.00 , 2000 , 507 , 0.00037306499481201173, 7.793042786727996e-06, 47.87154453294714
+PALMER1C , 40143.2411255121 , 175360.73723720576 , -336.84 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0004768775105476379, 8.614063262939454e-06, 55.36034458898422
+PALMER1D , 38838.68102031663 , 45421.66075524001 , -16.95 , True, 0.0 , 0.0 , 0.00 , 3500 , 1000 , 0.0005281685420445033, 2.0392179489135744e-05, 25.90054399657935
+PALMER1E , 31545.841528458997 , 37742.59112904983 , -19.64 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.000714377760887146, 1.723194122314453e-05, 41.45660385190105
+PALMER2 , 3933.02094414188 , 4706.620154926712 , -19.67 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.0007894455194473267, 4.495596885681152e-05, 17.560416103182558
+PALMER2A , 175.59072825893284 , 443.7803645656473 , -152.74 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0009437759717305501, 1.7165899276733397e-05, 54.979698792112856
+PALMER2B , 1136.440931815371 , 1166.8495067165788 , -2.68 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.000778048038482666, 2.5928735733032227e-05, 30.00717221593887
+PALMER2C , 1458.2566929233517 , 8479.995025585766 , -481.52 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0009304055571556091, 2.7450084686279297e-05, 33.8944512481109
+PALMER2E , 612.379217991725 , 1053.1313324449006 , -71.97 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0007037519812583923, 9.928941726684571e-06, 70.8788510025213
+PALMER3 , 2416.9805277318605 , 2416.9805681329453 , -0.00 , True, 1.3497761085680545e-10 , 5.421010862427522e-20 , 100.00 , 130 , 106 , 0.0005205832994901217, 1.3828277587890625e-05, 37.646286472148546
+PALMER3A , 62.578266724925825 , 89.18426333777415 , -42.52 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.000501252015431722, 7.826805114746094e-06, 64.04299175500589
+PALMER3B , 309.77081333812976 , 262.3123413543056 , 15.32 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.0003691519498825073, 7.181167602539063e-06, 51.40556108897742
+PALMER3C , 281.31682987791885 , 2346.7200763756005 , -734.19 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.00045891201496124265, 7.791996002197266e-06, 58.89530934459335
+PALMER3E , 136.96208957072193 , 188.42913878471836 , -37.58 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0005943915247917176, 1.3798952102661133e-05, 43.07512051419389
+PALMER4 , 2424.016566791981 , 2424.0166444549363 , -0.00 , True, 0.0 , 0.0 , 0.00 , 79 , 107 , 0.0021410380737690985, 2.3420726027444144e-05, 91.4163835595982
+PALMER4A , 69.40817027791556 , 90.92371379687197 , -31.00 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0006025390625, 1.1413812637329101e-05, 52.790341110855806
+PALMER4B , 254.3322023583863 , 135.37802511859908 , 46.77 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.000518918514251709, 1.2322664260864258e-05, 42.11090258295443
+PALMER4C , 272.0005141189913 , 2307.438636422316 , -748.32 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0007137389779090881, 2.374100685119629e-05, 30.06355132209245
+PALMER4E , 123.48847944611043 , 182.28782900584324 , -47.62 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0007795212268829346, 1.5221118927001953e-05, 51.21313555339745
+PALMER5A , 7473.394291347882 , 8271.203812982863 , -10.68 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0008203052878379822, 1.0535955429077149e-05, 77.85770292593514
+PALMER5B , 4992.511406614943 , 6192.495642725261 , -24.04 , True, 0.0 , 0.0 , 0.00 , 4500 , 1000 , 0.0006161942481994629, 1.0640859603881836e-05, 57.908314848423736
+PALMER5C , 2.1280866434670522 , 2.128086786937186 , -0.00 , True, 0.0 , 0.0 , 0.00 , 390 , 242 , 0.0005315994605039939, 1.0268747313948702e-05, 51.76867676760222
+PALMER5D , 517.3947921492595 , 831.3481735093361 , -60.68 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.0004996631145477295, 9.399890899658204e-06, 53.156267437731444
+PALMER5E , 0.4511720609317045 , 0.5416183301910702 , -20.05 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0006471437215805054, 1.3000965118408203e-05, 49.77659086741244
+PALMER6A , 40.20591002687489 , 51.83652561105828 , -28.93 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0006094636917114257, 1.1648893356323243e-05, 52.319449845473706
+PALMER6C , 195.75176237716278 , 226.47263022520832 , -15.69 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0007245749831199646, 1.049494743347168e-05, 69.040363252232
+PALMER6E , 29.03020349871904 , 137.81130902318995 , -374.72 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0007137767672538757, 9.70292091369629e-06, 73.56308204535961
+PALMER7A , 23.332481546463345 , 74.14079098301832 , -217.76 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.00047919702529907224, 7.803916931152344e-06, 61.40468043504827
+PALMER7C , 311.4131460348408 , 524.2604396316923 , -68.35 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.00046015465259552, 6.135940551757813e-06, 74.99333618277898
+PALMER7E , 72.70337739300605 , 139.13518155539796 , -91.37 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.00044088172912597656, 7.5361728668212895e-06, 58.502072194628106
+PALMER8A , 6.871746864460462 , 44.6500637135331 , -549.76 , True, 0.0 , 0.0 , 0.00 , 3000 , 1000 , 0.0004001479943593343, 7.546901702880859e-06, 53.02149280764937
+PALMER8C , 167.4385745025157 , 206.36506999750594 , -23.25 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0004783860445022583, 7.302045822143554e-06, 65.5139745975773
+PALMER8E , 32.90721047483019 , 72.64116733305937 , -120.75 , True, 0.0 , 0.0 , 0.00 , 4000 , 1000 , 0.0005176717042922974, 8.866071701049804e-06, 58.38794471186168
+PENLT1NE , 0.0 , 0.0 , 0.00 , False, 3.247402347028583e-15 , 4.665901482559659e-07 , -14368103953.47, 109 , 123 , 0.0009790538647852906, 4.59023607455618e-05, 21.329052555972368
+PENLT2NE , 0.0 , 0.0 , 0.00 , False, 7.149708869819871e-06 , 7.191284433716305e-06 , -0.58 , 57 , 72 , 0.0008256686361212479, 5.7084692849053274e-05, 14.463923600404224
+PENTAGON , 0.00014621320344806158 , 0.00014621329758032382 , -0.00 , True, 0.0 , 4.440892098500626e-16 , 0.00 , 111 , 84 , 0.001132135992651587, 1.860800243559338e-05, 60.84135019705489
+PFIT1 , 0.0 , 0.0 , 0.00 , False, 0.009972264424374266 , 0.01052368350320787 , -5.53 , 1500 , 1000 , 0.0008875425656636556, 3.311491012573242e-05, 26.801901690017807
+PFIT1LS , 0.5227670808206053 , 0.8773806454261258 , -67.83 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00040715853373209635, 8.332014083862305e-06, 48.86676014154767
+PFIT2 , 0.0 , 0.0 , 0.00 , False, 0.07107887331608609 , 0.07083355535754343 , 0.35 , 1500 , 1000 , 0.0007090678215026855, 3.2462120056152345e-05, 21.842930168336316
+PFIT2LS , 1.9481840450734285 , 12.256752302823445 , -529.14 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.00042748133341471354, 9.128093719482422e-06, 46.83139180553379
+PFIT3 , 0.0 , 0.0 , 0.00 , False, 0.18184498174026942 , 0.18201142470308795 , -0.09 , 1500 , 1000 , 0.0009600192705790202, 7.341623306274414e-05, 13.076389655657671
+PFIT3LS , 6.287985246701351 , 39.36165938886093 , -525.98 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0004496898651123047, 8.435964584350587e-06, 53.3062770256903
+PFIT4 , 0.0 , 0.0 , 0.00 , False, 0.32135655443162037 , 0.3223648377134225 , -0.31 , 1500 , 1000 , 0.0009535231590270996, 3.242111206054688e-05, 29.410563007412634
+PFIT4LS , 9.384636725256742 , 43.28808581992509 , -361.27 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0004047813415527344, 8.050203323364258e-06, 50.282126462313045
+POLAK1 , 2.7182818192195684 , 2.718281828408681 , -0.00 , True, 9.507688414345239e-09 , 5.4370286051153016e-11 , 99.43 , 110 , 292 , 0.0007416530088944868, 1.9456425758257305e-05, 38.11866671244739
+POLAK2 , 1.1 , -932.5289613566381 , 84875.36, True, 90.74478199714775 , 152434.20612772668 , -167881.24, 5500 , 1000 , 0.008732337301427667, 1.99432373046875e-05, 437.8595695381512
+POLAK3 , -13.957712874480018 , -1.9934046864105297 , -85.72 , True, 35.383402510344645 , 7.959991403795836 , 77.50 , 6000 , 1000 , 0.011217011491457622, 4.439616203308106e-05, 252.65723381898312
+POLAK4 , 1.1192154093882192e-13 , 5.212612849176793e-09 , -4657281.24, True, 1.3523488342899713e-10 , 7.631272879833192e-08 , -56329.77, 90 , 67 , 0.0009974532657199436, 2.584884415811567e-05, 38.58792523250123
+POLAK5 , 50.00000003503054 , 19.14766139213366 , 61.70 , True, 2.5795099389824827e-10 , 30.857625611031516 , -11962592252288.71, 1500 , 1000 , 0.000708303451538086, 1.5940189361572267e-05, 44.435071345239166
+POLAK6 , 0.0 , -993.9999986803389 , 0.00 , False, 0.0 , 993.0297799765005 , 0.00 , 2500 , 1000 , 0.0007019983291625977, 1.906609535217285e-05, 36.81919743900762
+PORTFL1 , 0.018457825613176597 , 0.018457830260677037 , -0.00 , True, 7.409911470835284e-23 , 1.0262945234025578e-19 , -138402.94, 1279 , 606 , 0.0015366028956457262, 1.9403967526879642e-05, 79.19013951745299
+PORTFL2 , 0.027989415850735305 , 0.0279894186824485 , -0.00 , True, 3.5007553171730114e-22 , 3.073893629126549e-21 , -778.07 , 476 , 197 , 0.0016860696447997534, 2.0111877906140943e-05, 83.83452070803047
+PORTFL3 , 0.02845310222824779 , 0.028453103470708885 , -0.00 , True, 4.974106347955733e-22 , 3.02997399669649e-20 , -5991.49, 244 , 188 , 0.001476114890614494, 2.053697058495055e-05, 71.8759801748067
+PORTFL4 , 0.021620228016928155 , 0.02162023097696929 , -0.00 , True, 2.881400829909583e-25 , 5.50250497949128e-20 , -19096531.48, 286 , 212 , 0.0014581988741467883, 2.048038086801205e-05, 71.19979279410393
+PORTFL6 , 0.023659274250001115 , 0.023659280802090735 , -0.00 , True, 4.1660600678407226e-22 , 1.0139328018602346e-19 , -24237.93, 865 , 610 , 0.0018705629888986577, 2.3501427447209593e-05, 79.59359035107275
+POWELLBS , 0.0 , 0.0 , 0.00 , False, 3.164135620181696e-14 , 3.7443014577753786e-08 , -118335581.75, 216 , 89 , 0.0006350422347033465, 3.143910611613413e-05, 20.199118650432982
+POWELLBSLS , 0.013862884415376104 , 9.891391626769396 , -71251.61, True, 0.0 , 0.0 , 0.00 , 39 , 27 , 0.00032882201365935494, 1.0667023835358796e-05, 30.826031584309728
+POWELLSE , 0.0 , 0.0 , 0.00 , False, 1.415723430320683e-13 , 2.5509793991955816e-08 , -18018810.65, 83 , 61 , 0.0013944097312099963, 4.819963799148309e-05, 28.92987975254896
+POWELLSQ , 0.0 , 0.0 , 0.00 , False, 8.742702096101641e-08 , 4.094709739029326e-08 , 53.16 , 1000 , 61 , 0.0006793529987335205, 3.929216353619685e-05, 17.289783447727046
+PSPDOC , 2.4142135623790284 , 2.4142136044786575 , -0.00 , True, 0.0 , 0.0 , 0.00 , 262 , 175 , 0.0006047742057392615, 9.400503976004465e-06, 64.3342322159531
+PT , 0.1783942254303165 , 0.17839422543031647 , 0.00 , True, 0.0 , 2.7755575615628914e-17 , 0.00 , 9 , 13 , 0.008801539738972982, 0.00014239091139573319, 61.812510733298986
+QC , -1055.316255331069 , -1077.8351725231637 , 2.13 , True, 1.1102230246251565e-16 , 2.255140518769849e-16 , -103.12 , 59 , 54 , 0.0013785725933010296, 2.109121393274378e-05, 65.36241098767753
+QCNEW , -806.5218543888329 , nan , nan , True, 0.0 , 0.486386486 , 0.00 , 32 , 82 , 0.0015308782458305359, 1.709635664777058e-05, 89.54412202380954
+RAT42 , 0.0 , 0.0 , 0.00 , False, 1.4056861586229985 , 4.6850703149595745 , -233.29 , 99 , 1000 , 0.0010965665181477864, 4.799485206604004e-05, 22.847586166927464
+RAT42LS , 469.0113295948495 , 356.49260098639155 , 23.99 , True, 0.0 , 0.0 , 0.00 , 1500 , 43 , 0.0004178573290506999, 9.675358617028525e-06, 43.187787201528174
+RAT43 , 0.0 , 0.0 , 0.00 , False, 44.50658873261841 , 129.00834863076386 , -189.86 , 169 , 1000 , 0.0010996852400740223, 6.424808502197266e-05, 17.11623373207051
+RAT43LS , 1076461.596373333 , 1536945.7935744931 , -42.78 , True, 0.0 , 0.0 , 0.00 , 110 , 1000 , 0.00040832649577747694, 7.721900939941406e-06, 52.879012428845705
+RECIPE , 0.0 , 0.0 , 0.00 , False, 4.890121115583538e-13 , 8.750355563305065e-09 , -1789294.45, 66 , 53 , 0.0009886062506473427, 4.988346459730616e-05, 19.81831572101209
+RES , 0.0 , 0.0 , 0.00 , False, 2.0 , 2.0 , 0.00 , 82 , 122 , 0.0011357446996177116, 5.291915330730501e-05, 21.461883432306017
+RK23 , 0.0833333340217678 , 0.08333333533190992 , -0.00 , True, 1.7546444852634124e-09 , 1.8116619315833304e-12 , 99.90 , 283 , 222 , 0.008404180776104067, 0.0002686655199205553, 31.281203403358916
+ROBOT , 5.462841228152021 , 5.462841265494781 , -0.00 , True, 1.1976530878143876e-12 , 2.1546298474561798e-08 , -1798943.37, 534 , 207 , 0.0013801801517215114, 3.424128472517078e-05, 40.307487373770776
+ROSENBR , 0.021302704016258677 , 0.27251509554666337 , -1179.25, True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0003733949661254883, 5.956172943115234e-06, 62.69041710031223
+ROSENBRTU , 0.8437947476454654 , 0.9527493227160052 , -12.91 , True, 0.0 , 0.0 , 0.00 , 1000 , 43 , 0.00036330699920654295, 8.771585863690043e-06, 41.418621997471554
+ROSENMMX , -0.998907700384006 , -44.00000008836955 , 4304.81 , True, 0.0 , 2.9291254799446165e-07 , 0.00 , 2500 , 223 , 0.0010089760780334473, 2.2246698627557454e-05, 45.353968915801616
+ROSZMAN1 , 0.0 , 0.0 , 0.00 , False, 0.008884331667433576 , 0.08172331231712542 , -819.86 , 69 , 1000 , 0.002274914064269135, 9.337711334228515e-05, 24.36265143397784
+ROSZMAN1LS , 0.4151155653428399 , 0.7434169787321968 , -79.09 , True, 0.0 , 0.0 , 0.00 , 51 , 59 , 0.00034927854350968904, 9.265996642031912e-06, 37.69465466081766
+RSNBRNE , 0.0 , 0.0 , 0.00 , False, 2.017327416226067e-09 , 5.668697938832068e-05 , -2809903.92, 12 , 14 , 0.0009406010309855143, 7.278578622000558e-05, 12.922866947434098
+S268 , 2.2637881498121715 , 2.5071222611513804 , -10.75 , True, 0.0 , 0.0 , 0.00 , 2500 , 972 , 0.0005982287406921387, 9.640744684164416e-06, 62.05212981884796
+S277-280 , 5.0761904640000095 , 5.076190464000009 , 0.00 , True, 0.0 , 0.0 , 0.00 , 18 , 27 , 0.001168052355448405, 1.9444359673394098e-05, 60.071525885558586
+S308 , 0.7731990564936675 , 0.7731990784534997 , -0.00 , True, 0.0 , 0.0 , 0.00 , 55 , 38 , 0.000318145751953125, 8.288182710346423e-06, 38.385465556396674
+S316-322 , 334.3145744015894 , 334.31457503278205 , -0.00 , True, 3.749951750648087e-09 , 2.0000125486291153e-10 , 94.67 , 76 , 80 , 0.000582353064888402, 2.6661157608032227e-05, 21.842752420958497
+S365 , -9.193904706873826e-24 , -1.2636421891639561e-26, -99.86 , True, 1.415640811214227e-11 , 0.00014695176193956888 , -1038058147.37, 111 , 97 , 0.0015531617241936761, 2.6742207635309278e-05, 58.07903914944356
+S365MOD , 0.030105695295638172 , 0.030118553780139296 , -0.04 , True, 0.3264900714874588 , 0.32661451887491977 , -0.04 , 302 , 141 , 0.001366668189598235, 2.6667371709296044e-05, 51.24870214044471
+S368 , -0.9999999999990771 , -0.9999999813502138 , -0.00 , True, 0.0 , 2.220446049250313e-16 , 0.00 , 154 , 138 , 0.0011759717743118087, 1.303527666174847e-05, 90.21456197877669
+SIM2BQP , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 6 , 8 , 0.000897685686747233, 5.4270029067993164e-05, 16.54109463664653
+SIMBQP , 7.438031981798952e-13 , 1.0982775924688045e-07 , -14765601.40, True, 8.762868012256883e-24 , 6.395849050447015e-26 , 99.27 , 53 , 49 , 0.0004982813349309957, 1.1434360426299427e-05, 43.577543155359294
+SIMPLLPA , 1.0 , 1.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 10 , 13 , 0.001021599769592285, 2.8445170475886418e-05, 35.91470019342359
+SIMPLLPB , 1.1 , 1.1 , 0.00 , False, 0.0 , 0.0 , 0.00 , 10 , 13 , 0.0009218931198120117, 2.839015080378606e-05, 32.472286821705424
+SINEVAL , 3.1609476046174545 , 3.852340661616279 , -21.87 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.0003617849349975586, 5.777120590209961e-06, 62.62374644051008
+SINVALNE , 0.0 , 0.0 , 0.00 , False, 5.851282756895132e-19 , 2.143824381935569e-05 , -3663853672785228.00, 41 , 127 , 0.0007131623058784298, 3.7833461611289674e-05, 18.850041088114946
+SIPOW1 , -1.0 , -1.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 14 , 16 , 0.017626643180847168, 0.00022730231285095215, 77.54713517765832
+SIPOW1M , -1.0000012353564025 , -1.0000012340015227 , -0.00 , True, 2.1402732919284517e-09 , 0.0 , 100.00 , 14 , 15 , 0.018777149064200267, 0.00023527145385742186, 79.81057096531357
+SIPOW2 , -1.000000003141572 , -1.0000000000000002 , -0.00 , True, 3.1415718915184243e-09 , 2.220446049250313e-16 , 100.00 , 13 , 16 , 0.017972304270817682, 0.00018668174743652344, 96.27242361725119
+SIPOW2M , -1.0000049350243543 , -1.0000049350243545 , 0.00 , True, 0.0 , 2.220446049250313e-16 , 0.00 , 12 , 15 , 0.014672736326853434, 0.00017647743225097657, 83.14228136539673
+SIPOW3 , 0.5346586469505029 , 0.8540845526582248 , -59.74 , True, 0.0 , 0.0 , 0.00 , 67 , 643 , 0.01223425366985264, 9.481680708391299e-05, 129.03043295926756
+SIPOW4 , 0.27236199874363276 , 0.2723619987436327 , 0.00 , True, 0.0 , 0.0 , 0.00 , 19 , 30 , 0.033267159211008174, 0.00035596688588460286, 93.45576942736382
+SISSER , 6.441637708678534e-25 , 3.3058660597067607e-19 , -51320173.03, True, 0.0 , 0.0 , 0.00 , 46 , 30 , 0.0004809576532115107, 1.076062520345052e-05, 44.696069616594954
+SNAIL , 12.022774080244304 , 13.576483168229196 , -12.92 , True, 0.0 , 0.0 , 0.00 , 1000 , 1000 , 0.00042391395568847654, 6.810188293457031e-06, 62.247024226298834
+SNAKE , -0.00024764316798738954, 1.4574929804722636e-15 , -100.00 , True, 1.2383349706605613e-08 , 2.699140313382545e-10 , 97.82 , 20 , 62 , 0.00126725435256958, 2.8471792897870464e-05, 44.50911669367909
+SPECAN , 0.005313719745498664 , 345.3788329706698 , -6499656.28, True, 0.0 , 0.0 , 0.00 , 4500 , 1000 , 0.0013042688369750977, 0.0005501198768615722, 2.370881133064918
+SPIRAL , 0.0986498913873749 , 0.09794059219331279 , 0.72 , True, 0.0 , 4.078534064624304e-13 , 0.00 , 1500 , 1000 , 0.0013044439951578775, 3.951811790466309e-05, 33.00875811709532
+SSI , 0.03310296732309187 , 0.056551691828758514 , -70.84 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0009063940048217773, 1.6466856002807616e-05, 55.0435374346649
+SSINE , 0.0 , 0.0 , 0.00 , False, 0.021882772401179196 , 0.04253055569877251 , -94.36 , 1500 , 1000 , 0.0015710312525431314, 0.00011667823791503907, 13.464646712329513
+STANCMIN , 4.250000000210743 , 4.25 , 0.00 , True, 0.0 , 0.0 , 0.00 , 20 , 150 , 0.003061044216156006, 1.9307136535644532e-05, 158.54470239565325
+STRATEC , 2496.131432762392 , 2501.215147471184 , -0.20 , True, 0.0 , 0.0 , 0.00 , 5000 , 206 , 0.001851170778274536, 0.0004126990883095751, 4.485521850452769
+STREG , 9.999999995318236e+19 , 9.99999859287021e+19 , 0.00 , True, 0.0 , 0.0 , 0.00 , 2000 , 1000 , 0.00046424543857574463, 5.77092170715332e-06, 80.44563106796117
+SUPERSIM , 0.0 , 0.0 , 0.00 , False, 0.0 , 0.0 , 0.00 , 9 , 13 , 0.0006428559621175131, 4.698679997370793e-05, 13.681628935727298
+SYNTHES1 , 0.7592843920963659 , 0.7592844830298012 , -0.00 , True, 4.2147742005922135e-11 , 4.2149559920551495e-09 , -9900.43, 110 , 104 , 0.001080090349370783, 3.0962320474477914e-05, 34.88402460859147
+SYNTHES2 , 142.0 , -0.55440669767132 , 100.39 , True, 0.0 , 1.0535177290549314e-06 , 0.00 , 52 , 83 , 0.003056058516869178, 4.8602919980704064e-05, 62.87808465175486
+SYNTHES3 , 122.0 , 15.082046800304942 , 87.64 , True, 0.0 , 5.324256235894609e-08 , 0.00 , 81 , 745 , 0.0056469617066559966, 5.2311276429451555e-05, 107.94922418441932
+TAME , 0.0 , 1e-08 , 0.00 , False, 0.0 , 6.734455237049213e-22 , 0.00 , 15 , 31 , 0.0005351384480794271, 2.032710659888483e-05, 26.326346323622143
+TENBARS1 , -142.05539019911558 , 48.01126898006518 , -133.80 , True, 589.884 , 10.575368607208219 , 98.21 , 151 , 1000 , 0.000873794618821302, 5.5129051208496096e-05, 15.849984711629483
+TENBARS2 , -142.05539019911558 , 48.01126898006518 , -133.80 , True, 589.884 , 10.575368607208219 , 98.21 , 151 , 1000 , 0.0008633057802718207, 5.156517028808594e-05, 16.742032954583035
+TENBARS3 , -142.05539019911558 , 48.01126898006518 , -133.80 , True, 589.884 , 10.575368607208219 , 98.21 , 151 , 1000 , 0.0008816940105514021, 5.071115493774414e-05, 17.386589038128182
+TENBARS4 , -142.05539019911558 , 48.01126898006518 , -133.80 , True, 589.884 , 10.575368607208219 , 98.21 , 151 , 1000 , 0.0008612310649543409, 5.423903465270996e-05, 15.87843645206379
+TFI1 , 5.334687279569737 , 5.334687303910119 , -0.00 , True, 3.628640499186986e-09 , 3.3437217439313827e-09 , 7.85 , 100 , 74 , 0.0008381295204162597, 0.00014032222129203178, 5.972892338070856
+TFI2 , 0.6490311069639091 , 0.6494441966408517 , -0.06 , True, 5.551115123125783e-17 , 0.0 , 100.00 , 15 , 46 , 0.0018640677134195963, 1.6761862713357676e-05, 111.20886415172129
+TFI3 , 4.301460324843022 , 4.301359348845657 , 0.00 , True, 2.220446049250313e-16 , 1.1102230246251565e-16 , 50.00 , 15 , 23 , 0.002618996302286784, 2.5966893071713656e-05, 100.85905522288755
+THURBER , 0.0 , 0.0 , 0.00 , False, 25.841769136535532 , 295.1508461812309 , -1042.15, 557 , 1000 , 0.001846734240410358, 0.00010926413536071777, 16.90155909177028
+THURBERLS , 987382.9954318649 , 986695.702624421 , 0.07 , True, 0.0 , 0.0 , 0.00 , 3500 , 288 , 0.00042790283475603374, 6.847083568572998e-06, 62.49417441318203
+TRIGGER , 0.0 , 0.0 , 0.00 , False, 0.006330708306140968 , 0.006331112885052103 , -0.01 , 153 , 70 , 0.0010180722654255386, 4.0715081351143973e-05, 25.00479506955311
+TRUSPYR1 , 7.65139242055767 , -2.292599165742959 , 129.96 , True, 0.18759591227451852 , 1.7306107637113926 , -822.52 , 5500 , 1000 , 0.010782542922280052, 0.00017972803115844725, 59.9936629405027
+TRUSPYR2 , 7.202479576756248 , -2.292599165742959 , 131.83 , True, 0.0892702950315187 , 1.7306107637113926 , -1838.62, 5500 , 1000 , 0.010902261994101785, 6.357216835021973e-05, 171.49426041347388
+TRY-B , 1.2718974807986884e-14 , 1.0103349146807851e-08 , -79435147.73, True, 2.00228722491147e-12 , 2.0044385595774372e-08 , -1000974.44, 58 , 50 , 0.0009936875310437433, 4.5762062072753906e-05, 21.714221038902245
+TWOBARS , 1.5086524161560113 , 1.5086523710872959 , 0.00 , True, 1.4766803033121468e-09 , 3.148290059578994e-08 , -2032.01, 58 , 47 , 0.0008748276480313005, 3.653384269552028e-05, 23.9456784035086
+VARDIMNE , 0.0 , 0.0 , 0.00 , False, 1.378536603774537e-12 , 2.58072901955605e-07 , -18720687.05, 135 , 135 , 0.00324740763063784, 6.192525227864584e-05, 52.44076545744923
+VESUVIA , 0.0 , 0.0 , 0.00 , False, 5.961691098163129 , 6.4552887587167005 , -8.28 , 84 , 74 , 0.015540713355654762, 0.005016594319730191, 3.097861290982483
+VESUVIALS , 3339.0188628590886 , 3000.8342667582765 , 10.13 , True, 0.0 , 0.0 , 0.00 , 90 , 82 , 0.0006101555294460721, 8.5903377067752e-05, 7.102811906507964
+VESUVIO , 0.0 , 0.0 , 0.00 , False, 5.664914830838203 , 5.94668528390914 , -4.97 , 4000 , 83 , 0.05681325745582581, 0.01871952953108822, 3.0349725061986152
+VESUVIOLS , 1670.982045452748 , 1763.4953258421388 , -5.54 , True, 0.0 , 0.0 , 0.00 , 94 , 83 , 0.0021003738362738427, 0.00035802427544651263, 5.866568219862594
+VESUVIOU , 0.0 , 0.0 , 0.00 , False, 0.10743803308694935 , 0.12491415383736779 , -16.27 , 538 , 123 , 0.06188858707598151, 0.018229155036492077, 3.395033228478758
+VESUVIOULS , 0.813033654890136 , 1.4580989849709194 , -79.34 , True, 0.0 , 0.0 , 0.00 , 94 , 82 , 0.0029504578164283265, 0.0009949149155035252, 2.9655378268553787
+VIBRBEAM , 883.1889634632254 , 466.11031682002766 , 47.22 , True, 0.0 , 0.0 , 0.00 , 84 , 103 , 0.0020195217359633673, 0.00022191214329988053, 9.10054630599593
+WACHBIEG , -0.8228756555322956 , -0.8228756579472484 , 0.00 , True, 0.1614378277661478 , 0.16143782897362424 , -0.00 , 21 , 24 , 0.009985901060558501, 0.00017754236857096353, 56.24517201688196
+WATSON , 0.0018914079165782333 , 0.012902495210350428 , -582.16 , True, 0.0 , 0.0 , 0.00 , 6000 , 1000 , 0.003643523971239726, 0.00011732983589172364, 31.053686758772134
+WATSONNE , 0.0 , 0.0 , 0.00 , False, 1.3069007098742702e-08 , 1.3472222004651258e-08 , -3.09 , 81 , 87 , 0.16275839158046393, 0.0021104812622070312, 77.11908866239338
+WEEDS , 3984.581659759342 , 5921.493070343549 , -48.61 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.002600839296976725, 2.945828437805176e-05, 88.28889230611512
+WOMFLET , -3.777737088703945e-21 , 1.0653097853279642e-20 , -382.00 , True, 5.9995998519862805e-09 , 2.0040906096505842e-08 , -234.04 , 68 , 91 , 0.004877279786502614, 0.00011216415153754936, 43.48340998121704
+YFIT , 171.83084122937814 , 182.51059870423828 , -6.22 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0021788078943888347, 1.791501045227051e-05, 121.61912493401294
+YFITNE , 0.0 , 0.0 , 0.00 , False, 4.0724756189547406e-07 , 1.1047210460191081e-05 , -2612.65, 121 , 451 , 0.006934981700802638, 0.00033038154145302107, 20.99082675836708
+YFITU , 171.83084122937814 , 182.51059870423828 , -6.22 , True, 0.0 , 0.0 , 0.00 , 1500 , 1000 , 0.0025989858309427897, 4.6470880508422854e-05, 55.92719144780833
+ZANGWIL2 , -18.200000009100002 , -18.199999996039626 , -0.00 , True, 0.0 , 0.0 , 0.00 , 34 , 25 , 0.0022616807152243223, 6.927490234375e-05, 32.64790910857735
+ZANGWIL3 , 0.0 , 0.0 , 0.00 , False, 1.9928836358928947e-10 , 6.6058269965196814e-15 , 100.00 , 13 , 121 , 0.010831466087928185, 4.282470577019305e-05, 252.9256393739692
+ZECEVIC2 , -4.124999999999986 , -4.124999997435835 , -0.00 , True, 0.0 , 0.0 , 0.00 , 43 , 35 , 0.0026445555132488872, 4.600116184779576e-05, 57.48888521552867
+ZECEVIC3 , 97.30945013022745 , 97.30945009186712 , 0.00 , True, 1.874998600825961e-09 , 1.9999224276645577e-08 , -966.63 , 60 , 42 , 0.002483550707499186, 7.112253279913039e-05, 34.91932317024503
+ZECEVIC4 , 7.557507768932092 , 7.557507825925506 , -0.00 , True, 0.0 , 0.0 , 0.00 , 49 , 46 , 0.0025835523799974092, 6.606786147407864e-05, 39.10452559465772
+ZY2 , 2.0000000000023004 , 2.000002431907019 , -0.00 , True, 2.55991584964972e-24 , 6.881493070779656e-18 , -268817059.43, 18 , 22 , 0.004822121726142036, 0.00012535398656671697, 38.46803646388673
diff --git a/pyprima/profiles_vs_python_bindings/results_with_comparing.csv b/pyprima/profiles_vs_python_bindings/results_with_comparing.csv
new file mode 100644
index 0000000000..7397034c83
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/results_with_comparing.csv
@@ -0,0 +1,517 @@
+Problem, Fortran result, Python result, Error, Fortran cstrv, Python cstrv, Error, Fortran nfev, Python nfev, Fresult.status
+AIRCRFTA , 0.0 , 0.0 , 0.00 , 8.61995381757491e-12 , 8.61995381757491e-12 , 0.00 , 33 , 33 , 0
+AIRCRFTB , 0.36027262356871653 , 0.36027262356871653 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+AKIVA , 6.170773522073405 , 6.170773522073405 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+ALLINIT , 16.705968432897922 , 16.705968432897922 , 0.00 , 0.0 , 0.0 , 0.00 , 85 , 85 , 0
+ALLINITA , 33.29662696801105 , 33.29662696801105 , 0.00 , 1.29867090414848e-08 , 1.29867090414848e-08 , 0.00 , 61 , 61 , 0
+ALLINITC , 30.49661795347201 , 30.49661795347201 , 0.00 , 1.007899408378421e-08 , 1.007899408378421e-08 , 0.00 , 46 , 46 , 0
+ALLINITU , 5.744384910324333 , 5.744384910324333 , 0.00 , 0.0 , 0.0 , 0.00 , 98 , 98 , 0
+ALSOTAME , 0.08208499862391111 , 0.08208499862391111 , 0.00 , 1.5010215292932116e-13 , 1.5010215292932116e-13 , 0.00 , 13 , 13 , 0
+ARGAUSS , 0.0 , 0.0 , 0.00 , 3.517346078718564e-05 , 3.517346078718564e-05 , 0.00 , 15 , 15 , 0
+AVGASA , -4.631925545270105 , -4.631925545270105 , -0.00 , 5.551106906001769e-17 , 5.551106906001769e-17 , 0.00 , 153 , 153 , 0
+AVGASB , -4.4832193648526735 , -4.4832193648526735 , -0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 148 , 148 , 0
+BARD , 0.010314822865069531 , 0.010314822865069531 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+BARDNE , 0.0 , 0.0 , 0.00 , 0.050816326530669365 , 0.050816326530669365 , 0.00 , 31 , 31 , 0
+BEALE , 1.852104547751497e-09 , 1.852104547751497e-09 , 0.00 , 0.0 , 0.0 , 0.00 , 735 , 735 , 0
+BENNETT5 , 0.0 , 0.0 , 0.00 , 0.007660478826139183 , 0.007660478826139183 , 0.00 , 1500 , 1500 , 3
+BENNETT5LS , 0.684541044282733 , 0.684541044282733 , 0.00 , 0.0 , 0.0 , 0.00 , 53 , 53 , 0
+BIGGS3 , 1.0631133951128183e-08 , 1.0631133951128183e-08 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+BIGGS5 , 0.0037254020202865394 , 0.0037254020202865394 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+BIGGS6 , 0.009233751892945407 , 0.009233751892945407 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+BIGGS6NE , 0.0 , 0.0 , 0.00 , 3.0298184655608296e-09 , 3.0298184655608296e-09 , 0.00 , 71 , 71 , 0
+BIGGSC4 , -24.375 , -24.375 , -0.00 , 0.0 , 0.0 , 0.00 , 19 , 19 , 0
+BOOTH , 0.0 , 0.0 , 0.00 , 8.881784197001252e-16 , 8.881784197001252e-16 , 0.00 , 9 , 9 , 0
+BOX2 , 5.846883652860379e-19 , 5.846883652860379e-19 , 0.00 , 0.0 , 0.0 , 0.00 , 34 , 34 , 0
+BOX3 , 5.846883652860379e-19 , 5.846883652860379e-19 , 0.00 , 0.0 , 0.0 , 0.00 , 41 , 41 , 0
+BOX3NE , 0.0 , 0.0 , 0.00 , 3.174127159027984e-10 , 3.174127159027984e-10 , 0.00 , 14 , 14 , 0
+BOXBOD , 0.0 , 0.0 , 0.00 , 19.45872044064301 , 19.45872044064301 , 0.00 , 35 , 35 , 0
+BOXBODLS , 2706.7053209986425 , 2706.7053209986425 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+BRKMCC , 0.16904267926782027 , 0.16904267926782027 , 0.00 , 0.0 , 0.0 , 0.00 , 240 , 240 , 0
+BROWNBS , 996487161510.3127 , 996487161510.3127 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+BROWNDEN , 85822.20135313956 , 85822.20135313956 , 0.00 , 0.0 , 0.0 , 0.00 , 821 , 821 , 0
+BROWNDENE , 0.0 , 0.0 , 0.00 , 115.70643967925741 , 115.70643967925741 , 0.00 , 152 , 152 , 0
+BT1 , -0.9839255863997494 , -0.9839255863997494 , -0.00 , 5.007552775215807e-10 , 5.007552775215807e-10 , 0.00 , 1000 , 1000 , 3
+BT10 , -1.0000000000001457 , -1.0000000000001457 , -0.00 , 2.184918912462308e-13 , 2.184918912462308e-13 , 0.00 , 23 , 23 , 0
+BT11 , 0.8248917775441493 , 0.8248917775441493 , 0.00 , 8.572899334868822e-11 , 8.572899334868822e-11 , 0.00 , 160 , 160 , 0
+BT12 , 6.188118810425604 , 6.188118810425604 , 0.00 , 9.053202239837774e-09 , 9.053202239837774e-09 , 0.00 , 281 , 281 , 0
+BT13 , -1.1684547475646606e-08, -1.1684547475646606e-08, -0.00 , 1.2413868567670141e-08 , 1.2413868567670141e-08 , 0.00 , 733 , 733 , 0
+BT2 , 0.03256820039408809 , 0.03256820039408809 , 0.00 , 2.8332891588433995e-13 , 2.8332891588433995e-13 , 0.00 , 159 , 159 , 0
+BT3 , 4.09302325581437 , 4.09302325581437 , 0.00 , 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 105 , 105 , 0
+BT4 , -45.510550745652516 , -45.510550745652516 , -0.00 , 1.875001487405825e-09 , 1.875001487405825e-09 , 0.00 , 86 , 86 , 0
+BT6 , 0.27704478842853564 , 0.27704478842853564 , 0.00 , 4.426459199180499e-13 , 4.426459199180499e-13 , 0.00 , 185 , 185 , 0
+BT7 , 306.49999431504455 , 306.49999431504455 , 0.00 , 3.884431763290827e-09 , 3.884431763290827e-09 , 0.00 , 181 , 181 , 0
+BT8 , 0.9999999961817733 , 0.9999999961817733 , 0.00 , 1.2139740305875072e-08 , 1.2139740305875072e-08 , 0.00 , 142 , 142 , 0
+BT9 , -1.000000005686783 , -1.000000005686783 , -0.00 , 5.6577898813565485e-09 , 5.6577898813565485e-09 , 0.00 , 146 , 146 , 0
+BYRDSPHR , -4.683300133687232 , -4.683300133687232 , -0.00 , 7.49991979631659e-09 , 7.49991979631659e-09 , 0.00 , 81 , 81 , 0
+CAMEL6 , -1.0316284534897109 , -1.0316284534897109 , -0.00 , 0.0 , 0.0 , 0.00 , 70 , 70 , 0
+CANTILVR , 1.3399563589493664 , 1.3399563589493664 , 0.00 , 3.870630996272162e-09 , 3.870630996272162e-09 , 0.00 , 187 , 187 , 0
+CB2 , 1.9522244912137912 , 1.9522244912137912 , 0.00 , 4.621474314348006e-09 , 4.621474314348006e-09 , 0.00 , 92 , 92 , 0
+CB3 , 1.9999999999998086 , 1.9999999999998086 , 0.00 , 3.6770586575585185e-13 , 3.6770586575585185e-13 , 0.00 , 27 , 27 , 0
+CHACONN1 , 1.952224490848229 , 1.952224490848229 , 0.00 , 4.619454596621608e-09 , 4.619454596621608e-09 , 0.00 , 74 , 74 , 0
+CHACONN2 , 2.000000000803364 , 2.000000000803364 , 0.00 , 0.0 , 0.0 , 0.00 , 28 , 28 , 0
+CHWIRUT1 , 0.0 , 0.0 , 0.00 , 13.100003505803315 , 13.100003505803315 , 0.00 , 21 , 21 , 0
+CHWIRUT1LS , 2610.6084708005064 , 2610.6084708005064 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+CHWIRUT2 , 0.0 , 0.0 , 0.00 , 8.550007574529019 , 8.550007574529019 , 0.00 , 31 , 31 , 0
+CHWIRUT2LS , 627.5103290371579 , 627.5103290371579 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+CLIFF , 0.20068728039814143 , 0.20068728039814143 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+CLUSTER , 0.0 , 0.0 , 0.00 , 3.455543131680771e-11 , 3.455543131680771e-11 , 0.00 , 23 , 23 , 0
+CONCON , -6230.7955476904135 , -6230.7955476904135 , -0.00 , 3.0144826545009335e-09 , 3.0144826545009335e-09 , 0.00 , 1841 , 1841 , 0
+CONGIGMZ , 27.99999999627225 , 27.99999999627225 , 0.00 , 3.727762987182359e-09 , 3.727762987182359e-09 , 0.00 , 40 , 40 , 0
+COOLHANS , 0.0 , 0.0 , 0.00 , 6.654471028777323e-13 , 6.654471028777323e-13 , 0.00 , 526 , 526 , 0
+CRESC100 , 12.344516657246785 , 12.344516657246785 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+CRESC132 , 22.027329882204143 , 22.027329882204143 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+CRESC4 , 2.2014732007195974 , 2.2014732007195974 , 0.00 , 2.369838850418249e-18 , 2.369838850418249e-18 , 0.00 , 3000 , 3000 , 3
+CRESC50 , 2.4825387320290133 , 2.4825387320290133 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+CSFI1 , -49.07519859222229 , -49.07519859222229 , -0.00 , 1.1193520776942023e-08 , 1.1193520776942023e-08 , 0.00 , 105 , 105 , 0
+CSFI2 , 55.01760720743365 , 55.01760720743365 , 0.00 , 7.934445989121741e-09 , 7.934445989121741e-09 , 0.00 , 130 , 130 , 0
+CUBE , 0.011249469195071457 , 0.011249469195071457 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+CUBENE , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 43 , 43 , 0
+DANWOOD , 0.0 , 0.0 , 0.00 , 0.03663810054155192 , 0.03663810054155192 , 0.00 , 20 , 20 , 0
+DANWOODLS , 0.138114157125975 , 0.138114157125975 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+DEGENLPA , 4.591745425365873 , 4.591745425365873 , 0.00 , 2.54815467584966e-13 , 2.54815467584966e-13 , 0.00 , 83 , 83 , 0
+DEGENLPB , -30.731246817992613 , -30.731246817992613 , -0.00 , 2.872528603869995e-13 , 2.872528603869995e-13 , 0.00 , 97 , 97 , 0
+DEMBO7 , 174.86529625946446 , 174.86529625946446 , 0.00 , 5.328243238335286e-11 , 5.328243238335286e-11 , 0.00 , 8000 , 8000 , 3
+DEMYMALO , -2.9999999999999996 , -2.9999999999999996 , -0.00 , 0.0 , 0.0 , 0.00 , 30 , 30 , 0
+DENSCHNB , 2.6945695224742453e-13 , 2.6945695224742453e-13 , 0.00 , 0.0 , 0.0 , 0.00 , 65 , 65 , 0
+DENSCHNC , 1.0258723343782074e-10 , 1.0258723343782074e-10 , 0.00 , 0.0 , 0.0 , 0.00 , 165 , 165 , 0
+DENSCHND , 4.736985431672521e-07 , 4.736985431672521e-07 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+DENSCHNE , 3.8548206981663624e-13 , 3.8548206981663624e-13 , 0.00 , 0.0 , 0.0 , 0.00 , 323 , 323 , 0
+DENSCHNF , 3.500480714730578e-11 , 3.500480714730578e-11 , 0.00 , 0.0 , 0.0 , 0.00 , 54 , 54 , 0
+DIPIGRI , 680.630057374634 , 680.630057374634 , 0.00 , 4.5813297600005853e-11 , 4.5813297600005853e-11 , 0.00 , 417 , 417 , 0
+DIXCHLNG , 2471.9000558807024 , 2471.9000558807024 , 0.00 , 2.0647288323516477e-09 , 2.0647288323516477e-09 , 0.00 , 389 , 389 , 0
+DJTL , -8950.910668174563 , -8950.910668174563 , -0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+DUALC1 , 6155.251686004852 , 6155.251686004852 , 0.00 , 2.9360577716497774e-24 , 2.9360577716497774e-24 , 0.00 , 190 , 190 , 0
+DUALC2 , 3551.3063845622737 , 3551.3063845622737 , 0.00 , 3.2131266153824106e-22 , 3.2131266153824106e-22 , 0.00 , 116 , 116 , 0
+DUALC5 , 427.23254727394436 , 427.23254727394436 , 0.00 , 1.1673796479837064e-08 , 1.1673796479837064e-08 , 0.00 , 176 , 176 , 0
+DUALC8 , 18309.360566957563 , 18309.360566957563 , 0.00 , 9.287690237297924e-09 , 9.287690237297924e-09 , 0.00 , 170 , 170 , 0
+ECKERLE4 , 0.0 , 0.0 , 0.00 , 0.013712801732086388 , 0.013712801732086388 , 0.00 , 68 , 68 , 0
+ECKERLE4LS , 0.699695856485977 , 0.699695856485977 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+ELATTAR , 51.47204869447854 , 51.47204869447854 , 0.00 , 0.0 , 0.0 , 0.00 , 3500 , 3500 , 3
+ENGVAL2 , 0.0066521853170942065 , 0.0066521853170942065 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+ENSO , 0.0 , 0.0 , 0.00 , 4.872796761247474 , 4.872796761247474 , 0.00 , 155 , 155 , 0
+ENSOLS , 788.5397866694871 , 788.5397866694871 , 0.00 , 0.0 , 0.0 , 0.00 , 2491 , 2491 , 0
+EQC , -829.5477053187723 , -829.5477053187723 , -0.00 , 8.326672684688674e-17 , 8.326672684688674e-17 , 0.00 , 30 , 30 , 0
+ERRINBAR , 203.48674841371607 , 203.48674841371607 , 0.00 , 3.4968691556686338 , 3.4968691556686338 , 0.00 , 9000 , 9000 , 3
+EXPFIT , 0.24051059462618385 , 0.24051059462618385 , 0.00 , 0.0 , 0.0 , 0.00 , 259 , 259 , 0
+EXPFITA , 0.001136611706167525 , 0.001136611706167525 , 0.00 , 7.105427357601002e-15 , 7.105427357601002e-15 , 0.00 , 118 , 118 , 0
+EXPFITB , 0.005019365520499348 , 0.005019365520499348 , 0.00 , 3.3306690738754696e-15 , 3.3306690738754696e-15 , 0.00 , 113 , 113 , 0
+EXPFITC , 0.02330257260645052 , 0.02330257260645052 , 0.00 , 0.0 , 0.0 , 0.00 , 138 , 138 , 0
+EXTRASIM , 1.0 , 1.0 , 0.00 , 0.0 , 0.0 , 0.00 , 10 , 10 , 0
+FBRAIN , 0.0 , 0.0 , 0.00 , 0.04787576461951498 , 0.04787576461951498 , 0.00 , 16 , 16 , 0
+FBRAIN2 , 0.0 , 0.0 , 0.00 , 0.031128547346446538 , 0.031128547346446538 , 0.00 , 522 , 522 , 0
+FBRAIN2LS , 0.39207832626399236 , 0.39207832626399236 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+FBRAIN3 , 0.0 , 0.0 , 0.00 , 0.027822835134793777 , 0.027822835134793777 , 0.00 , 3000 , 3000 , 3
+FBRAIN3LS , 0.36640222953974183 , 0.36640222953974183 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+FBRAINLS , 0.4948844132827409 , 0.4948844132827409 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+FCCU , 11.149109141488331 , 11.149109141488331 , 0.00 , 3.552713678800501e-15 , 3.552713678800501e-15 , 0.00 , 594 , 594 , 0
+FLETCHER , 11.656854202187198 , 11.656854202187198 , 0.00 , 9.999189920506524e-09 , 9.999189920506524e-09 , 0.00 , 130 , 130 , 0
+FLT , 5.378712120506562e-05 , 5.378712120506562e-05 , 0.00 , 1.520961492941767e-14 , 1.520961492941767e-14 , 0.00 , 63 , 63 , 0
+FREURONE , 0.0 , 0.0 , 0.00 , 4.94895209510279 , 4.94895209510279 , 0.00 , 66 , 66 , 0
+GAUSS1 , 0.0 , 0.0 , 0.00 , 6.3982592316549995 , 6.3982592316549995 , 0.00 , 678 , 678 , 0
+GAUSS1LS , 3552.649289380294 , 3552.649289380294 , 0.00 , 0.0 , 0.0 , 0.00 , 92 , 92 , 0
+GAUSS2 , 0.0 , 0.0 , 0.00 , 5.539526323253131 , 5.539526323253131 , 0.00 , 159 , 159 , 0
+GAUSS2LS , 5155.796364986979 , 5155.796364986979 , 0.00 , 0.0 , 0.0 , 0.00 , 307 , 307 , 0
+GAUSS3 , 0.0 , 0.0 , 0.00 , 5.420551209450949 , 5.420551209450949 , 0.00 , 220 , 220 , 0
+GAUSS3LS , 14723.692179276592 , 14723.692179276592 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+GAUSSIAN , 1.1304050654009694e-08 , 1.1304050654009694e-08 , 0.00 , 0.0 , 0.0 , 0.00 , 267 , 267 , 0
+GBRAIN , 0.0 , 0.0 , 0.00 , 0.3285258575483354 , 0.3285258575483354 , 0.00 , 17 , 17 , 0
+GBRAINLS , 31.639378435784558 , 31.639378435784558 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+GENHS28 , 0.9271827220986375 , 0.9271827220986375 , 0.00 , 2.220446049250313e-16 , 2.220446049250313e-16 , 0.00 , 129 , 129 , 0
+GIGOMEZ1 , -2.999999999999996 , -2.999999999999996 , -0.00 , 0.0 , 0.0 , 0.00 , 24 , 24 , 0
+GIGOMEZ2 , 1.9522244909024595 , 1.9522244909024595 , 0.00 , 4.619804760963575e-09 , 4.619804760963575e-09 , 0.00 , 85 , 85 , 0
+GIGOMEZ3 , 1.9999999999999987 , 1.9999999999999987 , 0.00 , 2.6645352591003757e-15 , 2.6645352591003757e-15 , 0.00 , 25 , 25 , 0
+GOTTFR , 0.0 , 0.0 , 0.00 , 4.27467727881492e-10 , 4.27467727881492e-10 , 0.00 , 14 , 14 , 0
+GROWTH , 0.0 , 0.0 , 0.00 , 0.4409413035711687 , 0.4409413035711687 , 0.00 , 686 , 686 , 0
+GROWTHLS , 14.197523033483348 , 14.197523033483348 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+GULF , 6.267728684025267 , 6.267728684025267 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+GULFNE , 0.0 , 0.0 , 0.00 , 0.029037560460857925 , 0.029037560460857925 , 0.00 , 84 , 84 , 0
+HAHN1 , 0.0 , 0.0 , 0.00 , 7.903071395717545 , 7.903071395717545 , 0.00 , 77 , 77 , 0
+HAHN1LS , 5271.868589886393 , 5271.868589886393 , 0.00 , 0.0 , 0.0 , 0.00 , 67 , 67 , 0
+HAIFAS , -0.45000001318128324 , -0.45000001318128324 , -0.00 , 1.3456683714552555e-08 , 1.3456683714552555e-08 , 0.00 , 287 , 287 , 0
+HAIRY , 20.000000000325755 , 20.000000000325755 , 0.00 , 0.0 , 0.0 , 0.00 , 90 , 90 , 0
+HALDMADS , 0.00012237099686381107 , 0.00012237099686381107 , 0.00 , 3.61577434659921e-12 , 3.61577434659921e-12 , 0.00 , 62 , 62 , 0
+HART6 , -3.3228868915570007 , -3.3228868915570007 , -0.00 , 0.0 , 0.0 , 0.00 , 209 , 209 , 0
+HATFLDA , 1.6411188694437973e-07 , 1.6411188694437973e-07 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+HATFLDB , 0.00557280900512232 , 0.00557280900512232 , 0.00 , 0.0 , 0.0 , 0.00 , 329 , 329 , 0
+HATFLDD , 0.0014731196825665605 , 0.0014731196825665605 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+HATFLDE , 0.0046816426137926605 , 0.0046816426137926605 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+HATFLDF , 0.0 , 0.0 , 0.00 , 6.851463840717997e-14 , 6.851463840717997e-14 , 0.00 , 123 , 123 , 0
+HATFLDFL , 6.656493180111125e-05 , 6.656493180111125e-05 , 0.00 , 0.0 , 0.0 , 0.00 , 156 , 156 , 0
+HATFLDH , -24.374999999999996 , -24.374999999999996 , -0.00 , 0.0 , 0.0 , 0.00 , 20 , 20 , 0
+HEART6 , 0.0 , 0.0 , 0.00 , 2.0605739337042905e-13 , 2.0605739337042905e-13 , 0.00 , 1808 , 1808 , 0
+HEART6LS , 8.24858224813558 , 8.24858224813558 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+HEART8 , 0.0 , 0.0 , 0.00 , 7.105427357601002e-15 , 7.105427357601002e-15 , 0.00 , 1217 , 1217 , 0
+HEART8LS , 1.4181245230979846 , 1.4181245230979846 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+HELIX , 3.272203026880364e-06 , 3.272203026880364e-06 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+HELIXNE , 0.0 , 0.0 , 0.00 , 1.0426623005503126e-11 , 1.0426623005503126e-11 , 0.00 , 34 , 34 , 0
+HET-Z , 0.9999980000553024 , 0.9999980000553024 , 0.00 , 0.0 , 0.0 , 0.00 , 14 , 14 , 0
+HIELOW , 933.4139380419493 , 933.4139380419493 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+HILBERTA , 2.784356174323199e-12 , 2.784356174323199e-12 , 0.00 , 0.0 , 0.0 , 0.00 , 161 , 161 , 0
+HILBERTB , 2.440616690682895e-12 , 2.440616690682895e-12 , 0.00 , 0.0 , 0.0 , 0.00 , 286 , 286 , 0
+HIMMELBA , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 9 , 9 , 0
+HIMMELBC , 0.0 , 0.0 , 0.00 , 1.7763568394002505e-15 , 1.7763568394002505e-15 , 0.00 , 18 , 18 , 0
+HIMMELBD , 0.0 , 0.0 , 0.00 , 2.421306136054515 , 2.421306136054515 , 0.00 , 66 , 66 , 0
+HIMMELBE , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 15 , 15 , 0
+HIMMELBF , 885.379842556993 , 885.379842556993 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+HIMMELBG , 5.361100772323128e-13 , 5.361100772323128e-13 , 0.00 , 0.0 , 0.0 , 0.00 , 60 , 60 , 0
+HIMMELBH , -0.9999999999997446 , -0.9999999999997446 , -0.00 , 0.0 , 0.0 , 0.00 , 72 , 72 , 0
+HIMMELP1 , -51.7378463582927 , -51.7378463582927 , -0.00 , 0.0 , 0.0 , 0.00 , 61 , 61 , 0
+HIMMELP2 , -62.05393553382589 , -62.05393553382589 , -0.00 , 0.0 , 0.0 , 0.00 , 83 , 83 , 0
+HIMMELP3 , -59.01317776912174 , -59.01317776912174 , -0.00 , 0.0 , 0.0 , 0.00 , 19 , 19 , 0
+HIMMELP4 , -59.01317776912174 , -59.01317776912174 , -0.00 , 0.0 , 0.0 , 0.00 , 20 , 20 , 0
+HIMMELP5 , -59.01317776912174 , -59.01317776912174 , -0.00 , 0.0 , 0.0 , 0.00 , 22 , 22 , 0
+HIMMELP6 , -59.01317776912174 , -59.01317776912174 , -0.00 , 0.0 , 0.0 , 0.00 , 24 , 24 , 0
+HONG , 22.571087363549214 , 22.571087363549214 , 0.00 , 0.0 , 0.0 , 0.00 , 86 , 86 , 0
+HS1 , 0.014236464990845877 , 0.014236464990845877 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+HS10 , -1.0000000014785269 , -1.0000000014785269 , -0.00 , 3.750422306048051e-09 , 3.750422306048051e-09 , 0.00 , 87 , 87 , 0
+HS100 , 680.6300573744186 , 680.6300573744186 , 0.00 , 1.3812950783176348e-11 , 1.3812950783176348e-11 , 0.00 , 494 , 494 , 0
+HS100LNP , 680.6300573744146 , 680.6300573744146 , 0.00 , 2.3419488570652902e-11 , 2.3419488570652902e-11 , 0.00 , 382 , 382 , 0
+HS100MOD , 678.6796378870499 , 678.6796378870499 , 0.00 , 5.243805389909539e-09 , 5.243805389909539e-09 , 0.00 , 291 , 291 , 0
+HS101 , 1810.9721677773778 , 1810.9721677773778 , 0.00 , 0.0 , 0.0 , 0.00 , 3500 , 3500 , 3
+HS102 , 3000.076360741547 , 3000.076360741547 , 0.00 , 0.07636220156756568 , 0.07636220156756568 , 0.00 , 3500 , 3500 , 3
+HS103 , 3000.2102326770078 , 3000.2102326770078 , 0.00 , 0.21023382818340428 , 0.21023382818340428 , 0.00 , 3500 , 3500 , 3
+HS104 , 3.9511633798338273 , 3.9511633798338273 , 0.00 , 9.32302749698707e-09 , 9.32302749698707e-09 , 0.00 , 3494 , 3494 , 0
+HS105 , 1062.0117612847187 , 1062.0117612847187 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+HS106 , 6403.84461691428 , 6403.84461691428 , 0.00 , 0.07250389269029256 , 0.07250389269029256 , 0.00 , 4000 , 4000 , 3
+HS107 , 5055.011797825531 , 5055.011797825531 , 0.00 , 2.0773698317100298e-09 , 2.0773698317100298e-09 , 0.00 , 127 , 127 , 0
+HS108 , -0.8660254047017132 , -0.8660254047017132 , -0.00 , 1.5396028896219605e-09 , 1.5396028896219605e-09 , 0.00 , 252 , 252 , 0
+HS109 , -111.57243927624917 , -111.57243927624917 , -0.00 , 38560.53255599182 , 38560.53255599182 , 0.00 , 4500 , 4500 , 3
+HS11 , -8.498464230059602 , -8.498464230059602 , -0.00 , 2.8177220556813154e-09 , 2.8177220556813154e-09 , 0.00 , 70 , 70 , 0
+HS110 , -45.778475531884865 , -45.7784755318843 , -0.00 , 0.0 , 0.0 , 0.00 , 228 , 265 , 0
+HS111 , -47.76109083484443 , -47.76109083484443 , -0.00 , 2.544230104373213e-11 , 2.544230104373213e-11 , 0.00 , 5000 , 5000 , 3
+HS111LNP , -47.761091063897 , -47.761091063897 , -0.00 , 1.2907698493380693e-08 , 1.2907698493380693e-08 , 0.00 , 5000 , 5000 , 3
+HS112 , -47.71663324635484 , -47.71663324635484 , -0.00 , 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 171 , 171 , 0
+HS113 , 24.306209068181655 , 24.306209068181655 , 0.00 , 5.115907697472721e-13 , 5.115907697472721e-13 , 0.00 , 278 , 278 , 0
+HS114 , -1634.2809767930557 , -1634.2809767930557 , -0.00 , 2.842170943040401e-14 , 2.842170943040401e-14 , 0.00 , 999 , 999 , 0
+HS116 , 118.97425136644837 , 118.97425136644837 , 0.00 , 9.270415174885203e-09 , 9.270415174885203e-09 , 0.00 , 6500 , 6500 , 3
+HS117 , 32.348678965728865 , 32.348678965728865 , 0.00 , 3.049782648645305e-13 , 3.049782648645305e-13 , 0.00 , 1499 , 1499 , 0
+HS118 , 664.8204499999993 , 664.8204499999993 , 0.00 , 4.263256414560601e-14 , 4.263256414560601e-14 , 0.00 , 83 , 83 , 0
+HS119 , 244.89969751680334 , 244.89969751680334 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 288 , 288 , 0
+HS12 , -30.00000000122298 , -30.00000000122298 , -0.00 , 2.5688748905849934e-09 , 2.5688748905849934e-09 , 0.00 , 62 , 62 , 0
+HS13 , 1.0000009773373966 , 1.0000009773373966 , 0.00 , 1.2564712928227616e-22 , 1.2564712928227616e-22 , 0.00 , 75 , 75 , 0
+HS14 , 1.3934649806418395 , 1.3934649806418395 , 0.00 , 2.570288426539946e-11 , 2.570288426539946e-11 , 0.00 , 15 , 15 , 0
+HS15 , 306.4999999128419 , 306.4999999128419 , 0.00 , 1.2451162323401377e-10 , 1.2451162323401377e-10 , 0.00 , 21 , 21 , 0
+HS16 , 23.144660975101118 , 23.144660975101118 , 0.00 , 0.0 , 0.0 , 0.00 , 14 , 14 , 0
+HS17 , 0.9999999999786774 , 0.9999999999786774 , 0.00 , 4.7749575873117735e-09 , 4.7749575873117735e-09 , 0.00 , 35 , 35 , 0
+HS18 , 4.99999999956311 , 4.99999999956311 , 0.00 , 3.4653027114472934e-09 , 3.4653027114472934e-09 , 0.00 , 65 , 65 , 0
+HS19 , -6961.813875580135 , -6961.813875580135 , -0.00 , 0.0 , 0.0 , 0.00 , 38 , 38 , 0
+HS2 , 4.941229318024095 , 4.941229318024095 , 0.00 , 0.0 , 0.0 , 0.00 , 42 , 42 , 0
+HS20 , 40.19872981077604 , 40.19872981077604 , 0.00 , 2.853273173286652e-14 , 2.853273173286652e-14 , 0.00 , 15 , 15 , 0
+HS21 , -99.96 , -99.96 , -0.00 , 0.0 , 0.0 , 0.00 , 34 , 34 , 0
+HS21MOD , -95.96 , -95.96 , -0.00 , 0.0 , 0.0 , 0.00 , 81 , 81 , 0
+HS22 , 1.0000000000000036 , 1.0000000000000036 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 19 , 19 , 0
+HS23 , 2.0000000017670727 , 2.0000000017670727 , 0.00 , 0.0 , 0.0 , 0.00 , 22 , 22 , 0
+HS24 , -1.0000000826919648 , -1.0000000826919648 , -0.00 , 0.0 , 0.0 , 0.00 , 10 , 10 , 0
+HS25 , 1.3629384448866604 , 1.3629384448866604 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+HS26 , 2.0117861896751436e-08 , 2.0117861896751436e-08 , 0.00 , 2.6467050773248957e-11 , 2.6467050773248957e-11 , 0.00 , 1500 , 1500 , 3
+HS268 , 2.2139807428320637 , 2.2139807428320637 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+HS27 , 0.039999999999920544 , 0.039999999999920544 , 0.00 , 4.23221484457637e-11 , 4.23221484457637e-11 , 0.00 , 805 , 805 , 0
+HS28 , 1.0401675481935155e-14 , 1.0401675481935155e-14 , 0.00 , 0.0 , 0.0 , 0.00 , 96 , 96 , 0
+HS29 , -22.62741700345932 , -22.62741700345932 , -0.00 , 9.463470007631258e-09 , 9.463470007631258e-09 , 0.00 , 100 , 100 , 0
+HS3 , 3.4052170504073325e-13 , 3.4052170504073325e-13 , 0.00 , 0.0 , 0.0 , 0.00 , 27 , 27 , 0
+HS30 , 1.000000000002668 , 1.000000000002668 , 0.00 , 0.0 , 0.0 , 0.00 , 77 , 77 , 0
+HS31 , 5.999999982263959 , 5.999999982263959 , 0.00 , 4.570218981037044e-09 , 4.570218981037044e-09 , 0.00 , 86 , 86 , 0
+HS32 , 1.0 , 1.0 , 0.00 , 9.840662800703482e-21 , 9.840662800703482e-21 , 0.00 , 20 , 20 , 0
+HS33 , -4.585786437626904 , -4.585786437626904 , -0.00 , 0.0 , 0.0 , 0.00 , 32 , 32 , 0
+HS34 , -0.8340324452479582 , -0.8340324452479582 , -0.00 , 5.329070518200751e-15 , 5.329070518200751e-15 , 0.00 , 28 , 28 , 0
+HS35 , 0.111111111111722 , 0.111111111111722 , 0.00 , 0.0 , 0.0 , 0.00 , 86 , 86 , 0
+HS35I , 0.111111111111722 , 0.111111111111722 , 0.00 , 0.0 , 0.0 , 0.00 , 86 , 86 , 0
+HS35MOD , 0.25 , 0.25 , 0.00 , 0.0 , 0.0 , 0.00 , 34 , 34 , 0
+HS36 , -3299.999999999999 , -3299.999999999999 , -0.00 , 0.0 , 0.0 , 0.00 , 20 , 20 , 0
+HS37 , -3456.000000000001 , -3456.000000000001 , -0.00 , 0.0 , 0.0 , 0.00 , 90 , 90 , 0
+HS38 , 7.637443873107534 , 7.637443873107534 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+HS39 , -1.000000005686783 , -1.000000005686783 , -0.00 , 5.6577898813565485e-09 , 5.6577898813565485e-09 , 0.00 , 146 , 146 , 0
+HS3MOD , 4.089468459443429e-14 , 4.089468459443429e-14 , 0.00 , 0.0 , 0.0 , 0.00 , 77 , 77 , 0
+HS4 , 2.666666664 , 2.666666664 , 0.00 , 0.0 , 0.0 , 0.00 , 10 , 10 , 0
+HS40 , -0.25000000154939434 , -0.25000000154939434 , -0.00 , 7.597619033905545e-09 , 7.597619033905545e-09 , 0.00 , 82 , 82 , 0
+HS41 , 1.9259259259259345 , 1.9259259259259345 , 0.00 , 2.220446049250313e-16 , 2.220446049250313e-16 , 0.00 , 81 , 81 , 0
+HS42 , 13.8578643700655 , 13.8578643700655 , 0.00 , 5.725128371381061e-09 , 5.725128371381061e-09 , 0.00 , 110 , 110 , 0
+HS43 , -44.000000011807266 , -44.000000011807266 , -0.00 , 4.0251952881931174e-09 , 4.0251952881931174e-09 , 0.00 , 109 , 109 , 0
+HS44 , -15.0 , -15.0 , -0.00 , 1.5766590820375744e-17 , 1.5766590820375744e-17 , 0.00 , 23 , 23 , 0
+HS44NEW , -15.0 , -15.0 , -0.00 , 0.0 , 0.0 , 0.00 , 19 , 19 , 0
+HS45 , 1.0000000004 , 1.0000000004 , 0.00 , 0.0 , 0.0 , 0.00 , 26 , 26 , 0
+HS46 , 9.110617542747234e-07 , 9.110617542747234e-07 , 0.00 , 1.3620372607547893e-09 , 1.3620372607547893e-09 , 0.00 , 2500 , 2500 , 3
+HS47 , 1.30080076710692e-09 , 1.30080076710692e-09 , 0.00 , 2.039590718538875e-12 , 2.039590718538875e-12 , 0.00 , 1359 , 1359 , 0
+HS48 , 2.4076629560869293e-12 , 2.4076629560869293e-12 , 0.00 , 8.881784197001252e-16 , 8.881784197001252e-16 , 0.00 , 126 , 126 , 0
+HS49 , 5.2915272548080974e-06 , 5.2915272548080974e-06 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+HS5 , -1.9132229549805313 , -1.9132229549805313 , -0.00 , 0.0 , 0.0 , 0.00 , 57 , 57 , 0
+HS50 , 4.919755075054164e-13 , 4.919755075054164e-13 , 0.00 , 8.881784197001252e-16 , 8.881784197001252e-16 , 0.00 , 154 , 154 , 0
+HS51 , 2.6082197118214614e-13 , 2.6082197118214614e-13 , 0.00 , 2.220446049250313e-16 , 2.220446049250313e-16 , 0.00 , 96 , 96 , 0
+HS52 , 5.326647564545302 , 5.326647564545302 , 0.00 , 2.7755575615628914e-17 , 2.7755575615628914e-17 , 0.00 , 172 , 172 , 0
+HS53 , 4.09302325581405 , 4.09302325581405 , 0.00 , 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 90 , 90 , 0
+HS54 , -0.1539790077340626 , -0.1539790077340626 , -0.00 , 0.0 , 0.0 , 0.00 , 82 , 82 , 0
+HS55 , 6.666666666666667 , 6.666666666666667 , 0.00 , 8.881784197001252e-16 , 8.881784197001252e-16 , 0.00 , 26 , 26 , 0
+HS56 , -3.4560000011102447 , -3.4560000011102447 , -0.00 , 6.83309142601729e-09 , 6.83309142601729e-09 , 0.00 , 544 , 544 , 0
+HS57 , 0.030646319844124605 , 0.030646319844124605 , 0.00 , 0.0 , 0.0 , 0.00 , 41 , 41 , 0
+HS59 , -7.802789403937213 , -7.802789403937213 , -0.00 , 3.681179805425927e-09 , 3.681179805425927e-09 , 0.00 , 67 , 67 , 0
+HS6 , 6.719569297825384e-15 , 6.719569297825384e-15 , 0.00 , 1.5020207300153743e-10 , 1.5020207300153743e-10 , 0.00 , 51 , 51 , 0
+HS60 , 0.03256820028601623 , 0.03256820028601623 , 0.00 , 1.5341061754270413e-12 , 1.5341061754270413e-12 , 0.00 , 78 , 78 , 0
+HS61 , -81.91909609513321 , -81.91909609513321 , -0.00 , 6.610445524302122e-10 , 6.610445524302122e-10 , 0.00 , 81 , 81 , 0
+HS62 , -26272.5146470247 , -26272.5146470247 , -0.00 , 0.0 , 0.0 , 0.00 , 362 , 362 , 0
+HS63 , 961.7151721281875 , 961.7151721281875 , 0.00 , 1.8749766184100736e-09 , 1.8749766184100736e-09 , 0.00 , 87 , 87 , 0
+HS64 , 6299.842412880226 , 6299.842412880226 , 0.00 , 6.59982296591366e-09 , 6.59982296591366e-09 , 0.00 , 169 , 169 , 0
+HS65 , 0.953528855367757 , 0.953528855367757 , 0.00 , 1.466633747781998e-08 , 1.466633747781998e-08 , 0.00 , 102 , 102 , 0
+HS66 , 0.5181632727830466 , 0.5181632727830466 , 0.00 , 4.566252709281571e-09 , 4.566252709281571e-09 , 0.00 , 70 , 70 , 0
+HS68 , -0.9204101858789134 , -0.9204101858789134 , -0.00 , 3.6950720261330616e-10 , 3.6950720261330616e-10 , 0.00 , 2000 , 2000 , 3
+HS7 , -1.7320508076737495 , -1.7320508076737495 , -0.00 , 3.99998256739309e-10 , 3.99998256739309e-10 , 0.00 , 71 , 71 , 0
+HS70 , 0.2690861852486161 , 0.2690861852486161 , 0.00 , 8.015053065690836e-10 , 8.015053065690836e-10 , 0.00 , 71 , 71 , 0
+HS71 , 17.01401728817721 , 17.01401728817721 , 0.00 , 1.8750361263641935e-09 , 1.8750361263641935e-09 , 0.00 , 120 , 120 , 0
+HS72 , 727.6791802684454 , 727.6791802684454 , 0.00 , 9.59813380770752e-09 , 9.59813380770752e-09 , 0.00 , 327 , 327 , 0
+HS73 , 29.89437816317189 , 29.89437816317189 , 0.00 , 4.9072215607930255e-22 , 4.9072215607930255e-22 , 0.00 , 22 , 22 , 0
+HS74 , 5126.498109897566 , 5126.498109897566 , 0.00 , 1.1290406831676592e-08 , 1.1290406831676592e-08 , 0.00 , 159 , 159 , 0
+HS75 , 5174.412695641726 , 5174.412695641726 , 0.00 , 4.3787196091216174e-13 , 4.3787196091216174e-13 , 0.00 , 88 , 88 , 0
+HS76 , -4.681818181817892 , -4.681818181817892 , -0.00 , 5.421010862427522e-23 , 5.421010862427522e-23 , 0.00 , 88 , 88 , 0
+HS76I , -4.681818181817892 , -4.681818181817892 , -0.00 , 5.421010862427522e-23 , 5.421010862427522e-23 , 0.00 , 88 , 88 , 0
+HS77 , 0.2415051283087972 , 0.2415051283087972 , 0.00 , 1.4992451724538114e-12 , 1.4992451724538114e-12 , 0.00 , 214 , 214 , 0
+HS78 , -2.9197004090473686 , -2.9197004090473686 , -0.00 , 1.240879621278168e-10 , 1.240879621278168e-10 , 0.00 , 124 , 124 , 0
+HS79 , 0.07877682098728211 , 0.07877682098728211 , 0.00 , 4.3720582709738665e-13 , 4.3720582709738665e-13 , 0.00 , 104 , 104 , 0
+HS8 , -1.0 , -1.0 , -0.00 , 1.1235457009206584e-13 , 1.1235457009206584e-13 , 0.00 , 15 , 15 , 0
+HS80 , 0.053949847761852854 , 0.053949847761852854 , 0.00 , 7.238273314058574e-10 , 7.238273314058574e-10 , 0.00 , 134 , 134 , 0
+HS83 , -30665.538671783306 , -30665.538671783306 , -0.00 , 0.0 , 0.0 , 0.00 , 36 , 36 , 0
+HS84 , -5280335.079198179 , -5280335.079198179 , -0.00 , 1.1641532182693481e-10 , 1.1641532182693481e-10 , 0.00 , 32 , 32 , 0
+HS85 , -2.215604688476282 , -2.215604688476282 , -0.00 , 5.684341886080802e-14 , 5.684341886080802e-14 , 0.00 , 45 , 45 , 0
+HS86 , -32.34867896572227 , -32.34867896572227 , -0.00 , 0.0 , 0.0 , 0.00 , 81 , 81 , 0
+HS87 , 8996.881026604726 , 8996.881026604726 , 0.00 , 1.2964257261881096e-12 , 1.2964257261881096e-12 , 0.00 , 200 , 200 , 0
+HS88 , 1.3626502251796233 , 1.3626502251796233 , 0.00 , 8.822988650106028e-09 , 8.822988650106028e-09 , 0.00 , 151 , 151 , 0
+HS89 , 1.3626451537619035 , 1.3626451537619035 , 0.00 , 1.104449660084539e-08 , 1.104449660084539e-08 , 0.00 , 281 , 281 , 0
+HS9 , -0.4999999999999995 , -0.4999999999999995 , -0.00 , 1.7763568394002505e-15 , 1.7763568394002505e-15 , 0.00 , 42 , 42 , 0
+HS90 , 1.362648281096731 , 1.362648281096731 , 0.00 , 8.815662938183098e-09 , 8.815662938183098e-09 , 0.00 , 1685 , 1685 , 0
+HS91 , 1.3626641188199062 , 1.3626641188199062 , 0.00 , 8.822004249543118e-09 , 8.822004249543118e-09 , 0.00 , 2500 , 2500 , 3
+HS92 , 1.362641880130116 , 1.362641880130116 , 0.00 , 1.490098172015643e-08 , 1.490098172015643e-08 , 0.00 , 2474 , 2474 , 0
+HS93 , 135.07596925568015 , 135.07596925568015 , 0.00 , 1.0736364646074526e-09 , 1.0736364646074526e-09 , 0.00 , 3000 , 3000 , 3
+HS95 , 0.015619525242393854 , 0.015619525242393854 , 0.00 , 0.0 , 0.0 , 0.00 , 27 , 27 , 0
+HS96 , 0.015619525242393854 , 0.015619525242393854 , 0.00 , 0.0 , 0.0 , 0.00 , 27 , 27 , 0
+HS97 , 4.071246356513221 , 4.071246356513221 , 0.00 , 2.6962671608384e-15 , 2.6962671608384e-15 , 0.00 , 90 , 90 , 0
+HS98 , 4.071246356513221 , 4.071246356513221 , 0.00 , 2.7056267184999924e-15 , 2.7056267184999924e-15 , 0.00 , 47 , 47 , 0
+HS99 , -831079891.3531996 , -831079891.3531996 , -0.00 , 4.656612873077393e-10 , 4.656612873077393e-10 , 0.00 , 836 , 836 , 0
+HUBFIT , 0.016893493939398714 , 0.016893493939398714 , 0.00 , 0.0 , 0.0 , 0.00 , 42 , 42 , 0
+HUMPS , 0.271692660681852 , 0.271692660681852 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+HYPCIR , 0.0 , 0.0 , 0.00 , 6.257883100602157e-11 , 6.257883100602157e-11 , 0.00 , 14 , 14 , 0
+INTEQNE , 0.0 , 0.0 , 0.00 , 1.1899801123065486e-16 , 1.1899801123065486e-16 , 0.00 , 63 , 63 , 0
+JENSMP , 245.5097053022826 , 245.5097053022826 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+JENSMPNE , 0.0 , 0.0 , 0.00 , 4.693376137742932 , 4.693376137742932 , 0.00 , 59 , 59 , 0
+KIRBY2 , 0.0 , 0.0 , 0.00 , 0.3247954040282792 , 0.3247954040282792 , 0.00 , 431 , 431 , 0
+KIRBY2LS , 1067.9706707179575 , 1067.9706707179575 , 0.00 , 0.0 , 0.0 , 0.00 , 45 , 45 , 0
+KIWCRESC , -9.240327366129015e-10 , -9.240327366129015e-10 , -0.00 , 1.8749840569043386e-09 , 1.8749840569043386e-09 , 0.00 , 78 , 78 , 0
+KOEBHELB , 112.23228919514905 , 112.23304984281278 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+KOWOSB , 0.00034700028730681916 , 0.00034700028730681916 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+KOWOSBNE , 0.0 , 0.0 , 0.00 , 0.008084368386043324 , 0.008084368386043324 , 0.00 , 39 , 39 , 0
+KSIP , 0.5757979235507544 , 0.5757979235507544 , 0.00 , 1.3694114953111125e-08 , 1.3694114953111125e-08 , 0.00 , 750 , 750 , 0
+LANCZOS1 , 0.0 , 0.0 , 0.00 , 2.6246195598822464e-09 , 2.6246195598822464e-09 , 0.00 , 507 , 507 , 0
+LANCZOS1LS , 0.001438579472562677 , 0.001438579472562677 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+LANCZOS2 , 0.0 , 0.0 , 0.00 , 2.5616923711169193e-06 , 2.5616923711169193e-06 , 0.00 , 741 , 741 , 0
+LANCZOS2LS , 0.0014781514972468002 , 0.0014781514972468002 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+LANCZOS3 , 0.0 , 0.0 , 0.00 , 3.8970466907151113e-05 , 3.8970466907151113e-05 , 0.00 , 3000 , 3000 , 3
+LANCZOS3LS , 0.001474893581124513 , 0.001474893581124513 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+LEWISPOL , 2.1740981202273453 , 2.1740981202273453 , 0.00 , 3.011805242536525e-05 , 3.011805242536525e-05 , 0.00 , 55 , 55 , 0
+LIN , -0.020175818254486055 , -0.020175818254486055 , -0.00 , 0.0 , 0.0 , 0.00 , 904 , 904 , 0
+LOGHAIRY , 6.007577226484713 , 6.007577226484713 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+LOGROS , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 34 , 34 , 0
+LOOTSMA , -8.9998777866624 , -8.9998777866624 , -0.00 , 1.000000000001 , 1.000000000001 , 0.00 , 89 , 89 , 0
+LOTSCHD , 2398.4158385317214 , 2398.4158385317214 , 0.00 , 8.881784197001252e-14 , 8.881784197001252e-14 , 0.00 , 54 , 54 , 0
+LSC1 , 0.0 , 0.0 , 0.00 , 20.59128159377724 , 20.59128159377724 , 0.00 , 117 , 117 , 0
+LSC1LS , 1094.4645070508968 , 1094.4645070508968 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+LSC2 , 0.0 , 0.0 , 0.00 , 1.857929600868374 , 1.857929600868374 , 0.00 , 146 , 146 , 0
+LSC2LS , 16.023713142592644 , 16.023713142592644 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+LSNNODOC , 123.11244879144516 , 123.11244879144516 , 0.00 , 3.5527136788005e-16 , 3.5527136788005e-16 , 0.00 , 33 , 33 , 0
+LSQFIT , 0.03378698787879743 , 0.03378698787879743 , 0.00 , 0.0 , 0.0 , 0.00 , 42 , 42 , 0
+MADSEN , 0.6164324350721612 , 0.6164324350721612 , 0.00 , 1.8759100939291784e-09 , 1.8759100939291784e-09 , 0.00 , 77 , 77 , 0
+MAKELA1 , -1.4142135675748042 , -1.4142135675748042 , -0.00 , 7.500361887124996e-09 , 7.500361887124996e-09 , 0.00 , 89 , 89 , 0
+MAKELA2 , 7.199999994045252 , 7.199999994045252 , 0.00 , 7.500004173266461e-09 , 7.500004173266461e-09 , 0.00 , 90 , 90 , 0
+MARATOS , -1.000000001792786 , -1.000000001792786 , -0.00 , 7.499750078721651e-09 , 7.499750078721651e-09 , 0.00 , 55 , 55 , 0
+MARATOSB , 1.0000001135252934 , 1.0000001135252934 , 0.00 , 0.0 , 0.0 , 0.00 , 46 , 46 , 0
+MATRIX2 , 2.824534071388212e-13 , 2.824534071388212e-13 , 0.00 , 3.37038528586148e-13 , 3.37038528586148e-13 , 0.00 , 137 , 137 , 0
+MAXLIKA , 1149.311907396927 , 1149.311907396927 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+MCONCON , -6230.795547690415 , -6230.795547690415 , -0.00 , 3.049235739382165e-09 , 3.049235739382165e-09 , 0.00 , 1937 , 1937 , 0
+MDHOLE , 5.78546146501778 , 5.78546146501778 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+MEXHAT , 0.1676149182987266 , 0.1676149182987266 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+MEYER3 , 7081469.356952106 , 7081469.356952106 , 0.00 , 0.0 , 0.0 , 0.00 , 95 , 95 , 0
+MEYER3NE , 0.0 , 0.0 , 0.00 , 33.91330594086685 , 33.91330594086685 , 0.00 , 1500 , 1500 , 3
+MGH09 , 0.0 , 0.0 , 0.00 , 0.01381740054783101 , 0.01381740054783101 , 0.00 , 2000 , 2000 , 3
+MGH09LS , 0.006827816057483676 , 0.006827816057483676 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+MGH10 , 0.0 , 0.0 , 0.00 , 15434.52601255495 , 15434.52601255495 , 0.00 , 1500 , 1500 , 3
+MGH10LS , 1366860257.1510346 , 1366860257.1510346 , 0.00 , 0.0 , 0.0 , 0.00 , 51 , 51 , 0
+MGH10S , 0.0 , 0.0 , 0.00 , 44.592726115468395 , 44.592726115468395 , 0.00 , 1500 , 1500 , 3
+MGH17 , 0.0 , 0.0 , 0.00 , 0.26499999969053323 , 0.26499999969053323 , 0.00 , 54 , 54 , 0
+MGH17LS , 1.10603621877387 , 1.106036218821421 , 0.00 , 0.0 , 0.0 , 0.00 , 256 , 272 , 0
+MGH17S , 0.0 , 0.0 , 0.00 , 0.26499999911643746 , 0.26499999911643746 , 0.00 , 55 , 55 , 0
+MIFFLIN1 , -1.000000000757027 , -1.000000000757027 , -0.00 , 1.8749380273534917e-09 , 1.8749380273534917e-09 , 0.00 , 76 , 76 , 0
+MIFFLIN2 , -1.0000000007777747 , -1.0000000007777747 , -0.00 , 7.031732753291723e-09 , 7.031732753291723e-09 , 0.00 , 86 , 86 , 0
+MINMAXBD , 822.559 , 822.559 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+MINMAXRB , 1.3877787807814457e-17 , 1.3877787807814457e-17 , 0.00 , 0.0 , 0.0 , 0.00 , 28 , 28 , 0
+MISRA1A , 0.0 , 0.0 , 0.00 , 0.12611092265660773 , 0.12611092265660773 , 0.00 , 91 , 91 , 0
+MISRA1ALS , 19.594518355352196 , 19.59761475637583 , 0.02 , 0.0 , 0.0 , 0.00 , 39 , 31 , 0
+MISRA1B , 0.0 , 0.0 , 0.00 , 0.09916875803321545 , 0.09916875803321545 , 0.00 , 101 , 101 , 0
+MISRA1BLS , 7.424831134022499 , 7.424831134022499 , 0.00 , 0.0 , 0.0 , 0.00 , 35 , 35 , 0
+MISRA1C , 0.0 , 0.0 , 0.00 , 0.07504991856126253 , 0.07504991856126253 , 0.00 , 97 , 97 , 0
+MISRA1CLS , 4.692559563212987 , 4.692559563212987 , 0.00 , 0.0 , 0.0 , 0.00 , 38 , 38 , 0
+MISRA1D , 0.0 , 0.0 , 0.00 , 0.0857960329662717 , 0.0857960329662717 , 0.00 , 96 , 96 , 0
+MISTAKE , -1.0000000026471214 , -1.0000000026471214 , -0.00 , 1.1817140488368238e-08 , 1.1817140488368238e-08 , 0.00 , 172 , 172 , 0
+MOREBVNE , 0.0 , 0.0 , 0.00 , 1.2981837047421567e-12 , 1.2981837047421567e-12 , 0.00 , 52 , 52 , 0
+MWRIGHT , 24.97880953861595 , 24.97880953861595 , 0.00 , 8.56517079483865e-11 , 8.56517079483865e-11 , 0.00 , 152 , 152 , 0
+NELSON , 0.0 , 0.0 , 0.00 , 0.5181181494376179 , 0.5181181494376179 , 0.00 , 1500 , 1500 , 3
+NELSONLS , 55.78451654625992 , 55.78451654625992 , 0.00 , 0.0 , 0.0 , 0.00 , 485 , 485 , 0
+NYSTROM5 , 0.0 , 0.0 , 0.00 , 0.009115798666323283 , 0.009115798666323283 , 0.00 , 7500 , 7500 , 3
+ODFITS , -2326.5550699033583 , -2326.5550699033583 , -0.00 , 0.0 , 0.0 , 0.00 , 69 , 69 , 0
+OET1 , 0.5382431193122431 , 0.5382431193122431 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 17 , 17 , 0
+OET2 , 0.08715963585435173 , 0.08715963585435173 , 0.00 , 1.7763568394002505e-15 , 1.7763568394002505e-15 , 0.00 , 24 , 24 , 0
+OET3 , 0.004505052872585095 , 0.004505052872585095 , 0.00 , 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 29 , 29 , 0
+OET4 , 0.004295429673128485 , 0.004295429673128485 , 0.00 , 2.983613356377646e-11 , 2.983613356377646e-11 , 0.00 , 35 , 35 , 0
+OET5 , 0.002650086531749752 , 0.002650086531749752 , 0.00 , 2.5552893134772603e-12 , 2.5552893134772603e-12 , 0.00 , 1338 , 1338 , 0
+OET6 , 0.002069732885596081 , 0.002069732885596081 , 0.00 , 1.017661510616108e-08 , 1.017661510616108e-08 , 0.00 , 353 , 353 , 0
+OET7 , 0.00028748986861539823 , 0.00028748986861539823 , 0.00 , 1.0456152610416325e-10 , 1.0456152610416325e-10 , 0.00 , 3500 , 3500 , 3
+OSBORNE1 , 0.0 , 0.0 , 0.00 , 0.0027817898858990553 , 0.0027817898858990553 , 0.00 , 61 , 61 , 0
+OSBORNE2 , 0.0 , 0.0 , 0.00 , 0.04802741461997104 , 0.04802741461997104 , 0.00 , 5500 , 5500 , 3
+OSBORNEA , 0.005033019604934856 , 0.005033019604934856 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+OSBORNEB , 0.24818291862716213 , 0.24818291862716213 , 0.00 , 0.0 , 0.0 , 0.00 , 5500 , 5500 , 3
+OSCIPANE , 0.0 , 0.0 , 0.00 , 0.9996566894137592 , 0.9996566894137592 , 0.00 , 604 , 604 , 0
+OSLBQP , 6.250000000000002 , 6.250000000000002 , 0.00 , 1.1648144326692191e-21 , 1.1648144326692191e-21 , 0.00 , 113 , 113 , 0
+PALMER1 , 18443.017565442216 , 18443.017565442216 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER1A , 15600.394338629283 , 15600.394338629283 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER1B , 20831.199562653826 , 20831.199562653826 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER1C , 40203.26503158763 , 40203.26503158763 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER1D , 39007.768412673526 , 39007.768412673526 , 0.00 , 0.0 , 0.0 , 0.00 , 3500 , 3500 , 3
+PALMER1E , 32006.1764699275 , 32006.1764699275 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER2 , 3736.4326483396276 , 3736.4326483396276 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER2A , 216.55650767675104 , 216.55650767675104 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER2B , 1097.5855717736188 , 1097.5855717736188 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER2C , 1414.0819761003331 , 1414.0819761003331 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER2E , 624.4537853833527 , 624.4537853833527 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER3 , 2416.980526876446 , 2416.980526876446 , 0.00 , 1.4216134175728932e-08 , 1.4216134175728932e-08 , 0.00 , 286 , 286 , 0
+PALMER3A , 60.66394582450659 , 60.66394582450659 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER3B , 324.6120596974852 , 324.6120596974852 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER3C , 288.33548019188027 , 288.33548019188027 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER3E , 133.58010762749961 , 133.58010762749961 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER4 , 2424.0165667957262 , 2424.0165667957262 , 0.00 , 0.0 , 0.0 , 0.00 , 83 , 83 , 0
+PALMER4A , 69.94907244177588 , 69.94907244177588 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER4B , 320.8542339362576 , 320.8542339362576 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER4C , 275.345708419572 , 275.345708419572 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER4E , 126.56418046055174 , 126.56418046055174 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER5A , 7354.655360384113 , 7354.655360384113 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER5B , 4984.588036742078 , 4984.588036742078 , 0.00 , 0.0 , 0.0 , 0.00 , 4500 , 4500 , 3
+PALMER5C , 2.1280866434670838 , 2.1280866434670838 , 0.00 , 0.0 , 0.0 , 0.00 , 390 , 390 , 0
+PALMER5D , 494.82619226725996 , 494.82619226725996 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+PALMER5E , 0.44915062905389014 , 0.44915062905389014 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER6A , 38.86788777259002 , 38.86788777259002 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER6C , 194.82556096879847 , 194.82556096879847 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER6E , 27.450616340567958 , 27.450616340567958 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER7A , 43.6870422445713 , 43.6870422445713 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER7C , 308.68548819097856 , 308.68548819097856 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER7E , 72.9445616450166 , 72.9445616450166 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER8A , 5.952976187477223 , 5.952976187477223 , 0.00 , 0.0 , 0.0 , 0.00 , 3000 , 3000 , 3
+PALMER8C , 169.5560709630756 , 169.5560709630756 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PALMER8E , 32.75437813724635 , 32.75437813724635 , 0.00 , 0.0 , 0.0 , 0.00 , 4000 , 4000 , 3
+PENLT1NE , 0.0 , 0.0 , 0.00 , 8.418836638851758e-06 , 8.418836638851758e-06 , 0.00 , 94 , 94 , 0
+PENLT2NE , 0.0 , 0.0 , 0.00 , 7.2823335373348894e-06 , 7.2823335373348894e-06 , 0.00 , 70 , 70 , 0
+PENTAGON , 0.00014621320344806123 , 0.00014621320344806123 , 0.00 , 0.0 , 0.0 , 0.00 , 111 , 111 , 0
+PFIT1 , 0.0 , 0.0 , 0.00 , 0.010135688459226522 , 0.010135688459226522 , 0.00 , 1500 , 1500 , 3
+PFIT1LS , 0.49063882182978624 , 0.49063882182978624 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+PFIT2 , 0.0 , 0.0 , 0.00 , 0.07109333000049922 , 0.07109333000049922 , 0.00 , 1500 , 1500 , 3
+PFIT2LS , 1.9681099460619362 , 1.9681099460619362 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+PFIT3 , 0.0 , 0.0 , 0.00 , 0.18183624871680237 , 0.18183624871680237 , 0.00 , 1500 , 1500 , 3
+PFIT3LS , 6.075516480019205 , 6.075516480019205 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+PFIT4 , 0.0 , 0.0 , 0.00 , 0.3209527455478849 , 0.3209527455478849 , 0.00 , 1500 , 1500 , 3
+PFIT4LS , 9.074886404378923 , 9.074886404378923 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+POLAK1 , 2.718281822381711 , 2.718281822381711 , 0.00 , 9.514375065577951e-09 , 9.514375065577951e-09 , 0.00 , 111 , 111 , 0
+POLAK2 , 1.1 , 1.1 , 0.00 , 90.74478199714775 , 90.74478199714775 , 0.00 , 5500 , 5500 , 3
+POLAK3 , -13.957712874480016 , -13.957712874480016 , -0.00 , 35.38340251034428 , 35.38340251034428 , 0.00 , 6000 , 6000 , 3
+POLAK4 , 1.0464029452042218e-12 , 1.0464029452042218e-12 , 0.00 , 1.0619966233592089e-09 , 1.0619966233592089e-09 , 0.00 , 91 , 91 , 0
+POLAK5 , 50.00000185704198 , 50.00000185704198 , 0.00 , 1.0514396464600395e-08 , 1.0514396464600395e-08 , 0.00 , 221 , 221 , 0
+POLAK6 , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+PORTFL1 , 0.02048627467571856 , 0.02048627467571856 , 0.00 , 6.896730776655354e-24 , 6.896730776655354e-24 , 0.00 , 590 , 590 , 0
+PORTFL2 , 0.029689239853496228 , 0.029689239853496228 , 0.00 , 1.963841223876566e-23 , 1.963841223876566e-23 , 0.00 , 482 , 482 , 0
+PORTFL3 , 0.032749709342919556 , 0.032749709342919556 , 0.00 , 1.1102230246251565e-16 , 1.1102230246251565e-16 , 0.00 , 608 , 608 , 0
+PORTFL4 , 0.026306950934774496 , 0.026306950934774496 , 0.00 , 8.617170601441278e-23 , 8.617170601441278e-23 , 0.00 , 509 , 509 , 0
+PORTFL6 , 0.02579179815375995 , 0.02579179815375995 , 0.00 , 4.3267716076655915e-09 , 4.3267716076655915e-09 , 0.00 , 517 , 517 , 0
+POWELLBS , 0.0 , 0.0 , 0.00 , 6.528252331078344e-17 , 6.528252331078344e-17 , 0.00 , 223 , 223 , 0
+POWELLBSLS , 0.013862884415376104 , 0.013862884415376104 , 0.00 , 0.0 , 0.0 , 0.00 , 39 , 39 , 0
+POWELLSE , 0.0 , 0.0 , 0.00 , 8.593934603639937e-13 , 8.593934603639937e-13 , 0.00 , 79 , 79 , 0
+POWELLSQ , 0.0 , 0.0 , 0.00 , 5.16745778613073e-08 , 5.16745778613073e-08 , 0.00 , 1000 , 1000 , 3
+PSPDOC , 2.4142135528727406 , 2.4142135528727406 , 0.00 , 1.3446441404774134e-08 , 1.3446441404774134e-08 , 0.00 , 289 , 289 , 0
+PT , 0.1783942254303165 , 0.1783942254303165 , 0.00 , 0.0 , 0.0 , 0.00 , 9 , 9 , 0
+QC , -1060.8552344686261 , -1060.8552344686261 , -0.00 , 3.469446951953614e-18 , 3.469446951953614e-18 , 0.00 , 74 , 74 , 0
+QCNEW , -806.5218543888329 , -806.5218543888329 , -0.00 , 0.0 , 0.0 , 0.00 , 40 , 40 , 0
+RAT42 , 0.0 , 0.0 , 0.00 , 1.4056861590851497 , 1.4056861590851497 , 0.00 , 130 , 130 , 0
+RAT42LS , 436.12590755906604 , 436.12590755906604 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+RAT43 , 0.0 , 0.0 , 0.00 , 44.50658873254321 , 44.50658873254321 , 0.00 , 133 , 133 , 0
+RAT43LS , 1076461.5963733331 , 1076461.5963733331 , 0.00 , 0.0 , 0.0 , 0.00 , 108 , 108 , 0
+RECIPE , 0.0 , 0.0 , 0.00 , 4.67160976445029e-13 , 4.67160976445029e-13 , 0.00 , 58 , 58 , 0
+RES , 0.0 , 0.0 , 0.00 , 9.2459e-15 , 9.2459e-15 , 0.00 , 81 , 81 , 0
+RK23 , 0.08333333419174745 , 0.08333333419174745 , 0.00 , 3.902116518794685e-09 , 3.902116518794685e-09 , 0.00 , 193 , 193 , 0
+ROBOT , 5.462841228149146 , 5.462841228149146 , 0.00 , 1.0925704785336166e-12 , 1.0925704785336166e-12 , 0.00 , 535 , 535 , 0
+ROSENBR , 0.023671603917145376 , 0.023671603917145376 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+ROSENBRTU , 0.8433245168607838 , 0.8433245168607838 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+ROSENMMX , -0.998907700384006 , -0.998907700384006 , -0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+ROSZMAN1 , 0.0 , 0.0 , 0.00 , 0.00888433166743352 , 0.00888433166743352 , 0.00 , 69 , 69 , 0
+ROSZMAN1LS , 0.41512040528979666 , 0.41512040528979666 , 0.00 , 0.0 , 0.0 , 0.00 , 51 , 51 , 0
+RSNBRNE , 0.0 , 0.0 , 0.00 , 7.650102773482104e-11 , 7.650102773482104e-11 , 0.00 , 35 , 35 , 0
+S268 , 2.2139807428320637 , 2.2139807428320637 , 0.00 , 0.0 , 0.0 , 0.00 , 2500 , 2500 , 3
+S277-280 , 5.076190464000009 , 5.076190464000009 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 18 , 18 , 0
+S308 , 0.7731990564935545 , 0.7731990564935545 , 0.00 , 0.0 , 0.0 , 0.00 , 54 , 54 , 0
+S316-322 , 334.3145739470576 , 334.3145739470576 , 0.00 , 7.497877911182753e-09 , 7.497877911182753e-09 , 0.00 , 73 , 73 , 0
+S365 , 2.677576711495538e-47 , 2.677576711495538e-47 , 0.00 , 2.791701907839698e-08 , 2.791701907839698e-08 , 0.00 , 106 , 106 , 0
+S365MOD , 0.030112242107272882 , 0.030112242107272882 , 0.00 , 0.3264712066910137 , 0.3264712066910137 , 0.00 , 280 , 280 , 0
+S368 , -0.9999999999994998 , -0.9999999999994998 , -0.00 , 0.0 , 0.0 , 0.00 , 164 , 164 , 0
+SIM2BQP , 0.0 , 0.0 , 0.00 , 0.0 , 0.0 , 0.00 , 6 , 6 , 0
+SIMBQP , 1.3438991662011304e-14 , 1.3438991662011304e-14 , 0.00 , 3.3881317890172016e-26 , 3.3881317890172016e-26 , 0.00 , 54 , 54 , 0
+SIMPLLPA , 1.0 , 1.0 , 0.00 , 0.0 , 0.0 , 0.00 , 10 , 10 , 0
+SIMPLLPB , 1.1 , 1.1 , 0.00 , 0.0 , 0.0 , 0.00 , 10 , 10 , 0
+SINEVAL , 3.2647123588530964 , 3.2647123588530964 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+SINVALNE , 0.0 , 0.0 , 0.00 , 3.490104628134213e-17 , 3.490104628134213e-17 , 0.00 , 47 , 47 , 0
+SIPOW1 , -1.0 , -1.0 , -0.00 , 0.0 , 0.0 , 0.00 , 12 , 12 , 0
+SIPOW1M , -1.0000012427663338 , -1.0000012427663338 , -0.00 , 9.550077395559242e-09 , 9.550077395559242e-09 , 0.00 , 14 , 14 , 0
+SIPOW2 , -1.0000000000572606 , -1.0000000000572606 , -0.00 , 5.726064067346215e-11 , 5.726064067346215e-11 , 0.00 , 19 , 19 , 0
+SIPOW2M , -1.0000049350243543 , -1.0000049350243543 , -0.00 , 0.0 , 0.0 , 0.00 , 12 , 12 , 0
+SIPOW3 , 0.5346586469505029 , 0.5346586469505029 , 0.00 , 0.0 , 0.0 , 0.00 , 84 , 84 , 0
+SIPOW4 , 0.2723619987436327 , 0.2723619987436327 , 0.00 , 2.7755575615628914e-17 , 2.7755575615628914e-17 , 0.00 , 19 , 19 , 0
+SISSER , 6.441637708678534e-25 , 6.441637708678534e-25 , 0.00 , 0.0 , 0.0 , 0.00 , 46 , 46 , 0
+SNAIL , 12.471594860861538 , 12.471594860861538 , 0.00 , 0.0 , 0.0 , 0.00 , 1000 , 1000 , 3
+SNAKE , -0.0002476431675960226 , -0.0002476431675960226 , -0.00 , 1.2383349687035763e-08 , 1.2383349687035763e-08 , 0.00 , 20 , 20 , 0
+SPECAN , 0.0039237972095049626 , 0.0039237972095049626 , 0.00 , 0.0 , 0.0 , 0.00 , 4500 , 4500 , 3
+SPIRAL , 0.10584524024726766 , 0.10584524024726766 , 0.00 , 5.068222347315378e-09 , 5.068222347315378e-09 , 0.00 , 1500 , 1500 , 3
+SSI , 0.033284982807277104 , 0.033284982807277104 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+SSINE , 0.0 , 0.0 , 0.00 , 0.021347340220382406 , 0.021347340220382406 , 0.00 , 1500 , 1500 , 3
+STANCMIN , 4.250000000210743 , 4.250000000210743 , 0.00 , 0.0 , 0.0 , 0.00 , 20 , 20 , 0
+STRATEC , 2490.761190691184 , 2490.761190691184 , 0.00 , 0.0 , 0.0 , 0.00 , 5000 , 5000 , 3
+STREG , 9.999999995363072e+19 , 9.999999995363072e+19 , 0.00 , 0.0 , 0.0 , 0.00 , 2000 , 2000 , 3
+SUPERSIM , 0.666666666666667 , 0.666666666666667 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 9 , 9 , 0
+SYNTHES1 , 0.7592843899078733 , 0.7592843899078733 , 0.00 , 1.5805184461629874e-09 , 1.5805184461629874e-09 , 0.00 , 111 , 111 , 0
+SYNTHES2 , -0.5544053305120915 , -0.5544053305120915 , -0.00 , 0.0 , 0.0 , 0.00 , 80 , 80 , 0
+SYNTHES3 , 15.082190858604376 , 15.082190858604376 , 0.00 , 1.7763568394002505e-15 , 1.7763568394002505e-15 , 0.00 , 313 , 313 , 0
+TAME , 0.0 , 0.0 , 0.00 , 2.220446049250313e-16 , 2.220446049250313e-16 , 0.00 , 26 , 26 , 0
+TENBARS1 , -30.27643654696821 , -30.27643654696821 , -0.00 , 1.971241986244479 , 1.971241986244479 , 0.00 , 9000 , 9000 , 3
+TENBARS2 , -30.27643654696821 , -30.27643654696821 , -0.00 , 1.971241986244479 , 1.971241986244479 , 0.00 , 9000 , 9000 , 3
+TENBARS3 , -30.27643654696821 , -30.27643654696821 , -0.00 , 1.971241986244479 , 1.971241986244479 , 0.00 , 9000 , 9000 , 3
+TENBARS4 , -30.27643654696821 , -30.27643654696821 , -0.00 , 1.971241986244479 , 1.971241986244479 , 0.00 , 9000 , 9000 , 3
+TFI1 , 5.334687277792823 , 5.334687277792823 , 0.00 , 6.445452171988109e-09 , 6.445452171988109e-09 , 0.00 , 102 , 102 , 0
+TFI2 , 0.6490311069639091 , 0.6490311069639091 , 0.00 , 0.0 , 0.0 , 0.00 , 20 , 20 , 0
+TFI3 , 4.301460324843021 , 4.301460324843021 , 0.00 , 4.440892098500626e-16 , 4.440892098500626e-16 , 0.00 , 15 , 15 , 0
+THURBER , 0.0 , 0.0 , 0.00 , 25.841769135611926 , 25.841769135611926 , 0.00 , 564 , 564 , 0
+THURBERLS , 987328.4838581729 , 987328.4838581729 , 0.00 , 0.0 , 0.0 , 0.00 , 3500 , 3500 , 3
+TRIGGER , 0.0 , 0.0 , 0.00 , 2.2196773986182805e-05 , 2.2196773986182805e-05 , 0.00 , 3000 , 3000 , 3
+TRUSPYR1 , 7.454658583092079 , 7.454658583092079 , 0.00 , 0.1881985762808833 , 0.1881985762808833 , 0.00 , 5500 , 5500 , 3
+TRUSPYR2 , 6.753418297288706 , 6.753418297288706 , 0.00 , 0.09168702114595399 , 0.09168702114595399 , 0.00 , 5500 , 5500 , 3
+TRY-B , 3.11913289067379e-14 , 3.11913289067379e-14 , 0.00 , 1.9994006450474444e-12 , 1.9994006450474444e-12 , 0.00 , 65 , 65 , 0
+TWOBARS , 1.5086524132762278 , 1.5086524132762278 , 0.00 , 2.951234812030176e-09 , 2.951234812030176e-09 , 0.00 , 64 , 64 , 0
+VARDIMNE , 0.0 , 0.0 , 0.00 , 7.194245199571014e-14 , 7.194245199571014e-14 , 0.00 , 73 , 73 , 0
+VESUVIA , 0.0 , 0.0 , 0.00 , 5.962644060006011 , 5.962644060006011 , 0.00 , 85 , 85 , 0
+VESUVIALS , 3339.6088704845483 , 3339.6088704845483 , 0.00 , 0.0 , 0.0 , 0.00 , 89 , 89 , 0
+VESUVIO , 0.0 , 0.0 , 0.00 , 4.911922067299593 , 4.911922067299593 , 0.00 , 949 , 949 , 0
+VESUVIOLS , 1670.5690849716018 , 1670.5690849716018 , 0.00 , 0.0 , 0.0 , 0.00 , 90 , 90 , 0
+VESUVIOU , 0.0 , 0.0 , 0.00 , 0.10794024944868219 , 0.10794024944868219 , 0.00 , 556 , 556 , 0
+VESUVIOULS , 0.8125893016985668 , 0.8125893016985668 , 0.00 , 0.0 , 0.0 , 0.00 , 84 , 84 , 0
+VIBRBEAM , 883.0536358071324 , 883.0536358071324 , 0.00 , 0.0 , 0.0 , 0.00 , 84 , 84 , 0
+WACHBIEG , -0.3660254041058419 , -0.3660254041058419 , -0.00 , 0.433012702052921 , 0.433012702052921 , 0.00 , 19 , 19 , 0
+WATSON , 0.0019153769553988131 , 0.0019153769553988131 , 0.00 , 0.0 , 0.0 , 0.00 , 6000 , 6000 , 3
+WATSONNE , 0.0 , 0.0 , 0.00 , 1.3069017312794529e-08 , 1.3069017312794529e-08 , 0.00 , 80 , 80 , 0
+WEEDS , 3594.8319670977385 , 3594.8319670977385 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+WOMFLET , -9.340380260941673e-23 , -9.340380260941673e-23 , -0.00 , 4.014742031637235e-12 , 4.014742031637235e-12 , 0.00 , 89 , 89 , 0
+YFIT , 172.24970628060524 , 172.24970628060524 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+YFITNE , 0.0 , 0.0 , 0.00 , 4.066709813344005e-07 , 4.066709813344005e-07 , 0.00 , 121 , 121 , 0
+YFITU , 172.24970628060524 , 172.24970628060524 , 0.00 , 0.0 , 0.0 , 0.00 , 1500 , 1500 , 3
+ZANGWIL2 , -18.200000009100002 , -18.200000009100002 , -0.00 , 0.0 , 0.0 , 0.00 , 34 , 34 , 0
+ZANGWIL3 , 0.0 , 0.0 , 0.00 , 5.0182080713057076e-14 , 5.0182080713057076e-14 , 0.00 , 13 , 13 , 0
+ZECEVIC2 , -4.124999999999986 , -4.124999999999986 , -0.00 , 0.0 , 0.0 , 0.00 , 43 , 43 , 0
+ZECEVIC3 , 97.30945013022745 , 97.30945013022745 , 0.00 , 1.874998600825961e-09 , 1.874998600825961e-09 , 0.00 , 64 , 64 , 0
+ZECEVIC4 , 7.557507768853459 , 7.557507768853459 , 0.00 , 6.079403647163417e-11 , 6.079403647163417e-11 , 0.00 , 47 , 47 , 0
+ZY2 , 2.0000000000023004 , 2.0000000000023004 , 0.00 , 0.0 , 0.0 , 0.00 , 18 , 18 , 0
diff --git a/pyprima/profiles_vs_python_bindings/scratch.py b/pyprima/profiles_vs_python_bindings/scratch.py
new file mode 100644
index 0000000000..a1c51aaf1b
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/scratch.py
@@ -0,0 +1,54 @@
+import numpy as np
+import pyprima
+from pyprima import minimize
+from scipy.optimize import NonlinearConstraint
+import sys
+import debugpy
+
+
+pyprima.common.linalg.USE_NAIVE_MATH = False
+
+# debugpy.listen(5678)
+# debugpy.wait_for_client()
+np.set_printoptions(precision=19, floatmode='fixed', suppress=False)
+
+def f(x):
+ return np.sum(x**2)
+
+# eps = np.finfo(float).eps
+lb = [-1, None, 1, None, -0.5]
+ub = [-0.5, -0.5, None, None, -0.5]
+bounds = [(a, b) for a, b in zip(lb, ub)]
+
+# lb_as_nlc = NonlinearConstraint(lambda x: x - lb, 0, np.inf)
+# ub_as_nlc = NonlinearConstraint(lambda x: ub - x, 0, np.inf)
+
+# res = minimize(f, x0=[1, 2, 3, 4, 5], method='cobyla', bounds=bounds, options={'rhobeg': 0.03997997997997998, 'iprint': 1})
+# sys.exit()
+# these are converted to Bounds internally
+
+# debugpy.breakpoint()
+failed = 0
+n = 100
+for i, rhobeg in enumerate(np.linspace(2e-2, 10, n)):
+ res = minimize(f, x0=np.array([1, 2, 3, 4, 5]), bounds=bounds, options={'rhobeg': rhobeg})
+ # print(res.nf)
+ ref = [-0.5, -0.5, 1, 0, -0.5]
+ ref = 1.75
+ try:
+ diff = (abs(res.fun - ref))
+ except AttributeError:
+ diff = (abs(res.f - ref))
+ try:
+ nf = res.nf
+ except AttributeError:
+ nf = res.nfev
+ # print(f"diff={diff}")
+ if diff > 1:
+ print(f"rhobeg={rhobeg} FAILED with diff={diff} and nf={nf}")
+ failed += 1
+ # break
+ if i % int(n/10) == 0:
+ print(f"rhobeg={rhobeg} passed with diff={diff} and nf={nf}")
+
+print("num failed: ", failed)
\ No newline at end of file
diff --git a/pyprima/profiles_vs_python_bindings/speedtest.py b/pyprima/profiles_vs_python_bindings/speedtest.py
new file mode 100644
index 0000000000..41866f867a
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/speedtest.py
@@ -0,0 +1,25 @@
+from pyprima.common.linalg import matprod, inprod
+import numpy as np
+import timeit
+import sys
+
+for numrows in [2, 3, 5, 10, 20, 50, 100]:
+ np.random.seed(0)
+ sys.modules['pyprima'].common.linalg.COMPARING = False
+ np_result = timeit.timeit(lambda: matprod(np.random.random((numrows, numrows)), np.random.random((numrows, numrows))), number=1000)
+ np.random.seed(0)
+ sys.modules['pyprima'].common.linalg.COMPARING = True
+ naive_result = timeit.timeit(lambda: matprod(np.random.random((numrows, numrows)), np.random.random((numrows, numrows))), number=1000)
+ speedup = naive_result / np_result
+ print(f"For {numrows} rows, numpy implementation takes {np_result} seconds, naive implementation takes {naive_result} seconds. Numpy is {speedup:.2f}x faster.")
+
+
+# for numrows in [2, 3, 5, 10, 20, 50, 100]:
+# np.random.seed(0)
+# sys.modules['pyprima'].common.linalg.COMPARING = False
+# np_result = timeit.timeit(lambda: inprod(np.random.random(numrows), np.random.random(numrows)), number=1000)
+# np.random.seed(0)
+# sys.modules['pyprima'].common.linalg.COMPARING = True
+# naive_result = timeit.timeit(lambda: inprod(np.random.random(numrows), np.random.random(numrows)), number=1000)
+# speedup = naive_result / np_result
+# print(f"For {numrows} rows, numpy implementation takes {np_result} seconds, naive implementation takes {naive_result} seconds. Numpy is {speedup:.2f}x faster.")
\ No newline at end of file
diff --git a/pyprima/profiles_vs_python_bindings/test_float.f90 b/pyprima/profiles_vs_python_bindings/test_float.f90
new file mode 100644
index 0000000000..b9c52f8bf8
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/test_float.f90
@@ -0,0 +1,88 @@
+program test_float
+ implicit none
+
+ real(8) :: x(4)
+ real(8) :: y(4)
+ real(8) :: result
+ integer(4) :: i
+
+
+
+ print *, float2bin(epsilon(0.0_8)**2), result
+
+contains
+
+function inprod(x, y) result(z)
+ !--------------------------------------------------------------------------------------------------!
+ ! INPROD calculates the inner product of X and Y, i.e., Z = X^T*Y, regarding X and Y as columns.
+ !--------------------------------------------------------------------------------------------------!
+ implicit none
+
+ ! Inputs
+ real(8), intent(in) :: x(:)
+ real(8), intent(in) :: y(:)
+ ! Outputs
+ real(8) :: z
+ ! Local variables
+ integer(4) :: i
+
+
+
+ !====================!
+ ! Calculation starts !
+ !====================!
+
+ z = 0.0_8
+ do i = 1, int(size(x), kind(i))
+ z = z + x(i) * y(i)
+ end do
+
+ !====================!
+ ! Calculation ends !
+ !====================!
+ end function inprod
+
+function bin2float(binary_string) result(result)
+ implicit none
+
+ character(len=*), intent(in) :: binary_string
+ real(8) :: result
+
+ integer(8) :: binary_value
+ integer :: i
+
+ ! Convert binary string to integer
+ binary_value = 0
+ do i = 1, 64
+ binary_value = binary_value * 2
+ if (binary_string(i:i) == '1') then
+ binary_value = binary_value + 1
+ end if
+ end do
+
+ ! Transfer the bit pattern to real(8)
+ result = transfer(binary_value, result)
+end function bin2float
+
+function float2bin(r) result(bin_str)
+ real(8), intent(in) :: r
+ character(len=64) :: bin_str
+ integer(8) :: int_bits
+ integer :: j
+
+ ! Transfer real bits to integer
+ int_bits = transfer(r, int_bits)
+
+ ! Convert to binary string
+ bin_str = ''
+ do j = 64, 1, -1
+ if (btest(int_bits, j-1)) then
+ bin_str(65-j:65-j) = '1'
+ else
+ bin_str(65-j:65-j) = '0'
+ end if
+ end do
+end function float2bin
+
+
+end program test_float
diff --git a/pyprima/profiles_vs_python_bindings/test_float.py b/pyprima/profiles_vs_python_bindings/test_float.py
new file mode 100644
index 0000000000..1d9037579b
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/test_float.py
@@ -0,0 +1,39 @@
+import struct
+import numpy as np
+
+np.set_printoptions(precision=53, floatmode='fixed', suppress=False)
+
+bin2float = lambda b: struct.unpack('>d', int(b, 2).to_bytes(8, byteorder="big"))[0]
+float2bin = lambda f: f'{struct.unpack(">Q", struct.pack(">d", f))[0]:064b}'
+
+simi_bin = [['1011111111000001000100010101001001010100001001001111111001101101','1011111110110010100111001100000010110101010010101000101000001110','0011111111011000000100110110001111000001001110111000111100111100','0000000000000000000000000000000000000000000000000000000000000000','0011111111011110001101100101111010010011011001110111100100101010','0011111111001011011101101011001010010110011101111010000000001101','1011111111100101111110001101101111101111010100011010010010011000','1011111111100101111110001101101111101111010100011010010010011000','1011111111100101111110001101101111101111010100011010010010011000','0011111111010100000011100100100000100001010111001011011011010001','1011111111100101111110001101101111101111010100011010010010011000',],
+['0011111111011111100101110111111011100110000101010000100110101011','1011111111100100010000100100011100101010001101100001110111100110','1011111111011101111101111110101001101001011001101000011111111110','0000000000000000000000000000000000000000000000000000000000000000','1011111111101010010010110001100111000001111000101010110011100011','1011111110000001001101100001100101101001101100101101110010100000','0011111111101010100011111111001000100111100010010111100001011010','0011111111101010100011111111001000100111100010010111100001011010','0011111111101010100011111111001000100111100010010111100001011010','0011111111101010100011111111001000100111100010010111100001011010','0011111111101010100011111111001000100111100010010111100001011010',],
+['1011111111001001110010110111011011000011000111111111011000011101','1011111111000110001000101100000001110010011000101001110100000101','0011111111111000000111001101010000001100111000101110001101111101','1000000000000000000000000000000000000000000000000000000000000000','0011111111100100000110010010111110101001000001101011010101100001','1011111111101101011000011011001000111011100100101000011110000110','0011111111010010100100010000010100100101000101111010010001001000','0011111111010010100100010000010100100101000101111010010001001000','0011111111010010100100010000010100100101000101111010010001001000','0011111111010010100100010000010100100101000101111010010001001000','0011111111010010100100010000010100100101000101111010010001001000',],
+['0011111111101001111100000111001010111011000101001101000000010111','0011111111101000001000001111011010100100000001010010000110111111','1011111111010101110111011100111010000011111010110010100101011100','1011111111110000000000000000000000000000000000000000000000000000','0011111111011110100011000100001100001111000111110011010111101110','1011111111100000001110100101001111011000110100000111100110100100','0011111110011110100001100100101000101000000110111101010110100000','0011111110011110100001100100101000101000000110111101010110100000','0011111110011110100001100100101000101000000110111101010110100000','0011111110011110100001100100101000101000000110111101010110100000','0011111110011110100001100100101000101000000110111101010110100000',],
+['1011111111100110000001111000010110111001001101111101010011110110','1011111111100100110001100000010101000101110000100100000111011110','0011111111011011011000110110000010010000011001110100100100100011','0011111111110000000000000000000000000000000000000000000000000000','1011111111101011011101011010110011100010000100000110110001010110','0011111110110000010101100001000011010110000110011101000000110000','1011111111001010010101000101010011100010110010110011011011000011','1011111111001010010101000101010011100010110010110011011011000011','1011111111001010010101000101010011100010110010110011011011000011','1011111111001010010101000101010011100010110010110011011011000011','1011111111001010010101000101010011100010110010110011011011000011',],
+['0011111111011010110000010001011010010010111111111000101010010000','0011111111011011111000001001010111011101011101110001101001011110','1011111110111100101110110011110101000011010001001001011100000000','0000000000000000000000000000000000000000000000000000000000000000','0011111111011010101000011010000000001101110101111000101001111001','1011111111010010001000001100111011001110001111111110100111011011','1011111111000001000000011010001001111111001011110100000100111101','1011111111000001000000011010001001111111001011110100000100111101','1011111111000001000000011010001001111111001011110100000100111101','1011111111000001000000011010001001111111001011110100000100111101','1011111111000001000000011010001001111111001011110100000100111101',],
+['1011111111001000011100110110000001101000000010001110100110011001','1011111111010111101111010111010110101111001101111110111000001101','1011111110100101001000001010010000010001001000111010000010011011','0000000000000000000000000000000000000000000000000000000000000000','0011111111000111100011000101001111100001100000010110100110011000','0011111110110011010111111000111100110111001011101000000010111001','0011111111100111101100001111100100100000101110011101010110000010','1011111111010000100111100000110110111110100011000101010011111011','1011111111010000100111100000110110111110100011000101010011111011','1011111111010000100111100000110110111110100011000101010011111011','1011111111010000100111100000110110111110100011000101010011111011',],
+['1011111110110101000011101001111100010110001100001110100010010000','1011111111001001010000101011101010101010000011001110100100111010','0011111110101011000000001010010111100111011010101000111011110011','0000000000000000000000000000000000000000000000000000000000000000','0011111111001011100111111111001011000011010101000100101011111010','0011111110111010001111010111000001011010000000000010110000110100','1011111111010100010111110101010101111000001010100011000010001011','0011111111100101110100000101010101000011111010101110011110111011','1011111111010100010111110101010101111000001010100011000010001011','1011111111010100010111110101010101111000001010100011000010001011','1011111111010100010111110101010101111000001010100011000010001011',],
+['1011111110100000101111001000110000100101100111100011101010111100','1011111110110001110111001011000000110010011000111011111010001100','1011111111000001001110010111001001000001110001000111101011010111','0000000000000000000000000000000000000000000000000000000000000000','0011111110100111001111010100001000001010011011111101011111000110','0011111110011101101010110101011100010000100010000110110000001000','1011111110110011000010010111011011001001010110100000011011100101','1011111110110011000010010111011011001001010110100000011011100101','0011111111101101100111101101000100100110110101001011111100100011','1011111110110011000010010111011011001001010110100000011011100101','1011111110110011000010010111011011001001010110100000011011100101',],
+['0011111111100100100110111100000010110100110001010101000001101101','1011111111010101010000001110101000110001010111101101111101010010','0011111111100011011100011011010100110100000100000110010000010001','0000000000000000000000000000000000000000000000000000000000000000','1011111111001110000000000111010000110000001101011011101001010110','0011111111011011111111101110001111101111100110000100101111110111','1011111111001001111111010101001110101110111110101101110110011010','1011111111001001111111010101001110101110111110101101110110011010','1011111111001001111111010101001110101110111110101101110110011010','1011111111001001111111010101001110101110111110101101110110011010','1011111111001001111111010101001110101110111110101101110110011010',],
+['0011111110001101111011101011000111001011001011100000001101100110','0011111110001001101011111011101010001110111010001100010001010101','1011111110111011111110101111101110010001000111001001011101111100','0000000000000000000000000000000000000000000000000000000000000000','1011111110100111010100101000000001010100100010100010111001101101','0011111110110001000011000001101110101010011100101110000010100001','1011111110010101100010110110111000000000101101110010010110100110','1011111110010101100010110110111000000000101101110010010110100110','1011111110010101100010110110111000000000101101110010010110100110','1011111110010101100010110110111000000000101101110010010110100110','0011111111101111010100111010010010001111111110100100011011010011',],]
+
+simi = np.zeros((len(simi_bin), len(simi_bin[0])))
+for i in range(len(simi_bin)):
+ for j in range(len(simi_bin[0])):
+ simi[i, j] = bin2float(simi_bin[i][j])
+
+# for i in range(simi.shape[0]):
+# print(repr(simi[i]))
+
+simi = np.random.random((3, 3))
+
+tempsum = -np.sum(simi, axis=0)
+mysum = np.zeros(simi.shape[1])
+for i in range(simi.shape[1]):
+ for j in range(simi.shape[0] - 1, -1, -1):
+ mysum[i] += simi[j, i]
+ # mysum[i] = -sum(simi[:, i])
+print(tempsum + mysum)
+# if any(np.abs(tempsum - mysum) > 0):
+# print("abs(tempsum - mysum) > 0")
\ No newline at end of file
diff --git a/pyprima/profiles_vs_python_bindings/test_updatexfc.py b/pyprima/profiles_vs_python_bindings/test_updatexfc.py
new file mode 100644
index 0000000000..157c1da3c9
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/test_updatexfc.py
@@ -0,0 +1,173 @@
+import numpy as np
+from pyprima.common.linalg import inv
+import debugpy
+
+np.set_printoptions(precision=16, floatmode='fixed', suppress=False)
+
+# if input("Type Y to debug, ENTER to continue: ") == 'Y':
+# print("Waiting for debugger to attach...")
+# debugpy.listen(5678)
+# debugpy.wait_for_client()
+
+np.random.seed(0)
+A = np.random.random((3,3))
+# A = np.zeros((12, 12))
+
+# for i in range(12):
+# A[0, i] = i + 1
+# for j in range(1, 12):
+# A[j] = A[j-1]
+# for i in range(12):
+# A[i, 11-i] = 0
+print(A)
+print(np.linalg.det(A))
+# print(np.linalg.inv(A))
+# debugpy.breakpoint()
+Bmine = inv(A.copy())
+Bnumpy = np.linalg.inv(A)
+print(Bmine - Bnumpy)
+
+# [-2.75236387645919705846608849242329597473144531250000000e+02
+# -3.30258202677836537608513367558771278709173202514648438e-02
+# -2.60292652305317805117024221317478804849088191986083984e-02
+# -1.03938739868627205709117333753965795040130615234375000e+00
+# -3.51264910543025277167572539838147349655628204345703125e-01
+# -2.75319715006458878292505687568336725234985351562500000e+01
+# -1.45491044079348375817062333226203918457031250000000000e+04
+# -7.59857225741902017034590244293212890625000000000000000e+05
+# -4.13219084514259904494792863260954618453979492187500000e+01
+# -1.35727092186378417768093977637151870130338693343219347e-08
+# 6.19213967364305863156914710998535156250000000000000000e+04
+# 1.32533105409530166383925864240049541376009756277198903e-08]
+# [-1.68916879926685368218386429361999034881591796875000000e+02
+# 2.67300378078544828563956059497286332771182060241699219e-02
+# 7.12995373010534040647545594993061968125402927398681641e-03
+# 2.01316482671386376424038644472602754831314086914062500e+00
+# -9.45194914791982032653550049872137606143951416015625000e-01
+# 6.40995497017158211150444913073442876338958740234375000e+00
+# -8.75828268959940942295361310243606567382812500000000000e+03
+# 4.78377892933179857209324836730957031250000000000000000e+05
+# -2.57636486629565013117826310917735099792480468750000000e+02
+# 4.26464824204714732011064571085001695394112175563350320e-07
+# 5.44144248112333225435577332973480224609375000000000000e+04
+# -4.26284063282236404940610946523449342748790513724088669e-07]
+# [-1.79266743124217331484260284923948347568511962890625000e+00
+# 1.18679048349766506215141603775009571108967065811157227e-02
+# -2.26948117405421055658987228298428817652165889739990234e-02
+# 8.44028992744987993646077484299894422292709350585937500e-01
+# 1.65154263578907778420301610822207294404506683349609375e-01
+# -1.94759218904803588223728638695320114493370056152343750e+00
+# 1.42768190635548726277193054556846618652343750000000000e+04
+# 1.55295472709112660959362983703613281250000000000000000e+06
+# -3.86782765420878513396019116044044494628906250000000000e+03
+# -3.01454064493740389813272728680715317750582471489906311e-06
+# -2.71507831270575334201566874980926513671875000000000000e+04
+# 3.01202914603102700694657080371374746619039797224104404e-06]
+# [ 1.24208516836780916037241695448756217956542968750000000e+02
+# -5.75045921262246073624169184768106788396835327148437500e-03
+# 3.47708082917587380997193058362881856737658381462097168e-03
+# -1.52338947815461644985646216809982433915138244628906250e+00
+# -3.85950559914885971135589670666377060115337371826171875e-01
+# -2.94022145387218847290000667271669954061508178710937500e+00
+# -1.53216826856923980813007801771163940429687500000000000e+04
+# 2.87612711699984734877943992614746093750000000000000000e+06
+# 9.51206475866360676718613831326365470886230468750000000e+01
+# -8.51835695469748415976354513623469699723500525578856468e-07
+# -3.83481842741625267080962657928466796875000000000000000e+05
+# 8.51475812958621226865345288370434673197451047599315643e-07]
+# [-3.20371478951535152646101778373122215270996093750000000e+02
+# 1.41639688833007908880246006333436525892466306686401367e-02
+# 3.27582674986352725965410570552194258198142051696777344e-02
+# -2.20952315452089154135251192201394587755203247070312500e+00
+# 3.33312928277775533114635209130938164889812469482421875e-01
+# 1.61394919871353650364653731230646371841430664062500000e+01
+# -1.01024087264925437921192497014999389648437500000000000e+04
+# 2.86856205426727305166423320770263671875000000000000000e+05
+# -8.98673684696424857065721880644559860229492187500000000e+02
+# -5.34338202834764093476348917333140775554056745022535324e-06
+# 1.40074836980978511746798176318407058715820312500000000e+03
+# 5.34065912334614892250121351291447524545219494029879570e-06]
+# [-5.09876216386349412346135068219155073165893554687500000e+01
+# -2.63542784930899994660924434697335527744144201278686523e-03
+# -2.51399461410528324289970214522327296435832977294921875e-02
+# 6.81475975441643511132383537187706679105758666992187500e-01
+# 2.48740892408481323316848943250079173594713211059570312e-01
+# 2.74170246240344006594114034669473767280578613281250000e+01
+# -2.15187796125509958073962479829788208007812500000000000e+04
+# 2.78071943791396357119083404541015625000000000000000000e+06
+# 4.40356172737426845742447767406702041625976562500000000e+02
+# 4.02641835422421341629181656962543911504326388239860535e-06
+# 1.78223058897371491184458136558532714843750000000000000e+05
+# -4.02444912037054329675099528795811920645064674317836761e-06]
+# [-2.86051101449182169744744896888732910156250000000000000e+02
+# -9.77053447771603810400806167102416566194733604788780212e-05
+# -1.11947531320210503374967814238516439218074083328247070e-02
+# 2.23997275019269759255280405341181904077529907226562500e+00
+# 3.48932607576893150103103380388347432017326354980468750e-01
+# 2.40955831131753628326919169921893626451492309570312500e+00
+# 1.63441173790672182803973555564880371093750000000000000e+04
+# 1.94363316550956893479451537132263183593750000000000000e+05
+# 2.03817412540019654443312902003526687622070312500000000e+03
+# 1.27988156343938833764092063288719813840543793048709631e-06
+# -3.02976024858838820364326238632202148437500000000000000e+05
+# -1.27940384613178134293458732689829915329937648493796587e-06]
+# [ 1.15818997729596233625670720357447862625122070312500000e+02
+# 1.12703741223333124207850453046830807579681277275085449e-03
+# 4.21798546158918662846160074764156888704746961593627930e-03
+# 2.61218789441259247041671187616884708404541015625000000e+00
+# 6.13701335209160050987442502901103580370545387268066406e-02
+# 1.18544842965778602916770978481508791446685791015625000e+01
+# -2.79720881523781681607943028211593627929687500000000000e+04
+# -2.17164937667809845879673957824707031250000000000000000e+06
+# 8.36200741229483668348620994947850704193115234375000000e+01
+# -5.68951954248849095605727876900914452562574297189712524e-06
+# -9.33898483185210934607312083244323730468750000000000000e+04
+# 5.68630296168503464440463143758641706426715245470404625e-06]
+# [ 4.79238946658743358852916571777313947677612304687500000e+01
+# 5.91489250320679713079918826679204357787966728210449219e-03
+# 1.19697735179991951448563725080020958557724952697753906e-02
+# 7.94627737917767640318800204113358631730079650878906250e-01
+# 2.26244097435694901943037393721169792115688323974609375e-01
+# -2.41108225904587740728857170324772596359252929687500000e+01
+# -2.41168676979214023958775214850902557373046875000000000e+03
+# 2.50508854610166698694229125976562500000000000000000000e+06
+# 2.66767985585901533340802416205406188964843750000000000e+03
+# -5.40057069348209971163064097021688780841941479593515396e-06
+# 1.74723260002154711401090025901794433593750000000000000e+05
+# 5.39748440515866924219266081985679761601204518228769302e-06]
+# [-3.08431611060574155658287054393440485000610351562500000e+01
+# -6.54828368150419184989807774854853050783276557922363281e-03
+# 1.13431801987995649338980541642740718089044094085693359e-02
+# 1.44274016581032582529076080390950664877891540527343750e+00
+# 5.04193256703811876207055320264771580696105957031250000e-01
+# -2.63623976718887185199946543434634804725646972656250000e+01
+# -2.52475128202312771463766694068908691406250000000000000e+04
+# 1.55154201332599273882806301116943359375000000000000000e+05
+# -1.94138664675841027928981930017471313476562500000000000e+03
+# 4.00428676598855276639070052691060652705346001312136650e-06
+# -5.61613407598229896393604576587677001953125000000000000e+04
+# -4.00182501009659589438034396624566113587206928059458733e-06]
+# [ 3.91479298936926412011416687164455652236938476562500000e+01
+# 3.27387177966747683344195252175268251448869705200195312e-02
+# -2.87916368627453081452394201278366381302475929260253906e-02
+# -2.10551022254688247059561945206951349973678588867187500e+00
+# 2.24290144675719754774334546709724236279726028442382812e-01
+# 2.46756667220616154878598536015488207340240478515625000e+00
+# -1.62737159935236104502109810709953308105468750000000000e+04
+# -1.46263394214811129495501518249511718750000000000000000e+06
+# 1.18826630533348566132190171629190444946289062500000000e+03
+# 1.65204916156191398146917299893932096210846793837845325e-06
+# -6.64766049830698757432401180267333984375000000000000000e+04
+# -1.65203916704949204015146629886956475274928379803895950e-06]
+# [ 4.24946360271901539817918092012405395507812500000000000e+03
+# 4.23806231477487290248973295092582702636718750000000000e+03
+# 4.22666162207404249784303829073905944824218750000000000e+03
+# 4.24946360742166416457621380686759948730468750000000000e+03
+# 4.23236195725721518101636320352554321289062500000000000e+03
+# 4.24946360830693447496742010116577148437500000000000000e+03
+# 4.24946307566331142879789695143699645996093750000000000e+03
+# 4.24956831633846013573929667472839355468750000000000000e+03
+# 4.24946356080193800153210759162902832031250000000000000e+03
+# -2.71827999997485614258607711235526949167251586914062500e+00
+# 4.24946300813852121791569516062736511230468750000000000e+03
+# 2.71827999997488367611708781623747199773788452148437500e+00]
\ No newline at end of file
diff --git a/pyprima/profiles_vs_python_bindings/uobyqa_newuoa.txt b/pyprima/profiles_vs_python_bindings/uobyqa_newuoa.txt
new file mode 100644
index 0000000000..8a3e876b3e
--- /dev/null
+++ b/pyprima/profiles_vs_python_bindings/uobyqa_newuoa.txt
@@ -0,0 +1,133 @@
+AKIVA
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS6
+BOX3
+BOXBODLS
+BRKMCC
+BROWNBS
+BROWNDEN
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA2
+DJTL
+ECKERLE4LS
+EGGCRATE
+ELATVIDU
+ENGVAL2
+ENSOLS
+EXP2
+EXPFIT
+FBRAIN3LS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HUMPS
+JENSMP
+JUDGE
+KIRBY2LS
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LOGHAIRY
+LSC1LS
+LSC2LS
+MARATOSB
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+PALMER1C
+PALMER1D
+PALMER2C
+PALMER3C
+PALMER4C
+PALMER5C
+PALMER5D
+PALMER6C
+PALMER7C
+PALMER8C
+PARKCH
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+PRICE3
+PRICE4
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+SINEVAL
+SISSER
+SNAIL
+SSI
+STRATEC
+STREG
+STRTCHDV
+THURBERLS
+TRIGON1
+TRIGON2
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA2
+YFITU
+ZANGWIL2
diff --git a/pyprima/pyproject.toml b/pyprima/pyproject.toml
new file mode 100644
index 0000000000..5050ff92c2
--- /dev/null
+++ b/pyprima/pyproject.toml
@@ -0,0 +1,19 @@
+[project]
+name = "pyprima"
+version = "0.0.1"
+dependencies = [
+ "numpy"
+]
+
+[tool.setuptools.packages.find]
+where = ["src"] # ["."] by default
+exclude = ["pyprima.examples*"] # empty by default
+
+[tools.ruff]
+line-length=88
+select= ["D"]
+
+[tool.pytest.ini_options]
+testpaths = [
+ "tests",
+]
diff --git a/pyprima/src/pyprima/__init__.py b/pyprima/src/pyprima/__init__.py
new file mode 100644
index 0000000000..704fbff75a
--- /dev/null
+++ b/pyprima/src/pyprima/__init__.py
@@ -0,0 +1,256 @@
+# Bounds may appear unused in this file but we need to import it to make it available to the user
+from scipy.optimize import NonlinearConstraint, LinearConstraint, Bounds
+from .common._nonlinear_constraints import process_nl_constraints
+from .common._linear_constraints import (
+ combine_multiple_linear_constraints,
+ separate_LC_into_eq_and_ineq,
+)
+from .common._bounds import process_bounds
+from enum import Enum
+from .common._project import _project
+from .common.infos import FIXED_SUCCESS
+from .common.linalg import get_arrays_tol
+from .cobyla.cobyla import cobyla, COBYLAResult
+import numpy as np
+from collections.abc import Iterable
+
+
+class ConstraintType(Enum):
+ LINEAR_OBJECT = 5
+ NONLINEAR_OBJECT = 10
+ LINEAR_DICT = 15
+ NONLINEAR_DICT = 20
+
+
+def get_constraint_type(constraint):
+ if isinstance(constraint, dict) and ("A" in constraint) and ("lb" in constraint) and ("ub" in constraint):
+ return ConstraintType.LINEAR_DICT
+ elif isinstance(constraint, dict) and ("fun" in constraint) and ("lb" in constraint) and ("ub" in constraint):
+ return ConstraintType.NONLINEAR_DICT
+ elif hasattr(constraint, "A") and hasattr(constraint, "lb") and hasattr(constraint, "ub"):
+ return ConstraintType.LINEAR_OBJECT
+ elif hasattr(constraint, "fun") and hasattr(constraint, "lb") and hasattr(constraint, "ub"):
+ return ConstraintType.NONLINEAR_OBJECT
+ else:
+ raise ValueError(f"Constraint type {type(constraint)} not recognized")
+
+
+def process_constraints(constraints):
+ # First throw it back if it's an empty tuple
+ if not constraints:
+ return None, None
+ # Next figure out if it's a list of constraints or a single constraint
+ # If it's a single constraint, make it a list, and then the remaining logic
+ # doesn't have to change
+ if not isinstance(constraints, Iterable):
+ constraints = [constraints]
+
+ # Separate out the linear and nonlinear constraints
+ linear_constraints = []
+ nonlinear_constraints = []
+ for constraint in constraints:
+ constraint_type = get_constraint_type(constraint)
+ if constraint_type is ConstraintType.LINEAR_OBJECT:
+ linear_constraints.append(constraint)
+ elif constraint_type is ConstraintType.NONLINEAR_OBJECT:
+ nonlinear_constraints.append(constraint)
+ elif constraint_type == ConstraintType.LINEAR_DICT:
+ linear_constraints.append(LinearConstraint(constraint["A"], constraint["lb"], constraint["ub"]))
+ elif constraint_type == ConstraintType.NONLINEAR_DICT:
+ nonlinear_constraints.append(NonlinearConstraint(constraint["fun"], constraint["lb"], constraint["ub"]))
+ else:
+ raise ValueError("Constraint type not recognized")
+
+ if len(nonlinear_constraints) > 0:
+ nonlinear_constraint_function = process_nl_constraints(nonlinear_constraints)
+ else:
+ nonlinear_constraint_function = None
+
+ # Determine if we have multiple linear constraints, just 1, or none, and process accordingly
+ if len(linear_constraints) > 1:
+ linear_constraint = combine_multiple_linear_constraints(linear_constraints)
+ elif len(linear_constraints) == 1:
+ linear_constraint = linear_constraints[0]
+ else:
+ linear_constraint = None
+
+ return linear_constraint, nonlinear_constraint_function
+
+
+def minimize(fun, x0, args=(), method=None, bounds=None, constraints=(), callback=None, options=None):
+
+ linear_constraint, nonlinear_constraint_function = process_constraints(constraints)
+
+ options = {'quiet': True} if options is None else options
+ quiet = options.get("quiet", True)
+
+ if method is None:
+ if nonlinear_constraint_function is not None:
+ if not quiet: print("Nonlinear constraints detected, applying COBYLA")
+ method = "cobyla"
+ elif linear_constraint is not None:
+ if not quiet: print("Linear constraints detected without nonlinear constraints, applying LINCOA")
+ method = "lincoa"
+ elif bounds is not None:
+ if not quiet: print("Bounds without linear or nonlinear constraints detected, applying BOBYQA")
+ method = "bobyqa"
+ else:
+ if not quiet: print("No bounds or constraints detected, applying NEWUOA")
+ method = "newuoa"
+ else:
+ # Raise some errors if methods were called with inappropriate options
+ method = method.lower()
+ if method not in ('newuoa', 'uobyqa', 'bobyqa', 'cobyla', 'lincoa'):
+ raise ValueError(f"Method must be one of NEWUOA, UOBYQA, BOBYQA, COBYLA, or LINCOA, not '{method}'")
+ if method != "cobyla" and nonlinear_constraint_function is not None:
+ raise ValueError("Nonlinear constraints were provided for an algorithm that cannot handle them")
+ if method not in ("cobyla", "lincoa") and linear_constraint is not None:
+ raise ValueError("Linear constraints were provided for an algorithm that cannot handle them")
+ if method not in ("cobyla", "bobyqa", "lincoa") and bounds is not None:
+ raise ValueError("Bounds were provided for an algorithm that cannot handle them")
+
+ # Try to get the length of x0. If we can't that likely means it's a scalar, and
+ # in that case we turn it into an array and wrap the original function so that it
+ # can accept an array and return a scalar.
+ try:
+ lenx0 = len(x0)
+ except TypeError:
+ x0 = np.array([x0])
+ original_scalar_fun = fun
+ def scalar_fun(x):
+ return original_scalar_fun(x[0], *args)
+ fun = scalar_fun
+ lenx0 = 1
+
+ lb, ub = process_bounds(bounds, lenx0)
+
+ # Check which variables are fixed and eliminate them from the problem.
+ # Save the indices and values so that we can call the original function with
+ # an array of the appropriate size, and so that we can add the fixed values to the
+ # result when COBYLA returns.
+ tol = get_arrays_tol(lb, ub)
+ _fixed_idx = (
+ (lb <= ub)
+ & (ub <= lb + tol)
+ )
+ if any(_fixed_idx) and not all(_fixed_idx):
+ # We should NOT reduce the problem if all variables are fixed. Otherwise, Aineq would be [], and
+ # then bineq will be set to [] in the end. In this way, we lose completely the information in
+ # these constraints. Consequently, we cannot evaluate the constraint violation correctly when needed.
+ _fixed_values = 0.5 * (
+ lb[_fixed_idx] + ub[_fixed_idx]
+ )
+ _fixed_values = np.clip(
+ _fixed_values,
+ lb[_fixed_idx],
+ ub[_fixed_idx],
+ )
+ x0 = x0[~_fixed_idx]
+ lb = lb[~_fixed_idx]
+ ub = ub[~_fixed_idx]
+ original_fun = fun
+ def fixed_fun(x):
+ newx = np.zeros(lenx0)
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = x
+ return original_fun(newx, *args)
+ fun = fixed_fun
+ # Adjust linear_constraint
+ if linear_constraint:
+ new_lb = linear_constraint.lb - linear_constraint.A[:, _fixed_idx] @ _fixed_values
+ new_ub = linear_constraint.ub - linear_constraint.A[:, _fixed_idx] @ _fixed_values
+ new_A = linear_constraint.A[:, ~_fixed_idx]
+ linear_constraint = LinearConstraint(new_A, new_lb, new_ub)
+ # Adjust nonlinear constraints
+ if nonlinear_constraint_function:
+ original_nlc_fun = nonlinear_constraint_function
+ def fixed_nlc_fun(x):
+ newx = np.zeros(lenx0)
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = x
+ return original_nlc_fun(newx, *args)
+ nonlinear_constraint_function = fixed_nlc_fun
+
+
+ # Project x0 onto the feasible set
+ if nonlinear_constraint_function is None:
+ result = _project(x0, lb, ub, {"linear": linear_constraint, "nonlinear": None})
+ x0 = result.x
+
+ if linear_constraint is not None:
+ A_eq, b_eq, A_ineq, b_ineq = separate_LC_into_eq_and_ineq(linear_constraint)
+ else:
+ A_eq, b_eq, A_ineq, b_ineq = None, None, None, None
+
+ if nonlinear_constraint_function is not None:
+ # If there is a nonlinear constraint function, we will call COBYLA, which needs the number of nonlinear
+ # constraints (m_nlcon). In order to get this number we need to evaluate the constraint function at x0.
+ # The constraint value at x0 (nlconstr0) is not discarded but passed down to the Fortran backend, as its
+ # evaluation is assumed to be expensive. We also evaluate the objective function at x0 and pass the result
+ # (f0) down to the Fortran backend, which expects nlconstr0 and f0 to be provided in sync.
+ def calcfc(x):
+ f = fun(x, *args)
+ nlconstr = nonlinear_constraint_function(x)
+ return f, nlconstr
+ else:
+ def calcfc(x):
+ f = fun(x, *args)
+ constr = np.zeros(0)
+ return f, constr
+
+ if all(_fixed_idx):
+ x=0.5 * ( lb[_fixed_idx] + ub[_fixed_idx] )
+ x = np.clip(
+ x,
+ lb[_fixed_idx],
+ ub[_fixed_idx],
+ )
+ f, nlconstr = calcfc(x)
+ cstrv = max(max((A_ineq @ x) - b_ineq) if A_ineq is not None else 0,
+ max((abs((A_eq @ x) - b_eq))) if A_eq is not None else 0,
+ max(np.append(0, nlconstr)))
+ result = COBYLAResult(
+ x=x,
+ f=f,
+ constr=nlconstr,
+ cstrv=cstrv,
+ nf=1,
+ xhist=[x],
+ fhist=[f],
+ chist=[cstrv],
+ conhist=[nlconstr],
+ info=FIXED_SUCCESS
+ )
+ return result
+
+ f0, nlconstr0 = calcfc(x0)
+
+ if 'quiet' in options:
+ del options['quiet']
+
+ if 'maxfev' in options:
+ options['maxfun'] = options['maxfev']
+ del options['maxfev']
+
+ result = cobyla(
+ calcfc,
+ len(nlconstr0),
+ x0,
+ A_ineq,
+ b_ineq,
+ A_eq,
+ b_eq,
+ lb,
+ ub,
+ f0=f0,
+ nlconstr0=nlconstr0,
+ callback=callback,
+ **options
+ )
+
+ if any(_fixed_idx):
+ newx = np.zeros(lenx0) + np.nan
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = result.x
+ result.x = newx
+ return result
diff --git a/pyprima/src/pyprima/cobyla/__init__.py b/pyprima/src/pyprima/cobyla/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pyprima/src/pyprima/cobyla/cobyla.py b/pyprima/src/pyprima/cobyla/cobyla.py
new file mode 100644
index 0000000000..e9f190668b
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/cobyla.py
@@ -0,0 +1,561 @@
+'''
+This module provides Powell's COBYLA algorithm.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+
+N.B.:
+
+1. The modern-Fortran reference implementation in PRIMA contains bug fixes and improvements over the
+original Fortran 77 implementation by Powell. Consequently, the PRIMA implementation behaves differently
+from the original Fortran 77 implementation by Powell. Therefore, it is important to point out that
+you are using PRIMA rather than the original solvers if you want your results to be reproducible.
+
+2. Compared to Powell's Fortran 77 implementation, the modern-Fortran implementation and hence any
+faithful translation like this one generally produce better solutions with fewer function evaluations,
+making them preferable for applications with expensive function evaluations. However, if function
+evaluations are not the dominant cost in your application, the Fortran 77 solvers are likely to be
+faster, as they are more efficient in terms of memory usage and flops thanks to the careful and
+ingenious (but unmaintained and unmaintainable) implementation by Powell.
+
+See the PRIMA documentation (www.libprima.net) for more information.
+'''
+
+from ..common.evaluate import evaluate, moderatex, moderatef, moderatec
+from ..common.consts import (EPS, RHOBEG_DEFAULT, RHOEND_DEFAULT, CTOL_DEFAULT,
+ CWEIGHT_DEFAULT, FTARGET_DEFAULT, IPRINT_DEFAULT,
+ MAXFUN_DIM_DEFAULT, DEBUGGING, BOUNDMAX,
+ ETA1_DEFAULT, ETA2_DEFAULT, GAMMA1_DEFAULT,
+ GAMMA2_DEFAULT)
+from ..common.preproc import preproc
+from ..common.present import present
+from ..common.linalg import matprod
+from .cobylb import cobylb
+import numpy as np
+from dataclasses import dataclass
+from copy import copy
+
+
+@dataclass
+class COBYLAResult:
+ x: np.ndarray
+ f: float
+ constr: np.ndarray
+ cstrv: float
+ nf: int
+ xhist: np.ndarray | None
+ fhist: np.ndarray | None
+ chist: np.ndarray | None
+ conhist: np.ndarray | None
+ info: int
+
+
+def cobyla(calcfc, m_nlcon, x, Aineq=None, bineq=None, Aeq=None, beq=None,
+ xl=None, xu=None, f0=None, nlconstr0=None, rhobeg=None, rhoend=None,
+ ftarget=FTARGET_DEFAULT, ctol=CTOL_DEFAULT, cweight=CWEIGHT_DEFAULT,
+ maxfun=None, iprint=IPRINT_DEFAULT, eta1=None, eta2=None,
+ gamma1=GAMMA1_DEFAULT, gamma2=GAMMA2_DEFAULT, maxhist=None, maxfilt=2000,
+ callback=None):
+ """
+ Among all the arguments, only CALCFC, M_NLCON, and X are obligatory. The others are
+ OPTIONAL and you can neglect them unless you are familiar with the algorithm. Any
+ unspecified optional input will take the default value detailed below. For
+ instance, we may invoke the solver as follows.
+
+ # First define CALCFC, M_NLCON, and X, and then do the following.
+ result = cobyla(calcfc, m_nlcon, x)
+
+ or
+
+ # First define CALCFC, M_NLCON, X, Aineq, and Bineq, and then do the following.
+ result = cobyla(calcfc, m_nlcon, x, Aineq=Aineq, bineq=bineq, rhobeg=1.0e0,
+ rhoend=1.0e-6)
+
+ ####################################################################################
+ # IMPORTANT NOTICE: The user must set M_NLCON correctly to the number of nonlinear
+ # constraints, namely the size of NLCONSTR introduced below. Set it to 0 if there
+ # is no nonlinear constraint.
+ ####################################################################################
+
+ See examples/cobyla/cobyla_example.py for a concrete example.
+
+ A detailed introduction to the arguments is as follows.
+
+ ####################################################################################
+ # INPUTS
+ ####################################################################################
+
+ CALCFC
+ Input, function.
+ f, nlconstr = CALCFC(X) should evaluate the objective function and nonlinear
+ constraints at the given vector X; it should return a tuple consisting of the
+ objective function value and the nonlinear constraint value. It must be provided
+ by the user, and its definition must conform to the following interface:
+ #-------------------------------------------------------------------------#
+ def calcfc(x):
+ f = 0.0
+ nlconstr = np.zeros(m_nlcon)
+ return f, nlconstr
+ #-------------------------------------------------------------------------#
+
+ M_NLCON
+ Input, scalar.
+ M_NLCON must be set to the number of nonlinear constraints, namely the size of
+ NLCONSTR(X).
+ N.B.:
+ 1. Why don't we define M_NLCON as optional and default it to 0 when it is absent?
+ This is because we need to allocate memory for CONSTR_LOC using M_NLCON. To
+ ensure that the size of CONSTR_LOC is correct, we require the user to specify
+ M_NLCON explicitly.
+
+ X
+ Input, vector.
+ As an input, X should be an N-dimensional vector that contains the starting
+ point, N being the dimension of the problem.
+
+ Aineq, Bineq
+ Input, matrix of size [Mineq, N] and vector of size Mineq unless they are both
+ empty, default: None and None.
+ Aineq and Bineq represent the linear inequality constraints: Aineq*X <= Bineq.
+
+ Aeq, Beq
+ Input, matrix of size [Meq, N] and vector of size Meq unless they are both
+ empty, default: None and None.
+ Aeq and Beq represent the linear equality constraints: Aeq*X = Beq.
+
+ XL, XU
+ Input, vectors of size N unless they are both None, default: None and None.
+ XL is the lower bound for X. If XL is None, X has no
+ lower bound. Any entry of XL that is NaN or below -BOUNDMAX will be taken as
+ -BOUNDMAX, which effectively means there is no lower bound for the corresponding
+ entry of X. The value of BOUNDMAX is 0.25*HUGE(X), which is about 8.6E37 for
+ single precision and 4.5E307 for double precision. XU is similar.
+
+ F0
+ Input, scalar.
+ F0, if present, should be set to the objective function value of the starting X.
+
+ NLCONSTR0
+ Input, vector.
+ NLCONSTR0, if present, should be set to the nonlinear constraint value at the
+ starting X; in addition, SIZE(NLCONSTR0) must be M_NLCON, or the solver will
+ abort.
+
+ RHOBEG, RHOEND
+ Inputs, scalars, default: RHOBEG = 1, RHOEND = 10^-6. RHOBEG and RHOEND must be
+ set to the initial and final values of a trust-region radius, both being positive
+ and RHOEND <= RHOBEG. Typically RHOBEG should be about one tenth of the greatest
+ expected change to a variable, and RHOEND should indicate the accuracy that is
+ required in the final values of the variables.
+
+ FTARGET
+ Input, scalar, default: -Inf.
+ FTARGET is the target function value. The algorithm will terminate when a
+ feasible point with a function value <= FTARGET is found.
+
+ CTOL
+ Input, scalar, default: sqrt(machine epsilon).
+ CTOL is the tolerance of constraint violation. X is considered feasible if
+ CSTRV(X) <= CTOL.
+ N.B.:
+ 1. CTOL is absolute, not relative.
+ 2. CTOL is used only when selecting the returned X. It does not affect the
+ iterations of the algorithm.
+
+ CWEIGHT
+ Input, scalar, default: CWEIGHT_DFT defined in common/consts.py.
+ CWEIGHT is the weight that the constraint violation takes in the selection of the
+ returned X.
+
+ MAXFUN
+ Input, integer scalar, default: MAXFUN_DIM_DFT*N with MAXFUN_DIM_DFT defined in
+ common/consts.py. MAXFUN is the maximal number of calls of CALCFC.
+
+ IPRINT
+ Input, integer scalar, default: 0.
+ The value of IPRINT should be set to 0, 1, -1, 2, -2, 3, or -3, which controls
+ how much information will be printed during the computation:
+ 0: there will be no printing;
+ 1: a message will be printed to the screen at the return, showing the best vector
+ of variables found and its objective function value;
+ 2: in addition to 1, each new value of RHO is printed to the screen, with the
+ best vector of variables so far and its objective function value; each new
+ value of CPEN is also printed;
+ 3: in addition to 2, each function evaluation with its variables will be printed
+ to the screen; -1, -2, -3: the same information as 1, 2, 3 will be printed,
+ not to the screen but to a file named COBYLA_output.txt; the file will be
+ created if it does not exist; the new output will be appended to the end of
+ this file if it already exists.
+ Note that IPRINT = +/-3 can be costly in terms of time and/or space.
+
+ ETA1, ETA2, GAMMA1, GAMMA2
+ Input, scalars, default: ETA1 = 0.1, ETA2 = 0.7, GAMMA1 = 0.5, and GAMMA2 = 2.
+ ETA1, ETA2, GAMMA1, and GAMMA2 are parameters in the updating scheme of the
+ trust-region radius detailed in the subroutine TRRAD in trustregion.py. Roughly
+ speaking, the trust-region radius is contracted by a factor of GAMMA1 when the
+ reduction ratio is below ETA1, and enlarged by a factor of GAMMA2 when the
+ reduction ratio is above ETA2. It is required that 0 < ETA1 <= ETA2 < 1 and
+ 0 < GAMMA1 < 1 < GAMMA2. Normally, ETA1 <= 0.25. It is NOT advised to set
+ ETA1 >= 0.5.
+
+ MAXFILT
+ Input, scalar.
+ MAXFILT is a nonnegative integer indicating the maximal length of the filter used
+ for selecting the returned solution; default: MAXFILT_DFT (a value lower than
+ MIN_MAXFILT is not recommended);
+ see common/consts.py for the definitions of MAXFILT_DFT and MIN_MAXFILT.
+
+ CALLBACK
+ Input, function to report progress and optionally request termination.
+
+
+ ####################################################################################
+ # OUTPUTS
+ ####################################################################################
+
+ The output is a single data structure, COBYLAResult, with the following fields:
+
+ X
+ Output, vector.
+ As an output, X will be set to an approximate minimizer.
+
+ F
+ Output, scalar.
+ F will be set to the objective function value of X at exit.
+
+ CONSTR
+ Output, vector.
+ CONSTR will be set to the constraint value of X at exit.
+
+ CSTRV
+ Output, scalar.
+ CSTRV will be set to the constraint violation of X at exit, i.e.,
+ max([0, XL - X, X - XU, Aineq*X - Bineq, ABS(Aeq*X -Beq), NLCONSTR(X)]).
+
+ NF
+ Output, scalar.
+ NF will be set to the number of calls of CALCFC at exit.
+
+ XHIST, FHIST, CHIST, CONHIST, MAXHIST
+ XHIST: Output, rank 2 array;
+ FHIST: Output, rank 1 array;
+ CHIST: Output, rank 1 array;
+ CONHIST: Output, rank 2 array;
+ MAXHIST: Input, scalar, default: MAXFUN
+ XHIST, if present, will output the history of iterates; FHIST, if present, will
+ output the history function values; CHIST, if present, will output the history of
+ constraint violations; CONHIST, if present, will output the history of constraint
+ values; MAXHIST should be a nonnegative integer, and XHIST/FHIST/CHIST/CONHIST
+ will output only the history of the last MAXHIST iterations.
+ Therefore, MAXHIST= 0 means XHIST/FHIST/CONHIST/CHIST will output
+ nothing, while setting MAXHIST = MAXFUN requests XHIST/FHIST/CHIST/CONHIST to
+ output all the history. If XHIST is present, its size at exit will be
+ (N, min(NF, MAXHIST)); if FHIST is present, its size at exit will be
+ min(NF, MAXHIST); if CHIST is present, its size at exit will be min(NF, MAXHIST);
+ if CONHIST is present, its size at exit will be (M, min(NF, MAXHIST)).
+
+ IMPORTANT NOTICE:
+ Setting MAXHIST to a large value can be costly in terms of memory for large
+ problems.
+ MAXHIST will be reset to a smaller value if the memory needed exceeds MAXHISTMEM
+ defined in common/consts.py
+ Use *HIST with caution!!! (N.B.: the algorithm is NOT designed for large
+ problems).
+
+ INFO
+ Output, scalar.
+ INFO is the exit flag. It will be set to one of the following values defined in
+ common/infos.py:
+ SMALL_TR_RADIUS: the lower bound for the trust region radius is reached;
+ FTARGET_ACHIEVED: the target function value is reached;
+ MAXFUN_REACHED: the objective function has been evaluated MAXFUN times;
+ MAXTR_REACHED: the trust region iteration has been performed MAXTR times (MAXTR = 2*MAXFUN);
+ NAN_INF_X: NaN or Inf occurs in X;
+ DAMAGING_ROUNDING: rounding errors are becoming damaging.
+ #--------------------------------------------------------------------------#
+ The following case(s) should NEVER occur unless there is a bug.
+ NAN_INF_F: the objective function returns NaN or +Inf;
+ NAN_INF_MODEL: NaN or Inf occurs in the model;
+ TRSUBP_FAILED: a trust region step failed to reduce the model
+ #--------------------------------------------------------------------------#
+ """
+
+ # Local variables
+ solver = "COBYLA"
+ srname = "COBYLA"
+
+ # Sizes
+ mineq = len(bineq) if present(bineq) else 0
+ meq = len(beq) if present(beq) else 0
+ mxl = sum(xl > -BOUNDMAX) if present(xl) else 0
+ mxu = sum(xu < BOUNDMAX) if present(xu) else 0
+ mmm = mxu + mxl + 2*meq + mineq + m_nlcon
+ num_vars = len(x)
+
+ # Preconditions
+ if DEBUGGING:
+ assert m_nlcon >= 0, f'{srname} M_NLCON >= 0'
+ assert num_vars >= 1, f'{srname} N >= 1'
+
+ assert present(Aineq) == present(bineq), \
+ f'{srname} Aineq and Bineq are both present or both absent'
+ if (present(Aineq)):
+ assert Aineq.shape == (mineq, num_vars), f'{srname} SIZE(Aineq) == [Mineq, N]'
+
+ assert present(Aeq) == present(beq), \
+ f'{srname} Aeq and Beq are both present or both absent'
+ if (present(Aeq)):
+ assert Aeq.shape == (meq, num_vars), f'{srname} SIZE(Aeq) == [Meq, N]'
+
+ if (present(xl)):
+ assert len(xl) == num_vars, f'{srname} SIZE(XL) == N'
+ if (present(xu)):
+ assert len(xu) == num_vars, f'{srname} SIZE(XU) == N'
+
+
+ # N.B.: If NLCONSTR0 is present, then F0 must be present, and we assume that
+ # F(X0) = F0 even if F0 is NaN; if NLCONSTR0 is absent, then F0 must be either
+ # absent or NaN, both of which will be interpreted as F(X0) is not provided.
+ if present(nlconstr0):
+ assert present(f0), f'{srname} If NLCONSTR0 is present, then F0 is present'
+ if present(f0):
+ assert np.isnan(f0) or present(nlconstr0), \
+ f'{srname} If F0 is present and not NaN, then NLCONSTR0 is present'
+
+
+
+ # Exit if the size of NLCONSTR0 is inconsistent with M_NLCON.
+ if present(nlconstr0):
+ assert np.size(nlconstr0) == m_nlcon
+
+ # Read the inputs.
+
+ if xl is not None:
+ xl = copy(xl)
+ xl[np.isnan(xl)] = -BOUNDMAX
+ xl[xl < -BOUNDMAX] = -BOUNDMAX
+
+ if xu is not None:
+ xu = copy(xu)
+ xu[np.isnan(xu)] = BOUNDMAX
+ xu[xu > BOUNDMAX] = BOUNDMAX
+
+ # Wrap the linear and bound constraints into a single constraint: AMAT@X <= BVEC.
+ amat, bvec = get_lincon(Aeq, Aineq, beq, bineq, xl, xu)
+
+ # Create constraint vector
+ constr = np.zeros(mmm)
+
+ # Set [F_LOC, CONSTR_LOC] to [F(X0), CONSTR(X0)] after evaluating the latter if
+ # needed. In this way, COBYLB only needs one interface.
+ # N.B.: Due to the preconditions above, there are two possibilities for F0 and
+ # NLCONSTR0.
+ # If NLCONSTR0 is present, then F0 must be present, and we assume that F(X0) = F0
+ # even if F0 is NaN.
+ # If NLCONSTR0 is absent, then F0 must be either absent or NaN, both of which will
+ # be interpreted as F(X0) is not provided and we have to evaluate F(X0) and
+ # NLCONSTR(X0) now.
+ if (present(f0) and present(nlconstr0) and all(np.isfinite(x))):
+ f = moderatef(f0)
+ if amat is not None:
+ constr[:mmm - m_nlcon] = moderatec(matprod(amat, x) - bvec)
+ constr[mmm - m_nlcon:] = moderatec(nlconstr0)
+ else:
+ x = moderatex(x)
+ f, constr = evaluate(calcfc, x, m_nlcon, amat, bvec)
+ constr[:mmm - m_nlcon] = moderatec(constr[:mmm - m_nlcon])
+ # N.B.: Do NOT call FMSG, SAVEHIST, or SAVEFILT for the function/constraint evaluation at X0.
+ # They will be called during the initialization, which will read the function/constraint at X0.
+ cstrv = max(np.append(0, constr))
+
+
+ # If RHOBEG is present, use it; otherwise, RHOBEG takes the default value for
+ # RHOBEG, taking the value of RHOEND into account. Note that RHOEND is considered
+ # only if it is present and it is VALID (i.e., finite and positive). The other
+ # inputs are read similarly.
+ if present(rhobeg):
+ rhobeg = rhobeg
+ elif present(rhoend) and np.isfinite(rhoend) and rhoend > 0:
+ rhobeg = max(10 * rhoend, RHOBEG_DEFAULT)
+ else:
+ rhobeg = RHOBEG_DEFAULT
+
+ if present(rhoend):
+ rhoend = rhoend
+ elif rhobeg > 0:
+ rhoend = max(EPS, min(RHOEND_DEFAULT/RHOBEG_DEFAULT * rhobeg, RHOEND_DEFAULT))
+ else:
+ rhoend = RHOEND_DEFAULT
+
+ maxfun = maxfun if present(maxfun) else MAXFUN_DIM_DEFAULT * num_vars
+
+ if present(eta1):
+ eta1 = eta1
+ elif present(eta2) and 0 < eta2 < 1:
+ eta1 = max(EPS, eta2 / 7)
+ else:
+ eta1 = ETA1_DEFAULT
+
+ if present(eta2):
+ eta2 = eta2
+ elif 0 < eta1 < 1:
+ eta2 = (eta1 + 2) / 3
+ else:
+ eta2 = ETA2_DEFAULT
+
+ maxhist = (
+ maxhist
+ if present(maxhist)
+ else max(maxfun, num_vars + 2, MAXFUN_DIM_DEFAULT * num_vars)
+ )
+
+ # Preprocess the inputs in case some of them are invalid. It does nothing if all
+ # inputs are valid.
+ (
+ iprint,
+ maxfun,
+ maxhist,
+ ftarget,
+ rhobeg,
+ rhoend,
+ npt, # Unused in COBYLA
+ maxfilt,
+ ctol,
+ cweight,
+ eta1,
+ eta2,
+ gamma1,
+ gamma2,
+ _x0, # Unused in COBYLA
+ ) = preproc(
+ solver,
+ num_vars,
+ iprint,
+ maxfun,
+ maxhist,
+ ftarget,
+ rhobeg,
+ rhoend,
+ num_constraints=mmm,
+ maxfilt=maxfilt,
+ ctol=ctol,
+ cweight=cweight,
+ eta1=eta1,
+ eta2=eta2,
+ gamma1=gamma1,
+ gamma2=gamma2,
+ is_constrained=(mmm > 0),
+ )
+
+ # Further revise MAXHIST according to MAXHISTMEM, and allocate memory for the history.
+ # In MATLAB/Python/Julia/R implementation, we should simply set MAXHIST = MAXFUN and initialize
+ # CHIST = NaN(1, MAXFUN), CONHIST = NaN(M, MAXFUN), FHIST = NaN(1, MAXFUN), XHIST = NaN(N, MAXFUN)
+ # if they are requested; replace MAXFUN with 0 for the history that is not requested.
+ # prehist(maxhist, num_vars, present(xhist), xhist_loc, present(fhist), fhist_loc, &
+ # & present(chist), chist_loc, m, present(conhist), conhist_loc)
+
+ # call cobylb, which performs the real calculations
+ x, f, constr, cstrv, nf, xhist, fhist, chist, conhist, info = cobylb(
+ calcfc,
+ iprint,
+ maxfilt,
+ maxfun,
+ amat,
+ bvec,
+ ctol,
+ cweight,
+ eta1,
+ eta2,
+ ftarget,
+ gamma1,
+ gamma2,
+ rhobeg,
+ rhoend,
+ constr,
+ f,
+ x,
+ maxhist,
+ callback
+ )
+
+ constr = constr[mmm - m_nlcon:]
+
+ return COBYLAResult(x, f, constr, cstrv, nf, xhist, fhist, chist, conhist, info)
+
+
+def get_lincon(Aeq=None, Aineq=None, beq=None, bineq=None, xl=None, xu=None):
+ """
+ This subroutine wraps the linear and bound constraints into a single constraint:
+ AMAT*X <= BVEC.
+
+ N.B.:
+
+ LINCOA normalizes the linear constraints so that each constraint has a gradient
+ of norm 1. However, COBYLA does not do this.
+ """
+
+ # Sizes
+ if Aeq is not None:
+ num_vars = Aeq.shape[1]
+ elif Aineq is not None:
+ num_vars = Aineq.shape[1]
+ elif xl is not None:
+ num_vars = len(xl)
+ elif xu is not None:
+ num_vars = len(xu)
+ else:
+ return None, None
+
+ # Preconditions
+ if DEBUGGING:
+ assert Aineq is None or Aineq.shape == (len(bineq), num_vars)
+ assert Aeq is None or Aeq.shape == (len(beq), num_vars)
+ assert (xl is None or xu is None) or len(xl) == len(xu) == num_vars
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Define the indices of the nontrivial bound constraints.
+ ixl = np.where(xl > -BOUNDMAX)[0] if xl is not None else None
+ ixu = np.where(xu < BOUNDMAX)[0] if xu is not None else None
+
+ # Wrap the linear constraints.
+ # The bound constraint XL <= X <= XU is handled as two constraints:
+ # -X <= -XL, X <= XU.
+ # The equality constraint Aeq*X = Beq is handled as two constraints:
+ # -Aeq*X <= -Beq, Aeq*X <= Beq.
+ # N.B.:
+ # 1. The treatment of the equality constraints is naive. One may choose to
+ # eliminate them instead.
+ idmat = np.eye(num_vars)
+ amat = np.vstack([
+ -idmat[ixl, :] if ixl is not None else np.empty((0, num_vars)),
+ idmat[ixu, :] if ixu is not None else np.empty((0, num_vars)),
+ -Aeq if Aeq is not None else np.empty((0, num_vars)),
+ Aeq if Aeq is not None else np.empty((0, num_vars)),
+ Aineq if Aineq is not None else np.empty((0, num_vars))
+ ])
+ bvec = np.hstack([
+ -xl[ixl] if ixl is not None else np.empty(0),
+ xu[ixu] if ixu is not None else np.empty(0),
+ -beq if beq is not None else np.empty(0),
+ beq if beq is not None else np.empty(0),
+ bineq if bineq is not None else np.empty(0)
+ ])
+
+ amat = amat if amat.shape[0] > 0 else None
+ bvec = bvec if bvec.shape[0] > 0 else None
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert (amat is None and bvec is None) or amat.shape == (len(bvec), num_vars)
+
+ return amat, bvec
diff --git a/pyprima/src/pyprima/cobyla/cobylb.py b/pyprima/src/pyprima/cobyla/cobylb.py
new file mode 100644
index 0000000000..84a9d96585
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/cobylb.py
@@ -0,0 +1,722 @@
+'''
+This module performs the major calculations of COBYLA.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+
+N.B. (Zaikun 20220131): Powell's implementation of COBYLA uses RHO rather than DELTA as the
+trust-region radius, and RHO is never increased. DELTA does not exist in Powell's COBYLA code.
+Following the idea in Powell's other solvers (UOBYQA, ..., LINCOA), our code uses DELTA as the
+trust-region radius, while RHO works a lower bound of DELTA and indicates the current resolution
+of the algorithm. DELTA is updated in a classical way subject to DELTA >= RHO, whereas RHO is
+updated as in Powell's COBYLA code and is never increased. The new implementation improves the
+performance of COBYLA.
+'''
+
+import numpy as np
+from ..common.checkbreak import checkbreak_con
+from ..common.consts import REALMAX, EPS, DEBUGGING, MIN_MAXFILT
+from ..common.infos import INFO_DEFAULT, MAXTR_REACHED, DAMAGING_ROUNDING, \
+ SMALL_TR_RADIUS, CALLBACK_TERMINATE
+from ..common.evaluate import evaluate
+from ..common.history import savehist
+from ..common.linalg import isinv, matprod, inprod, norm, primasum, primapow2
+from ..common.message import fmsg, retmsg, rhomsg
+from ..common.ratio import redrat
+from ..common.redrho import redrho
+from ..common.selectx import savefilt, selectx
+from .update import updatepole, findpole, updatexfc
+from .geometry import setdrop_tr, geostep
+from .trustregion import trstlp, trrad
+from .initialize import initxfc, initfilt
+
+
+def cobylb(calcfc, iprint, maxfilt, maxfun, amat, bvec, ctol, cweight, eta1, eta2,
+ ftarget, gamma1, gamma2, rhobeg, rhoend, constr, f, x, maxhist, callback):
+ '''
+ This subroutine performs the actual computations of COBYLA.
+ '''
+
+ # Outputs
+ xhist = []
+ fhist = []
+ chist = []
+ conhist = []
+
+ # Local variables
+ solver = 'COBYLA'
+ A = np.zeros((np.size(x), np.size(constr))) # A contains the approximate gradient for the constraints
+ distsq = np.zeros(np.size(x) + 1)
+ # CPENMIN is the minimum of the penalty parameter CPEN for the L-infinity
+ # constraint violation in the merit function. Note that CPENMIN = 0 in Powell's
+ # implementation, which allows CPEN to be 0. Here, we take CPENMIN > 0 so that CPEN
+ # is always positive. This avoids the situation where PREREM becomes 0 when
+ # PREREF = 0 = CPEN. It brings two advantages as follows.
+ # 1. If the trust-region subproblem solver works correctly and the trust-region
+ # center is not optimal for the subproblem, then PREREM > 0 is guaranteed. This
+ # is because, in theory, PREREC >= 0 and MAX(PREREC, PREREF) > 0, and the
+ # definition of CPEN in GETCPEN ensures that PREREM > 0.
+ # 2. There is no need to revise ACTREM and PREREM when CPEN = 0 and F = FVAL(N+1)
+ # as in lines 312--314 of Powell's cobylb.f code. Powell's code revises ACTREM
+ # to CVAL(N + 1) - CSTRV and PREREM to PREREC in this case, which is crucial for
+ # feasibility problems.
+ cpenmin = EPS
+
+ # Sizes
+ m_lcon = np.size(bvec) if bvec is not None else 0
+ num_constraints = np.size(constr)
+ m_nlcon = num_constraints - m_lcon
+ num_vars = np.size(x)
+
+ # Preconditions
+ if DEBUGGING:
+ assert abs(iprint) <= 3
+ assert num_constraints >= m_lcon and m_lcon >= 0
+ assert num_vars >= 1
+ assert maxfun >= num_vars + 2
+ assert rhobeg >= rhoend and rhoend > 0
+ assert all(np.isfinite(x))
+ assert 0 <= eta1 <= eta2 < 1
+ assert 0 < gamma1 < 1 < gamma2
+ assert 0 <= ctol
+ assert 0 <= cweight
+ assert 0 <= maxhist <= maxfun
+ assert amat is None or np.shape(amat) == (m_lcon, num_vars)
+ assert min(MIN_MAXFILT, maxfun) <= maxfilt <= maxfun
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Initialize SIM, FVAL, CONMAT, and CVAL, together with the history.
+ # After the initialization, SIM[:, NUM_VARS] holds the vertex of the initial
+ # simplex with the smallest function value (regardless of the constraint
+ # violation), and SIM[:, :NUM_VARS] holds the displacements from the other vertices
+ # to SIM[:, NUM_VARS]. FVAL, CONMAT, and CVAL hold the function values, constraint
+ # values, and constraint violations on the vertices in the order corresponding to
+ # SIM.
+ evaluated, conmat, cval, sim, simi, fval, nf, subinfo = initxfc(calcfc, iprint,
+ maxfun, constr, amat, bvec, ctol, f, ftarget, rhobeg, x,
+ xhist, fhist, chist, conhist, maxhist)
+
+ # Initialize the filter, including xfilt, ffilt, confilt, cfilt, and nfilt.
+ # N.B.: The filter is used only when selecting which iterate to return. It does not
+ # interfere with the iterations. COBYLA is NOT a filter method but a trust-region
+ # method based on an L-infinity merit function. Powell's implementation does not
+ # use a filter to select the iterate, possibly returning a suboptimal iterate.
+ cfilt = np.zeros(np.minimum(np.maximum(maxfilt, 1), maxfun))
+ confilt = np.zeros((np.size(constr), np.size(cfilt)))
+ ffilt = np.zeros(np.size(cfilt))
+ xfilt = np.zeros((np.size(x), np.size(cfilt)))
+ nfilt = initfilt(conmat, ctol, cweight, cval, fval, sim, evaluated, cfilt, confilt,
+ ffilt, xfilt)
+
+ # Check whether to return due to abnormal cases that may occur during the initialization.
+ if subinfo != INFO_DEFAULT:
+ info = subinfo
+ # Return the best calculated values of the variables
+ # N.B: Selectx and findpole choose X by different standards, one cannot replace the other
+ kopt = selectx(ffilt[:nfilt], cfilt[:nfilt], cweight, ctol)
+ x = xfilt[:, kopt]
+ f = ffilt[kopt]
+ constr = confilt[:, kopt]
+ cstrv = cfilt[kopt]
+ # print a return message according to IPRINT.
+ retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
+ # Postconditions
+ if DEBUGGING:
+ assert nf <= maxfun
+ assert np.size(x) == num_vars and not any(np.isnan(x))
+ assert not (np.isnan(f) or np.isposinf(f))
+ # assert np.size(xhist, 0) == n and np.size(xhist, 1) == maxxhist
+ # assert not any(np.isnan(xhist(:, 1:min(nf, maxxhist))))
+ # The last calculated X can be Inf (finite + finite can be Inf numerically).
+ # assert np.size(fhist) == maxfhist
+ # assert not any(np.isnan(fhist(1:min(nf, maxfhist))) or np.isposinf(fhist(1:min(nf, maxfhist))))
+ # assert np.size(conhist, 0) == m and np.size(conhist, 1) == maxconhist
+ # assert not any(np.isnan(conhist(:, 1:min(nf, maxconhist))) or np.isneginf(conhist(:, 1:min(nf, maxconhist))))
+ # assert np.size(chist) == maxchist
+ # assert not any(chist(1:min(nf, maxchist)) < 0 or np.isnan(chist(1:min(nf, maxchist))) or np.isposinf(chist(1:min(nf, maxchist))))
+ # nhist = minval([nf, maxfhist, maxchist])
+ # assert not any(isbetter(fhist(1:nhist), chist(1:nhist), f, cstrv, ctol))
+ return x, f, constr, cstrv, nf, xhist, fhist, chist, conhist, info
+
+
+ # Set some more initial values.
+ # We must initialize shortd, ratio, and jdrop_tr because these get defined on
+ # branches that are not guaranteed to be executed, but their values are used later.
+ # Our initialization of CPEN differs from Powell's in two ways. First, we use the
+ # ratio defined in (13) of Powell's COBYLA paper to initialize CPEN. Second, we
+ # impose CPEN >= CPENMIN > 0. Powell's code simply initializes CPEN to 0.
+ rho = rhobeg
+ delta = rhobeg
+ cpen = np.maximum(cpenmin, np.minimum(1.0E3, fcratio(conmat, fval))) # Powell's code: CPEN = ZERO
+ shortd = False
+ ratio = -1
+ jdrop_tr = 0
+
+ # If DELTA <= GAMMA3*RHO after an update, we set DELTA to RHO. GAMMA3 must be less
+ # than GAMMA2. The reason is as follows. Imagine a very successful step with
+ # DNORM = the un-updated DELTA = RHO. The TRRAD will update DELTA to GAMMA2*RHO.
+ # If GAMMA3 >= GAMMA2, then DELTA will be reset to RHO, which is not reasonable as
+ # D is very successful. See paragraph two of Sec 5.2.5 in T. M. Ragonneau's thesis:
+ # "Model-Based Derivative-Free Optimization Methods and Software." According to
+ # test on 20230613, for COBYLA, this Powellful updating scheme of DELTA works
+ # slightly better than setting directly DELTA = max(NEW_DELTA, RHO).
+ gamma3 = np.maximum(1, np.minimum(0.75 * gamma2, 1.5))
+
+ # MAXTR is the maximal number of trust region iterations. Each trust-region
+ # iteration takes 1 or 2 function evaluations unless the trust-region step is short
+ # or the trust-region subproblem solver fails but the geometry step is not invoked.
+ # Thus the following MAXTR is unlikely to be reached.
+ maxtr = 10 * maxfun
+ info = MAXTR_REACHED
+
+ # Begin the iterative procedure
+ # After solving a trust-region subproblem, we use three boolean variables to
+ # control the workflow.
+ # SHORTD - Is the trust-region trial step too short to invoke # a function
+ # evaluation?
+ # IMPROVE_GEO - Will we improve the model after the trust-region iteration? If yes,
+ # a geometry step will be taken, corresponding to the "Branch (Delta)"
+ # in the COBYLA paper.
+ # REDUCE_RHO - Will we reduce rho after the trust-region iteration?
+ # COBYLA never sets IMPROVE_GEO and REDUCE_RHO to True simultaneously.
+ for tr in range(maxtr):
+ # Increase the penalty parameter CPEN, if needed, so that
+ # PREREM = PREREF + CPEN * PREREC > 0.
+ # This is the first (out of two) update of CPEN, where CPEN increases or
+ # remains the same.
+ # N.B.: CPEN and the merit function PHI = FVAL + CPEN*CVAL are used in three
+ # places only.
+ # 1. In FINDPOLE/UPDATEPOLE, deciding the optimal vertex of the current simplex.
+ # 2. After the trust-region trial step, calculating the reduction ratio.
+ # 3. In GEOSTEP, deciding the direction of the geometry step.
+ # They do not appear explicitly in the trust-region subproblem, though the
+ # trust-region center (i.e. the current optimal vertex) is defined by them.
+ cpen = getcpen(amat, bvec, conmat, cpen, cval, delta, fval, rho, sim, simi)
+
+ # Switch the best vertex of the current simplex to SIM[:, NUM_VARS].
+ conmat, cval, fval, sim, simi, subinfo = updatepole(cpen, conmat, cval, fval,
+ sim, simi)
+ # Check whether to exit due to damaging rounding in UPDATEPOLE.
+ if subinfo == DAMAGING_ROUNDING:
+ info = subinfo
+ break # Better action to take? Geometry step, or simply continue?
+
+ # Does the interpolation set have adequate geometry? It affects improve_geo and
+ # reduce_rho.
+ adequate_geo = all(primasum(primapow2(sim[:, :num_vars]), axis=0) <= 4 * primapow2(delta))
+
+ # Calculate the linear approximations to the objective and constraint functions.
+ # N.B.: TRSTLP accesses A mostly by columns, so it is more reasonable to save A
+ # instead of A^T.
+ # Zaikun 2023108: According to a test on 2023108, calculating G and
+ # A(:, M_LCON+1:M) by solving the linear systems SIM^T*G = FVAL(1:N)-FVAL(N+1)
+ # and SIM^T*A = CONMAT(:, 1:N)-CONMAT(:, N+1) does not seem to improve or worsen
+ # the performance of COBYLA in terms of the number of function evaluations. The
+ # system was solved by SOLVE in LINALG_MOD based on a QR factorization of SIM
+ # (not necessarily a good algorithm). No preconditioning or scaling was used.
+ g = matprod((fval[:num_vars] - fval[num_vars]), simi)
+ A[:, :m_lcon] = amat.T if amat is not None else amat
+ A[:, m_lcon:] = matprod((conmat[m_lcon:, :num_vars] -
+ np.tile(conmat[m_lcon:, num_vars], (num_vars, 1)).T), simi).T
+
+ # Calculate the trust-region trial step d. Note that d does NOT depend on cpen.
+ d = trstlp(A, -conmat[:, num_vars], delta, g)
+ dnorm = min(delta, norm(d))
+
+ # Is the trust-region trial step short? N.B.: we compare DNORM with RHO, not
+ # DELTA. Powell's code especially defines SHORTD by SHORTD = (DNORM < 0.5 *
+ # RHO). In our tests 1/10 seems to work better than 1/2 or 1/4, especially for
+ # linearly constrained problems. Note that LINCOA has a slightly more
+ # sophisticated way of defining SHORTD, taking into account whether D causes a
+ # change to the active set. Should we try the same here?
+ shortd = (dnorm <= 0.1 * rho)
+
+ # Predict the change to F (PREREF) and to the constraint violation (PREREC) due
+ # to D. We have the following in precise arithmetic. They may fail to hold due
+ # to rounding errors.
+ # 1. B[:NUM_CONSTRAINTS] = -CONMAT[:, NUM_VARS] and hence
+ # np.max(np.append(B[:NUM_CONSTRAINTS] - D@A[:, :NUM_CONSTRAINTS], 0)) is the
+ # L-infinity violation of the linearized constraints corresponding to D. When
+ # D=0, the violation is np.max(np.append(B[:NUM_CONSTRAINTS], 0)) =
+ # CVAL[NUM_VARS]. PREREC is the reduction of this violation achieved by D,
+ # which is nonnegative in theory; PREREC = 0 iff B[:NUM_CONSTRAINTS] <= 0, i.e.
+ # the trust-region center satisfies the linearized constraints.
+ # 2. PREREF may be negative or 0, but it is positive when PREREC = 0 and shortd
+ # is False
+ # 3. Due to 2, in theory, max(PREREC, PREREF) > 0 if shortd is False.
+ preref = -inprod(d, g) # Can be negative
+ prerec = cval[num_vars] - np.max(np.append(0, conmat[:, num_vars] + matprod(d, A)))
+
+ # Evaluate PREREM, which is the predicted reduction in the merit function.
+ # In theory, PREREM >= 0 and it is 0 iff CPEN = 0 = PREREF. This may not be true
+ # numerically.
+ prerem = preref + cpen * prerec
+ trfail = not (prerem > 1.0E-6 * min(cpen, 1) * rho)
+
+ if shortd or trfail:
+ # Reduce DELTA if D is short or if D fails to render PREREM > 0. The latter
+ # can only happen due to rounding errors. This seems quite important for
+ # performance
+ delta *= 0.1
+ if delta <= gamma3 * rho:
+ delta = rho # set delta to rho when it is close to or below
+ else:
+ # Calculate the next value of the objective and constraint functions.
+ # If X is close to one of the points in the interpolation set, then we do
+ # not evaluate the objective and constraints at X, assuming them to have
+ # the values at the closest point.
+ # N.B.: If this happens, do NOT include X into the filter, as F and CONSTR
+ # are inaccurate.
+ x = sim[:, num_vars] + d
+ distsq[num_vars] = primasum(primapow2(x - sim[:, num_vars]))
+ distsq[:num_vars] = primasum(primapow2(x.reshape(num_vars, 1) -
+ (sim[:, num_vars].reshape(num_vars, 1) + sim[:, :num_vars])), axis=0)
+ j = np.argmin(distsq)
+ if distsq[j] <= primapow2(1e-4 * rhoend):
+ f = fval[j]
+ constr = conmat[:, j]
+ cstrv = cval[j]
+ else:
+ # Evaluate the objective and constraints at X, taking care of possible
+ # inf/nan values.
+ f, constr = evaluate(calcfc, x, m_nlcon, amat, bvec)
+ cstrv = np.max(np.append(0, constr))
+ nf += 1
+ # Save X, F, CONSTR, CSTRV into the history.
+ savehist(maxhist, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+ # Save X, F, CONSTR, CSTRV into the filter.
+ nfilt, cfilt, ffilt, xfilt, confilt = savefilt(cstrv, ctol, cweight, f,
+ x, nfilt, cfilt, ffilt,
+ xfilt, constr, confilt)
+
+ # Print a message about the function/constraint evaluation according to
+ # iprint
+ fmsg(solver, 'Trust region', iprint, nf, delta, f, x, cstrv, constr)
+
+ # Evaluate ACTREM, which is the actual reduction in the merit function
+ actrem = (fval[num_vars] + cpen * cval[num_vars]) - (f + cpen * cstrv)
+
+ # Calculate the reduction ratio by redrat, which hands inf/nan carefully
+ ratio = redrat(actrem, prerem, eta1)
+
+ # Update DELTA. After this, DELTA < DNORM may hold.
+ # N.B.:
+ # 1. Powell's code uses RHO as the trust-region radius and updates it as
+ # follows.
+ # Reduce RHO to GAMMA1*RHO if ADEQUATE_GEO is TRUE and either SHORTD is
+ # TRUE or RATIO < ETA1, and then revise RHO to RHOEND if its new value is
+ # not more than GAMMA3*RHOEND; RHO remains unchanged in all other cases;
+ # in particular, RHO is never increased.
+ # 2. Our implementation uses DELTA as the trust-region radius, while using
+ # RHO as a lower bound for DELTA. DELTA is updated in a way that is
+ # typical for trust-region methods, and it is revised to RHO if its new
+ # value is not more than GAMMA3*RHO. RHO reflects the current resolution
+ # of the algorithm; its update is essentially the same as the update of
+ # RHO in Powell's code (see the definition of REDUCE_RHO below). Our
+ # implementation aligns with UOBYQA/NEWUOA/BOBYQA/LINCOA and improves the
+ # performance of COBYLA.
+ # 3. The same as Powell's code, we do not reduce RHO unless ADEQUATE_GEO is
+ # TRUE. This is also how Powell updated RHO in
+ # UOBYQA/NEWUOA/BOBYQA/LINCOA. What about we also use ADEQUATE_GEO ==
+ # TRUE as a prerequisite for reducing DELTA? The argument would be that
+ # the bad (small) value of RATIO may be because of a bad geometry (and
+ # hence a bad model) rather than an improperly large DELTA, and it might
+ # be good to try improving the geometry first without reducing DELTA.
+ # However, according to a test on 20230206, it does not improve the
+ # performance if we skip the update of DELTA when ADEQUATE_GEO is FALSE
+ # and RATIO < 0.1. Therefore, we choose to update DELTA without checking
+ # ADEQUATE_GEO.
+
+ delta = trrad(delta, dnorm, eta1, eta2, gamma1, gamma2, ratio)
+ if delta <= gamma3*rho:
+ delta = rho # Set delta to rho when it is close to or below.
+
+ # Is the newly generated X better than the current best point?
+ ximproved = actrem > 0 # If ACTREM is NaN, then XIMPROVED should and will be False
+
+ # Set JDROP_TR to the index of the vertex to be replaced with X. JDROP_TR = 0 means there
+ # is no good point to replace, and X will not be included into the simplex; in this case,
+ # the geometry of the simplex likely needs improvement, which will be handled below.
+ jdrop_tr = setdrop_tr(ximproved, d, delta, rho, sim, simi)
+
+ # Update SIM, SIMI, FVAL, CONMAT, and CVAL so that SIM[:, JDROP_TR] is replaced with D.
+ # UPDATEXFC does nothing if JDROP_TR is None, as the algorithm decides to discard X.
+ sim, simi, fval, conmat, cval, subinfo = updatexfc(jdrop_tr, constr, cpen, cstrv, d, f, conmat, cval, fval, sim, simi)
+ # Check whether to break due to damaging rounding in UPDATEXFC
+ if subinfo == DAMAGING_ROUNDING:
+ info = subinfo
+ break # Better action to take? Geometry step, or a RESCUE as in BOBYQA?
+
+ # Check whether to break due to maxfun, ftarget, etc.
+ subinfo = checkbreak_con(maxfun, nf, cstrv, ctol, f, ftarget, x)
+ if subinfo != INFO_DEFAULT:
+ info = subinfo
+ break
+ # End of if SHORTD or TRFAIL. The normal trust-region calculation ends.
+
+ # Before the next trust-region iteration, we possibly improve the geometry of the simplex or
+ # reduce RHO according to IMPROVE_GEO and REDUCE_RHO. Now we decide these indicators.
+ # N.B.: We must ensure that the algorithm does not set IMPROVE_GEO = True at infinitely many
+ # consecutive iterations without moving SIM[:, NUM_VARS] or reducing RHO. Otherwise, the algorithm
+ # will get stuck in repetitive invocations of GEOSTEP. This is ensured by the following facts:
+ # 1. If an iteration sets IMPROVE_GEO to True, it must also reduce DELTA or set DELTA to RHO.
+ # 2. If SIM[:, NUM_VARS] and RHO remain unchanged, then ADEQUATE_GEO will become True after at
+ # most NUM_VARS invocations of GEOSTEP.
+
+ # BAD_TRSTEP: Is the last trust-region step bad?
+ bad_trstep = shortd or trfail or ratio <= 0 or jdrop_tr is None
+ # IMPROVE_GEO: Should we take a geometry step to improve the geometry of the interpolation set?
+ improve_geo = bad_trstep and not adequate_geo
+ # REDUCE_RHO: Should we enhance the resolution by reducing rho?
+ reduce_rho = bad_trstep and adequate_geo and max(delta, dnorm) <= rho
+
+ # COBYLA never sets IMPROVE_GEO and REDUCE_RHO to True simultaneously.
+ # assert not (IMPROVE_GEO and REDUCE_RHO), 'IMPROVE_GEO or REDUCE_RHO are not both TRUE, COBYLA'
+
+ # If SHORTD or TRFAIL is True, then either IMPROVE_GEO or REDUCE_RHO is True unless ADEQUATE_GEO
+ # is True and max(DELTA, DNORM) > RHO.
+ # assert not (shortd or trfail) or (improve_geo or reduce_rho or (adequate_geo and max(delta, dnorm) > rho)), \
+ # 'If SHORTD or TRFAIL is TRUE, then either IMPROVE_GEO or REDUCE_RHO is TRUE unless ADEQUATE_GEO is TRUE and MAX(DELTA, DNORM) > RHO'
+
+ # Comments on BAD_TRSTEP:
+ # 1. Powell's definition of BAD_TRSTEP is as follows. The one used above seems to work better,
+ # especially for linearly constrained problems due to the factor TENTH (= ETA1).
+ # !bad_trstep = (shortd .or. actrem <= 0 .or. actrem < TENTH * prerem .or. jdrop_tr == 0)
+ # Besides, Powell did not check PREREM > 0 in BAD_TRSTEP, which is reasonable to do but has
+ # little impact upon the performance.
+ # 2. NEWUOA/BOBYQA/LINCOA would define BAD_TRSTEP, IMPROVE_GEO, and REDUCE_RHO as follows. Two
+ # different thresholds are used in BAD_TRSTEP. It outperforms Powell's version.
+ # !bad_trstep = (shortd .or. trfail .or. ratio <= eta1 .or. jdrop_tr == 0)
+ # !improve_geo = bad_trstep .and. .not. adequate_geo
+ # !bad_trstep = (shortd .or. trfail .or. ratio <= 0 .or. jdrop_tr == 0)
+ # !reduce_rho = bad_trstep .and. adequate_geo .and. max(delta, dnorm) <= rho
+ # 3. Theoretically, JDROP_TR > 0 when ACTREM > 0 (guaranteed by RATIO > 0). However, in Powell's
+ # implementation, JDROP_TR may be 0 even RATIO > 0 due to NaN. The modernized code has rectified
+ # this in the function SETDROP_TR. After this rectification, we can indeed simplify the
+ # definition of BAD_TRSTEP by removing the condition JDROP_TR == 0. We retain it for robustness.
+
+ # Comments on REDUCE_RHO:
+ # When SHORTD is TRUE, UOBYQA/NEWUOA/BOBYQA/LINCOA all set REDUCE_RHO to TRUE if the recent
+ # models are sufficiently accurate according to certain criteria. See the paragraph around (37)
+ # in the UOBYQA paper and the discussions about Box 14 in the NEWUOA paper. This strategy is
+ # crucial for the performance of the solvers. However, as of 20221111, we have not managed to
+ # make it work in COBYLA. As in NEWUOA, we recorded the errors of the recent models, and set
+ # REDUCE_RHO to true if they are small (e.g., ALL(ABS(MODERR_REC) <= 0.1 * MAXVAL(ABS(A))*RHO) or
+ # ALL(ABS(MODERR_REC) <= RHO**2)) when SHORTD is TRUE. It made little impact on the performance.
+
+
+ # Since COBYLA never sets IMPROVE_GEO and REDUCE_RHO to TRUE simultaneously, the following
+ # two blocks are exchangeable: IF (IMPROVE_GEO) ... END IF and IF (REDUCE_RHO) ... END IF.
+
+ # Improve the geometry of the simplex by removing a point and adding a new one.
+ # If the current interpolation set has acceptable geometry, then we skip the geometry step.
+ # The code has a small difference from Powell's original code here: If the current geometry
+ # is acceptable, then we will continue with a new trust-region iteration; however, at the
+ # beginning of the iteration, CPEN may be updated, which may alter the pole point SIM(:, N+1)
+ # by UPDATEPOLE; the quality of the interpolation point depends on SIM(:, N + 1), meaning
+ # that the same interpolation set may have good or bad geometry with respect to different
+ # "poles"; if the geometry turns out bad with the new pole, the original COBYLA code will
+ # take a geometry step, but our code here will NOT do it but continue to take a trust-region
+ # step. The argument is this: even if the geometry step is not skipped in the first place, the
+ # geometry may turn out bad again after the pole is altered due to an update to CPEN; should
+ # we take another geometry step in that case? If no, why should we do it here? Indeed, this
+ # distinction makes no practical difference for CUTEst problems with at most 100 variables
+ # and 5000 constraints, while the algorithm framework is simplified.
+ if improve_geo and not all(primasum(primapow2(sim[:, :num_vars]), axis=0) <= 4 * primapow2(delta)):
+ # Before the geometry step, updatepole has been called either implicitly by UPDATEXFC or
+ # explicitly after CPEN is updated, so that SIM[:, :NUM_VARS] is the optimal vertex.
+
+ # Decide a vertex to drop from the simplex. It will be replaced with SIM[:, NUM_VARS] + D to
+ # improve the geometry of the simplex.
+ # N.B.:
+ # 1. COBYLA never sets JDROP_GEO = num_vars.
+ # 2. The following JDROP_GEO comes from UOBYQA/NEWUOA/BOBYQA/LINCOA.
+ # 3. In Powell's original algorithm, the geometry of the simplex is considered acceptable
+ # iff the distance between any vertex and the pole is at most 2.1*DELTA, and the distance
+ # between any vertex and the opposite face of the simplex is at least 0.25*DELTA, as
+ # specified in (14) of the COBYLA paper. Correspondingly, JDROP_GEO is set to the index of
+ # the vertex with the largest distance to the pole provided that the distance is larger than
+ # 2.1*DELTA, or the vertex with the smallest distance to the opposite face of the simplex,
+ # in which case the distance must be less than 0.25*DELTA, as the current simplex does not
+ # have acceptable geometry (see (15)--(16) of the COBYLA paper). Once JDROP_GEO is set, the
+ # algorithm replaces SIM(:, JDROP_GEO) with D specified in (17) of the COBYLA paper, which
+ # is orthogonal to the face opposite to SIM(:, JDROP_GEO) and has a length of 0.5*DELTA,
+ # intending to improve the geometry of the simplex as per (14).
+ # 4. Powell's geometry-improving procedure outlined above has an intrinsic flaw: it may lead
+ # to infinite cycling, as was observed in a test on 20240320. In this test, the geometry-
+ # improving point introduced in the previous iteration was replaced with the trust-region
+ # trial point in the current iteration, which was then replaced with the same geometry-
+ # improving point in the next iteration, and so on. In this process, the simplex alternated
+ # between two configurations, neither of which had acceptable geometry. Thus RHO was never
+ # reduced, leading to infinite cycling. (N.B.: Our implementation uses DELTA as the trust
+ # region radius, with RHO being its lower bound. When the infinite cycling occurred in this
+ # test, DELTA = RHO and it could not be reduced due to the requirement that DELTA >= RHO.)
+ jdrop_geo = np.argmax(primasum(primapow2(sim[:, :num_vars]), axis=0), axis=0)
+
+ # Calculate the geometry step D.
+ delbar = delta/2
+ d = geostep(jdrop_geo, amat, bvec, conmat, cpen, cval, delbar, fval, simi)
+
+ # Calculate the next value of the objective and constraint functions.
+ # If X is close to one of the points in the interpolation set, then we do not evaluate the
+ # objective and constraints at X, assuming them to have the values at the closest point.
+ # N.B.:
+ # 1. If this happens, do NOT include X into the filter, as F and CONSTR are inaccurate.
+ # 2. In precise arithmetic, the geometry improving step ensures that the distance between X
+ # and any interpolation point is at least DELBAR, yet X may be close to them due to
+ # rounding. In an experiment with single precision on 20240317, X = SIM(:, N+1) occurred.
+ x = sim[:, num_vars] + d
+ distsq[num_vars] = primasum(primapow2(x - sim[:, num_vars]))
+ distsq[:num_vars] = primasum(primapow2(x.reshape(num_vars, 1) -
+ (sim[:, num_vars].reshape(num_vars, 1) + sim[:, :num_vars])), axis=0)
+ j = np.argmin(distsq)
+ if distsq[j] <= primapow2(1e-4 * rhoend):
+ f = fval[j]
+ constr = conmat[:, j]
+ cstrv = cval[j]
+ else:
+ # Evaluate the objective and constraints at X, taking care of possible
+ # inf/nan values.
+ f, constr = evaluate(calcfc, x, m_nlcon, amat, bvec)
+ cstrv = np.max(np.append(0, constr))
+ nf += 1
+ # Save X, F, CONSTR, CSTRV into the history.
+ savehist(maxhist, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+ # Save X, F, CONSTR, CSTRV into the filter.
+ nfilt, cfilt, ffilt, xfilt, confilt = savefilt(cstrv, ctol, cweight, f,
+ x, nfilt, cfilt, ffilt,
+ xfilt, constr, confilt)
+
+ # Print a message about the function/constraint evaluation according to iprint
+ fmsg(solver, 'Geometry', iprint, nf, delta, f, x, cstrv, constr)
+ # Update SIM, SIMI, FVAL, CONMAT, and CVAL so that SIM(:, JDROP_GEO) is replaced with D.
+ sim, simi, fval, conmat, cval, subinfo = updatexfc(jdrop_geo, constr, cpen, cstrv, d, f, conmat, cval, fval, sim, simi)
+ # Check whether to break due to damaging rounding in UPDATEXFC
+ if subinfo == DAMAGING_ROUNDING:
+ info = subinfo
+ break # Better action to take? Geometry step, or simply continue?
+
+ # Check whether to break due to maxfun, ftarget, etc.
+ subinfo = checkbreak_con(maxfun, nf, cstrv, ctol, f, ftarget, x)
+ if subinfo != INFO_DEFAULT:
+ info = subinfo
+ break
+ # end of if improve_geo. The procedure of improving the geometry ends.
+
+ # The calculations with the current RHO are complete. Enhance the resolution of the algorithm
+ # by reducing RHO; update DELTA and CPEN at the same time.
+ if reduce_rho:
+ if rho <= rhoend:
+ info = SMALL_TR_RADIUS
+ break
+ delta = max(0.5 * rho, redrho(rho, rhoend))
+ rho = redrho(rho, rhoend)
+ # THe second (out of two) updates of CPEN, where CPEN decreases or remains the same.
+ # Powell's code: cpen = min(cpen, fcratio(fval, conmat)), which may set CPEN to 0.
+ cpen = np.maximum(cpenmin, np.minimum(cpen, fcratio(conmat, fval)))
+ # Print a message about the reduction of rho according to iprint
+ rhomsg(solver, iprint, nf, fval[num_vars], rho, sim[:, num_vars], cval[num_vars], conmat[:, num_vars], cpen)
+ conmat, cval, fval, sim, simi, subinfo = updatepole(cpen, conmat, cval, fval, sim, simi)
+ # Check whether to break due to damaging rounding detected in updatepole
+ if subinfo == DAMAGING_ROUNDING:
+ info = subinfo
+ break # Better action to take? Geometry step, or simply continue?
+ # End of if reduce_rho. The procedure of reducing RHO ends.
+ # Report the current best value, and check if user asks for early termination.
+ if callback:
+ terminate = callback(sim[:, num_vars], fval[num_vars], nf, tr, cval[num_vars], conmat[:, num_vars])
+ if terminate:
+ info = CALLBACK_TERMINATE
+ break
+ # End of for loop. The iterative procedure ends
+
+ # Return from the calculation, after trying the last trust-region step if it has not been tried yet.
+ # Ensure that D has not been updated after SHORTD == TRUE occurred, or the code below is incorrect.
+ x = sim[:, num_vars] + d
+ if (info == SMALL_TR_RADIUS and
+ shortd and
+ norm(x - sim[:, num_vars]) > 1.0E-3 * rhoend and
+ nf < maxfun):
+ # Zaikun 20230615: UPDATEXFC or UPDATEPOLE is not called since the last trust-region step. Hence
+ # SIM[:, NUM_VARS] remains unchanged. Otherwise SIM[:, NUM_VARS] + D would not make sense.
+ f, constr = evaluate(calcfc, x, m_nlcon, amat, bvec)
+ cstrv = np.max(np.append(0, constr))
+ nf += 1
+ savehist(maxhist, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+ nfilt, cfilt, ffilt, xfilt, confilt = savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr, confilt)
+ # Zaikun 20230512: DELTA has been updated. RHO is only indicative here. TO BE IMPROVED.
+ fmsg(solver, 'Trust region', iprint, nf, rho, f, x, cstrv, constr)
+
+ # Return the best calculated values of the variables
+ # N.B.: SELECTX and FINDPOLE choose X by different standards, one cannot replace the other.
+ kopt = selectx(ffilt[:nfilt], cfilt[:nfilt], max(cpen, cweight), ctol)
+ x = xfilt[:, kopt]
+ f = ffilt[kopt]
+ constr = confilt[:, kopt]
+ cstrv = cfilt[kopt]
+
+ # Print a return message according to IPRINT.
+ retmsg(solver, info, iprint, nf, f, x, cstrv, constr)
+ return x, f, constr, cstrv, nf, xhist, fhist, chist, conhist, info
+
+
+
+def getcpen(amat, bvec, conmat, cpen, cval, delta, fval, rho, sim, simi):
+ '''
+ This function gets the penalty parameter CPEN so that PREREM = PREREF + CPEN * PREREC > 0.
+ See the discussions around equation (9) of the COBYLA paper.
+ '''
+
+ # Even after nearly all of the pycutest problems were showing nearly bit for bit
+ # identical results between Python and the Fortran bindings, HS102 was still off by
+ # more than machine epsilon. It turned out to be due to the fact that getcpen was
+ # modifying fval, among other. It just goes to show that even when you're nearly
+ # perfect, you can still have non trivial bugs.
+ conmat = conmat.copy()
+ cval = cval.copy()
+ fval = fval.copy()
+ sim = sim.copy()
+ simi = simi.copy()
+
+ # Intermediate variables
+ A = np.zeros((np.size(sim, 0), np.size(conmat, 0)))
+ itol = 1
+
+ # Sizes
+ m_lcon = np.size(bvec) if bvec is not None else 0
+ num_constraints = np.size(conmat, 0)
+ num_vars = np.size(sim, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= 0
+ assert num_vars >= 1
+ assert cpen > 0
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and \
+ not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(np.max(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert isinv(sim[:, :num_vars], simi, itol)
+ assert delta >= rho and rho > 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Initialize INFO which is needed in the postconditions
+ info = INFO_DEFAULT
+
+ # Increase CPEN if necessary to ensure PREREM > 0. Branch back for the next loop
+ # if this change alters the optimal vertex of the current simplex.
+ # Note the following:
+ # 1. In each loop, CPEN is changed only if PREREC > 0 > PREREF, in which case
+ # PREREM is guaranteed positive after the update. Note that PREREC >= 0 and
+ # max(PREREC, PREREF) > 0 in theory. If this holds numerically as well then CPEN
+ # is not changed only if PREREC = 0 or PREREF >= 0, in which case PREREM is
+ # currently positive, explaining why CPEN needs no update.
+ # 2. Even without an upper bound for the loop counter, the loop can occur at most
+ # NUM_VARS+1 times. This is because the update of CPEN does not decrease CPEN,
+ # and hence it can make vertex J (J <= NUM_VARS) become the new optimal vertex
+ # only if CVAL[J] is less than CVAL[NUM_VARS], which can happen at most NUM_VARS
+ # times. See the paragraph below (9) in the COBYLA paper. After the "correct"
+ # optimal vertex is found, one more loop is needed to calculate CPEN, and hence
+ # the loop can occur at most NUM_VARS+1 times.
+ for iter in range(num_vars + 1):
+ # Switch the best vertex of the current simplex to SIM[:, NUM_VARS]
+ conmat, cval, fval, sim, simi, info = updatepole(cpen, conmat, cval, fval, sim,
+ simi)
+ # Check whether to exit due to damaging rounding in UPDATEPOLE
+ if info == DAMAGING_ROUNDING:
+ break
+
+ # Calculate the linear approximations to the objective and constraint functions.
+ g = matprod(fval[:num_vars] - fval[num_vars], simi)
+ A[:, :m_lcon] = amat.T if amat is not None else amat
+ A[:, m_lcon:] = matprod((conmat[m_lcon:, :num_vars] -
+ np.tile(conmat[m_lcon:, num_vars], (num_vars, 1)).T), simi).T
+
+ # Calculate the trust-region trial step D. Note that D does NOT depend on CPEN.
+ d = trstlp(A, -conmat[:, num_vars], delta, g)
+
+ # Predict the change to F (PREREF) and to the constraint violation (PREREC) due
+ # to D.
+ preref = -inprod(d, g) # Can be negative
+ prerec = cval[num_vars] - np.max(np.append(0, conmat[:, num_vars] + matprod(d, A)))
+
+ # PREREC <= 0 or PREREF >=0 or either is NaN
+ if not (prerec > 0 and preref < 0):
+ break
+
+ # Powell's code defines BARMU = -PREREF / PREREC, and CPEN is increased to
+ # 2*BARMU if and only if it is currently less than 1.5*BARMU, a very
+ # "Powellful" scheme. In our implementation, however, we set CPEN directly to
+ # the maximum between its current value and 2*BARMU while handling possible
+ # overflow. The simplifies the scheme without worsening the performance of
+ # COBYLA.
+ cpen = max(cpen, min(-2 * preref / prerec, REALMAX))
+
+ if findpole(cpen, cval, fval) == num_vars:
+ break
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert cpen >= cpen and cpen > 0
+ assert preref + cpen * prerec > 0 or info == DAMAGING_ROUNDING or \
+ not (prerec >= 0 and np.maximum(prerec, preref) > 0) or not np.isfinite(preref)
+
+ return cpen
+
+
+def fcratio(conmat, fval):
+ '''
+ This function calculates the ratio between the "typical change" of F and that of CONSTR.
+ See equations (12)-(13) in Section 3 of the COBYLA paper for the definition of the ratio.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert np.size(fval) >= 1
+ assert np.size(conmat, 1) == np.size(fval)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ cmin = np.min(-conmat, axis=1)
+ cmax = np.max(-conmat, axis=1)
+ fmin = min(fval)
+ fmax = max(fval)
+ if any(cmin < 0.5 * cmax) and fmin < fmax:
+ denom = np.min(np.maximum(cmax, 0) - cmin, where=cmin < 0.5 * cmax, initial=np.inf)
+ # Powell mentioned the following alternative in section 4 of his COBYLA paper. According to a test
+ # on 20230610, it does not make much difference to the performance.
+ # denom = np.max(max(*cmax, 0) - cmin, mask=(cmin < 0.5 * cmax))
+ r = (fmax - fmin) / denom
+ else:
+ r = 0
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert r >= 0
+
+ return r
diff --git a/pyprima/src/pyprima/cobyla/geometry.py b/pyprima/src/pyprima/cobyla/geometry.py
new file mode 100644
index 0000000000..c03e562669
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/geometry.py
@@ -0,0 +1,226 @@
+'''
+This module contains subroutines concerning the geometry-improving of the interpolation set.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from ..common.consts import DEBUGGING
+from ..common.linalg import isinv, matprod, inprod, norm, primasum, primapow2
+import numpy as np
+
+
+def setdrop_tr(ximproved, d, delta, rho, sim, simi):
+ '''
+ This function finds (the index) of a current interpolation point to be replaced with
+ the trust-region trial point. See (19)-(22) of the COBYLA paper.
+ N.B.:
+ 1. If XIMPROVED == True, then JDROP > 0 so that D is included into XPT. Otherwise,
+ it is a bug.
+ 2. COBYLA never sets JDROP = NUM_VARS
+ TODO: Check whether it improves the performance if JDROP = NUM_VARS is allowed when
+ XIMPROVED is True. Note that UPDATEXFC should be revised accordingly.
+ '''
+
+ # Local variables
+ itol = 0.1
+
+ # Sizes
+ num_vars = np.size(sim, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_vars >= 1
+ assert np.size(d) == num_vars and all(np.isfinite(d))
+ assert delta >= rho and rho > 0
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(np.max(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert isinv(sim[:, :num_vars], simi, itol)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # -------------------------------------------------------------------------------------------------- #
+ # The following code is Powell's scheme for defining JDROP.
+ # -------------------------------------------------------------------------------------------------- #
+ # ! JDROP = 0 by default. It cannot be removed, as JDROP may not be set below in some cases (e.g.,
+ # ! when XIMPROVED == FALSE, MAXVAL(ABS(SIMID)) <= 1, and MAXVAL(VETA) <= EDGMAX).
+ # jdrop = 0
+ #
+ # ! SIMID(J) is the value of the J-th Lagrange function at D. It is the counterpart of VLAG in UOBYQA
+ # ! and DEN in NEWUOA/BOBYQA/LINCOA, but it excludes the value of the (N+1)-th Lagrange function.
+ # simid = matprod(simi, d)
+ # if (any(abs(simid) > 1) .or. (ximproved .and. any(.not. is_nan(simid)))) then
+ # jdrop = int(maxloc(abs(simid), mask=(.not. is_nan(simid)), dim=1), kind(jdrop))
+ # !!MATLAB: [~, jdrop] = max(simid, [], 'omitnan');
+ # end if
+ #
+ # ! VETA(J) is the distance from the J-th vertex of the simplex to the best vertex, taking the trial
+ # ! point SIM(:, N+1) + D into account.
+ # if (ximproved) then
+ # veta = sqrt(sum((sim(:, 1:n) - spread(d, dim=2, ncopies=n))**2, dim=1))
+ # !!MATLAB: veta = sqrt(sum((sim(:, 1:n) - d).^2)); % d should be a column! Implicit expansion
+ # else
+ # veta = sqrt(sum(sim(:, 1:n)**2, dim=1))
+ # end if
+ #
+ # ! VSIG(J) (J=1, .., N) is the Euclidean distance from vertex J to the opposite face of the simplex.
+ # vsig = ONE / sqrt(sum(simi**2, dim=2))
+ # sigbar = abs(simid) * vsig
+ #
+ # ! The following JDROP will overwrite the previous one if its premise holds.
+ # mask = (veta > factor_delta * delta .and. (sigbar >= factor_alpha * delta .or. sigbar >= vsig))
+ # if (any(mask)) then
+ # jdrop = int(maxloc(veta, mask=mask, dim=1), kind(jdrop))
+ # !!MATLAB: etamax = max(veta(mask)); jdrop = find(mask & ~(veta < etamax), 1, 'first');
+ # end if
+ #
+ # ! Powell's code does not include the following instructions. With Powell's code, if SIMID consists
+ # ! of only NaN, then JDROP can be 0 even when XIMPROVED == TRUE (i.e., D reduces the merit function).
+ # ! With the following code, JDROP cannot be 0 when XIMPROVED == TRUE, unless VETA is all NaN, which
+ # ! should not happen if X0 does not contain NaN, the trust-region/geometry steps never contain NaN,
+ # ! and we exit once encountering an iterate containing Inf (due to overflow).
+ # if (ximproved .and. jdrop <= 0) then ! Write JDROP <= 0 instead of JDROP == 0 for robustness.
+ # jdrop = int(maxloc(veta, mask=(.not. is_nan(veta)), dim=1), kind(jdrop))
+ # !!MATLAB: [~, jdrop] = max(veta, [], 'omitnan');
+ # end if
+ # -------------------------------------------------------------------------------------------------- #
+ # Powell's scheme ends here.
+ # -------------------------------------------------------------------------------------------------- #
+
+ # The following definition of JDROP is inspired by SETDROP_TR in UOBYQA/NEWUOA/BOBYQA/LINCOA.
+ # It is simpler and works better than Powell's scheme. Note that we allow JDROP to be NUM_VARS+1 if
+ # XIMPROVED is True, whereas Powell's code does not.
+ # See also (4.1) of Scheinberg-Toint-2010: Self-Correcting Geometry in Model-Based Algorithms for
+ # Derivative-Free Unconstrained Optimization, which refers to the strategy here as the "combined
+ # distance/poisedness criteria".
+
+ # DISTSQ[j] is the square of the distance from the jth vertex of the simplex to get "best" point so
+ # far, taking the trial point SIM[:, NUM_VARS] + D into account.
+ distsq = np.zeros(np.size(sim, 1))
+ if ximproved:
+ distsq[:num_vars] = primasum(primapow2(sim[:, :num_vars] - np.tile(d, (num_vars, 1)).T), axis=0)
+ distsq[num_vars] = primasum(d*d)
+ else:
+ distsq[:num_vars] = primasum(primapow2(sim[:, :num_vars]), axis=0)
+ distsq[num_vars] = 0
+
+ weight = np.maximum(1, distsq / primapow2(np.maximum(rho, delta/10))) # Similar to Powell's NEWUOA code.
+
+ # Other possible definitions of weight. They work almost the same as the one above.
+ # weight = distsq # Similar to Powell's LINCOA code, but WRONG. See comments in LINCOA/geometry.f90.
+ # weight = max(1, max(25 * distsq / delta**2)) # Similar to Powell's BOBYQA code, works well.
+ # weight = max(1, max(10 * distsq / delta**2))
+ # weight = max(1, max(1e2 * distsq / delta**2))
+ # weight = max(1, max(distsq / rho**2)) ! Similar to Powell's UOBYQA
+
+ # If 0 <= j < NUM_VARS, SIMID[j] is the value of the jth Lagrange function at D; the value of the
+ # (NUM_VARS+1)th Lagrange function is 1 - sum(SIMID). [SIMID, 1 - sum(SIMID)] is the counterpart of
+ # VLAG in UOBYQA and DEN in NEWUOA/BOBYQA/LINCOA.
+ simid = matprod(simi, d)
+ score = weight * abs(np.array([*simid, 1 - primasum(simid)]))
+
+ # If XIMPROVED = False (D does not render a better X), set SCORE[NUM_VARS] = -1 to avoid JDROP = NUM_VARS.
+ if not ximproved:
+ score[num_vars] = -1
+
+ # score[j] is NaN implies SIMID[j] is NaN, but we want abs(SIMID) to be big. So we
+ # exclude such j.
+ score[np.isnan(score)] = -1
+
+ jdrop = None
+ # The following if statement works a bit better than
+ # `if any(score > 1) or (any(score > 0) and ximproved)` from Powell's UOBYQA and
+ # NEWUOA code.
+ if any(score > 0): # Powell's BOBYQA and LINCOA code.
+ jdrop = np.argmax(score)
+
+ if (ximproved and jdrop is None):
+ jdrop = np.argmax(distsq)
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert jdrop is None or (0 <= jdrop < num_vars + 1)
+ assert jdrop <= num_vars or ximproved
+ assert jdrop >= 0 or not ximproved
+ # JDROP >= 1 when XIMPROVED = TRUE unless NaN occurs in DISTSQ, which should not happen if the
+ # starting point does not contain NaN and the trust-region/geometry steps never contain NaN.
+
+ return jdrop
+
+
+
+
+def geostep(jdrop, amat, bvec, conmat, cpen, cval, delbar, fval, simi):
+ '''
+ This function calculates a geometry step so that the geometry of the interpolation set is improved
+ when SIM[: JDROP_GEO] is replaced with SIM[:, NUM_VARS] + D. See (15)--(17) of the COBYLA paper.
+ '''
+
+ # Sizes
+ m_lcon = np.size(bvec, 0) if bvec is not None else 0
+ num_constraints = np.size(conmat, 0)
+ num_vars = np.size(simi, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= m_lcon >= 0
+ assert num_vars >= 1
+ assert delbar > 0
+ assert cpen > 0
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not np.any(np.isnan(conmat) | np.isposinf(conmat))
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert 0 <= jdrop < num_vars
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # SIMI[JDROP, :] is a vector perpendicular to the face of the simplex to the opposite of vertex
+ # JDROP. Set D to the vector in this direction and with length DELBAR.
+ d = simi[jdrop, :]
+ d = delbar * (d / norm(d))
+
+ # The code below chooses the direction of D according to an approximation of the merit function.
+ # See (17) of the COBYLA paper and line 225 of Powell's cobylb.f.
+
+ # Calculate the coefficients of the linear approximations to the objective and constraint functions.
+ # N.B.: CONMAT and SIMI have been updated after the last trust-region step, but G and A have not.
+ # So we cannot pass G and A from outside.
+ g = matprod(fval[:num_vars] - fval[num_vars], simi)
+ A = np.zeros((num_vars, num_constraints))
+ A[:, :m_lcon] = amat.T if amat is not None else amat
+ A[:, m_lcon:] = matprod((conmat[m_lcon:, :num_vars] -
+ np.tile(conmat[m_lcon:, num_vars], (num_vars, 1)).T), simi).T
+ # CVPD and CVND are the predicted constraint violation of D and -D by the linear models.
+ cvpd = np.max(np.append(0, conmat[:, num_vars] + matprod(d, A)))
+ cvnd = np.max(np.append(0, conmat[:, num_vars] - matprod(d, A)))
+ if -inprod(d, g) + cpen * cvnd < inprod(d, g) + cpen * cvpd:
+ d *= -1
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert np.size(d) == num_vars and all(np.isfinite(d))
+ # In theory, ||S|| == DELBAR, which may be false due to rounding, but not too far.
+ # It is crucial to ensure that the geometry step is nonzero, which holds in theory.
+ assert 0.9 * delbar < np.linalg.norm(d) <= 1.1 * delbar
+ return d
diff --git a/pyprima/src/pyprima/cobyla/initialize.py b/pyprima/src/pyprima/cobyla/initialize.py
new file mode 100644
index 0000000000..c8c3da8e2c
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/initialize.py
@@ -0,0 +1,215 @@
+'''
+This module contains subroutines for initialization.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from ..common.checkbreak import checkbreak_con
+from ..common.consts import DEBUGGING, REALMAX
+from ..common.infos import INFO_DEFAULT
+from ..common.evaluate import evaluate
+from ..common.history import savehist
+from ..common.linalg import inv
+from ..common.message import fmsg
+from ..common.selectx import savefilt
+
+import numpy as np
+
+def initxfc(calcfc, iprint, maxfun, constr0, amat, bvec, ctol, f0, ftarget, rhobeg, x0,
+ xhist, fhist, chist, conhist, maxhist):
+ '''
+ This subroutine does the initialization concerning X, function values, and
+ constraints.
+ '''
+
+ # Local variables
+ solver = 'COBYLA'
+ srname = "INITIALIZE"
+
+ # Sizes
+ num_constraints = np.size(constr0)
+ m_lcon = np.size(bvec) if bvec is not None else 0
+ m_nlcon = num_constraints - m_lcon
+ num_vars = np.size(x0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= 0, f'M >= 0 {srname}'
+ assert num_vars >= 1, f'N >= 1 {srname}'
+ assert abs(iprint) <= 3, f'IPRINT is 0, 1, -1, 2, -2, 3, or -3 {srname}'
+ # assert conmat.shape == (num_constraints , num_vars + 1), f'CONMAT.shape = [M, N+1] {srname}'
+ # assert cval.size == num_vars + 1, f'CVAL.size == N+1 {srname}'
+ # assert maxchist * (maxchist - maxhist) == 0, f'CHIST.shape == 0 or MAXHIST {srname}'
+ # assert conhist.shape[0] == num_constraints and maxconhist * (maxconhist - maxhist) == 0, 'CONHIST.shape[0] == num_constraints, SIZE(CONHIST, 2) == 0 or MAXHIST {srname)}'
+ # assert maxfhist * (maxfhist - maxhist) == 0, f'FHIST.shape == 0 or MAXHIST {srname}'
+ # assert xhist.shape[0] == num_vars and maxxhist * (maxxhist - maxhist) == 0, 'XHIST.shape[0] == N, SIZE(XHIST, 2) == 0 or MAXHIST {srname)}'
+ assert all(np.isfinite(x0)), f'X0 is finite {srname}'
+ assert rhobeg > 0, f'RHOBEG > 0 {srname}'
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Initialize info to the default value. At return, a value different from this
+ # value will indicate an abnormal return
+ info = INFO_DEFAULT
+
+ # Initialize the simplex. It will be revised during the initialization.
+ sim = np.eye(num_vars, num_vars+1) * rhobeg
+ sim[:, num_vars] = x0
+
+ # Initialize the matrix simi. In most cases simi is overwritten, but not always.
+ simi = np.eye(num_vars) / rhobeg
+
+ # evaluated[j] = True iff the function/constraint of SIM[:, j] has been evaluated.
+ evaluated = np.zeros(num_vars+1, dtype=bool)
+
+ # Initialize fval
+ fval = np.zeros(num_vars+1) + REALMAX
+ cval = np.zeros(num_vars+1) + REALMAX
+ conmat = np.zeros((num_constraints, num_vars+1)) + REALMAX
+
+
+ for k in range(num_vars + 1):
+ x = sim[:, num_vars].copy()
+ # We will evaluate F corresponding to SIM(:, J).
+ if k == 0:
+ j = num_vars
+ f = f0
+ constr = constr0
+ else:
+ j = k - 1
+ x[j] += rhobeg
+ f, constr = evaluate(calcfc, x, m_nlcon, amat, bvec)
+ cstrv = np.max(np.append(0, constr))
+
+ # Print a message about the function/constraint evaluation according to IPRINT.
+ fmsg(solver, 'Initialization', iprint, k, rhobeg, f, x, cstrv, constr)
+
+ # Save X, F, CONSTR, CSTRV into the history.
+ savehist(maxhist, x, xhist, f, fhist, cstrv, chist, constr, conhist)
+
+ # Save F, CONSTR, and CSTRV to FVAL, CONMAT, and CVAL respectively.
+ evaluated[j] = True
+ fval[j] = f
+ conmat[:, j] = constr
+ cval[j] = cstrv
+
+ # Check whether to exit.
+ subinfo = checkbreak_con(maxfun, k, cstrv, ctol, f, ftarget, x)
+ if subinfo != INFO_DEFAULT:
+ info = subinfo
+ break
+
+ # Exchange the new vertex of the initial simplex with the optimal vertex if necessary.
+ # This is the ONLY part that is essentially non-parallel.
+ if j < num_vars and fval[j] < fval[num_vars]:
+ fval[j], fval[num_vars] = fval[num_vars], fval[j]
+ cval[j], cval[num_vars] = cval[num_vars], cval[j]
+ conmat[:, [j, num_vars]] = conmat[:, [num_vars, j]]
+ sim[:, num_vars] = x
+ sim[j, :j+1] = -rhobeg # SIM[:, :j+1] is lower triangular
+
+ nf = np.count_nonzero(evaluated)
+
+ if evaluated.all():
+ # Initialize SIMI to the inverse of SIM[:, :num_vars]
+ simi = inv(sim[:, :num_vars])
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert nf <= maxfun, f'NF <= MAXFUN {srname}'
+ assert evaluated.size == num_vars + 1, f'EVALUATED.size == Num_vars + 1 {srname}'
+ # assert chist.size == maxchist, f'CHIST.size == MAXCHIST {srname}'
+ # assert conhist.shape== (num_constraints, maxconhist), f'CONHIST.shape == [M, MAXCONHIST] {srname}'
+ assert conmat.shape == (num_constraints, num_vars + 1), f'CONMAT.shape = [M, N+1] {srname}'
+ assert not (np.isnan(conmat).any() or np.isneginf(conmat).any()), f'CONMAT does not contain NaN/-Inf {srname}'
+ assert cval.size == num_vars + 1 and not (any(cval < 0) or any(np.isnan(cval)) or any(np.isposinf(cval))), f'CVAL.shape == Num_vars+1 and CVAL does not contain negative values or NaN/+Inf {srname}'
+ # assert fhist.shape == maxfhist, f'FHIST.shape == MAXFHIST {srname}'
+ # assert maxfhist * (maxfhist - maxhist) == 0, f'FHIST.shape == 0 or MAXHIST {srname}'
+ assert fval.size == num_vars + 1 and not (any(np.isnan(fval)) or any(np.isposinf(fval))), f'FVAL.shape == Num_vars+1 and FVAL is not NaN/+Inf {srname}'
+ # assert xhist.shape == (num_vars, maxxhist), f'XHIST.shape == [N, MAXXHIST] {srname}'
+ assert sim.shape == (num_vars, num_vars + 1), f'SIM.shape == [N, N+1] {srname}'
+ assert np.isfinite(sim).all(), f'SIM is finite {srname}'
+ assert all(np.max(abs(sim[:, :num_vars]), axis=0) > 0), f'SIM(:, 1:N) has no zero column {srname}'
+ assert simi.shape == (num_vars, num_vars), f'SIMI.shape == [N, N] {srname}'
+ assert np.isfinite(simi).all(), f'SIMI is finite {srname}'
+ assert np.allclose(sim[:, :num_vars] @ simi, np.eye(num_vars), rtol=0.1, atol=0.1) or not all(evaluated), f'SIMI = SIM(:, 1:N)^{-1} {srname}'
+
+ return evaluated, conmat, cval, sim, simi, fval, nf, info
+
+
+def initfilt(conmat, ctol, cweight, cval, fval, sim, evaluated, cfilt, confilt, ffilt, xfilt):
+ '''
+ This function initializes the filter (XFILT, etc) that will be used when selecting
+ x at the end of the solver.
+ N.B.:
+ 1. Why not initialize the filters using XHIST, etc? Because the history is empty if
+ the user chooses not to output it.
+ 2. We decouple INITXFC and INITFILT so that it is easier to parallelize the former
+ if needed.
+ '''
+
+ # Sizes
+ num_constraints = conmat.shape[0]
+ num_vars = sim.shape[0]
+ maxfilt = len(ffilt)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= 0
+ assert num_vars >= 1
+ assert maxfilt >= 1
+ assert np.size(confilt, 0) == num_constraints and np.size(confilt, 1) == maxfilt
+ assert np.size(cfilt) == maxfilt
+ assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
+ assert np.size(ffilt) == maxfilt
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(np.max(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(evaluated) == num_vars + 1
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+
+ nfilt = 0
+ for i in range(num_vars+1):
+ if evaluated[i]:
+ if i < num_vars:
+ x = sim[:, i] + sim[:, num_vars]
+ else:
+ x = sim[:, i] # i == num_vars, i.e. the last column
+ nfilt, cfilt, ffilt, xfilt, confilt = savefilt(cval[i], ctol, cweight, fval[i], x, nfilt, cfilt, ffilt, xfilt, conmat[:, i], confilt)
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert nfilt <= maxfilt
+ assert np.size(confilt, 0) == num_constraints and np.size(confilt, 1) == maxfilt
+ assert not (np.isnan(confilt[:, :nfilt]) | np.isneginf(confilt[:, :nfilt])).any()
+ assert np.size(cfilt) == maxfilt
+ assert not any(cfilt[:nfilt] < 0 | np.isnan(cfilt[:nfilt]) | np.isposinf(cfilt[:nfilt]))
+ assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
+ assert not (np.isnan(xfilt[:, :nfilt])).any()
+ # The last calculated X can be Inf (finite + finite can be Inf numerically).
+ assert np.size(ffilt) == maxfilt
+ assert not any(np.isnan(ffilt[:nfilt]) | np.isposinf(ffilt[:nfilt]))
+
+ return nfilt
diff --git a/pyprima/src/pyprima/cobyla/trustregion.py b/pyprima/src/pyprima/cobyla/trustregion.py
new file mode 100644
index 0000000000..3e97d7c0b5
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/trustregion.py
@@ -0,0 +1,492 @@
+'''
+This module provides subroutines concerning the trust-region calculations of COBYLA.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+import numpy.typing as npt
+from ..common.consts import DEBUGGING, REALMIN, REALMAX, EPS
+from ..common.powalg import qradd_Rdiag, qrexc_Rdiag
+from ..common.linalg import isminor, matprod, inprod, lsqr, primasum
+
+
+def trstlp(A, b, delta, g):
+ '''
+ This function calculated an n-component vector d by the following two stages. In the first
+ stage, d is set to the shortest vector that minimizes the greatest violation of the constraints
+ A.T @ D <= B, K = 1, 2, 3, ..., M,
+ subject to the Euclidean length of d being at most delta. If its length is strictly less than
+ delta, then the second stage uses the resultant freedom in d to minimize the objective function
+ G.T @ D
+ subject to no increase in any greatest constraint violation.
+
+ It is possible but rare that a degeneracy may prevent d from attaining the target length delta.
+
+ cviol is the largest constraint violation of the current d: max(max(A.T@D - b), 0)
+ icon is the index of a most violated constraint if cviol is positive.
+
+ nact is the number of constraints in the active set and iact[0], ..., iact[nact-1] are their indices,
+ while the remainder of the iact contains a permutation of the remaining constraint indicies.
+ N.B.: nact <= min(num_constraints, num_vars). Obviously nact <= num_constraints. In addition, the constraints
+ in iact[0, ..., nact-1] have linearly independent gradients (see the comments above the instruction
+ that delete a constraint from the active set to make room for the new active constraint with index iact[icon]);
+ it can also be seen from the update of nact: starting from 0, nact is incremented only if nact < n.
+
+ Further, Z is an orthogonal matrix whose first nact columns can be regarded as the result of
+ Gram-Schmidt applied to the active constraint gradients. For j = 0, 1, ..., nact-1, the number
+ zdota[j] is the scalar product of the jth column of Z with the gradient of the jth active
+ constraint. d is the current vector of variables and here the residuals of the active constraints
+ should be zero. Further, the active constraints have nonnegative Lagrange multipliers that are
+ held at the beginning of vmultc. The remainder of this vector holds the residuals of the inactive
+ constraints at d, the ordering of the components of vmultc being in agreement with the permutation
+ of the indices of the constraints that is in iact. All these residuals are nonnegative, which is
+ achieved by the shift cviol that makes the least residual zero.
+
+ N.B.:
+ 0. In Powell's implementation, the constraints are A.T @ D >= B. In other words, the A and B in
+ our implementation are the negative of those in Powell's implementation.
+ 1. The algorithm was NOT documented in the COBYLA paper. A note should be written to introduce it!
+ 2. As a major part of the algorithm (see trstlp_sub), the code maintains and updates the QR
+ factorization of A[iact[:nact]], i.e. the gradients of all the active (linear) constraints. The
+ matrix Z is indeed Q, and the vector zdota is the diagonal of R. The factorization is updated by
+ Givens rotations when an index is added in or removed from iact.
+ 3. There are probably better algorithms available for the trust-region linear programming problem.
+ '''
+
+ # Sizes
+ num_constraints = A.shape[1]
+ num_vars = A.shape[0]
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_vars >= 1
+ assert num_constraints >= 0
+ assert np.size(g) == num_vars
+ assert np.size(b) == num_constraints
+ assert delta > 0
+
+
+ vmultc = np.zeros(num_constraints + 1)
+ iact = np.zeros(num_constraints + 1, dtype=int)
+ nact = 0
+ d = np.zeros(num_vars)
+ z = np.zeros((num_vars, num_vars))
+
+ # ==================
+ # Calculation starts
+ # ==================
+
+ # Form A_aug and B_aug. This allows the gradient of the objective function to be regarded as the
+ # gradient of a constraint in the second stage.
+ A_aug = np.hstack([A, g.reshape((num_vars, 1))])
+ b_aug = np.hstack([b, 0])
+
+
+ # Scale the problem if A contains large values. Otherwise floating point exceptions may occur.
+ # Note that the trust-region step is scale invariant.
+ for i in range(num_constraints+1): # Note that A_aug.shape[1] == num_constraints+1
+ if (maxval:=max(abs(A_aug[:, i]))) > 1e12:
+ modscal = max(2*REALMIN, 1/maxval)
+ A_aug[:, i] *= modscal
+ b_aug[i] *= modscal
+
+ # Stage 1: minimize the 1+infinity constraint violation of the linearized constraints.
+ iact[:num_constraints], nact, d, vmultc[:num_constraints], z = trstlp_sub(iact[:num_constraints], nact, 1, A_aug[:, :num_constraints], b_aug[:num_constraints], delta, d, vmultc[:num_constraints], z)
+
+ # Stage 2: minimize the linearized objective without increasing the 1_infinity constraint violation.
+ iact, nact, d, vmultc, z = trstlp_sub(iact, nact, 2, A_aug, b_aug, delta, d, vmultc, z)
+
+ # ================
+ # Calculation ends
+ # ================
+
+ # Postconditions
+ if DEBUGGING:
+ assert all(np.isfinite(d))
+ # Due to rounding, it may happen that ||D|| > DELTA, but ||D|| > 2*DELTA is highly improbable.
+ assert np.linalg.norm(d) <= 2 * delta
+
+ return d
+
+def trstlp_sub(iact: npt.NDArray, nact: int, stage, A, b, delta, d, vmultc, z):
+ '''
+ This subroutine does the real calculations for trstlp, both stage 1 and stage 2.
+ Major differences between stage 1 and stage 2:
+ 1. Initialization. Stage 2 inherits the values of some variables from stage 1, so they are
+ initialized in stage 1 but not in stage 2.
+ 2. cviol. cviol is updated after at iteration in stage 1, while it remains a constant in stage2.
+ 3. sdirn. See the definition of sdirn in the code for details.
+ 4. optnew. The two stages have different objectives, so optnew is updated differently.
+ 5. step. step <= cviol in stage 1.
+ '''
+ zdasav = np.zeros(z.shape[1])
+ vmultd = np.zeros(np.size(vmultc))
+ zdota = np.zeros(np.size(z, 1))
+
+ # Sizes
+ mcon = np.size(A, 1)
+ num_vars = np.size(A, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_vars >= 1
+ assert stage == 1 or stage == 2
+ assert (mcon >= 0 and stage == 1) or (mcon >= 1 and stage == 2)
+ assert np.size(b) == mcon
+ assert np.size(iact) == mcon
+ assert np.size(vmultc) == mcon
+ assert np.size(d) == num_vars
+ assert np.size(z, 0) == num_vars and np.size(z, 1) == num_vars
+ assert delta > 0
+ if stage == 2:
+ assert all(np.isfinite(d)) and np.linalg.norm(d) <= 2 * delta
+ assert nact >= 0 and nact <= np.minimum(mcon, num_vars)
+ assert all(vmultc[:mcon]) >= 0
+ # N.B.: Stage 1 defines only VMULTC(1:M); VMULTC(M+1) is undefined!
+
+
+ # Initialize according to stage
+ if stage == 1:
+ iact = np.linspace(0, mcon-1, mcon, dtype=int)
+ nact = 0
+ d = np.zeros(num_vars)
+ cviol = np.max(np.append(0, -b))
+ vmultc = cviol + b
+ z = np.eye(num_vars)
+ if mcon == 0 or cviol <= 0:
+ # Check whether a quick return is possible. Make sure the in-outputs have been initialized.
+ return iact, nact, d, vmultc, z
+
+ if all(np.isnan(b)):
+ return iact, nact, d, vmultc, z
+ else:
+ icon = np.nanargmax(-b)
+ num_constraints = mcon
+ sdirn = np.zeros(len(d))
+ else:
+ if inprod(d, d) >= delta*delta:
+ # Check whether a quick return is possible.
+ return iact, nact, d, vmultc, z
+
+ iact[mcon-1] = mcon-1
+ vmultc[mcon-1] = 0
+ num_constraints = mcon - 1
+ icon = mcon - 1
+
+ # In Powell's code, stage 2 uses the zdota and cviol calculated by stage1. Here we recalculate
+ # them so that they need not be passed from stage 1 to 2, and hence the coupling is reduced.
+ cviol = np.max(np.append(0, matprod(d, A[:, :num_constraints]) - b[:num_constraints]))
+ zdota[:nact] = [inprod(z[:, k], A[:, iact[k]]) for k in range(nact)]
+
+ # More initialization
+ optold = REALMAX
+ nactold = nact
+ nfail = 0
+
+ # Zaikun 20211011: vmultd is computed from scratch at each iteration, but vmultc is inherited
+
+ # Powell's code can encounter infinite cycling, which did happen when testing the following CUTEst
+ # problems: DANWOODLS, GAUSS1LS, GAUSS2LS, GAUSS3LS, KOEBHELB, TAX13322, TAXR13322. Indeed, in all
+ # these cases, Inf/NaN appear in d due to extremely large values in A (up to 10^219). To resolve
+ # this, we set the maximal number of iterations to maxiter, and terminate if Inf/NaN occurs in d.
+ maxiter = np.minimum(10000, 100*max(num_constraints, num_vars))
+ for iter in range(maxiter):
+ if DEBUGGING:
+ assert all(vmultc >= 0)
+ if stage == 1:
+ optnew = cviol
+ else:
+ optnew = inprod(d, A[:, mcon-1])
+
+ # End the current stage of the calculation if 3 consecutive iterations have either failed to
+ # reduce the best calculated value of the objective function or to increase the number of active
+ # constraints since the best value was calculated. This strategy prevents cycling, but there is
+ # a remote possibility that it will cause premature termination.
+ if optnew < optold or nact > nactold:
+ nactold = nact
+ nfail = 0
+ else:
+ nfail += 1
+ optold = np.minimum(optold, optnew)
+ if nfail == 3:
+ break
+
+ # If icon exceeds nact, then we add the constraint with index iact[icon] to the active set.
+ if icon >= nact: # In Python this needs to be >= since Python is 0-indexed (in Fortran we have 1 > 0, in Python we need 0 >= 0)
+ zdasav[:nact] = zdota[:nact]
+ nactsav = nact
+ z, zdota, nact = qradd_Rdiag(A[:, iact[icon]], z, zdota, nact) # May update nact to nact+1
+ # Indeed it suffices to pass zdota[:min(num_vars, nact+1)] to qradd as follows:
+ # qradd(A[:, iact[icon]], z, zdota[:min(num_vars, nact+1)], nact)
+
+ if nact == nactsav + 1:
+ # N.B.: It is possible to index arrays using [nact, icon] when nact == icon.
+ # Zaikun 20211012: Why should vmultc[nact] = 0?
+ if nact != (icon + 1): # Need to add 1 to Python for 0 indexing
+ vmultc[[icon, nact-1]] = vmultc[nact-1], 0
+ iact[[icon, nact-1]] = iact[[nact-1, icon]]
+ else:
+ vmultc[nact-1] = 0
+ else:
+ # Zaikun 20211011:
+ # 1. VMULTD is calculated from scratch for the first time (out of 2) in one iteration.
+ # 2. Note that IACT has not been updated to replace IACT[NACT] with IACT[ICON]. Thus
+ # A[:, IACT[:NACT]] is the UNUPDATED version before QRADD (note Z[:, :NACT] remains the
+ # same before and after QRADD). Therefore if we supply ZDOTA to LSQR (as Rdiag) as
+ # Powell did, we should use the UNUPDATED version, namely ZDASAV.
+ # vmultd[:nact] = lsqr(A[:, iact[:nact]], A[:, iact[icon]], z[:, :nact], zdasav[:nact])
+ vmultd[:nact] = lsqr(A[:, iact[:nact]], A[:, iact[icon]], z[:, :nact], zdasav[:nact])
+ if not any(np.logical_and(vmultd[:nact] > 0, iact[:nact] <= num_constraints)):
+ # N.B.: This can be triggered by NACT == 0 (among other possibilities)! This is
+ # important, because NACT will be used as an index in the sequel.
+ break
+ # vmultd[NACT+1:mcon] is not used, but we have to initialize it in Fortran, or compilers
+ # complain about the where construct below (another solution: restrict where to 1:NACT).
+ vmultd[nact:mcon] = -1 # len(vmultd) == mcon
+
+ # Revise the Lagrange multipliers. The revision is not applicable to vmultc[nact:num_constraints].
+ fracmult = [vmultc[i]/vmultd[i] if vmultd[i] > 0 and iact[i] <= num_constraints else REALMAX for i in range(nact)]
+ # Only the places with vmultd > 0 and iact <= m is relevant below, if any.
+ frac = min(fracmult[:nact]) # fracmult[nact:mcon] may contain garbage
+ vmultc[:nact] = np.maximum(np.zeros(len(vmultc[:nact])), vmultc[:nact] - frac*vmultd[:nact])
+
+ # Reorder the active constraints so that the one to be replaced is at the end of the list.
+ # Exit if the new value of zdota[nact] is not acceptable. Powell's condition for the
+ # following If: not abs(zdota[nact]) > 0. Note that it is different from
+ # 'abs(zdota[nact]) <=0)' as zdota[nact] can be NaN.
+ # N.B.: We cannot arrive here with nact == 0, which should have triggered a break above
+ if np.isnan(zdota[nact - 1]) or abs(zdota[nact - 1]) <= EPS**2:
+ break
+ vmultc[[icon, nact - 1]] = 0, frac # vmultc[[icon, nact]] is valid as icon > nact
+ iact[[icon, nact - 1]] = iact[[nact - 1, icon]]
+ # end if nact == nactsav + 1
+
+ # In stage 2, ensure that the objective continues to be treated as the last active constraint.
+ # Zaikun 20211011, 20211111: Is it guaranteed for stage 2 that iact[nact-1] = mcon when
+ # iact[nact] != mcon??? If not, then how does the following procedure ensure that mcon is
+ # the last of iact[:nact]?
+ if stage == 2 and iact[nact - 1] != (mcon - 1):
+ if nact <= 1:
+ # We must exit, as nact-2 is used as an index below. Powell's code does not have this.
+ break
+ z, zdota[:nact] = qrexc_Rdiag(A[:, iact[:nact]], z, zdota[:nact], nact - 2) # We pass nact-2 in Python instead of nact-1
+ # Indeed, it suffices to pass Z[:, :nact] to qrexc as follows:
+ # z[:, :nact], zdota[:nact] = qrexc(A[:, iact[:nact]], z[:, :nact], zdota[:nact], nact - 1)
+ iact[[nact-2, nact-1]] = iact[[nact-1, nact-2]]
+ vmultc[[nact-2, nact-1]] = vmultc[[nact-1, nact-2]]
+ # Zaikun 20211117: It turns out that the last few lines do not guarantee iact[nact] == num_vars in
+ # stage 2; the following test cannot be passed. IS THIS A BUG?!
+ # assert iact[nact] == mcon or stage == 1, 'iact[nact] must == mcon in stage 2'
+
+ # Powell's code does not have the following. It avoids subsequent floating points exceptions.
+ if np.isnan(zdota[nact-1]) or abs(zdota[nact-1]) <= EPS**2:
+ break
+
+ # Set sdirn to the direction of the next change to the current vector of variables
+ # Usually during stage 1 the vector sdirn gives a search direction that reduces all the
+ # active constraint violations by one simultaneously.
+ if stage == 1:
+ sdirn -= ((inprod(sdirn, A[:, iact[nact-1]]) + 1)/zdota[nact-1])*z[:, nact-1]
+ else:
+ sdirn = -1/zdota[nact-1]*z[:, nact-1]
+ else: # icon < nact
+ # Delete the constraint with the index iact[icon] from the active set, which is done by
+ # reordering iact[icon:nact] into [iact[icon+1:nact], iact[icon]] and then reduce nact to
+ # nact - 1. In theory, icon > 0.
+ # assert icon > 0, "icon > 0 is required" # For Python I think this is irrelevant
+ z, zdota[:nact] = qrexc_Rdiag(A[:, iact[:nact]], z, zdota[:nact], icon) # qrexc does nothing if icon == nact
+ # Indeed, it suffices to pass Z[:, :nact] to qrexc as follows:
+ # z[:, :nact], zdota[:nact] = qrexc(A[:, iact[:nact]], z[:, :nact], zdota[:nact], icon)
+ iact[icon:nact] = [*iact[icon+1:nact], iact[icon]]
+ vmultc[icon:nact] = [*vmultc[icon+1:nact], vmultc[icon]]
+ nact -= 1
+
+ # Powell's code does not have the following. It avoids subsequent exceptions.
+ # Zaikun 20221212: In theory, nact > 0 in stage 2, as the objective function should always
+ # be considered as an "active constraint" --- more precisely, iact[nact] = mcon. However,
+ # looking at the code, I cannot see why in stage 2 nact must be positive after the reduction
+ # above. It did happen in stage 1 that nact became 0 after the reduction --- this is
+ # extremely rare, and it was never observed until 20221212, after almost one year of
+ # random tests. Maybe nact is theoretically positive even in stage 1?
+ if stage == 2 and nact < 0:
+ break # If this case ever occurs, we have to break, as nact is used as an index below.
+ if nact > 0:
+ if np.isnan(zdota[nact-1]) or abs(zdota[nact-1]) <= EPS**2:
+ break
+
+ # Set sdirn to the direction of the next change to the current vector of variables.
+ if stage == 1:
+ sdirn -= inprod(sdirn, z[:, nact]) * z[:, nact]
+ # sdirn is orthogonal to z[:, nact+1]
+ else:
+ sdirn = -1/zdota[nact-1] * z[:, nact-1]
+ # end if icon > nact
+
+ # Calculate the step to the trust region boundary or take the step that reduces cviol to 0.
+ # ----------------------------------------------------------------------------------------- #
+ # The following calculation of step is adopted from NEWUOA/BOBYQA/LINCOA. It seems to improve
+ # the performance of COBYLA. We also found that removing the precaution about underflows is
+ # beneficial to the overall performance of COBYLA --- the underflows are harmless anyway.
+ dd = delta*delta - inprod(d, d)
+ ss = inprod(sdirn, sdirn)
+ sd = inprod(sdirn, d)
+ if dd <= 0 or ss <= EPS * delta*delta or np.isnan(sd):
+ break
+ # sqrtd: square root of a discriminant. The max avoids sqrtd < abs(sd) due to underflow
+ sqrtd = max(np.sqrt(ss*dd + sd*sd), abs(sd), np.sqrt(ss * dd))
+ if sd > 0:
+ step = dd / (sqrtd + sd)
+ else:
+ step = (sqrtd - sd) / ss
+ # step < 0 should not happen. Step can be 0 or NaN when, e.g., sd or ss becomes inf
+ if step <= 0 or not np.isfinite(step):
+ break
+
+ # Powell's approach and comments are as follows.
+ # -------------------------------------------------- #
+ # The two statements below that include the factor eps prevent
+ # some harmless underflows that occurred in a test calculation
+ # (Zaikun: here, eps is the machine epsilon; Powell's original
+ # code used 1.0e-6, and Powell's code was written in single
+ # precision). Further, we skip the step if it could be 0 within
+ # a reasonable tolerance for computer rounding errors.
+
+ # !dd = delta*delta - sum(d**2, mask=(abs(d) >= EPS * delta))
+ # !ss = inprod(sdirn, sdirn)
+ # !if (dd <= 0) then
+ # ! exit
+ # !end if
+ # !sd = inprod(sdirn, d)
+ # !if (abs(sd) >= EPS * sqrt(ss * dd)) then
+ # ! step = dd / (sqrt(ss * dd + sd*sd) + sd)
+ # !else
+ # ! step = dd / (sqrt(ss * dd) + sd)
+ # !end if
+ # -------------------------------------------------- #
+
+ if stage == 1:
+ if isminor(cviol, step):
+ break
+ step = min(step, cviol)
+
+ # Set dnew to the new variables if step is the steplength, and reduce cviol to the corresponding
+ # maximum residual if stage 1 is being done
+ dnew = d + step * sdirn
+ if stage == 1:
+ cviol = np.max(np.append(0, matprod(dnew, A[:, iact[:nact]]) - b[iact[:nact]]))
+ # N.B.: cviol will be used when calculating vmultd[nact+1:mcon].
+
+ # Zaikun 20211011:
+ # 1. vmultd is computed from scratch for the second (out of 2) time in one iteration.
+ # 2. vmultd[:nact] and vmultd[nact:mcon] are calculated separately with no coupling.
+ # 3. vmultd will be calculated from scratch again in the next iteration.
+ # Set vmultd to the vmultc vector that would occur if d became dnew. A device is included to
+ # force vmultd[k] = 0 if deviations from this value can be attributed to computer rounding
+ # errors. First calculate the new Lagrange multipliers.
+ vmultd[:nact] = -lsqr(A[:, iact[:nact]], dnew, z[:, :nact], zdota[:nact])
+ if stage == 2:
+ vmultd[nact-1] = max(0, vmultd[nact-1]) # This seems never activated.
+ # Complete vmultd by finding the new constraint residuals. (Powell wrote "Complete vmultc ...")
+ cvshift = cviol - (matprod(dnew, A[:, iact]) - b[iact]) # Only cvshift[nact+1:mcon] is needed
+ cvsabs = matprod(abs(dnew), abs(A[:, iact])) + abs(b[iact]) + cviol
+ cvshift[isminor(cvshift, cvsabs)] = 0
+ vmultd[nact:mcon] = cvshift[nact:mcon]
+
+ # Calculate the fraction of the step from d to dnew that will be taken
+ fracmult = [vmultc[i]/(vmultc[i] - vmultd[i]) if vmultd[i] < 0 else REALMAX for i in range(len(vmultd))]
+ # Only the places with vmultd < 0 are relevant below, if any.
+ icon = np.argmin(np.append(1, fracmult)) - 1
+ frac = min(np.append(1, fracmult))
+
+ # Update d, vmultc, and cviol
+ dold = d
+ d = (1 - frac)*d + frac * dnew
+ vmultc = np.maximum(0, (1 - frac)*vmultc + frac*vmultd)
+ # Break in the case of inf/nan in d or vmultc.
+ if not (np.isfinite(primasum(abs(d))) and np.isfinite(primasum(abs(vmultc)))):
+ d = dold # Should we restore also iact, nact, vmultc, and z?
+ break
+
+ if stage == 1:
+ # cviol = (1 - frac) * cvold + frac * cviol # Powell's version
+ # In theory, cviol = np.max(np.append(d@A - b, 0)), yet the
+ # cviol updated as above can be quite different from this value if A has huge entries (e.g., > 1e20)
+ cviol = np.max(np.append(0, matprod(d, A) - b))
+
+ if icon < 0 or icon >= mcon:
+ # In Powell's code, the condition is icon == 0. Indeed, icon < 0 cannot hold unless
+ # fracmult contains only nan, which should not happen; icon >= mcon should never occur.
+ break
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert np.size(iact) == mcon
+ assert np.size(vmultc) == mcon
+ assert all(vmultc >= 0)
+ assert np.size(d) == num_vars
+ assert all(np.isfinite(d))
+ assert np.linalg.norm(d) <= 2 * delta
+ assert np.size(z, 0) == num_vars and np.size(z, 1) == num_vars
+ assert nact >= 0 and nact <= np.minimum(mcon, num_vars)
+
+ return iact, nact, d, vmultc, z
+
+
+def trrad(delta_in, dnorm, eta1, eta2, gamma1, gamma2, ratio):
+ '''
+ This function updates the trust region radius according to RATIO and DNORM.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert delta_in >= dnorm > 0
+ assert 0 <= eta1 <= eta2 < 1
+ assert 0 < gamma1 < 1 < gamma2
+ # By the definition of RATIO in ratio.f90, RATIO cannot be NaN unless the
+ # actual reduction is NaN, which should NOT happen due to the moderated extreme
+ # barrier.
+ assert not np.isnan(ratio)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if ratio <= eta1:
+ delta = gamma1 * dnorm # Powell's UOBYQA/NEWUOA
+ # delta = gamma1 * delta_in # Powell's COBYLA/LINCOA
+ # delta = min(gamma1 * delta_in, dnorm) # Powell's BOBYQA
+ elif ratio <= eta2:
+ delta = max(gamma1 * delta_in, dnorm) # Powell's UOBYQA/NEWUOA/BOBYQA/LINCOA
+ else:
+ delta = max(gamma1 * delta_in, gamma2 * dnorm) # Powell's NEWUOA/BOBYQA
+ # delta = max(delta_in, gamma2 * dnorm) # Modified version. Works well for UOBYQA
+ # For noise-free CUTEst problems of <= 100 variables, Powell's version works slightly better
+ # than the modified one.
+ # delta = max(delta_in, 1.25*dnorm, dnorm + rho) # Powell's UOBYQA
+ # delta = min(max(gamma1 * delta_in, gamma2 * dnorm), gamma3 * delta_in) # Powell's LINCOA, gamma3 = np.sqrt(2)
+
+ # For noisy problems, the following may work better.
+ # if ratio <= eta1:
+ # delta = gamma1 * dnorm
+ # elseif ratio <= eta2: # Ensure DELTA >= DELTA_IN
+ # delta = delta_in
+ # else: # Ensure DELTA > DELTA_IN with a constant factor
+ # delta = max(delta_in * (1 + gamma2) / 2, gamma2 * dnorm)
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert delta > 0
+ return delta
diff --git a/pyprima/src/pyprima/cobyla/update.py b/pyprima/src/pyprima/cobyla/update.py
new file mode 100644
index 0000000000..66a3fa3623
--- /dev/null
+++ b/pyprima/src/pyprima/cobyla/update.py
@@ -0,0 +1,289 @@
+'''
+This module contains subroutines concerning the update of the interpolation set.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from ..common.consts import DEBUGGING
+from ..common.infos import DAMAGING_ROUNDING, INFO_DEFAULT
+from ..common.linalg import isinv, matprod, outprod, inprod, inv, primasum
+import numpy as np
+
+
+def updatexfc(jdrop, constr, cpen, cstrv, d, f, conmat, cval, fval, sim, simi):
+ '''
+ This function revises the simplex by updating the elements of SIM, SIMI, FVAL, CONMAT, and CVAL
+ '''
+
+ # Local variables
+ itol = 1
+
+ # Sizes
+ num_constraints = np.size(constr)
+ num_vars = np.size(sim, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= 0
+ assert num_vars >= 1
+ assert jdrop >= 0 and jdrop <= num_vars + 1
+ assert not any(np.isnan(constr) | np.isneginf(constr))
+ assert not (np.isnan(cstrv) | np.isposinf(cstrv))
+ assert np.size(d) == num_vars and all(np.isfinite(d))
+ assert not (np.isnan(f) | np.isposinf(f))
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(primasum(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert isinv(sim[:, :num_vars], simi, itol)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+
+ # Do nothing when JDROP is None. This can only happen after a trust-region step.
+ if jdrop is None: # JDROP is None is impossible if the input is correct.
+ return conmat, cval, fval, sim, simi, INFO_DEFAULT
+
+ sim_old = sim
+ simi_old = simi
+ if jdrop < num_vars:
+ sim[:, jdrop] = d
+ simi_jdrop = simi[jdrop, :] / inprod(simi[jdrop, :], d)
+ simi -= outprod(matprod(simi, d), simi_jdrop)
+ simi[jdrop, :] = simi_jdrop
+ else: # jdrop == num_vars
+ sim[:, num_vars] += d
+ sim[:, :num_vars] -= np.tile(d, (num_vars, 1)).T
+ simid = matprod(simi, d)
+ sum_simi = primasum(simi, axis=0)
+ simi += outprod(simid, sum_simi / (1 - sum(simid)))
+
+ # Check whether SIMI is a poor approximation to the inverse of SIM[:, :NUM_VARS]
+ # Calculate SIMI from scratch if the current one is damaged by rounding errors.
+ itol = 1
+ erri = np.max(abs(matprod(simi, sim[:, :num_vars]) - np.eye(num_vars))) # np.max returns NaN if any input is NaN
+ if erri > 0.1 * itol or np.isnan(erri):
+ simi_test = inv(sim[:, :num_vars])
+ erri_test = np.max(abs(matprod(simi_test, sim[:, :num_vars]) - np.eye(num_vars)))
+ if erri_test < erri or (np.isnan(erri) and not np.isnan(erri_test)):
+ simi = simi_test
+ erri = erri_test
+
+ # If SIMI is satisfactory, then update FVAL, CONMAT, CVAL, and the pole position. Otherwise restore
+ # SIM and SIMI, and return with INFO = DAMAGING_ROUNDING.
+ if erri <= itol:
+ fval[jdrop] = f
+ conmat[:, jdrop] = constr
+ cval[jdrop] = cstrv
+ # Switch the best vertex to the pole position SIM[:, NUM_VARS] if it is not there already
+ conmat, cval, fval, sim, simi, info = updatepole(cpen, conmat, cval, fval, sim, simi)
+ else:
+ info = DAMAGING_ROUNDING
+ sim = sim_old
+ simi = simi_old
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(primasum(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert isinv(sim[:, :num_vars], simi, itol) or info == DAMAGING_ROUNDING
+
+ return sim, simi, fval, conmat, cval, info
+
+def findpole(cpen, cval, fval):
+ '''
+ This subroutine identifies the best vertex of the current simplex with respect to the merit
+ function PHI = F + CPEN * CSTRV.
+ '''
+
+ # Size
+ num_vars = np.size(fval) - 1
+
+ # Preconditions
+ if DEBUGGING:
+ assert cpen > 0
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Identify the optimal vertex of the current simplex
+ jopt = np.size(fval) - 1
+ phi = fval + cpen * cval
+ phimin = min(phi)
+ # Essentially jopt = np.argmin(phi). However, we keep jopt = num_vars unless there
+ # is a strictly better choice. When there are multiple choices, we choose the jopt
+ # with the smallest value of cval.
+ if phimin < phi[jopt] or any((cval < cval[jopt]) & (phi <= phi[jopt])):
+ # While we could use argmin(phi), there may be two places where phi achieves
+ # phimin, and in that case we should choose the one with the smallest cval.
+ jopt = np.ma.array(cval, mask=(phi > phimin)).argmin()
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert jopt >= 0 and jopt < num_vars + 1
+ assert jopt == num_vars or phi[jopt] < phi[num_vars] or (phi[jopt] <= phi[num_vars] and cval[jopt] < cval[num_vars])
+ return jopt
+
+
+def updatepole(cpen, conmat, cval, fval, sim, simi):
+ #--------------------------------------------------------------------------------------------------!
+ # This subroutine identifies the best vertex of the current simplex with respect to the merit
+ # function PHI = F + CPEN * CSTRV, and then switch this vertex to SIM[:, NUM_VARS], which Powell called
+ # the "pole position" in his comments. CONMAT, CVAL, FVAL, and SIMI are updated accordingly.
+ #
+ # N.B. 1: In precise arithmetic, the following two procedures produce the same results:
+ # 1) apply UPDATEPOLE to SIM twice, first with CPEN = CPEN1 and then with CPEN = CPEN2;
+ # 2) apply UPDATEPOLE to SIM with CPEN = CPEN2.
+ # In finite-precision arithmetic, however, they may produce different results unless CPEN1 = CPEN2.
+ #
+ # N.B. 2: When JOPT == N+1, the best vertex is already at the pole position, so there is nothing to
+ # switch. However, as in Powell's code, the code below will check whether SIMI is good enough to
+ # work as the inverse of SIM(:, 1:N) or not. If not, Powell's code would invoke an error return of
+ # COBYLB; our implementation, however, will try calculating SIMI from scratch; if the recalculated
+ # SIMI is still of poor quality, then UPDATEPOLE will return with INFO = DAMAGING_ROUNDING,
+ # informing COBYLB that SIMI is poor due to damaging rounding errors.
+ #
+ # N.B. 3: UPDATEPOLE should be called when and only when FINDPOLE can potentially returns a value
+ # other than N+1. The value of FINDPOLE is determined by CPEN, CVAL, and FVAL, the latter two being
+ # decided by SIM. Thus UPDATEPOLE should be called after CPEN or SIM changes. COBYLA updates CPEN at
+ # only two places: the beginning of each trust-region iteration, and when REDRHO is called;
+ # SIM is updated only by UPDATEXFC, which itself calls UPDATEPOLE internally. Therefore, we only
+ # need to call UPDATEPOLE after updating CPEN at the beginning of each trust-region iteration and
+ # after each invocation of REDRHO.
+
+ # Local variables
+ itol = 1
+
+ # Sizes
+ num_constraints = conmat.shape[0]
+ num_vars = sim.shape[0]
+
+ # Preconditions
+ if DEBUGGING:
+ assert num_constraints >= 0
+ assert num_vars >= 1
+ assert cpen > 0
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(primasum(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ assert isinv(sim[:, :num_vars], simi, itol)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # INFO must be set, as it is an output.
+ info = INFO_DEFAULT
+
+ # Identify the optimal vertex of the current simplex.
+ jopt = findpole(cpen, cval, fval)
+
+ # Switch the best vertex to the pole position SIM[:, NUM_VARS] if it is not there already and update
+ # SIMI. Before the update, save a copy of SIM and SIMI. If the update is unsuccessful due to
+ # damaging rounding errors, we restore them and return with INFO = DAMAGING_ROUNDING.
+ sim_old = sim.copy()
+ simi_old = simi.copy()
+ if 0 <= jopt < num_vars:
+ # Unless there is a bug in FINDPOLE it is guaranteed that JOPT >= 0
+ # When JOPT == NUM_VARS, there is nothing to switch; in addition SIMI[JOPT, :] will be illegal.
+ # fval[[jopt, -1]] = fval[[-1, jopt]]
+ # conmat[:, [jopt, -1]] = conmat[:, [-1, jopt]] # Exchange CONMAT[:, JOPT] AND CONMAT[:, -1]
+ # cval[[jopt, -1]] = cval[[-1, jopt]]
+ sim[:, num_vars] += sim[:, jopt]
+ sim_jopt = sim[:, jopt].copy()
+ sim[:, jopt] = 0 # np.zeros(num_constraints)?
+ sim[:, :num_vars] -= np.tile(sim_jopt, (num_vars, 1)).T
+ # The above update is equivalent to multiplying SIM[:, :NUM_VARS] from the right side by a matrix whose
+ # JOPT-th row is [-1, -1, ..., -1], while all the other rows are the same as those of the
+ # identity matrix. It is easy to check that the inverse of this matrix is itself. Therefore,
+ # SIMI should be updated by a multiplication with this matrix (i.e. its inverse) from the left
+ # side, as is done in the following line. The JOPT-th row of the updated SIMI is minus the sum
+ # of all rows of the original SIMI, whereas all the other rows remain unchanged.
+ # NDB 20250114: In testing the cutest problem 'SYNTHES2' between the Python implementation and
+ # the Fortran bindings, I saw a difference between the following for loop and the
+ # np.sum command. The differences were small, on the order of 1e-16, i.e. epsilon.
+ # According to numpy documentation, np.sum sometimes uses partial pairwise summation,
+ # depending on the memory layout of the array and the axis specified.
+ # for i in range(simi.shape[1]):
+ # simi[jopt, i] = -sum(simi[:, i])
+ simi[jopt, :] = -primasum(simi, axis=0)
+
+ # Check whether SIMI is a poor approximation to the inverse of SIM[:, :NUM_VARS]
+ # Calculate SIMI from scratch if the current one is damaged by rounding errors.
+ erri = np.max(abs(matprod(simi, sim[:, :num_vars]) - np.eye(num_vars))) # np.max returns NaN if any input is NaN
+ itol = 1
+ if erri > 0.1 * itol or np.isnan(erri):
+ simi_test = inv(sim[:, :num_vars])
+ erri_test = np.max(abs(matprod(simi_test, sim[:, :num_vars]) - np.eye(num_vars)))
+ if erri_test < erri or (np.isnan(erri) and not np.isnan(erri_test)):
+ simi = simi_test
+ erri = erri_test
+
+
+ # If SIMI is satisfactory, then update FVAL, CONMAT, and CVAL. Otherwise restore SIM and SIMI, and
+ # return with INFO = DAMAGING_ROUNDING.
+ if erri <= itol:
+ if 0 <= jopt < num_vars:
+ fval[[jopt, num_vars]] = fval[[num_vars, jopt]]
+ conmat[:, [jopt, num_vars]] = conmat[:, [num_vars, jopt]]
+ cval[[jopt, num_vars]] = cval[[num_vars, jopt]]
+ else: # erri > itol or erri is NaN
+ info = DAMAGING_ROUNDING
+ sim = sim_old
+ simi = simi_old
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert findpole(cpen, cval, fval) == num_vars or info == DAMAGING_ROUNDING
+ assert np.size(conmat, 0) == num_constraints and np.size(conmat, 1) == num_vars + 1
+ assert not (np.isnan(conmat) | np.isneginf(conmat)).any()
+ assert np.size(cval) == num_vars + 1 and not any(cval < 0 | np.isnan(cval) | np.isposinf(cval))
+ assert np.size(fval) == num_vars + 1 and not any(np.isnan(fval) | np.isposinf(fval))
+ assert np.size(sim, 0) == num_vars and np.size(sim, 1) == num_vars + 1
+ assert np.isfinite(sim).all()
+ assert all(primasum(abs(sim[:, :num_vars]), axis=0) > 0)
+ assert np.size(simi, 0) == num_vars and np.size(simi, 1) == num_vars
+ assert np.isfinite(simi).all()
+ # Do not check SIMI = SIM[:, :num_vars]^{-1}, as it may not be true due to damaging rounding.
+ assert isinv(sim[:, :num_vars], simi, itol) or info == DAMAGING_ROUNDING
+
+ return conmat, cval, fval, sim, simi, info
diff --git a/pyprima/src/pyprima/common/__init__.py b/pyprima/src/pyprima/common/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pyprima/src/pyprima/common/_bounds.py b/pyprima/src/pyprima/common/_bounds.py
new file mode 100644
index 0000000000..c5a81718ce
--- /dev/null
+++ b/pyprima/src/pyprima/common/_bounds.py
@@ -0,0 +1,39 @@
+import numpy as np
+from scipy.optimize import Bounds
+
+def process_bounds(bounds, lenx0):
+ '''
+ `bounds` can either be an object with the properties lb and ub, or a list of tuples
+ indicating a lower bound and an upper bound for each variable. If the list contains
+ fewer entries than the length of x0, the remaining entries will generated as -/+ infinity.
+ Some examples of valid lists of tuple, assuming len(x0) == 3:
+ [(0, 1), (2, 3), (4, 5)] -> returns [0, 2, 4], [1, 3, 5]
+ [(0, 1), (None, 3)] -> returns [0, -inf, -inf], [1, 3, inf]
+ [(0, 1), (-np.inf, 3)] -> returns [0, -inf, -inf], [1, 3, inf]
+ '''
+
+ if bounds is None:
+ lb = np.array([-np.inf]*lenx0, dtype=np.float64)
+ ub = np.array([np.inf]*lenx0, dtype=np.float64)
+ return lb, ub
+
+ if isinstance(bounds, Bounds):
+ lb = np.array(bounds.lb, dtype=np.float64)
+ ub = np.array(bounds.ub, dtype=np.float64)
+ lb = np.concatenate((lb, -np.inf*np.ones(lenx0 - len(lb))))
+ ub = np.concatenate((ub, np.inf*np.ones(lenx0 - len(ub))))
+ return lb, ub
+
+ # If neither of the above conditions are true, we assume that bounds is a list of tuples
+ lb = np.array([bound[0] if bound[0] is not None else -np.inf for bound in bounds], dtype=np.float64)
+ ub = np.array([bound[1] if bound[1] is not None else np.inf for bound in bounds], dtype=np.float64)
+ # If there were fewer bounds than variables, pad the rest with -/+ infinity
+ lb = np.concatenate((lb, -np.inf*np.ones(lenx0 - len(lb))))
+ ub = np.concatenate((ub, np.inf*np.ones(lenx0 - len(ub))))
+
+ # Check the infeasibility of the bounds.
+ # TODO: Return a code instead of asserting because a feasibility check is a valid solution
+ infeasible = np.logical_not(lb <= ub)
+ assert not np.any(infeasible), f"Some of the provided bounds are infeasible. {infeasible=} {lb[infeasible]=}, {ub[infeasible]=}"
+
+ return lb, ub
diff --git a/pyprima/src/pyprima/common/_linear_constraints.py b/pyprima/src/pyprima/common/_linear_constraints.py
new file mode 100644
index 0000000000..be508dced7
--- /dev/null
+++ b/pyprima/src/pyprima/common/_linear_constraints.py
@@ -0,0 +1,46 @@
+import numpy as np
+from scipy.optimize import LinearConstraint
+
+
+def combine_multiple_linear_constraints(constraints):
+ full_A = constraints[0].A
+ full_lb = constraints[0].lb
+ full_ub = constraints[0].ub
+ for constraint in constraints[1:]:
+ full_A = np.concatenate((full_A, constraint.A), axis=0)
+ full_lb = np.concatenate((full_lb, constraint.lb), axis=0)
+ full_ub = np.concatenate((full_ub, constraint.ub), axis=0)
+ return LinearConstraint(full_A, full_lb, full_ub)
+
+
+def separate_LC_into_eq_and_ineq(linear_constraint):
+ # The Python interface receives linear constraints lb <= A*x <= ub, but the
+ # Fortran backend of PRIMA expects that the linear constraints are specified
+ # as A_eq*x = b_eq, A_ineq*x <= b_ineq.
+ # As such, we must:
+ # 1. for constraints with lb == ub, rewrite them as A_eq*x = lb;
+ # 2. for constraints with lb < ub, rewrite them as A_ineq*x <= b_ineq.
+
+ # We suppose lb == ub if ub <= lb + 2*epsilon, assuming that the preprocessing
+ # ensures lb <= ub.
+ epsilon = np.finfo(np.float64).eps
+
+ eq_indices = (linear_constraint.ub <= (linear_constraint.lb + 2*epsilon))
+ A_eq = linear_constraint.A[eq_indices]
+ b_eq = (linear_constraint.lb[eq_indices] + linear_constraint.ub[eq_indices])/2.0
+
+ ineq_lb_indices = (linear_constraint.lb > -np.inf)
+ A_ineq_lb = -linear_constraint.A[~eq_indices & ineq_lb_indices]
+ b_ineq_lb = -linear_constraint.lb[~eq_indices & ineq_lb_indices]
+ ineq_ub_indices = (linear_constraint.ub < np.inf)
+ A_ineq_ub = linear_constraint.A[~eq_indices & ineq_ub_indices]
+ b_ineq_ub = linear_constraint.ub[~eq_indices & ineq_ub_indices]
+ A_ineq = np.concatenate((A_ineq_lb, A_ineq_ub))
+ b_ineq = np.concatenate((b_ineq_lb, b_ineq_ub))
+
+ # Ensure dtype is float64, or set to None if empty
+ A_eq = np.array(A_eq, dtype=np.float64) if len(A_eq) > 0 else None
+ b_eq = np.array(b_eq, dtype=np.float64) if len(b_eq) > 0 else None
+ A_ineq = np.array(A_ineq, dtype=np.float64) if len(A_ineq) > 0 else None
+ b_ineq = np.array(b_ineq, dtype=np.float64) if len(b_ineq) > 0 else None
+ return A_eq, b_eq, A_ineq, b_ineq
diff --git a/pyprima/src/pyprima/common/_nonlinear_constraints.py b/pyprima/src/pyprima/common/_nonlinear_constraints.py
new file mode 100644
index 0000000000..bfee3b7f2c
--- /dev/null
+++ b/pyprima/src/pyprima/common/_nonlinear_constraints.py
@@ -0,0 +1,54 @@
+import numpy as np
+
+def transform_constraint_function(nlc):
+ '''
+ The Python interfaces receives the constraints as lb <= constraint(x) <= ub,
+ but the Fortran backend expects the nonlinear constraints to be constraint(x) <= 0.
+ Thus a conversion is needed.
+
+ In addition to the conversion, we add a check to ensure that the provided lower/upper bounds
+ have a shape consistent with the output of the constraint function.
+ '''
+
+ def newconstraint(x):
+ values = np.atleast_1d(np.array(nlc.fun(x), dtype=np.float64))
+
+ # Upgrade the lower/upper bounds to vectors if necessary
+ lb = nlc.lb
+ try:
+ _ = len(lb)
+ except TypeError:
+ lb = np.array([nlc.lb]*len(values), dtype=np.float64)
+
+ ub = nlc.ub
+ try:
+ _ = len(ub)
+ except TypeError:
+ ub = np.array([nlc.ub]*len(values), dtype=np.float64)
+
+
+ # Check the shapes and raise an exception if they do not match
+ if len(values) != len(lb):
+ raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the lower bound.")
+ if len(values) != len(ub):
+ raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the upper bound.")
+
+ # Combine the upper and lower bounds to transform the function into the form
+ # expected by the Fortran backend.
+ return np.concatenate(([lb_ii - vi for lb_ii, vi in zip(lb, values) if lb_ii > -np.inf],
+ [vi - ub_ii for ub_ii, vi in zip(ub, values) if ub_ii < np.inf],
+ ))
+ return newconstraint
+
+
+def process_nl_constraints(nlcs):
+ functions = []
+ for nlc in nlcs:
+ fun_i = transform_constraint_function(nlc)
+ functions.append(fun_i)
+ def constraint_function(x):
+ values = np.empty(0, dtype=np.float64)
+ for fun in functions:
+ values = np.concatenate((values, fun(x)))
+ return values
+ return constraint_function
diff --git a/pyprima/src/pyprima/common/_project.py b/pyprima/src/pyprima/common/_project.py
new file mode 100644
index 0000000000..233a4e32e9
--- /dev/null
+++ b/pyprima/src/pyprima/common/_project.py
@@ -0,0 +1,173 @@
+'''
+This module provides the _project function that attempts to project the initial guess
+onto the feasible set.
+
+Adapted from the corresponding function in the PDFO package (https://www.pdfo.net) by
+Tom M. Ragonneau (https://ragonneau.github.io) and Zaikun Zhang (https://www.zhangzk.net).
+'''
+
+import numpy as np
+from ._linear_constraints import LinearConstraint
+from scipy.optimize import OptimizeResult
+
+# All the accepted scalar types; np.generic correspond to all NumPy types.
+scalar_types = (int, float, np.generic)
+eps = np.finfo(np.float64).eps
+
+def _project(x0, lb, ub, constraints):
+ """Projection of the initial guess onto the feasible set.
+
+ Parameters
+ ----------
+ x0: ndarray, shape (n,)
+ The same as in prepdfo.
+ lb: ndarray, shape (n,)
+ The same as in prepdfo.
+ ub: ndarray, shape (n,)
+ The same as in prepdfo.
+ constraints: dict
+ The general constraints of the problem, defined as a dictionary with
+ fields:
+ linear: LinearConstraint
+ The linear constraints of the problem.
+ nonlinear: dict
+ The nonlinear constraints of the problem. When ``_project`` is called, the nonlinear constraints are
+ None.
+
+ Returns
+ -------
+ result: OptimizeResult
+ The result of the projection.
+
+ Authors
+ -------
+ Tom M. RAGONNEAU (ragonneau.github.io)
+ and Zaikun ZHANG (www.zhangzk.net)
+
+ Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
+ """
+ invoker = 'prima'
+
+ # Validate x0.
+ if isinstance(x0, scalar_types):
+ x0_c = [x0]
+ elif hasattr(x0, '__len__'):
+ x0_c = x0
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should be a vector.'.format(invoker))
+ try:
+ x0_c = np.asarray(x0_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should contain only scalars.'.format(invoker))
+ if len(x0_c.shape) != 1:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should be a vector.'.format(invoker))
+ lenx0 = x0_c.size
+
+ # Validate lb.
+ if isinstance(lb, scalar_types):
+ lb_c = [lb]
+ elif hasattr(lb, '__len__'):
+ lb_c = lb
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: lb should be a vector.'.format(invoker))
+ try:
+ lb_c = np.asarray(lb_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: lb should contain only scalars.'.format(invoker))
+ if len(lb_c.shape) != 1 or lb_c.size != lenx0:
+ raise ValueError('{}: UNEXPECTED ERROR: the size of lb is inconsistent with x0.'.format(invoker))
+
+ # Validate ub.
+ if isinstance(ub, scalar_types):
+ ub_c = [ub]
+ elif hasattr(ub, '__len__'):
+ ub_c = ub
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: ub should be a vector.'.format(invoker))
+ try:
+ ub_c = np.asarray(ub_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: ub should contain only scalars.'.format(invoker))
+ if len(ub_c.shape) != 1 or ub_c.size != lenx0:
+ raise ValueError('{}: UNEXPECTED ERROR: the size of ub is inconsistent with x0.'.format(invoker))
+
+ # Validate constraints.
+ if not isinstance(constraints, dict) or not ({'linear', 'nonlinear'} <= set(constraints.keys())) or \
+ not (isinstance(constraints['linear'], LinearConstraint) or constraints['linear'] is None):
+ # the nonlinear constraints will not be taken into account in this function and are, therefore, not validated
+ raise ValueError('{}: UNEXPECTED ERROR: The constraints are ill-defined.'.format(invoker))
+
+ max_con = 1e20 # Decide whether an inequality constraint can be ignored
+
+ # Project onto the feasible set.
+ if constraints['linear'] is None:
+ # Direct projection onto the bound constraints
+ x_proj = np.nanmin((np.nanmax((x0_c, lb_c), axis=0), ub_c), axis=0)
+ return OptimizeResult(x=x_proj)
+ elif all(np.less_equal(np.abs(constraints['linear'].ub - constraints['linear'].lb), eps)) and \
+ np.max(lb_c) <= -max_con and np.min(ub_c) >= max_con:
+ # The linear constraints are all equality constraints. The projection can therefore be done by solving the
+ # least-squares problem: min ||A*x - (b - A*x_0)||.
+ a = constraints['linear'].A
+ b = (constraints['linear'].lb + constraints['linear'].ub) / 2
+ xi, _, _, _ = np.linalg.lstsq(a, b - np.dot(a, x0_c), rcond=None)
+
+ # The problem is not bounded. However, if the least-square solver returned values bigger in absolute value
+ # than max_con, they will be reduced to this bound.
+ x_proj = np.nanmin((np.nanmax((x0_c + xi, lb_c), axis=0), ub_c), axis=0)
+
+ return OptimizeResult(x=x_proj)
+
+ if constraints['linear'] is not None:
+ try:
+ # Project the initial guess onto the linear constraints via SciPy.
+ from scipy.optimize import minimize
+ from scipy.optimize import Bounds as ScipyBounds
+ from scipy.optimize import LinearConstraint as ScipyLinearConstraint
+
+ linear = constraints['linear']
+
+ # To be more efficient, SciPy asks to separate the equality and the inequality constraints into two
+ # different LinearConstraint structures
+ pc_args_ineq, pc_args_eq = dict(), dict()
+ pc_args_ineq['A'], pc_args_eq['A'] = np.asarray([[]]), np.asarray([[]])
+ pc_args_ineq['A'] = pc_args_ineq['A'].reshape(0, linear.A.shape[1])
+ pc_args_eq['A'] = pc_args_eq['A'].reshape(0, linear.A.shape[1])
+ pc_args_ineq['lb'], pc_args_eq['lb'] = np.asarray([]), np.asarray([])
+ pc_args_ineq['ub'], pc_args_eq['ub'] = np.asarray([]), np.asarray([])
+
+ for i in range(linear.lb.size):
+ if linear.lb[i] != linear.ub[i]:
+ pc_args_ineq['A'] = np.concatenate((pc_args_ineq['A'], linear.A[i:i+1, :]), axis=0)
+ pc_args_ineq['lb'] = np.r_[pc_args_ineq['lb'], linear.lb[i]]
+ pc_args_ineq['ub'] = np.r_[pc_args_ineq['ub'], linear.ub[i]]
+ else:
+ pc_args_eq['A'] = np.concatenate((pc_args_eq['A'], linear.A[i:i+1, :]), axis=0)
+ pc_args_eq['lb'] = np.r_[pc_args_eq['lb'], linear.lb[i]]
+ pc_args_eq['ub'] = np.r_[pc_args_eq['ub'], linear.ub[i]]
+
+ if pc_args_ineq['A'].size > 0 and pc_args_ineq['lb'].size > 0 and pc_args_eq['lb'].size > 0:
+ project_constraints = [ScipyLinearConstraint(**pc_args_ineq), ScipyLinearConstraint(**pc_args_eq)]
+ elif pc_args_ineq['A'].size > 0 and pc_args_ineq['lb'].size > 0:
+ project_constraints = ScipyLinearConstraint(**pc_args_ineq)
+ elif pc_args_eq['A'].size > 0:
+ project_constraints = ScipyLinearConstraint(**pc_args_eq)
+ else:
+ project_constraints = ()
+
+ # Perform the actual projection.
+ ax_ineq = np.dot(pc_args_ineq['A'], x0_c)
+ ax_eq = np.dot(pc_args_eq['A'], x0_c)
+ if np.greater(ax_ineq, pc_args_ineq['ub']).any() or np.greater(pc_args_ineq['lb'], ax_ineq).any() or \
+ np.not_equal(ax_eq, pc_args_eq['lb']).any() or \
+ np.greater(x0_c, ub_c).any() or np.greater(lb_c, x0_c).any():
+ return minimize(lambda x: np.dot(x - x0_c, x - x0_c) / 2, x0_c, jac=lambda x: (x - x0_c),
+ bounds=ScipyBounds(lb_c, ub_c), constraints=project_constraints)
+ else:
+ # Do not perform any projection if the initial guess is feasible.
+ return OptimizeResult(x=x0_c)
+
+ except ImportError:
+ return OptimizeResult(x=x0_c)
+
+ return OptimizeResult(x=x0_c)
diff --git a/pyprima/src/pyprima/common/checkbreak.py b/pyprima/src/pyprima/common/checkbreak.py
new file mode 100644
index 0000000000..95efd6c14b
--- /dev/null
+++ b/pyprima/src/pyprima/common/checkbreak.py
@@ -0,0 +1,93 @@
+'''
+This module checks whether to break out of the solver loop.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from .infos import INFO_DEFAULT, NAN_INF_X, NAN_INF_F, FTARGET_ACHIEVED, MAXFUN_REACHED
+
+import numpy as np
+
+def checkbreak_unc(maxfun, nf, f, ftarget, x):
+ '''
+ This module checks whether to break out of the solver loop in the unconstrained case.
+ '''
+
+ # Outputs
+ info = INFO_DEFAULT
+
+ # Local variables
+ srname = "CHECKbreak_UNC"
+
+ # Preconditions
+ assert INFO_DEFAULT not in [NAN_INF_X, NAN_INF_F, FTARGET_ACHIEVED, MAXFUN_REACHED], f'NAN_INF_X, NAN_INF_F, FTARGET_ACHIEVED, and MAXFUN_REACHED differ from INFO_DFT {srname}'
+ # X does not contain NaN if the initial X does not contain NaN and the subroutines generating
+ # trust-region/geometry steps work properly so that they never produce a step containing NaN/Inf.
+ assert not any(np.isnan(x)), f'X does not contain NaN {srname}'
+ # With the moderated extreme barrier, F cannot be NaN/+Inf.
+ assert not (any(np.isnan(f)) or any(np.isposinf(f))), f'F is not NaN/+Inf {srname}'
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Although X should not contain NaN unless there is a bug, we include the following for security.
+ # X can be Inf, as finite + finite can be Inf numerically.
+ if any(np.isnan(x)) or any(np.isinf(x)):
+ info = NAN_INF_X
+
+ # Although NAN_INF_F should not happen unless there is a bug, we include the following for security.
+ if any(np.isnan(f)) or any(np.isposinf(f)):
+ info = NAN_INF_F
+
+ if f <= ftarget:
+ info = FTARGET_ACHIEVED
+
+ if nf >= maxfun:
+ info = MAXFUN_REACHED
+
+ return info
+
+def checkbreak_con(maxfun, nf, cstrv, ctol, f, ftarget, x):
+ '''
+ This module checks whether to break out of the solver loop in the constrained case.
+ '''
+
+ # Outputs
+ info = INFO_DEFAULT
+
+ # Local variables
+ srname = "CHECKbreak_CON"
+
+ # Preconditions
+ assert INFO_DEFAULT not in [NAN_INF_X, NAN_INF_F, FTARGET_ACHIEVED, MAXFUN_REACHED], f'NAN_INF_X, NAN_INF_F, FTARGET_ACHIEVED, and MAXFUN_REACHED differ from INFO_DFT {srname}'
+ # X does not contain NaN if the initial X does not contain NaN and the subroutines generating
+ # trust-region/geometry steps work properly so that they never produce a step containing NaN/Inf.
+ assert not any(np.isnan(x)), f'X does not contain NaN {srname}'
+ # With the moderated extreme barrier, F or CSTRV cannot be NaN/+Inf.
+ assert not (np.isnan(f) or np.isposinf(f) or np.isnan(cstrv) or np.isposinf(cstrv)), f'F or CSTRV is not NaN/+Inf {srname}'
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Although X should not contain NaN unless there is a bug, we include the following for security.
+ # X can be Inf, as finite + finite can be Inf numerically.
+ if any(np.isnan(x)) or any(np.isinf(x)):
+ info = NAN_INF_X
+
+ # Although NAN_INF_F should not happen unless there is a bug, we include the following for security.
+ if np.isnan(f) or np.isposinf(f) or np.isnan(cstrv) or np.isposinf(cstrv):
+ info = NAN_INF_F
+
+ if cstrv <= ctol and f <= ftarget:
+ info = FTARGET_ACHIEVED
+
+ if nf >= maxfun:
+ info = MAXFUN_REACHED
+
+ return info
diff --git a/pyprima/src/pyprima/common/consts.py b/pyprima/src/pyprima/common/consts.py
new file mode 100644
index 0000000000..1916bae39c
--- /dev/null
+++ b/pyprima/src/pyprima/common/consts.py
@@ -0,0 +1,47 @@
+'''
+This is a module defining some constants.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+import os
+
+DEBUGGING = bool(os.getenv('PRIMA_DEBUGGING'))
+
+REALMIN = np.finfo(float).tiny
+REALMAX = np.finfo(float).max
+FUNCMAX = 10.0**30
+CONSTRMAX = FUNCMAX
+EPS = np.finfo(float).eps
+
+# Any bound with an absolute value at least BOUNDMAX is considered as no bound.
+BOUNDMAX = REALMAX/4
+
+# Some default values
+RHOBEG_DEFAULT = 1
+RHOEND_DEFAULT = 1e-6
+FTARGET_DEFAULT = -REALMAX
+CTOL_DEFAULT = np.sqrt(EPS)
+CWEIGHT_DEFAULT = 1e8
+ETA1_DEFAULT = 0.1
+ETA2_DEFAULT = 0.7
+GAMMA1_DEFAULT = 0.5
+GAMMA2_DEFAULT = 2
+IPRINT_DEFAULT = 0
+MAXFUN_DIM_DEFAULT = 500
+
+PRIMA_MAX_HIST_MEM_MB = 300 # 1MB > 10^5*REAL64. 100 can be too small.
+
+# Maximal amount of memory (Byte) allowed for XHIST, FHIST, CONHIST, CHIST, and the filters.
+MHM = PRIMA_MAX_HIST_MEM_MB * 10**6
+# Make sure that MAXHISTMEM does not exceed HUGE(0) to avoid overflow and memory errors.
+MAXHISTMEM = min(MHM, np.iinfo(np.int32).max)
+
+# Maximal length of the filter used in constrained solvers.
+MIN_MAXFILT = 200 # Should be positive; < 200 is not recommended.
+MAXFILT_DEFAULT = 10 * MIN_MAXFILT
diff --git a/pyprima/src/pyprima/common/evaluate.py b/pyprima/src/pyprima/common/evaluate.py
new file mode 100644
index 0000000000..075e1d42fa
--- /dev/null
+++ b/pyprima/src/pyprima/common/evaluate.py
@@ -0,0 +1,99 @@
+'''
+This is a module evaluating the objective/constraint function with Nan/Inf handling.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+from .consts import FUNCMAX, CONSTRMAX, REALMAX, DEBUGGING
+from .linalg import matprod, primasum
+
+# This is a module evaluating the objective/constraint function with Nan/Inf handling.
+
+
+def moderatex(x):
+ '''
+ This function moderates a decision variable. It replaces NaN by 0 and Inf/-Inf by
+ REALMAX/-REALMAX.
+ '''
+ x[np.isnan(x)] = 0
+ x = np.clip(x, -REALMAX, REALMAX)
+ return x
+
+def moderatef(f):
+ """
+ This function moderates the function value of a MINIMIZATION problem. It replaces
+ NaN and any value above FUNCMAX by FUNCMAX.
+ """
+ f = FUNCMAX if np.isnan(f) else f
+ f = np.clip(f, -REALMAX, FUNCMAX)
+ # We may moderate huge negative function values as follows, but we decide not to.
+ # f = np.clip(f, -FUNCMAX, FUNCMAX)
+ return f
+
+
+def moderatec(c):
+ """
+ This function moderates the constraint value, the constraint demanding this value
+ to be NONNEGATIVE. It replaces any value below -CONSTRMAX by -CONSTRMAX, and any
+ NaN or value above CONSTRMAX by CONSTRMAX.
+ """
+ np.nan_to_num(c, copy=False, nan=CONSTRMAX)
+ c = np.clip(c, -CONSTRMAX, CONSTRMAX)
+ return c
+
+
+def evaluate(calcfc, x, m_nlcon, amat, bvec):
+ """
+ This function evaluates CALCFC at X, returning the objective function value and the
+ constraint value. Nan/Inf are handled by a moderated extreme barrier.
+ """
+
+ # Sizes
+ m_lcon = len(bvec) if bvec is not None else 0
+
+ # Preconditions
+ if DEBUGGING:
+ # X should not contain NaN if the initial X does not contain NaN and the
+ # subroutines generating # trust-region/geometry steps work properly so that
+ # they never produce a step containing NaN/Inf.
+ assert not any(np.isnan(x))
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ constr = np.zeros(m_lcon + m_nlcon)
+ if amat is not None:
+ constr[:m_lcon] = matprod(x, amat.T) - bvec
+
+ if any(np.isnan(x)):
+ # Although this should not happen unless there is a bug, we include this case
+ # for robustness.
+ f = primasum(x)
+ constr = np.ones(m_nlcon) * f
+ else:
+ f, constr[m_lcon:] = calcfc(moderatex(x))
+
+ # Moderated extreme barrier: replace NaN/huge objective or constraint values
+ # with a large but finite value. This is naive, and better approaches surely
+ # exist.
+ f = moderatef(f)
+ constr[m_lcon:] = moderatec(constr[m_lcon:])
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ # With X not containing NaN, and with the moderated extreme barrier, F cannot
+ # be NaN/+Inf, and CONSTR cannot be NaN/-Inf.
+ assert not (np.isnan(f) or np.isposinf(f))
+ assert not any(np.isnan(constr) | np.isposinf(constr))
+
+ return f, constr
diff --git a/pyprima/src/pyprima/common/history.py b/pyprima/src/pyprima/common/history.py
new file mode 100644
index 0000000000..212db7305d
--- /dev/null
+++ b/pyprima/src/pyprima/common/history.py
@@ -0,0 +1,38 @@
+'''
+This module provides subroutines that handle the X/F/C histories of the solver, taking into
+account that MAXHIST may be smaller than NF.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+def savehist(maxhist, x, xhist, f, fhist, cstrv, chist, constr, conhist):
+ '''
+ Save the data values to the history lists.
+
+ The implementation of this function is vastly different from the Fortran implementation.
+ This is mostly due to the ease of creating and appending to lists in Python
+
+ However just like the Fortran version we should be concerned about both performance
+ and memory constraints. It will probably be better to initialize an array of NaN for
+ each of the histories and keep track of how many indices we have stored. Not needed for
+ the moment.
+ '''
+ if len(xhist) < maxhist:
+ xhist.append(x)
+ fhist.append(f)
+ chist.append(cstrv)
+ conhist.append(constr)
+ else:
+ # This effectively accomplishes what rangehist does in the Fortran implementation
+ xhist.pop(0)
+ fhist.pop(0)
+ chist.pop(0)
+ conhist.pop(0)
+ xhist.append(x)
+ fhist.append(f)
+ chist.append(cstrv)
+ conhist.append(constr)
diff --git a/pyprima/src/pyprima/common/infos.py b/pyprima/src/pyprima/common/infos.py
new file mode 100644
index 0000000000..dc2975576b
--- /dev/null
+++ b/pyprima/src/pyprima/common/infos.py
@@ -0,0 +1,31 @@
+'''
+This is a module defining exit flags.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+INFO_DEFAULT = 0
+SMALL_TR_RADIUS = 0
+FTARGET_ACHIEVED = 1
+TRSUBP_FAILED = 2
+MAXFUN_REACHED = 3
+FIXED_SUCCESS = 13
+MAXTR_REACHED = 20
+NAN_INF_X = -1
+NAN_INF_F = -2
+NAN_INF_MODEL = -3
+NO_SPACE_BETWEEN_BOUNDS = 6
+DAMAGING_ROUNDING = 7
+ZERO_LINEAR_CONSTRAINT = 8
+CALLBACK_TERMINATE = 30
+
+# Stop-codes.
+# The following codes are used by ERROR STOP as stop-codes, which should be default integers.
+INVALID_INPUT = 100
+ASSERTION_FAILS = 101
+VALIDATION_FAILS = 102
+MEMORY_ALLOCATION_FAILS = 103
diff --git a/pyprima/src/pyprima/common/linalg.py b/pyprima/src/pyprima/common/linalg.py
new file mode 100644
index 0000000000..a5ee756aaf
--- /dev/null
+++ b/pyprima/src/pyprima/common/linalg.py
@@ -0,0 +1,435 @@
+'''
+This module provides some basic linear algebra procedures.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+from .consts import DEBUGGING, EPS, REALMAX, REALMIN
+from .present import present
+
+
+# We use naive implementations of matrix multiplication and other routines for two
+# reasons:
+# 1. When Fortran is compiled in debug mode, and Python is using these routines, we
+# can get bit for bit identical results as compared to Fortran. This is helpful
+# for comparing the two implementations. It will be particularly helpful when porting
+# the other implementations like LINCOA, etc.
+# 2. On some problems this algorithm is very sensitive to errors in finite precision
+# arithmetic. Switching to naive implementation will slow down the algorithm, but
+# may be more stable.
+USE_NAIVE_MATH = False
+
+
+def inprod(x, y):
+ if not USE_NAIVE_MATH:
+ return np.dot(x, y)
+ result = 0
+ for i in range(len(x)):
+ result += x[i] * y[i]
+ return result
+
+
+def matprod12(x, y):
+ result = np.zeros(y.shape[1])
+ for i in range(y.shape[1]):
+ result[i] = inprod(x, y[:, i])
+ return result
+
+
+def matprod21(x, y):
+ result = np.zeros(x.shape[0])
+ for i in range(x.shape[1]):
+ result += x[:, i] * y[i]
+ return result
+
+
+def matprod22(x, y):
+ result = np.zeros((x.shape[0], y.shape[1]))
+ for i in range(y.shape[1]):
+ for j in range(x.shape[1]):
+ result[:, j] += x[:, i] * y[i, j]
+ return result
+
+
+def matprod(x, y):
+ if not USE_NAIVE_MATH:
+ return x@y
+ if len(x.shape) == 1 and len(y.shape) == 1:
+ return inprod(x, y)
+ elif len(x.shape) == 1 and len(y.shape) == 2:
+ return matprod12(x, y)
+ elif len(x.shape) == 2 and len(y.shape) == 1:
+ return matprod21(x, y)
+ elif len(x.shape) == 2 and len(y.shape) == 2:
+ return matprod22(x, y)
+ else:
+ raise ValueError(f'Invalid shapes for x and y: {x.shape} and {y.shape}')
+
+
+def outprod(x, y):
+ if not USE_NAIVE_MATH:
+ return np.outer(x, y)
+ result = np.zeros((len(x), len(y)))
+ for i in range(len(x)):
+ result[:, i] = x * y[i]
+ return result
+
+
+def lsqr(A, b, Q, Rdiag):
+ if not USE_NAIVE_MATH:
+ return np.linalg.lstsq(A, b, rcond=None)[0]
+
+ m = A.shape[0]
+ n = A.shape[1]
+
+ rank = min(m, n)
+
+ x = np.zeros(n)
+ y = b.copy()
+
+ for i in range(rank - 1, -1, -1):
+ yq = inprod(y, Q[:, i])
+ yqa = inprod(np.abs(y), np.abs(Q[:, i]))
+ if isminor(yq, yqa):
+ x[i] = 0
+ else:
+ x[i] = yq / Rdiag[i]
+ y = y - x[i] * A[:, i]
+ return x
+
+
+def hypot(x1, x2):
+ if not USE_NAIVE_MATH:
+ return np.hypot(x1, x2)
+ if not np.isfinite(x1):
+ r = abs(x1)
+ elif not np.isfinite(x2):
+ r = abs(x2)
+ else:
+ y = abs(np.array([x1, x2]))
+ y = np.array([min(y), max(y)])
+ if y[0] > np.sqrt(REALMIN) and y[1] < np.sqrt(REALMAX/2.1):
+ r = np.sqrt(sum(y*y))
+ elif y[1] > 0:
+ r = y[1] * np.sqrt((y[0]/y[1])*(y[0]/y[1]) + 1)
+ else:
+ r = 0
+ return r
+
+
+def norm(x):
+ if not USE_NAIVE_MATH:
+ return np.linalg.norm(x)
+ # NOTE: Avoid np.pow! And exponentiation in general!
+ # It appears that in Fortran, x*x and x**2 are the same, but in Python they are not!
+ # Try it with x = 5 - 1e-15
+ result = np.sqrt(sum([xi*xi for xi in x]))
+ return result
+
+
+def istril(A, tol=0):
+ return primasum(abs(A) - np.tril(abs(A))) <= tol
+
+def istriu(A, tol=0):
+ return primasum(abs(A) - np.triu(abs(A))) <= tol
+
+
+def inv(A):
+ if not USE_NAIVE_MATH:
+ return np.linalg.inv(A)
+ A = A.copy()
+ n = A.shape[0]
+ if istril(A):
+ # This case is invoked in COBYLA.
+ R = A.T
+ B = np.zeros((n, n))
+ for i in range(n):
+ B[i, i] = 1 / R[i, i]
+ B[:i, i] = -matprod(B[:i, :i], R[:i, i]) / R[i, i]
+ return B.T
+ elif istriu(A):
+ B = np.zeros((n, n))
+ for i in range(n):
+ B[i, i] = 1 / A[i, i]
+ B[:i, i] = -matprod(B[:i, :i], A[:i, i]) / A[i, i]
+ else:
+ # This is NOT the best algorithm for the inverse, but since the QR subroutine is available ...
+ Q, R, P = qr(A)
+ R = R.T
+ B = np.zeros((n, n))
+ for i in range(n - 1, -1, -1):
+ B[:, i] = (Q[:, i] - matprod(B[:, i + 1:n], R[i + 1:n, i])) / R[i, i]
+ InvP = np.zeros(n, dtype=int)
+ InvP[P] = np.linspace(0, n-1, n)
+ B = B[:, InvP].T
+ return B
+
+
+def qr(A):
+ m = A.shape[0]
+ n = A.shape[1]
+
+ Q = np.eye(m)
+ T = A.T
+ P = np.linspace(0, n-1, n, dtype=int)
+
+ for j in range(n):
+ k = np.argmax(primasum(primapow2(T[j:n+1, j:m+1]), axis=1), axis=0)
+ if k > 0 and k <= n - j - 1:
+ k += j
+ P[j], P[k] = P[k], P[j]
+ T[[j, k], :] = T[[k, j], :]
+ for i in range(m-1, j, -1):
+ G = planerot(T[j, [j, i]]).T
+ T[j, [j, i]] = np.append(hypot(T[j, j], T[j, i]), 0)
+ T[j + 1:n + 1, [j, i]] = matprod(T[j + 1:n + 1, [j, i]], G)
+ Q[:, [j, i]] = matprod(Q[:, [j, i]], G)
+
+ R = T.T
+
+ return Q, R, P
+
+
+def primasum(x, axis=None):
+ '''
+ According to its documentation, np.sum will sometimes do partial pairwise summation.
+ For our purposes, when comparing, we want don't want to do anything fancy, and we
+ just want to add things up one at a time.
+ '''
+ if not USE_NAIVE_MATH:
+ return np.sum(x, axis=axis)
+ if axis is None:
+ if x.ndim == 2:
+ # Sum columns first, then sum the result
+ return sum(primasum(x, axis=0))
+ else:
+ return sum(x)
+ elif axis == 0:
+ result = np.zeros(x.shape[1])
+ for i in range(x.shape[1]):
+ result[i] = sum(x[:, i])
+ return result
+ elif axis == 1:
+ result = np.zeros(x.shape[0])
+ for i in range(x.shape[0]):
+ result[i] = sum(x[i, :])
+ return result
+
+
+def primapow2(x):
+ '''
+ Believe it or now, x**2 is not always the same as x*x in Python. In Fortran they
+ appear to be identical. Here's a quick one-line to find an example on your system
+ (well, two liner after importing numpy):
+ list(filter(lambda x: x[1], [(x:=np.random.random(), x**2 - x*x != 0) for _ in range(10000)]))
+ '''
+ return x*x
+
+
+def planerot(x):
+ '''
+ As in MATLAB, planerot(x) returns a 2x2 Givens matrix G for x in R2 so that Y=G@x has Y[1] = 0.
+ Roughly speaking, G = np.array([[x[0]/R, x[1]/R], [-x[1]/R, x[0]/R]]), where R = np.linalg.norm(x).
+ 0. We need to take care of the possibilities of R=0, Inf, NaN, and over/underflow.
+ 1. The G defined above is continuous with respect to X except at 0. Following this definition,
+ G = np.array([[np.sign(x[0]), 0], [0, np.sign(x[0])]]) if x[1] == 0,
+ G = np.array([[0, np.sign(x[1])], [np.sign(x[1]), 0]]) if x[0] == 0
+ Yet some implementations ignore the signs, leading to discontinuity and numerical instability.
+ 2. Difference from MATLAB: if x contains NaN of consists of only Inf, MATLAB returns a NaN matrix,
+ but we return an identity matrix or a matrix of +/-np.sqrt(2). We intend to keep G always orthogonal.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert len(x) == 2, "x must be a 2-vector"
+
+ # ==================
+ # Calculation starts
+ # ==================
+
+ # Define C = X(1) / R and S = X(2) / R with R = HYPOT(X(1), X(2)). Handle Inf/NaN, over/underflow.
+ if (any(np.isnan(x))):
+ # In this case, MATLAB sets G to NaN(2, 2). We refrain from doing so to keep G orthogonal.
+ c = 1
+ s = 0
+ elif (all(np.isinf(x))):
+ # In this case, MATLAB sets G to NaN(2, 2). We refrain from doing so to keep G orthogonal.
+ c = 1 / np.sqrt(2) * np.sign(x[0])
+ s = 1 / np.sqrt(2) * np.sign(x[1])
+ elif (abs(x[0]) <= 0 and abs(x[1]) <= 0): # X(1) == 0 == X(2).
+ c = 1
+ s = 0
+ elif (abs(x[1]) <= EPS * abs(x[0])):
+ # N.B.:
+ # 0. With <= instead of <, this case covers X(1) == 0 == X(2), which is treated above separately
+ # to avoid the confusing SIGN(., 0) (see 1).
+ # 1. SIGN(A, 0) = ABS(A) in Fortran but sign(0) = 0 in MATLAB, Python, Julia, and R#
+ # 2. Taking SIGN(X(1)) into account ensures the continuity of G with respect to X except at 0.
+ c = np.sign(x[0])
+ s = 0
+ elif (abs(x[0]) <= EPS * abs(x[1])):
+ # N.B.: SIGN(A, X) = ABS(A) * sign of X /= A * sign of X # Therefore, it is WRONG to define G
+ # as SIGN(RESHAPE([ZERO, -ONE, ONE, ZERO], [2, 2]), X(2)). This mistake was committed on
+ # 20211206 and took a whole day to debug! NEVER use SIGN on arrays unless you are really sure.
+ c = 0
+ s = np.sign(x[1])
+ else:
+ # Here is the normal case. It implements the Givens rotation in a stable & continuous way as in:
+ # Bindel, D., Demmel, J., Kahan, W., and Marques, O. (2002). On computing Givens rotations
+ # reliably and efficiently. ACM Transactions on Mathematical Software (TOMS), 28(2), 206-238.
+ # N.B.: 1. Modern compilers compute SQRT(REALMIN) and SQRT(REALMAX/2.1) at compilation time.
+ # 2. The direct calculation without involving T and U seems to work better; use it if possible.
+ if (all(np.logical_and(np.sqrt(REALMIN) < np.abs(x), np.abs(x) < np.sqrt(REALMAX / 2.1)))):
+ # Do NOT use HYPOTENUSE here; the best implementation for one may be suboptimal for the other
+ r = norm(x)
+ c = x[0] / r
+ s = x[1] / r
+ elif (abs(x[0]) > abs(x[1])):
+ t = x[1] / x[0]
+ u = max(1, abs(t), np.sqrt(1 + t*t)) # MAXVAL: precaution against rounding error.
+ u *= np.sign(x[0]) ##MATLAB: u = sign(x(1))*sqrt(1 + t**2)
+ c = 1 / u
+ s = t / u
+ else:
+ t = x[0] / x[1]
+ u = max([1, abs(t), np.sqrt(1 + t*t)]) # MAXVAL: precaution against rounding error.
+ u *= np.sign(x[1]) ##MATLAB: u = sign(x(2))*sqrt(1 + t**2)
+ c = t / u
+ s = 1 / u
+
+ G = np.array([[c, s], [-s, c]]) # MATLAB: G = [c, s; -s, c]
+
+ #====================#
+ # Calculation ends #
+ #====================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert G.shape == (2,2)
+ assert np.all(np.isfinite(G))
+ assert abs(G[0, 0] - G[1, 1]) + abs(G[0, 1] + G[1, 0]) <= 0
+ tol = np.maximum(1.0E-10, np.minimum(1.0E-1, 1.0E6 * EPS))
+ assert isorth(G, tol)
+ if all(np.logical_and(np.isfinite(x), np.abs(x) < np.sqrt(REALMAX / 2.1))):
+ r = np.linalg.norm(x)
+ assert max(abs(G@x - [r, 0])) <= max(tol, tol * r), 'G @ X = [||X||, 0]'
+
+ return G
+
+
+def isminor(x, ref):
+ '''
+ This function tests whether x is minor compared to ref. It is used by Powell, e.g., in COBYLA.
+ In precise arithmetic, isminor(x, ref) is true if and only if x == 0; in floating point
+ arithmetic, isminor(x, ref) is true if x is 0 or its nonzero value can be attributed to
+ computer rounding errors according to ref.
+ Larger sensitivity means the function is more strict/precise, the value 0.1 being due to Powell.
+
+ For example:
+ isminor(1e-20, 1e300) -> True, because in floating point arithmetic 1e-20 cannot be added to
+ 1e300 without being rounded to 1e300.
+ isminor(1e300, 1e-20) -> False, because in floating point arithmetic adding 1e300 to 1e-20
+ dominates the latter number.
+ isminor(3, 4) -> False, because 3 can be added to 4 without being rounded off
+ '''
+
+ sensitivity = 0.1
+ refa = abs(ref) + sensitivity * abs(x)
+ refb = abs(ref) + 2 * sensitivity * abs(x)
+ return np.logical_or(abs(ref) >= refa, refa >= refb)
+
+
+def isinv(A, B, tol=None):
+ '''
+ This procedure tests whether A = B^{-1} up to the tolerance TOL.
+ '''
+
+ # Sizes
+ n = np.size(A, 0)
+
+ # Preconditions
+ if DEBUGGING:
+ assert np.size(A, 0) == np.size(A, 1)
+ assert np.size(B, 0) == np.size(B, 1)
+ assert np.size(A, 0) == np.size(B, 0)
+ if present(tol):
+ assert tol >= 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ tol = tol if present(tol) else np.minimum(1e-3, 1e2 * EPS * np.maximum(np.size(A, 0), np.size(A, 1)))
+ tol = np.max([tol, tol * np.max(abs(A)), tol * np.max(abs(B))])
+ is_inv = ((abs(matprod(A, B)) - np.eye(n)) <= tol).all() or ((abs(matprod(B, A) - np.eye(n))) <= tol).all()
+
+ #===================#
+ # Calculation ends #
+ #===================#
+ return is_inv
+
+
+def isorth(A, tol=None):
+ '''
+ This function tests whether the matrix A has orthonormal columns up to the tolerance TOL.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ if present(tol):
+ assert tol >= 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ num_vars = np.size(A, 1)
+
+ if num_vars > np.size(A, 0):
+ is_orth = False
+ elif (np.isnan(primasum(abs(A)))):
+ is_orth = False
+ else:
+ if present(tol):
+ is_orth = (abs(matprod(A.T, A) - np.eye(num_vars)) <= np.maximum(tol, tol * np.max(abs(A)))).all()
+ else:
+ is_orth = (abs(matprod(A.T, A) - np.eye(num_vars)) <= 0).all()
+
+ #====================#
+ # Calculation ends #
+ #====================#
+ return is_orth
+
+
+def get_arrays_tol(*arrays):
+ """
+ Get a relative tolerance for a set of arrays. Borrowed from COBYQA
+
+ Parameters
+ ----------
+ *arrays: tuple
+ Set of `numpy.ndarray` to get the tolerance for.
+
+ Returns
+ -------
+ float
+ Relative tolerance for the set of arrays.
+
+ Raises
+ ------
+ ValueError
+ If no array is provided.
+ """
+ if len(arrays) == 0:
+ raise ValueError("At least one array must be provided.")
+ size = max(array.size for array in arrays)
+ weight = max(
+ np.max(np.abs(array[np.isfinite(array)]), initial=1.0)
+ for array in arrays
+ )
+ return 10.0 * EPS * max(size, 1.0) * weight
diff --git a/pyprima/src/pyprima/common/message.py b/pyprima/src/pyprima/common/message.py
new file mode 100644
index 0000000000..899d5fa63d
--- /dev/null
+++ b/pyprima/src/pyprima/common/message.py
@@ -0,0 +1,290 @@
+'''
+This module provides some functions that print messages to terminal/files.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+
+N.B.:
+1. In case parallelism is desirable (especially during initialization), the functions may
+have to be modified or disabled due to the IO operations.
+2. IPRINT indicates the level of verbosity, which increases with the absolute value of IPRINT.
+IPRINT = +/-3 can be expensive due to high IO operations.
+'''
+
+from .consts import DEBUGGING
+from .infos import FTARGET_ACHIEVED, MAXFUN_REACHED, MAXTR_REACHED, \
+ SMALL_TR_RADIUS, TRSUBP_FAILED, NAN_INF_F, NAN_INF_X, NAN_INF_MODEL, DAMAGING_ROUNDING, \
+ NO_SPACE_BETWEEN_BOUNDS, ZERO_LINEAR_CONSTRAINT, CALLBACK_TERMINATE
+from .present import present
+import numpy as np
+
+spaces = ' '
+
+
+def get_info_string(solver, info):
+ if info == FTARGET_ACHIEVED:
+ reason = 'the target function value is achieved.'
+ elif info == MAXFUN_REACHED:
+ reason = 'the objective function has been evaluated MAXFUN times.'
+ elif info == MAXTR_REACHED:
+ reason = 'the maximal number of trust region iterations has been reached.'
+ elif info == SMALL_TR_RADIUS:
+ reason = 'the trust region radius reaches its lower bound.'
+ elif info == TRSUBP_FAILED:
+ reason = 'a trust region step has failed to reduce the quadratic model.'
+ elif info == NAN_INF_X:
+ reason = 'NaN or Inf occurs in x.'
+ elif info == NAN_INF_F:
+ reason = 'the objective function returns NaN/+Inf.'
+ elif info == NAN_INF_MODEL:
+ reason = 'NaN or Inf occurs in the models.'
+ elif info == DAMAGING_ROUNDING:
+ reason = 'rounding errors are becoming damaging.'
+ elif info == NO_SPACE_BETWEEN_BOUNDS:
+ reason = 'there is no space between the lower and upper bounds of variable.'
+ elif info == ZERO_LINEAR_CONSTRAINT:
+ reason = 'one of the linear constraints has a zero gradient'
+ elif info == CALLBACK_TERMINATE:
+ reason = 'the callback function requested termination'
+ else:
+ reason = 'UNKNOWN EXIT FLAG'
+ ret_message = f'Return from {solver} because {reason.strip()}'
+ return ret_message
+
+
+def retmsg(solver, info, iprint, nf, f, x, cstrv=None, constr=None):
+ '''
+ This function prints messages at return.
+ '''
+ # Local variables
+ valid_exit_codes = [FTARGET_ACHIEVED, MAXFUN_REACHED, MAXTR_REACHED,
+ SMALL_TR_RADIUS, TRSUBP_FAILED, NAN_INF_F, NAN_INF_X, NAN_INF_MODEL, DAMAGING_ROUNDING,
+ NO_SPACE_BETWEEN_BOUNDS, ZERO_LINEAR_CONSTRAINT, CALLBACK_TERMINATE]
+
+ # Preconditions
+ if DEBUGGING:
+ assert info in valid_exit_codes
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if abs(iprint) < 1: # No printing (iprint == 0)
+ return
+ elif iprint > 0: # Print the message to the standard out.
+ fname = ''
+ else: # Print the message to a file named FNAME.
+ fname = f'{solver}_output.txt'
+
+ # Decide whether the problem is truly constrained.
+ if present(constr):
+ is_constrained = (np.size(constr) > 0)
+ else:
+ is_constrained = present(cstrv)
+
+ # Decide the constraint violation.
+ if present(cstrv):
+ cstrv_loc = cstrv
+ elif present(constr):
+ cstrv_loc = np.max(np.append(0, -constr)) # N.B.: We assume that the constraint is CONSTR >= 0.
+ else:
+ cstrv_loc = 0
+
+ # Decide the return message.
+ ret_message = get_info_string(solver, info)
+
+ if np.size(x) <= 2:
+ x_message = f'\nThe corresponding X is: {x}' # Printed in one line
+ else:
+ x_message = f'\nThe corresponding X is:\n{x}'
+
+ if is_constrained:
+ nf_message = (f'\nNumber of function values = {nf}{spaces}'
+ f'Least value of F = {f}{spaces}Constraint violation = {cstrv_loc}')
+ else:
+ nf_message = f'\nNumber of function values = {nf}{spaces}Least value of F = {f}'
+
+ if is_constrained and present(constr):
+ if np.size(constr) <= 2:
+ constr_message = f'\nThe constraint value is: {constr}' # Printed in one line
+ else:
+ constr_message = f'\nThe constraint value is:\n{constr}'
+ else:
+ constr_message = ''
+
+ # Print the message.
+ if abs(iprint) >= 2:
+ message = f'\n{ret_message}{nf_message}{x_message}{constr_message}\n'
+ else:
+ message = f'{ret_message}{nf_message}{x_message}{constr_message}\n'
+ if len(fname) > 0:
+ with open(fname, 'a') as f: f.write(message)
+ else:
+ print(message)
+
+
+def rhomsg(solver, iprint, nf, delta, f, rho, x, cstrv=None, constr=None, cpen=None):
+ '''
+ This function prints messages when RHO is updated.
+ '''
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if abs(iprint) < 2: # No printing
+ return
+ elif iprint > 0: # Print the message to the standard out.
+ fname = ''
+ else: # Print the message to a file named FNAME.
+ fname = f'{solver.strip()}_output.txt'
+
+ # Decide whether the problem is truly constrained.
+ if present(constr):
+ is_constrained = (np.size(constr) > 0)
+ else:
+ is_constrained = present(cstrv)
+
+ # Decide the constraint violation.
+ if present(cstrv):
+ cstrv_loc = cstrv
+ elif present(constr):
+ cstrv_loc = np.max(np.append(0, -constr)) # N.B.: We assume that the constraint is CONSTR >= 0.
+ else:
+ cstrv_loc = 0
+
+ if present(cpen):
+ rho_message = (f'\nNew RHO = {rho}{spaces}Delta = {delta}{spaces}'
+ f'CPEN = {cpen}')
+ else:
+ rho_message = f'\nNew RHO = {rho}{spaces}Delta = {delta}'
+
+ if np.size(x) <= 2:
+ x_message = f'\nThe corresponding X is: {x}' # Printed in one line
+ else:
+ x_message = f'\nThe corresponding X is:\n{x}'
+
+ if is_constrained:
+ nf_message = (f'\nNumber of function values = {nf}{spaces}'
+ f'Least value of F = {f}{spaces}Constraint violation = {cstrv_loc}')
+ else:
+ nf_message = f'\nNumber of function values = {nf}{spaces}Least value of F = {f}'
+
+ if is_constrained and present(constr):
+ if np.size(constr) <= 2:
+ constr_message = f'\nThe constraint value is: {constr}' # Printed in one line
+ else:
+ constr_message = f'\nThe constraint value is:\n{constr}'
+ else:
+ constr_message = ''
+
+ # Print the message.
+ if abs(iprint) >= 3:
+ message = f'\n{rho_message}{nf_message}{x_message}{constr_message}'
+ else:
+ message = f'{rho_message}{nf_message}{x_message}{constr_message}'
+ if len(fname) > 0:
+ with open(fname, 'a') as f: f.write(message)
+ else:
+ print(message)
+
+ #====================#
+ # Calculation ends #
+ #====================#
+
+
+def cpenmsg(solver, iprint, cpen):
+ '''
+ This function prints a message when CPEN is updated.
+ '''
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if abs(iprint) < 2: # No printing
+ return
+ elif iprint > 0: # Print the message to the standard out.
+ fname = ''
+ else: # Print the message to a file named FNAME.
+ fname = f'{solver.strip()}_output.txt'
+
+ # Print the message.
+ if abs(iprint) >= 3:
+ message = f'\nSet CPEN to {cpen}'
+ else:
+ message = f'\n\nSet CPEN to {cpen}'
+ if len(fname) > 0:
+ with open(fname, 'a') as f: f.write(message)
+ else:
+ print(message)
+
+ #====================#
+ # Calculation ends #
+ #====================#
+
+
+def fmsg(solver, state, iprint, nf, delta, f, x, cstrv=None, constr=None):
+ '''
+ This subroutine prints messages for each evaluation of the objective function.
+ '''
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if abs(iprint) < 2: # No printing
+ return
+ elif iprint > 0: # Print the message to the standard out.
+ fname = ''
+ else: # Print the message to a file named FNAME.
+ fname = f'{solver.strip()}_output.txt'
+
+ # Decide whether the problem is truly constrained.
+ if present(constr):
+ is_constrained = (np.size(constr) > 0)
+ else:
+ is_constrained = present(cstrv)
+
+ # Decide the constraint violation.
+ if present(cstrv):
+ cstrv_loc = cstrv
+ elif present(constr):
+ cstrv_loc = np.max(np.append(0, -constr)) # N.B.: We assume that the constraint is CONSTR >= 0.
+ else:
+ cstrv_loc = 0
+
+ delta_message = f'\n{state} step with radius = {delta}'
+
+ if is_constrained:
+ nf_message = (f'\nNumber of function values = {nf}{spaces}'
+ f'Least value of F = {f}{spaces}Constraint violation = {cstrv_loc}')
+ else:
+ nf_message = f'\nNumber of function values = {nf}{spaces}Least value of F = {f}'
+
+ if np.size(x) <= 2:
+ x_message = f'\nThe corresponding X is: {x}' # Printed in one line
+ else:
+ x_message = f'\nThe corresponding X is:\n{x}'
+
+ if is_constrained and present(constr):
+ if np.size(constr) <= 2:
+ constr_message = f'\nThe constraint value is: {constr}' # Printed in one line
+ else:
+ constr_message = f'\nThe constraint value is:\n{constr}'
+ else:
+ constr_message = ''
+
+ # Print the message.
+ message = f'{delta_message}{nf_message}{x_message}{constr_message}'
+ if len(fname) > 0:
+ with open(fname, 'a') as f: f.write(message)
+ else:
+ print(message)
+
+ #====================#
+ # Calculation ends #
+ #====================#
diff --git a/pyprima/src/pyprima/common/powalg.py b/pyprima/src/pyprima/common/powalg.py
new file mode 100644
index 0000000000..1a8268b263
--- /dev/null
+++ b/pyprima/src/pyprima/common/powalg.py
@@ -0,0 +1,131 @@
+'''
+This module provides some Powell-style linear algebra procedures.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+from .linalg import isminor, planerot, matprod, inprod, hypot
+from .consts import DEBUGGING, EPS
+
+
+def qradd_Rdiag(c, Q, Rdiag, n):
+ '''
+ This function updates the QR factorization of an MxN matrix A of full column rank, attempting to
+ add a new column C to this matrix as the LAST column while maintaining the full-rankness.
+ Case 1. If C is not in range(A) (theoretically, it implies N < M), then the new matrix is np.hstack([A, C])
+ Case 2. If C is in range(A), then the new matrix is np.hstack([A[:, :n-1], C])
+ N.B.:
+ 0. Instead of R, this subroutine updates Rdiag, which is np.diag(R), with a size at most M and at
+ least min(m, n+1). The number is min(m, n+1) rather than min(m, n) as n may be augmented by 1 in
+ the function.
+ 1. With the two cases specified as above, this function does not need A as an input.
+ 2. The function changes only Q[:, nsave:m] (nsave is the original value of n) and
+ R[:, n-1] (n takes the updated value)
+ 3. Indeed, when C is in range(A), Powell wrote in comments that "set iOUT to the index of the
+ constraint (here, column of A --- Zaikun) to be deleted, but branch if no suitable index can be
+ found". The idea is to replace a column of A by C so that the new matrix still has full rank
+ (such a column must exist unless C = 0). But his code essentially sets iout=n always. Maybe he
+ found this worked well enough in practice. Meanwhile, Powell's code includes a snippet that can
+ never be reached, which was probably intended to deal with the case that IOUT != n
+ '''
+ m = Q.shape[1]
+ nsave = n # Needed for debugging (only)
+
+ # As in Powell's COBYLA, CQ is set to 0 at the positions with CQ being negligible as per ISMINOR.
+ # This may not be the best choice if the subroutine is used in other contexts, e.g. LINCOA.
+ cq = matprod(c, Q)
+ cqa = matprod(abs(c), abs(Q))
+ # The line below basically makes an element of cq 0 if adding it to the corresponding element of
+ # cqa does not change the latter.
+ cq = np.array([0 if isminor(cqi, cqai) else cqi for cqi, cqai in zip(cq, cqa)])
+
+ # Update Q so that the columns of Q[:, n+1:m] are orthogonal to C. This is done by applying a 2D
+ # Givens rotation to Q[:, [k, k+1]] from the right to zero C' @ Q[:, k+1] out for K=n+1, ... m-1.
+ # Nothing will be done if n >= m-1
+ for k in range(m-2, n-1, -1):
+ if abs(cq[k+1]) > 0:
+ # Powell wrote cq[k+1] != 0 instead of abs. The two differ if cq[k+1] is NaN.
+ # If we apply the rotation below when cq[k+1] = 0, then cq[k] will get updated to |cq[k]|.
+ G = planerot(cq[k:k+2])
+ Q[:, [k, k+1]] = matprod(Q[:, [k, k+1]], G.T)
+ cq[k] = hypot(*cq[k:k+2])
+
+ # Augment n by 1 if C is not in range(A)
+ if n < m:
+ # Powell's condition for the following if: cq[n+1] != 0
+ if abs(cq[n]) > EPS**2 and not isminor(cq[n], cqa[n]):
+ n += 1
+
+ # Update Rdiag so that Rdiag[n] = cq[n] = np.dot(c, q[:, n]). Note that N may be been augmented.
+ if n - 1 >= 0 and n - 1 < m: # n >= m should not happen unless the input is wrong
+ Rdiag[n - 1] = cq[n - 1]
+
+ if DEBUGGING:
+ assert nsave <= n <= min(nsave + 1, m)
+ assert n <= len(Rdiag) <= m
+ assert Q.shape == (m, m)
+
+ return Q, Rdiag, n
+
+
+def qrexc_Rdiag(A, Q, Rdiag, i): # Used in COBYLA
+ '''
+ This function updates the QR factorization for an MxN matrix A=Q@R so that the updated Q and
+ R form a QR factorization of [A_0, ..., A_{I-1}, A_{I+1}, ..., A_{N-1}, A_I] which is the matrix
+ obtained by rearranging columns [I, I+1, ... N-1] of A to [I+1, ..., N-1, I]. Here A is ASSUMED TO
+ BE OF FULL COLUMN RANK, Q is a matrix whose columns are orthogonal, and R, which is not present,
+ is an upper triangular matrix whose diagonal entries are nonzero. Q and R need not be square.
+ N.B.:
+ 0. Instead of R, this function updates Rdiag, which is np.diag(R), the size being n.
+ 1. With L = Q.shape[1] = R.shape[0], we have M >= L >= N. Most often L = M or N.
+ 2. This function changes only Q[:, i:] and Rdiag[i:]
+ 3. (NDB 20230919) In Python, i is either icon or nact - 2, whereas in FORTRAN it is either icon or nact - 1.
+ '''
+
+ # Sizes
+ m, n = A.shape
+
+ # Preconditions
+ assert n >= 1 and n <= m
+ assert i >= 0 and i < n
+ assert len(Rdiag) == n
+ assert Q.shape[0] == m and Q.shape[1] >= n and Q.shape[1] <= m
+ # tol = max(1.0E-8, min(1.0E-1, 1.0E8 * EPS * m + 1))
+ # assert isorth(Q, tol) # Costly!
+
+
+ if i < 0 or i >= n:
+ return Q, Rdiag
+
+ # Let R be the upper triangular matrix in the QR factorization, namely R = Q.T@A.
+ # For each k, find the Givens rotation G with G@(R[k:k+2, :]) = [hypt, 0], and update Q[:, k:k+2]
+ # to Q[:, k:k+2]@(G.T). Then R = Q.T@A is an upper triangular matrix as long as A[:, [k, k+1]] is
+ # updated to A[:, [k+1, k]]. Indeed, this new upper triangular matrix can be obtained by first
+ # updating R[[k, k+1], :] to G@(R[[k, k+1], :]) and then exchanging its columns K and K+1; at the same
+ # time, entries k and k+1 of R's diagonal Rdiag become [hypt, -(Rdiag[k+1]/hypt)*RDiag[k]].
+ # After this is done for each k = 0, ..., n-2, we obtain the QR factorization of the matrix that
+ # rearranges columns [i, i+1, ... n-1] of A as [i+1, ..., n-1, i].
+ # Powell's code, however, is slightly different: before everything, he first exchanged columns k and
+ # k+1 of Q (as well as rows k and k+1 of R). This makes sure that the entries of the update Rdiag
+ # are all positive if it is the case for the original Rdiag.
+ for k in range(i, n-1):
+ G = planerot([Rdiag[k+1], inprod(Q[:, k], A[:, k+1])])
+ Q[:, [k, k+1]] = matprod(Q[:, [k+1, k]], (G.T))
+ # Powell's code updates Rdiag in the following way:
+ # hypt = np.sqrt(Rdiag[k+1]**2 + np.dot(Q[:, k], A[:, k+1])**2)
+ # Rdiag[[k, k+1]] = [hypt, (Rdiag[k+1]/hypt)*Rdiag[k]]
+ # Note that Rdiag[n-1] inherits all rounding in Rdiag[i:n-1] and Q[:, i:n-1] and hence contains
+ # significant errors. Thus we may modify Powell's code to set only Rdiag[k] = hypt here and then
+ # calculate Rdiag[n] by an inner product after the loop. Nevertheless, we simple calculate RDiag
+ # from scratch below.
+
+ # Calculate Rdiag(i:n) from scratch
+ Rdiag[i:n-1] = [inprod(Q[:, k], A[:, k+1]) for k in range(i, n-1)]
+ Rdiag[n-1] = inprod(Q[:, n-1], A[:, i])
+
+ return Q, Rdiag
diff --git a/pyprima/src/pyprima/common/preproc.py b/pyprima/src/pyprima/common/preproc.py
new file mode 100644
index 0000000000..0726b2ae68
--- /dev/null
+++ b/pyprima/src/pyprima/common/preproc.py
@@ -0,0 +1,277 @@
+'''
+This is a module that preprocesses the inputs.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from .consts import DEBUGGING, EPS, IPRINT_DEFAULT, FTARGET_DEFAULT, \
+ MIN_MAXFILT, MAXFILT_DEFAULT, MAXHISTMEM, ETA1_DEFAULT, ETA2_DEFAULT, \
+ GAMMA1_DEFAULT, GAMMA2_DEFAULT, RHOBEG_DEFAULT, RHOEND_DEFAULT, \
+ CTOL_DEFAULT, CWEIGHT_DEFAULT
+from .present import present
+from warnings import warn
+import numpy as np
+
+
+def preproc(solver, num_vars, iprint, maxfun, maxhist, ftarget, rhobeg, rhoend,
+ num_constraints=None, npt=None, maxfilt=None, ctol=None, cweight=None,
+ eta1=None, eta2=None, gamma1=None, gamma2=None, is_constrained=None, has_rhobeg=None,
+ honour_x0=None, xl=None, xu=None, x0=None):
+ '''
+ This subroutine preprocesses the inputs. It does nothing to the inputs that are valid.
+ '''
+ # Preconditions
+ if DEBUGGING:
+ assert num_vars >= 1
+ if present(num_constraints):
+ assert num_constraints >= 0
+ assert num_constraints == 0 or solver.lower() == 'cobyla'
+ if solver.lower() == 'cobyla' and present(num_constraints) and present(is_constrained):
+ assert num_constraints == 0 or is_constrained
+ if solver.lower() == 'bobyqa':
+ assert present(xl) and present(xu)
+ assert all(xu - xl >= 2 * EPS)
+ if present(honour_x0):
+ assert solver.lower() == 'bobyqa' and present(has_rhobeg) and present(xl) and present(xu) and present(x0)
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Read num_constraints, if necessary
+ num_constraints = num_constraints if (present(num_constraints) and solver.lower() == 'cobyla') else 0
+
+ # Decide whether the problem is truly constrained
+ is_constrained = is_constrained if (present(is_constrained)) else num_constraints > 0
+
+ # Validate IPRINT
+ if np.abs(iprint) > 3:
+ iprint = IPRINT_DEFAULT
+ warn(f'{solver}: Invalid IPRINT; it should be 0, 1, -1, 2, -2, 3, or -3; it is set to {iprint}')
+
+ # Validate MAXFUN
+ if solver.lower() == 'uobyqa':
+ min_maxfun = (num_vars + 1) * (num_vars + 2) / 2 + 1
+ min_maxfun_str = '(N+1)(N+2)/2 + 1'
+ elif solver.lower() == 'cobyla':
+ min_maxfun = num_vars + 2
+ min_maxfun_str = 'num_vars + 2'
+ else: # CASE ('NEWUOA', 'BOBYQA', 'LINCOA')
+ min_maxfun = num_vars + 3
+ min_maxfun_str = 'num_vars + 3'
+ if maxfun < min_maxfun:
+ maxfun = min_maxfun
+ warn(f'{solver}: Invalid MAXFUN; it should be at least {min_maxfun_str}; it is set to {maxfun}')
+
+ # Validate MAXHIST
+ if maxhist < 0:
+ maxhist = maxfun
+ warn(f'{solver}: Invalid MAXHIST; it should be a nonnegative integer; it is set to {maxhist}')
+ maxhist = min(maxhist, maxfun) # MAXHIST > MAXFUN is never needed.
+
+ # Validate FTARGET
+ if np.isnan(ftarget):
+ ftarget = FTARGET_DEFAULT
+ warn(f'{solver}: Invalid FTARGET; it should be a real number; it is set to {ftarget}')
+
+ # Validate NPT
+ if (solver.lower() == 'newuoa' or solver.lower() == 'bobyqa' or solver.lower() == 'lincoa') and present(npt):
+ if (npt < num_vars + 2 or npt > min(maxfun - 1, ((num_vars + 2) * (num_vars + 1)) / 2)):
+ npt = int(min(maxfun - 1, 2 * num_vars + 1))
+ warn(f'{solver}: Invalid NPT; it should be an integer in the interval [N+2, (N+1)(N+2)/2] and less than MAXFUN; it is set to {npt}')
+
+ # Validate MAXFILT
+ if present(maxfilt) and (solver.lower() == 'lincoa' or solver.lower() == 'cobyla'):
+ maxfilt_in = maxfilt
+ if maxfilt < 1:
+ maxfilt = MAXFILT_DEFAULT # The inputted MAXFILT is obviously wrong.
+ else:
+ maxfilt = max(MIN_MAXFILT, maxfilt) # The inputted MAXFILT is too small.
+ # Further revise MAXFILT according to MAXHISTMEM.
+ if solver.lower() == 'lincoa':
+ unit_memo = (num_vars + 2) * np.dtype(float).itemsize
+ elif solver.lower() == 'cobyla':
+ unit_memo = (num_constraints + num_vars + 2) * np.dtype(float).itemsize
+ else:
+ unit_memo = 1
+ # We cannot simply set MAXFILT = MIN(MAXFILT, MAXHISTMEM/...), as they may not have
+ # the same kind, and compilers may complain. We may convert them, but overflow may occur.
+ if maxfilt > MAXHISTMEM / unit_memo:
+ maxfilt = int(MAXHISTMEM / unit_memo) # Integer division.
+ maxfilt = min(maxfun, max(MIN_MAXFILT, maxfilt))
+ if is_constrained:
+ if maxfilt_in < 1:
+ warn(f'{solver}: Invalid MAXFILT; it should be a positive integer; it is set to {maxfilt}')
+ elif maxfilt_in < min(maxfun, MIN_MAXFILT):
+ warn(f'{solver}: MAXFILT is too small; it is set to {maxfilt}')
+ elif maxfilt < min(maxfilt_in, maxfun):
+ warn(f'{solver}: MAXFILT is set to {maxfilt} due to memory limit')
+
+ # Validate ETA1 and ETA2
+ eta1_local = eta1 if present(eta1) else ETA1_DEFAULT
+ eta2_local = eta2 if present(eta2) else ETA2_DEFAULT
+
+ # When the difference between ETA1 and ETA2 is tiny, we force them to equal.
+ # See the explanation around RHOBEG and RHOEND for the reason.
+ if present(eta1) and present(eta2):
+ if np.abs(eta1 - eta2) < 1.0E2 * EPS * max(np.abs(eta1), 1):
+ eta2 = eta1
+
+ if present(eta1):
+ if np.isnan(eta1):
+ # In this case, we take the value hard coded in Powell's original code
+ # without any warning. It is useful when interfacing with MATLAB/Python.
+ eta1 = ETA1_DEFAULT
+ elif eta1 < 0 or eta1 >= 1:
+ # Take ETA2 into account if it has a valid value.
+ if present(eta2) and eta2_local > 0 and eta2_local <= 1:
+ eta1 = max(EPS, eta2 / 7.0)
+ else:
+ eta1 = ETA1_DEFAULT
+ warn(f'{solver}: Invalid ETA1; it should be in the interval [0, 1) and not more than ETA2; it is set to {eta1}')
+
+ if present(eta2):
+ if np.isnan(eta2):
+ # In this case, we take the value hard coded in Powell's original code
+ # without any warning. It is useful when interfacing with MATLAB/Python.
+ eta2 = ETA2_DEFAULT
+ elif present(eta1) and (eta2 < eta1_local or eta2 > 1):
+ eta2 = (eta1 + 2) / 3.0
+ warn(f'{solver}: Invalid ETA2; it should be in the interval [0, 1) and not less than ETA1; it is set to {eta2}')
+
+ # Validate GAMMA1 and GAMMA2
+ if present(gamma1):
+ if np.isnan(gamma1):
+ # In this case, we take the value hard coded in Powell's original code
+ # without any warning. It is useful when interfacing with MATLAB/Python.
+ gamma1 = GAMMA1_DEFAULT
+ elif gamma1 <= 0 or gamma1 >= 1:
+ gamma1 = GAMMA1_DEFAULT
+ warn(f'{solver}: Invalid GAMMA1; it should in the interval (0, 1); it is set to {gamma1}')
+
+ if present(gamma2):
+ if np.isnan(gamma2):
+ # In this case, we take the value hard coded in Powell's original code
+ # without any warning. It is useful when interfacing with MATLAB/Python.
+ gamma2 = GAMMA2_DEFAULT
+ elif gamma2 < 1 or np.isinf(gamma2):
+ gamma2 = GAMMA2_DEFAULT
+ warn(f'{solver}: Invalid GAMMA2; it should be a real number not less than 1; it is set to {gamma2}')
+
+ # Validate RHOBEG and RHOEND
+
+ if np.abs(rhobeg - rhoend) < 1.0e2 * EPS * np.maximum(np.abs(rhobeg), 1):
+ # When the data is passed from the interfaces (e.g., MEX) to the Fortran code, RHOBEG, and RHOEND
+ # may change a bit. It was observed in a MATLAB test that MEX passed 1 to Fortran as
+ # 0.99999999999999978. Therefore, if we set RHOEND = RHOBEG in the interfaces, then it may happen
+ # that RHOEND > RHOBEG, which is considered as an invalid input. To avoid this situation, we
+ # force RHOBEG and RHOEND to equal when the difference is tiny.
+ rhoend = rhobeg
+
+ # Revise the default values for RHOBEG/RHOEND according to the solver.
+ if solver.lower() == 'bobyqa':
+ rhobeg_default = np.maximum(EPS, np.min(RHOBEG_DEFAULT, np.min(xu - xl) / 4.0))
+ rhoend_default = np.maximum(EPS, np.min(0.1 * rhobeg_default, RHOEND_DEFAULT))
+ else:
+ rhobeg_default = RHOBEG_DEFAULT
+ rhoend_default = RHOEND_DEFAULT
+
+ if solver.lower() == 'bobyqa':
+ # Do NOT merge the IF below into the ELIF above! Otherwise, XU and XL may be accessed even if
+ # the solver is not BOBYQA, because the logical evaluation is not short-circuit.
+ if rhobeg > np.min(xu - xl) / 2:
+ # Do NOT make this revision if RHOBEG not positive or not finite, because otherwise RHOBEG
+ # will get a huge value when XU or XL contains huge values that indicate unbounded variables.
+ rhobeg = np.min(xu - xl) / 4.0 # Here, we do not take RHOBEG_DEFAULT.
+ warn(f'{solver}: Invalid RHOBEG; {solver} requires 0 < RHOBEG <= np.min(XU-XL)/2; it is set to np.min(XU-XL)/4')
+ if rhobeg <= 0 or np.isnan(rhobeg) or np.isinf(rhobeg):
+ # Take RHOEND into account if it has a valid value. We do not do this if the solver is BOBYQA,
+ # which requires that RHOBEG <= (XU-XL)/2.
+ if np.isfinite(rhoend) and rhoend > 0 and solver.lower() != 'bobyqa':
+ rhobeg = max(10 * rhoend, rhobeg_default)
+ else:
+ rhobeg = rhobeg_default
+ warn(f'{solver}: Invalid RHOBEG; it should be a positive number; it is set to {rhobeg}')
+
+ if rhoend <= 0 or rhobeg < rhoend or np.isnan(rhoend) or np.isinf(rhoend):
+ rhoend = max(EPS, min(0.1 * rhobeg, rhoend_default))
+ warn(f'{solver}: Invalid RHOEND; it should be a positive number and RHOEND <= RHOBEG; it is set to {rhoend}')
+
+ # For BOBYQA, revise X0 or RHOBEG so that the distance between X0 and the inactive bounds is at
+ # least RHOBEG. If HONOUR_X0 == TRUE, revise RHOBEG if needed; otherwise, revise HONOUR_X0 if needed.
+ if present(honour_x0):
+ if honour_x0:
+ rhobeg_in = rhobeg;
+ lbx = np.isfinite(xl) & (x0 - xl <= EPS * np.maximum(1, np.abs(xl))) # X0 essentially equals XL
+ ubx = np.isfinite(xu) & (x0 - xu >= -EPS * np.maximum(1, np.abs(xu))) # X0 essentially equals XU
+ x0[lbx] = xl[lbx]
+ x0[ubx] = xu[ubx]
+ rhobeg = max(EPS, np.min([rhobeg, x0[~lbx] - xl[~lbx], xu[~ubx] - x0[~ubx]]))
+ if rhobeg_in - rhobeg > EPS * max(1, rhobeg_in):
+ rhoend = max(EPS, min(0.1 * rhobeg, rhoend)) # We do not revise RHOEND unless RHOBEG is truly revised.
+ if has_rhobeg:
+ warn(f'{solver}: RHOBEG is revised to {rhobeg} and RHOEND to at most 0.1*RHOBEG so that the distance between X0 and the inactive bounds is at least RHOBEG')
+ else:
+ rhoend = np.minimum(rhoend, rhobeg) # This may update RHOEND slightly.
+ else:
+ x0_in = x0 # Recorded to see whether X0 is really revised.
+ # N.B.: The following revision is valid only if XL <= X0 <= XU and RHOBEG <= MINVAL(XU-XL)/2,
+ # which should hold at this point due to the revision of RHOBEG and moderation of X0.
+ # The cases below are mutually exclusive in precise arithmetic as MINVAL(XU-XL) >= 2*RHOBEG.
+ lbx = x0 <= xl + 0.5 * rhobeg
+ lbx_plus = (x0 > xl + 0.5 * rhobeg) & (x0 < xl + rhobeg)
+ ubx = x0 >= xu - 0.5 * rhobeg
+ ubx_minus = (x0 < xu - 0.5 * rhobeg) & (x0 > xu - rhobeg)
+ x0[lbx] = xl[lbx]
+ x0[lbx_plus] = xl[lbx_plus] + rhobeg
+ x0[ubx] = xu[ubx]
+ x0[ubx_minus] = xu[ubx_minus] - rhobeg
+
+ if (any(np.abs(x0_in - x0) > 0)):
+ warn(f'{solver}: X0 is revised so that the distance between X0 and the inactive bounds is at least RHOBEG set HONOUR_X0 to .TRUE. if you prefer to keep X0 unchanged')
+
+ # Validate CTOL (it can be 0)
+ if (present(ctol)):
+ if (np.isnan(ctol) or ctol < 0):
+ ctol = CTOL_DEFAULT
+ if (is_constrained):
+ warn(f'{solver}: Invalid CTOL; it should be a nonnegative number; it is set to {ctol}')
+
+ # Validate CWEIGHT (it can be +Inf)
+ if (present(cweight)):
+ if (np.isnan(cweight) or cweight < 0):
+ cweight = CWEIGHT_DEFAULT
+ if (is_constrained):
+ warn(f'{solver}: Invalid CWEIGHT; it should be a nonnegative number; it is set to {cweight}')
+
+ #====================#
+ # Calculation ends #
+ #====================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert abs(iprint) <= 3
+ assert maxhist >= 0 and maxhist <= maxfun
+ if present(npt):
+ assert maxfun >= npt + 1
+ assert npt >= 3
+ if present(maxfilt):
+ assert maxfilt >= np.minimum(MIN_MAXFILT, maxfun) and maxfilt <= maxfun
+ if present(eta1) and present(eta2):
+ assert eta1 >= 0 and eta1 <= eta2 and eta2 < 1
+ if present(gamma1) and present(gamma2):
+ assert gamma1 > 0 and gamma1 < 1 and gamma2 > 1
+ assert rhobeg >= rhoend and rhoend > 0
+ if solver.lower() == 'bobyqa':
+ assert all(rhobeg <= (xu - xl) / 2)
+ assert all(np.isfinite(x0))
+ assert all(x0 >= xl and (x0 <= xl or x0 >= xl + rhobeg))
+ assert all(x0 <= xu and (x0 >= xu or x0 <= xu - rhobeg))
+ if present(ctol):
+ assert ctol >= 0
+
+ return iprint, maxfun, maxhist, ftarget, rhobeg, rhoend, npt, maxfilt, ctol, cweight, eta1, eta2, gamma1, gamma2, x0
diff --git a/pyprima/src/pyprima/common/present.py b/pyprima/src/pyprima/common/present.py
new file mode 100644
index 0000000000..15f8b47b67
--- /dev/null
+++ b/pyprima/src/pyprima/common/present.py
@@ -0,0 +1,5 @@
+def present(x):
+ '''
+ This is a Python equivalent of the Fortran 'present' function for optional arguments.
+ '''
+ return x is not None
\ No newline at end of file
diff --git a/pyprima/src/pyprima/common/ratio.py b/pyprima/src/pyprima/common/ratio.py
new file mode 100644
index 0000000000..c1c3a28ef4
--- /dev/null
+++ b/pyprima/src/pyprima/common/ratio.py
@@ -0,0 +1,54 @@
+'''
+This module calculates the reduction ratio for trust-region methods.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from .consts import DEBUGGING, REALMAX
+import numpy as np
+
+def redrat(ared, pred, rshrink):
+ '''
+ This function evaluates the reduction ratio of a trust-region step, handling inf/nan properly.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert rshrink >= 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ if np.isnan(ared):
+ # This should not happen in unconstrained problems due to the moderated extreme barrier.
+ ratio = -REALMAX
+ elif np.isnan(pred) or pred <= 0:
+ # The trust-region subproblem solver fails in this rare case. Instead of terminating as Powell's
+ # original code does, we set ratio as follows so that the solver may continue to progress.
+ if ared > 0:
+ # The trial point will be accepted, but the trust-region radius will be shrunk if rshrink>0
+ ratio = rshrink/2
+ else:
+ # Set the ration to a large negative number to signify a bad trust-region step, so that the
+ # solver will check whether to take a geometry step or reduce rho.
+ ratio = -REALMAX
+ elif np.isposinf(pred) and np.isposinf(ared):
+ ratio = 1 # ared/pred = NaN if calculated directly
+ elif np.isposinf(pred) and np.isneginf(ared):
+ ratio = -REALMAX # ared/pred = NaN if calculated directly
+ else:
+ ratio = ared/pred
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert not np.isnan(ratio)
+ return ratio
diff --git a/pyprima/src/pyprima/common/redrho.py b/pyprima/src/pyprima/common/redrho.py
new file mode 100644
index 0000000000..4b4c09b840
--- /dev/null
+++ b/pyprima/src/pyprima/common/redrho.py
@@ -0,0 +1,47 @@
+'''
+This module provides a function that calculates RHO when it needs to be reduced.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+from .consts import DEBUGGING
+import numpy as np
+
+def redrho(rho_in, rhoend):
+ '''
+ This function calculates RHO when it needs to be reduced.
+ The scheme is shared by UOBYQA, NEWUOA, BOBYQA, LINCOA. For COBYLA, Powell's code reduces RHO by
+ 'RHO *= 0.5; if RHO <= 1.5 * RHOEND: RHO = RHOEND' as specified in (11) of the COBYLA
+ paper. However, this scheme seems to work better, especially after we introduce DELTA.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert rho_in > rhoend > 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ rho_ratio = rho_in / rhoend
+
+ if rho_ratio > 250:
+ rho = 0.1 * rho_in
+ elif rho_ratio <= 16:
+ rho = rhoend
+ else:
+ rho = np.sqrt(rho_ratio) * rhoend # rho = np.sqrt(rho * rhoend)
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert rho_in > rho >= rhoend
+
+ return rho
diff --git a/pyprima/src/pyprima/common/selectx.py b/pyprima/src/pyprima/common/selectx.py
new file mode 100644
index 0000000000..63d76ad709
--- /dev/null
+++ b/pyprima/src/pyprima/common/selectx.py
@@ -0,0 +1,296 @@
+'''
+This module provides subroutines that ensure the returned X is optimal among all the calculated
+points in the sense that no other point achieves both lower function value and lower constraint
+violation at the same time. This module is needed only in the constrained case.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+import numpy.typing as npt
+from .consts import DEBUGGING, EPS, CONSTRMAX, REALMAX, FUNCMAX
+from .present import present
+
+def isbetter(f1: float, c1: float, f2: float, c2: float, ctol: float) -> bool:
+ '''
+ This function compares whether FC1 = (F1, C1) is (strictly) better than FC2 = (F2, C2), which
+ basically means that (F1 < F2 and C1 <= C2) or (F1 <= F2 and C1 < C2).
+ It takes care of the cases where some of these values are NaN or Inf, even though some cases
+ should never happen due to the moderated extreme barrier.
+ At return, BETTER = TRUE if and only if (F1, C1) is better than (F2, C2).
+ Here, C means constraint violation, which is a nonnegative number.
+ '''
+
+ # Preconditions
+ if DEBUGGING:
+ assert not any(np.isnan([f1, c1]) | np.isposinf([f2, c2]))
+ assert not any(np.isnan([f2, c2]) | np.isposinf([f2, c2]))
+ assert c1 >= 0 and c2 >= 0
+ assert ctol >= 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ is_better = False
+ # Even though NaN/+Inf should not occur in FC1 or FC2 due to the moderated extreme barrier, for
+ # security and robustness, the code below does not make this assumption.
+ is_better = is_better or (any(np.isnan([f1, c1]) | np.isposinf([f1, c1])) and not any(np.isnan([f2, c2]) | np.isposinf([f2, c2])))
+
+ is_better = is_better or (f1 < f2 and c1 <= c2)
+ is_better = is_better or (f1 <= f2 and c1 < c2)
+
+ # If C1 <= CTOL and C2 is significantly larger/worse than CTOL, i.e., C2 > MAX(CTOL,CREF),
+ # then FC1 is better than FC2 as long as F1 < REALMAX. Normally CREF >= CTOL so MAX(CTOL, CREF)
+ # is indeed CREF. However, this may not be true if CTOL > 1E-1*CONSTRMAX.
+ cref = 10 * max(EPS, min(ctol, 1.0E-2 * CONSTRMAX)) # The MIN avoids overflow.
+ is_better = is_better or (f1 < REALMAX and c1 <= ctol and (c2 > max(ctol, cref) or np.isnan(c2)))
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert not (is_better and f1 >= f2 and c1 >= c2)
+ assert is_better or not (f1 <= f2 and c1 < c2)
+ assert is_better or not (f1 < f2 and c1 <= c2)
+
+ return is_better
+
+
+def savefilt(cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr=None, confilt=None):
+ '''
+ This subroutine saves X, F, and CSTRV in XFILT, FFILT, and CFILT (and CONSTR in CONFILT
+ if they are present), unless a vector in XFILT[:, :NFILT] is better than X.
+ If X is better than some vectors in XFILT[:, :NFILT] then these vectors will be
+ removed. If X is not better than any of XFILT[:, :NFILT], but NFILT == MAXFILT,
+ then we remove a column from XFILT according to the merit function
+ PHI = FFILT + CWEIGHT * max(CFILT - CTOL, 0)
+ N.B.:
+ 1. Only XFILT[:, :NFILT] and FFILT[:, :NFILT] etc contains valid information,
+ while XFILT[:, NFILT+1:MAXFILT] and FFILT[:, NFILT+1:MAXFILT] etc are not
+ initialized yet.
+ 2. We decide whether and X is better than another by the ISBETTER function
+ '''
+
+ # Sizes
+ if present(constr):
+ num_constraints = len(constr)
+ else:
+ num_constraints = 0
+ num_vars = len(x)
+ maxfilt = len(ffilt)
+
+ # Preconditions
+ if DEBUGGING:
+ # Check the size of X.
+ assert num_vars >= 1
+ # Check CWEIGHT and CTOL
+ assert cweight >= 0
+ assert ctol >= 0
+ # Check NFILT
+ assert nfilt >= 0 and nfilt <= maxfilt
+ # Check the sizes of XFILT, FFILT, CFILT.
+ assert maxfilt >= 1
+ assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
+ assert np.size(cfilt) == maxfilt
+ # Check the values of XFILT, FFILT, CFILT.
+ assert not (np.isnan(xfilt[:, :nfilt])).any()
+ assert not any(np.isnan(ffilt[:nfilt]) | np.isposinf(ffilt[:nfilt]))
+ assert not any(cfilt[:nfilt] < 0 | np.isnan(cfilt[:nfilt]) | np.isposinf(cfilt[:nfilt]))
+ # Check the values of X, F, CSTRV.
+ # X does not contain NaN if X0 does not and the trust-region/geometry steps are proper.
+ assert not any(np.isnan(x))
+ # F cannot be NaN/+Inf due to the moderated extreme barrier.
+ assert not (np.isnan(f) | np.isposinf(f))
+ # CSTRV cannot be NaN/+Inf due to the moderated extreme barrier.
+ assert not (cstrv < 0 | np.isnan(cstrv) | np.isposinf(cstrv))
+ # Check CONSTR and CONFILT.
+ assert present(constr) == present(confilt)
+ if present(constr):
+ # CONSTR cannot contain NaN/-Inf due to the moderated extreme barrier.
+ assert not any(np.isnan(constr) | np.isneginf(constr))
+ assert np.size(confilt, 0) == num_constraints and np.size(confilt, 1) == maxfilt
+ assert not (np.isnan(confilt[:, :nfilt]) | np.isneginf(confilt[:, :nfilt])).any()
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # Return immediately if any column of XFILT is better than X.
+ if any((isbetter(ffilt_i, cfilt_i, f, cstrv, ctol) for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt]))) or \
+ any(np.logical_and(ffilt[:nfilt] <= f, cfilt[:nfilt] <= cstrv)):
+ return nfilt, cfilt, ffilt, xfilt, confilt
+
+ # Decide which columns of XFILT to keep.
+ keep = np.logical_not([isbetter(f, cstrv, ffilt_i, cfilt_i, ctol) for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])])
+
+ # If NFILT == MAXFILT and X is not better than any column of XFILT, then we remove the worst column
+ # of XFILT according to the merit function PHI = FFILT + CWEIGHT * MAX(CFILT - CTOL, ZERO).
+ if sum(keep) == maxfilt: # In this case, NFILT = SIZE(KEEP) = COUNT(KEEP) = MAXFILT > 0.
+ cfilt_shifted = np.maximum(cfilt - ctol, 0)
+ if cweight <= 0:
+ phi = ffilt
+ elif np.isposinf(cweight):
+ phi = cfilt_shifted
+ # We should not use CFILT here; if MAX(CFILT_SHIFTED) is attained at multiple indices, then
+ # we will check FFILT to exhaust the remaining degree of freedom.
+ else:
+ phi = np.maximum(ffilt, -REALMAX)
+ phi = np.nan_to_num(phi, nan=-REALMAX) # Replace NaN with -REALMAX and +/- inf with large numbers
+ phi += cweight * cfilt_shifted
+ # We select X to maximize PHI. In case there are multiple maximizers, we take the one with the
+ # largest CSTRV_SHIFTED; if there are more than one choices, we take the one with the largest F;
+ # if there are several candidates, we take the one with the largest CSTRV; if the last comparison
+ # still leads to more than one possibilities, then they are equally bad and we choose the first.
+ # N.B.:
+ # 1. This process is the opposite of selecting KOPT in SELECTX.
+ # 2. In finite-precision arithmetic, PHI_1 == PHI_2 and CSTRV_SHIFTED_1 == CSTRV_SHIFTED_2 do
+ # not ensure that F_1 == F_2!
+ phimax = max(phi)
+ cref = max(cfilt_shifted[phi >= phimax])
+ fref = max(ffilt[cfilt_shifted >= cref])
+ kworst = np.ma.array(cfilt, mask=(ffilt > fref)).argmax()
+ if kworst < 0 or kworst >= len(keep): # For security. Should not happen.
+ kworst = 0
+ keep[kworst] = False
+
+ # Keep the good xfilt values and remove all the ones that are strictly worse than the new x.
+ nfilt = sum(keep)
+ index_to_keep = np.where(keep)[0]
+ xfilt[:, :nfilt] = xfilt[:, index_to_keep]
+ ffilt[:nfilt] = ffilt[index_to_keep]
+ cfilt[:nfilt] = cfilt[index_to_keep]
+ if confilt is not None and constr is not None:
+ confilt[:, :nfilt] = confilt[:, index_to_keep]
+
+ # Once we have removed all the vectors that are strictly worse than x,
+ # we add x to the filter.
+ xfilt[:, nfilt] = x
+ ffilt[nfilt] = f
+ cfilt[nfilt] = cstrv
+ if confilt is not None and constr is not None:
+ confilt[:, nfilt] = constr
+ nfilt += 1 # In Python we need to increment the index afterwards
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ # Check NFILT and the sizes of XFILT, FFILT, CFILT.
+ assert nfilt >= 1 and nfilt <= maxfilt
+ assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
+ assert np.size(ffilt) == maxfilt
+ assert np.size(cfilt) == maxfilt
+ # Check the values of XFILT, FFILT, CFILT.
+ assert not (np.isnan(xfilt[:, :nfilt])).any()
+ assert not any(np.isnan(ffilt[:nfilt]) | np.isposinf(ffilt[:nfilt]))
+ assert not any(cfilt[:nfilt] < 0 | np.isnan(cfilt[:nfilt]) | np.isposinf(cfilt[:nfilt]))
+ # Check that no point in the filter is better than X, and X is better than no point.
+ assert not any([isbetter(ffilt_i, cfilt_i, f, cstrv, ctol) for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])])
+ assert not any([isbetter(f, cstrv, ffilt_i, cfilt_i, ctol) for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])])
+ # Check CONFILT.
+ if present(confilt):
+ assert np.size(confilt, 0) == num_constraints and np.size(confilt, 1) == maxfilt
+ assert not (np.isnan(confilt[:, :nfilt]) | np.isneginf(confilt[:, :nfilt])).any()
+
+
+ return nfilt, cfilt, ffilt, xfilt, confilt
+
+
+def selectx(fhist: npt.NDArray, chist: npt.NDArray, cweight: float, ctol: float):
+ '''
+ This subroutine selects X according to FHIST and CHIST, which represents (a part of) history
+ of F and CSTRV. Normally, FHIST and CHIST are not the full history but only a filter, e.g. ffilt
+ and CFILT generated by SAVEFILT. However, we name them as FHIST and CHIST because the [F, CSTRV]
+ in a filter should not dominate each other, but this subroutine does NOT assume such a property.
+ N.B.: CTOL is the tolerance of the constraint violation (CSTRV). A point is considered feasible if
+ its constraint violation is at most CTOL. Not that CTOL is absolute, not relative.
+ '''
+
+ # Sizes
+ nhist = len(fhist)
+
+ # Preconditions
+ if DEBUGGING:
+ assert nhist >= 1
+ assert np.size(chist) == nhist
+ assert not any(np.isnan(fhist) | np.isposinf(fhist))
+ assert not any(chist < 0 | np.isnan(chist) | np.isposinf(chist))
+ assert cweight >= 0
+ assert ctol >= 0
+
+ #====================#
+ # Calculation starts #
+ #====================#
+
+ # We select X among the points with F < FREF and CSTRV < CREF.
+ # Do NOT use F <= FREF, because F == FREF (FUNCMAX or REALMAX) may mean F == INF in practice!
+ if any(np.logical_and(fhist < FUNCMAX, chist < CONSTRMAX)):
+ fref = FUNCMAX
+ cref = CONSTRMAX
+ elif any(np.logical_and(fhist < REALMAX, chist < CONSTRMAX)):
+ fref = REALMAX
+ cref = CONSTRMAX
+ elif any(np.logical_and(fhist < FUNCMAX, chist < REALMAX)):
+ fref = FUNCMAX
+ cref = REALMAX
+ else:
+ fref = REALMAX
+ cref = REALMAX
+
+ if not any(np.logical_and(fhist < fref, chist < cref)):
+ kopt = nhist - 1
+ else:
+ # Shift the constraint violations by ctol, so that cstrv <= ctol is regarded as no violation.
+ chist_shifted = np.maximum(chist - ctol, 0)
+ # cmin is the minimal shift constraint violation attained in the history.
+ cmin = np.min(chist_shifted[fhist < fref])
+ # We consider only the points whose shifted constraint violations are at most the cref below.
+ # N.B.: Without taking np.maximum(EPS, .), cref would be 0 if cmin = 0. In that case, asking for
+ # cstrv_shift < cref would be WRONG!
+ cref = np.maximum(EPS, 2*cmin)
+ # We use the following phi as our merit function to select X.
+ if cweight <= 0:
+ phi = fhist
+ elif np.isposinf(cweight):
+ phi = chist_shifted
+ # We should not use chist here; if np.minimum(chist_shifted) is attained at multiple indices, then
+ # we will check fhist to exhaust the remaining degree of freedom.
+ else:
+ phi = np.maximum(fhist, -REALMAX) + cweight * chist_shifted
+ # np.maximum(fhist, -REALMAX) makes sure that phi will not contain NaN (unless there is a bug).
+
+ # We select X to minimize phi subject to f < fref and cstrv_shift <= cref (see the comments
+ # above for the reason of taking "<" and "<=" in these two constraints). In case there are
+ # multiple minimizers, we take the one with the least cstrv_shift; if there is more than one
+ # choice, we take the one with the least f; if there are several candidates, we take the one
+ # with the least cstrv; if the last comparison still leads to more than one possibility, then
+ # they are equally good and we choose the first.
+ # N.B.:
+ # 1. This process is the opposite of selecting kworst in savefilt
+ # 2. In finite-precision arithmetic, phi_2 == phi_2 and cstrv_shift_1 == cstrv_shifted_2 do
+ # not ensure thatn f_1 == f_2!
+ phimin = np.min(phi[np.logical_and(fhist < fref, chist_shifted <= cref)])
+ cref = np.min(chist_shifted[np.logical_and(fhist < fref, phi <= phimin)])
+ fref = np.min(fhist[chist_shifted <= cref])
+ # Can't use argmin here because using it with a mask throws off the index
+ kopt = np.ma.array(chist, mask=(fhist > fref)).argmin()
+
+ #==================#
+ # Calculation ends #
+ #==================#
+
+ # Postconditions
+ if DEBUGGING:
+ assert kopt >= 0 and kopt < nhist
+ assert not any([isbetter(fhisti, chisti, fhist[kopt], chist[kopt], ctol) for fhisti, chisti in zip(fhist[:nhist], chist[:nhist])])
+
+ return kopt
diff --git a/pyprima/src/pyprima/examples/cobyla/cobyla_example.py b/pyprima/src/pyprima/examples/cobyla/cobyla_example.py
new file mode 100644
index 0000000000..6d04cbb6b8
--- /dev/null
+++ b/pyprima/src/pyprima/examples/cobyla/cobyla_example.py
@@ -0,0 +1,86 @@
+'''
+This is an example to illustrate the usage of the solver.
+
+Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
+
+Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
+
+Python translation by Nickolai Belakovski.
+'''
+
+import numpy as np
+from pyprima.cobyla.cobyla import cobyla
+
+
+def test_chebyquad():
+ f, _ = calcfc_chebyquad([1, 2])
+ assert np.isclose(f, 91+1/9, atol=1e-6)
+
+
+def calcfc_chebyquad(x):
+ x = np.array(x)
+ n = len(x) # or shape?
+ y = np.zeros((n + 1, n + 1))
+ y[:n, 0] = 1
+ y[:n, 1] = 2*x - 1
+ for i in range(1, n):
+ y[:n, i+1] = 2*y[:n, 1] * y[:n, i] - y[:n, i - 1]
+
+ f = 0
+ for i in range(n+1):
+ tmp = sum(y[0:n, i]) / n
+ if i % 2 == 0:
+ tmp += 1/((i)**2 - 1)
+ f += tmp**2
+ constr = np.zeros(0)
+ return f, constr
+
+def calcfc_hexagon(x):
+ # Test problem 10 in Powell's original algorithm
+
+ assert len(x) == 9
+
+ f = -0.5 * (x[0] * x[3] - x[1] * x[2] + x[2] * x[8] - x[4] * x[8] + x[4] * x[7] - x[5] * x[6])
+ constr = np.zeros(14)
+ constr[0] = -1 + x[2]**2 + x[3]**2
+ constr[1] = -1 + x[8]**2
+ constr[2] = -1 + x[4]**2 + x[5]**2
+ constr[3] = -1 + x[0]**2 + (x[1] - x[8])**2
+ constr[4] = -1 + (x[0] - x[4])**2 + (x[1] - x[5])**2
+ constr[5] = -1 + (x[0] - x[6])**2 + (x[1] - x[7])**2
+ constr[6] = -1 + (x[2] - x[4])**2 + (x[3] - x[5])**2
+ constr[7] = -1 + (x[2] - x[6])**2 + (x[3] - x[7])**2
+ constr[8] = -1 + x[6]**2 + (x[7] - x[8])**2
+ constr[9] = -x[0] * x[3] + x[1] * x[2]
+ constr[10] = -x[2] * x[8]
+ constr[11] = -x[4] * x[8]
+ constr[12] = -x[4] * x[7] + x[5] * x[6]
+ constr[13] = -x[8]
+
+ return f, constr
+
+
+if __name__ == "__main__":
+ n_chebyquad = 6
+
+ # The following lines illustrates how to call the solver to solve the Chebyquad problem.
+ x_chebyquad = np.array([i/(n_chebyquad+1) for i in range(1, n_chebyquad+1)]) # Starting point
+ m = 0 # Dimension of constraints. M must be specified correctly, or the program will crash!
+ result = cobyla(calcfc_chebyquad, m, x_chebyquad) # This call will not print anything
+
+ # In addition to the compulsory arguments, the following illustration specifies also CONSTR, RHOBEG,
+ # and IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+ # take their default values coded in the solver.
+ x_chebyquad = np.array([i/(n_chebyquad+1) for i in range(1, n_chebyquad+1)]) # Starting point
+ result = cobyla(calcfc_chebyquad, m, x_chebyquad, rhobeg=0.2 * x_chebyquad[0], iprint=1)
+
+ # The following lines illustrates how to call the solver to solve the Hexagon problem.
+ x_hexagon = np.zeros(9) + 2
+ m_hexagon = 14 # Dimension of constraints. M must the specified correctly, or the program will crash!
+ result = cobyla(calcfc_hexagon, m_hexagon, x_hexagon) # This call will not print anything.
+
+ # In addition to the compulsory arguments, the following illustration specifies also CONSTR, RHOBEG,
+ # and IPRINT, which are optional. All the unspecified optional arguments (RHOEND, MAXFUN, etc.) will
+ # take their default values coded in the solver. Note that CONSTR is an output, which will be set to
+ # the value of CONSTR(X_HEXAGON) when the solver returns.
+ result = cobyla(calcfc_hexagon, m_hexagon, x_hexagon, rhobeg=1.0, iprint=1)
diff --git a/pyprima/tests/conftest.py b/pyprima/tests/conftest.py
new file mode 100644
index 0000000000..ac25923992
--- /dev/null
+++ b/pyprima/tests/conftest.py
@@ -0,0 +1,20 @@
+import os
+import pytest
+import sys
+
+@pytest.fixture(scope='function')
+def minimize_with_debugging():
+ # This is a hack to force us to use DEBUGGING for a test, so that we get
+ # code coverage. We definitely don't want to do this for the pycutest tests,
+ # they are slow enough with USE_NAIVE_MATH set to True.
+ os.environ['PRIMA_DEBUGGING'] = "True"
+ if 'pyprima' in sys.modules:
+ modules_to_delete = [m for m in sys.modules if 'pyprima' in m]
+ for m in modules_to_delete:
+ del sys.modules[m]
+ # We have to reimport minimize here, if the tests try to use the minimize that
+ # was imported at the top of the file, it will be the existing object which
+ # captured DEBUGGING=False.
+ from pyprima import minimize
+ yield minimize
+ del os.environ['PRIMA_DEBUGGING']
\ No newline at end of file
diff --git a/pyprima/tests/requirements.txt b/pyprima/tests/requirements.txt
new file mode 100644
index 0000000000..264f4a8b15
--- /dev/null
+++ b/pyprima/tests/requirements.txt
@@ -0,0 +1,5 @@
+numpy
+pycutest
+pytest
+pytest-cov
+pytest-order
\ No newline at end of file
diff --git a/pyprima/tests/test_bounds.py b/pyprima/tests/test_bounds.py
new file mode 100644
index 0000000000..1c4f8b3a25
--- /dev/null
+++ b/pyprima/tests/test_bounds.py
@@ -0,0 +1,136 @@
+from pyprima import minimize, LinearConstraint as LC, NonlinearConstraint as NLC
+import numpy as np
+import pytest
+
+def test_eliminate_fixed_bounds():
+ # Test the logic for detecting and eliminating fixed bounds
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, 1, None, -0.5]
+ ub = [-0.5, -0.5, None, None, -0.5]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ res = minimize(f, x0=np.array([1, 2, 3, 4, 5]), bounds=bounds)
+ assert np.allclose(res.x, np.array([-0.5, -0.5, 1, 0, -0.5]), atol=1e-3)
+ assert np.allclose(res.f, 1.75, atol=1e-3)
+
+
+def test_eliminate_fixed_bounds_with_linear_constraints():
+ # Ensure that the logic for fixed bounds also modifies linear constraints
+ # appropriately
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ # Internally, the algorithm should modify lc to be a 1x2 matrix instead of 1x3,
+ # since it will modify x0 and the objective function to eliminate the first
+ # variable. If it fails to modify lc, we will get shape mismatch errors.
+ lc = LC(np.array([1, 1, 1]), lb=9, ub=15)
+ res = minimize(f, x0=np.array([1, 1, 1]), constraints=lc, bounds=bounds)
+ assert np.isclose(res.x[0], -1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[2], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.f, 51, atol=1e-6, rtol=1e-6)
+
+
+def test_eliminate_fixed_bounds_with_nonlinear_constraints():
+ # Ensure that the logic for fixed bounds also modifies the nonlinear constraint
+ # function appropriately
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ x0 = np.array([1, 1, 1])
+ # Have the nonlinear constraint function operate on the last element of x, but be
+ # explicit about the length of x. This ensures that the test is still valid if the
+ # fixed bound is removed. If we simply used x[-1] this test would pass but it
+ # wouldn't actually test if we had properly modified the nonlinear constraint
+ # function after removing the fixed bounds
+ nlc = NLC(lambda x: x[len(x0)-1]**2, lb=9, ub=15)
+ res = minimize(f, x0=x0, constraints=nlc, bounds=bounds)
+ assert np.isclose(res.x[0], -1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 0, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[2], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.f, 10, atol=1e-6, rtol=1e-6)
+
+
+def test_infeasible_bounds():
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ with pytest.raises(AssertionError) as excinfo:
+ minimize(f, x0=np.array([1, 2, 3]), bounds=bounds)
+ assert str(excinfo.value) == "Some of the provided bounds are infeasible. infeasible=array([ True, False, False]) lb[infeasible]=array([1.]), ub[infeasible]=array([-1.])"
+
+
+def test_infeasible_bounds_nan():
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [np.nan, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ with pytest.raises(AssertionError) as excinfo:
+ minimize(f, x0=np.array([1, 2, 3]), bounds=bounds)
+ assert str(excinfo.value) == "Some of the provided bounds are infeasible. infeasible=array([ True, False, False]) lb[infeasible]=array([nan]), ub[infeasible]=array([-1.])"
+
+
+@pytest.mark.parametrize('with_Aineq', [False, True])
+@pytest.mark.parametrize('with_Aeq', [False, True])
+@pytest.mark.parametrize('with_nlcon', [False, True])
+def test_all_fixed(with_nlcon, with_Aeq, with_Aineq):
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, 2, 4]
+ ub = [-1, 2, 4]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ nlc = NLC(lambda x: x[2]**2, lb=-np.inf, ub=0)
+ lc_eq = LC([0, 0, 1], lb=21, ub=21)
+ lc_ineq = LC([0, 0, 1], lb=22, ub=23)
+ constraints = []
+ if with_nlcon:
+ constraints.append(nlc)
+ if with_Aeq:
+ constraints.append(lc_eq)
+ if with_Aineq:
+ constraints.append(lc_ineq)
+
+ result = minimize(f, x0=np.array([1, 2, 3]), bounds=bounds, constraints=constraints)
+ assert all(result.x == [-1, 2, 4])
+ assert result.f == 21
+ assert result.nf == 1
+ if not with_nlcon and not with_Aeq and not with_Aineq:
+ assert np.array_equal(result.constr, [])
+ assert result.cstrv == 0
+ if not with_nlcon and not with_Aeq and with_Aineq:
+ assert np.array_equal(result.constr, [])
+ assert result.cstrv == 18
+ if not with_nlcon and with_Aeq and not with_Aineq:
+ assert np.array_equal(result.constr, [])
+ assert result.cstrv == 17
+ if not with_nlcon and with_Aeq and with_Aineq:
+ assert np.array_equal(result.constr, [])
+ assert result.cstrv == 18
+ if with_nlcon and not with_Aeq and not with_Aineq:
+ assert np.array_equal(result.constr, [16])
+ assert result.cstrv == 16
+ if with_nlcon and not with_Aeq and with_Aineq:
+ assert np.array_equal(result.constr, [16])
+ assert result.cstrv == 18
+ if with_nlcon and with_Aeq and not with_Aineq:
+ assert np.array_equal(result.constr, [16])
+ assert result.cstrv == 17
+ if with_nlcon and with_Aeq and with_Aineq:
+ assert np.array_equal(result.constr, [16])
+ assert result.cstrv == 18
diff --git a/pyprima/tests/test_end_to_end.py b/pyprima/tests/test_end_to_end.py
new file mode 100644
index 0000000000..5122384596
--- /dev/null
+++ b/pyprima/tests/test_end_to_end.py
@@ -0,0 +1,36 @@
+from pyprima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+import numpy as np
+
+def obj(x):
+ return (x[0] - 1)**2 + (x[1] - 2.5)**2
+obj.x0 = np.array([5, 5])
+obj.optimal = np.array([1, 2.5])
+
+
+def test_end_to_end_no_constraints():
+ result = minimize(obj, obj.x0, method='cobyla')
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_end_to_end_bounds():
+ bounds = Bounds([-5, 10], [5, 10])
+ result = minimize(obj, obj.x0, method='cobyla', bounds=bounds)
+ assert np.allclose(result.x, np.array([1, 10]), atol=1e-3)
+
+
+def test_end_to_end_linear_constraints(minimize_with_debugging):
+ # x1 + x2 = 5
+ A = np.array([[1, 1]])
+ b = np.array([5])
+ result = minimize_with_debugging(obj, obj.x0, method='cobyla', constraints=[LinearConstraint(A, b, b)])
+ assert np.allclose(result.x, np.array([1.75, 3.25]), atol=1e-3)
+ assert np.allclose(A @ result.x, b, atol=1e-3)
+
+
+def test_end_to_end_nonlinear_constraint():
+ def cons(x):
+ # x1**2 - 0.25 <= 0, i.e. x1 <= 0.5
+ return x[0]**2 - 0.25
+ result = minimize(obj, obj.x0, method='cobyla', constraints=[NonlinearConstraint(cons, 0, 0)])
+ assert np.allclose(result.x, np.array([0.5, 2.5]), atol=1e-3)
+ assert np.isclose(cons(result.x), 0, atol=1e-8)
diff --git a/pyprima/tests/test_get_lincon.py b/pyprima/tests/test_get_lincon.py
new file mode 100644
index 0000000000..0eaf570891
--- /dev/null
+++ b/pyprima/tests/test_get_lincon.py
@@ -0,0 +1,62 @@
+from pyprima.cobyla.cobyla import get_lincon
+from pyprima.common.consts import BOUNDMAX
+import numpy as np
+
+def test_get_lincon():
+ Aeq = np.array([[1, 2], [3, 4]])
+ Aineq = np.array([[5, 6], [7, 8]])
+ beq = np.array([9, 10])
+ bineq = np.array([11, 12])
+ xl = np.array([0, -1])
+ xu = np.array([13, 14])
+ amat, bvec = get_lincon(Aeq, Aineq, beq, bineq, xl, xu)
+ assert np.allclose(amat, np.array([
+ [-1, 0],
+ [0, -1],
+ [1, 0],
+ [0, 1],
+ [-1, -2],
+ [-3, -4],
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ]))
+ assert np.allclose(bvec, np.array([0, 1, 13, 14, -9, -10, 9, 10, 11, 12]))
+
+
+def test_get_lincon_boundmax():
+ Aeq = np.array([[1, 2], [3, 4]])
+ Aineq = np.array([[5, 6], [7, 8]])
+ beq = np.array([9, 10])
+ bineq = np.array([11, 12])
+ # Since the first element is below BOUNDMAX, we should ultimately
+ # see only 3 bounds in the resultant matrix/vector.
+ xl = np.array([-BOUNDMAX - 1, -1])
+ xu = np.array([13, 14])
+ amat, bvec = get_lincon(Aeq, Aineq, beq, bineq, xl, xu)
+ assert np.allclose(amat, np.array([
+ # Note that the first row is missing because the first element of xl is below BOUNDMAX.
+ [0, -1],
+ [1, 0],
+ [0, 1],
+ [-1, -2],
+ [-3, -4],
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ]))
+ assert np.allclose(bvec, np.array([1, 13, 14, -9, -10, 9, 10, 11, 12]))
+
+
+def test_none():
+ Aeq = None
+ Aineq = None
+ beq = None
+ bineq = None
+ xl = None
+ xu = None
+ amat, bvec = get_lincon(Aeq, Aineq, beq, bineq, xl, xu)
+ assert amat is None
+ assert bvec is None
diff --git a/pyprima/tests/test_miscellaneous.py b/pyprima/tests/test_miscellaneous.py
new file mode 100644
index 0000000000..79b979661f
--- /dev/null
+++ b/pyprima/tests/test_miscellaneous.py
@@ -0,0 +1,75 @@
+from pyprima import minimize, NonlinearConstraint
+from pyprima.common.infos import CALLBACK_TERMINATE, SMALL_TR_RADIUS
+import numpy as np
+import pytest
+import platform
+
+def obj(x):
+ return (x[0] - 1)**2 + (x[1] - 2.5)**2
+obj.x0 = np.array([5, 5])
+obj.optimal = np.array([1, 2.5])
+
+
+def test_callback_terminate(minimize_with_debugging):
+ def callback(x, *args):
+ return True
+ result = minimize_with_debugging(obj, obj.x0, method='cobyla', callback=callback)
+ assert result.nf == 4
+ assert result.info == CALLBACK_TERMINATE
+
+
+def test_callback_no_terminate():
+ def callback(x, *args):
+ pass
+ result = minimize(obj, obj.x0, method='cobyla', callback=callback)
+ # I'm not sure why but ubuntu finishes the problem with 2 fewer function evaluations :shrug:
+ assert result.nf == 54 if platform.system() == 'Linux' else 56
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+ assert result.info == SMALL_TR_RADIUS
+
+
+def test_rhoend_without_rhobeg():
+ result = minimize(obj, obj.x0, method='cobyla', options={'rhoend': 4e-4})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_rhobeg_without_rhoend():
+ # Needs to be negative to trigger the right section of code...
+ result = minimize(obj, obj.x0, method='cobyla', options={'rhobeg': -1})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_eta2_without_eta1():
+ result = minimize(obj, obj.x0, method='cobyla', options={'eta2': 0.7})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_eta2_without_eta1_and_eta2_out_of_range():
+ result = minimize(obj, obj.x0, method='cobyla', options={'eta2': 1.7})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_eta1_without_eta2_and_eta1_out_of_range():
+ result = minimize(obj, obj.x0, method='cobyla', options={'eta1': 1.7})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+@pytest.mark.parametrize('iprint', [-1, 0, 1, 2, 3, 4])
+def test_iprint(iprint):
+ result = minimize(obj, obj.x0, method='cobyla', options={'iprint': iprint})
+ assert np.allclose(result.x, obj.optimal, atol=1e-3)
+
+
+def test_minimize_constraint_violation():
+ # We set up conflicting constraints so that the algorithm will be
+ # guaranteed to end up with maxcv > 0.
+ cons = [NonlinearConstraint(lambda x: x - 4, -np.inf, 0),
+ NonlinearConstraint(lambda x: 5 - x, -np.inf, 0)]
+ result = minimize(lambda x: x[0], np.array([0]), method='cobyla', constraints=cons,
+ )
+ assert result.cstrv > 0.1
+ assert result.info == SMALL_TR_RADIUS
+
+
+def test_scalar():
+ result = minimize(lambda x: x**2, 5, method='cobyla')
+ assert np.allclose(result.x, 0, atol=1e-3)
\ No newline at end of file
diff --git a/pyprima/tests/test_pycutest.py b/pyprima/tests/test_pycutest.py
new file mode 100644
index 0000000000..e0829b6aa9
--- /dev/null
+++ b/pyprima/tests/test_pycutest.py
@@ -0,0 +1,373 @@
+import pytest
+# This exists mainly for those CI tests in which cutest/pycutest/optiprofiler are not installed.
+optiprofiler = pytest.importorskip("optiprofiler", exc_type=ImportError)
+
+from optiprofiler.problems import load_cutest_problem
+import pyprima
+from pyprima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+import numpy as np
+
+
+'''
+This module tests various problem from the CUTEST set in order to really stress test
+the implementation and also cover some cases not covered by the naive tests in
+test_end_to_end.py. The list is semi-arbitrary, some of these helped to fine bugs
+when testing the Python implementation against the Fortran one.
+'''
+
+
+@pytest.fixture(autouse=True, scope='module')
+def set_comparing():
+ # This is a hack to force these tests to use manual math instead of optimized
+ # numpy or other routines. This should ensure we have the same results across
+ # different architectures.
+ pyprima.common.linalg.USE_NAIVE_MATH = True
+ yield
+ pyprima.common.linalg.USE_NAIVE_MATH = False
+
+
+def get_constraints(problem):
+ constraints = []
+ if problem.m_linear_ub > 0:
+ constraints.append(LinearConstraint(problem.a_ub, -np.inf, problem.b_ub))
+ if problem.m_linear_eq > 0:
+ constraints.append(LinearConstraint(problem.a_eq, problem.b_eq, problem.b_eq))
+ if problem.m_nonlinear_ub > 0:
+ constraints.append(NonlinearConstraint(problem.c_ub, -np.inf, np.zeros(problem.m_nonlinear_ub)))
+ if problem.m_nonlinear_eq > 0:
+ constraints.append(NonlinearConstraint(problem.c_eq, np.zeros(problem.m_nonlinear_eq), np.zeros(problem.m_nonlinear_eq)))
+ return constraints
+
+
+def run_problem(name, expected_x, expected_f, expected_constraints, expected_nf, options=None):
+ problem = load_cutest_problem(name)
+ constraints = get_constraints(problem)
+ bounds = Bounds(problem.lb, problem.ub)
+ result = minimize(problem.fun, problem.x0, method='cobyla', constraints=constraints, bounds=bounds, options=options)
+ assert np.allclose(result.x, expected_x, atol=1e-15)
+ assert np.isclose(result.f, expected_f, atol=1e-15)
+ assert np.allclose(result.constr, expected_constraints, atol=1e-15)
+ assert result.nf == expected_nf
+
+
+@pytest.mark.order(2) # This test takes the second longest.
+@pytest.mark.xdist_group(name="errinbar") # Need to use with --dist loadgroup at command line
+def test_errinbar():
+ # Expected values are just obtained from running the problem and collecting the results
+ # If future changes improve the algorithm, these values may need to be updated.
+ expected_x = np.array([-245.5081612710879710448, -54.2958188399598853380,
+ 262.7230936253279196535, -45.9309872501519365073,
+ -2.2691358288677578869, 7.6733425543485722642,
+ -31.0344774501767943775, 26.9394969852641779084,
+ -1.1718811167782969829, -2.8508188399598868301,
+ -2.8508188399598868301, -2.8508188399598868301,
+ 62.5293726076140217174, 1.0329450380513451879,
+ 7.1143643043640603096, 3.3150533074468162553,
+ 1.1289565001348098594, 13.0411964959208557246])
+ expected_f = 203.48674841371607
+ expected_constraints = np.array([
+ 3.4958188399598881801, -4.8690127498480606505, -58.4733425543485694220,
+ -77.7394969852641679608, 1.8168811167782970006, 3.4958188399598868479,
+ 3.4958188399598868479, 3.4958188399598868479, -61.8843726076140185910,
+ -0.3879450380513451702, -6.4693643043640598478, -2.6700533074468162376,
+ -0.4839565001348098416, -12.3961964959208561510, -31.2248315898079482622,
+ 3.4957795914671430104, -3.4963193946205968210, 3.4957779371938499935,
+ 3.4955724606965077328, -3.4957078923282551841, -3.4968691556686337663,
+ -3.4959635816089189575, 3.4948114345599776698, -3.4957795914671430104,
+ 3.4963193946205968210, -3.4957779371938499935, -3.4955724606965077328,
+ 3.4957078923282551841, 3.4968691556686337663, 3.4959635816089189575,
+ -3.4948114345599776698])
+ expected_nf = 9000
+ run_problem('ERRINBAR', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_palmer2c():
+ expected_x = np.array([0.8853310838319223830, 0.8885299788096961970, 0.8805439805147466936,
+ 0.7783464729241426072, 0.5850067768175214455, 0.0316686276050338403,
+ 0.0244429707945372325, -0.0163015984157743772])
+ expected_f = 1414.0819761003331
+ expected_constraints = np.array([])
+ expected_nf = 4000
+ run_problem('PALMER2C', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_palmer3b():
+ expected_x = np.array([1.2090027787973145479, 7.5461811633941122679, 0.4433420649209307562,
+ 0.0387612788701699879])
+ expected_f = 324.6120596974852
+ expected_constraints = np.array([-0.4433320649209307462, -0.0387512788701699848])
+ expected_nf = 2000
+ run_problem('PALMER3B', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_tfi3():
+ expected_x = np.array([1.0052196032967017914, -0.1118710807081673281, -0.3933485225885347547])
+ expected_f = 4.301460324843021
+ expected_constraints = np.array([
+ -5.2196032967017913506e-03, -4.1615476373613180527e-03,
+ -3.2246822735029212481e-03, -2.4086482051270952098e-03,
+ -1.7128464322335723580e-03, -1.1364439548220417464e-03,
+ -6.7836977289303934668e-04, -3.3732688644627639718e-04,
+ -1.1178629548169727315e-04, 4.4408920985006261617e-16,
+ 2.2204460492503130808e-16, -1.0960729548215031315e-04,
+ -3.2643888644678931144e-04, -6.4790977289375106807e-04,
+ -1.0712459548231079154e-03, -1.5934894322346959683e-03,
+ -2.2115062051284439804e-03, -2.9219972735047283763e-03,
+ -3.7215016373631781832e-03, -4.6064132967039705946e-03,
+ -5.5729842515268579461e-03, -6.6173405018321584947e-03,
+ -7.7354830476199509803e-03, -8.9233058888898986183e-03,
+ -1.0176604025642022044e-02, -1.1491079457876418601e-02,
+ -1.2862357185593364939e-02, -1.4285991208792370166e-02,
+ -1.5757473527473808694e-02, -1.7272248141637414065e-02,
+ -1.8825719051283495986e-02, -2.0413257256411743157e-02,
+ -2.2030212757022216152e-02, -2.3671921553115149450e-02,
+ -2.5333716644690307263e-02, -2.7010934031747702022e-02,
+ -2.8698923714287394304e-02, -3.0393054692309462439e-02,
+ -3.2088722965813776256e-02, -3.3781361534800380397e-02,
+ -3.5466441399269243995e-02, -3.7139485559220530853e-02,
+ -3.8796067014653989879e-02, -4.0431820765569725928e-02,
+ -4.2042444811967838447e-02, -4.3623709153848166942e-02,
+ -4.5171455791210846087e-02, -4.6681605724055774687e-02,
+ -4.8150161952382974917e-02, -4.9573211476192646785e-02,
+ -5.0946932295484415043e-02, -5.2267591410258495976e-02,
+ -5.3531548820514918141e-02, -5.4735260526253770585e-02,
+ -5.5875281527474673915e-02, -5.6948261824178003643e-02,
+ -5.7950953416363470971e-02, -5.8880207304031539906e-02,
+ -5.9732975487181705354e-02, -6.0506310965814047442e-02,
+ -6.1197368739928981185e-02, -6.1803404809525952501e-02,
+ -6.2321776174605214571e-02, -6.2749936835166941762e-02,
+ -6.3085444791210809612e-02, -6.3325952042737054803e-02,
+ -6.3469211589745611057e-02, -6.3513071432236389846e-02,
+ -6.3455471570209587284e-02, -6.3294449003664987607e-02,
+ -6.3028130732602605235e-02, -6.2654733757022573748e-02,
+ -6.2172564076924952126e-02, -6.1580011692309422067e-02,
+ -6.0875553603176313366e-02, -6.0057748809525413058e-02,
+ -5.9125236311357021179e-02, -5.8076733108670675065e-02,
+ -5.6911032201466649205e-02, -5.5627003589744927758e-02,
+ -5.4223586273505763877e-02, -5.2699791252748595660e-02,
+ -5.1054694527473909460e-02, -4.9287441097681239377e-02,
+ -4.7397237963371252611e-02, -4.5383353124543224233e-02,
+ -4.3245114581197507952e-02, -4.0981907333334288701e-02,
+ -3.8593172380953277845e-02, -3.6078400724054549009e-02,
+ -3.3437139362637924300e-02, -3.0668980296703729493e-02,
+ -2.7773565526252097513e-02, -2.4750580051282500271e-02,
+ -2.1599752871795274700e-02, -1.8320855987790318764e-02,
+ -1.4913700399267648677e-02, -1.1378134106227122402e-02,
+ -7.7140431086689664752e-03, -3.9213474065933340285e-03,
+ 2.7755575615628913511e-16 ])
+ expected_nf = 15
+ run_problem('TFI3', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_hs103():
+ # This one hits the section in trustregion.py which scales the problem if A
+ # contains large values.
+ expected_x = np.array([2.5289807396883552393, 0.4636478250644894272, 3.4406699575922705669,
+ 9.1465471863584326684, 2.2192889216323616886, 2.8022288337905920663,
+ 0.0173213436307221025])
+ expected_f = 3000.2102326770078
+ expected_constraints = np.array([
+ -2.4289807396883551505e+00, -3.6364782506448944943e-01,
+ -3.3406699575922704781e+00, -9.0465471863584330237e+00,
+ -2.1192889216323615997e+00, -2.7022288337905919775e+00,
+ -7.3213436307221022720e-03, -7.4710192603116443166e+00,
+ -9.5363521749355104618e+00, -6.5593300424077298771e+00,
+ -8.5345281364156733162e-01, -7.7807110783676378674e+00,
+ -7.1977711662094083778e+00, -9.9826786563692770926e+00,
+ -4.8208275891177221339e-01, 2.1023382818340427547e-01,
+ 2.1023382797198550409e-01, -7.4925408486881206471e-01,
+ -2.9002102326770077525e+03, 2.1023267700795286728e-01])
+ expected_nf = 3500
+ run_problem('HS103', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_cresc4():
+ expected_x = np.array([
+ -2.4712333082037314824e+01, 1.2118172433432175539e-01,
+ 1.0544855650501498978e+00, 2.3019400598944947944e+01,
+ -2.3698388504182490846e-18, 3.9000000000000001332e-01])
+ expected_f = 2.2014732007195974
+ expected_constraints = np.array([
+ -1.0544855550501499586e+00, -2.2019400598944947944e+01,
+ 2.3698388504182490846e-18, 0.0000000000000000000e+00,
+ -6.2831852000000001368e+00, -1.9744556915237687633e-03,
+ -5.2844327442343001167e+01, -1.1217527730463550117e+00,
+ -3.1772978295997518217e+00, -6.3702587570906854531e-01,
+ -3.6620247269370338472e+00, -1.1906818900346176626e+00,
+ -2.7381994360305725422e+01])
+ expected_nf = 3000
+ run_problem('CRESC4', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_mgh10ls():
+ # This one also hits the section in trustregion.py which scales the problem if A
+ # contains large values.
+ expected_x = np.array([1.4950790839504562481e-03, 3.9999949550186062697e+05,
+ 2.5001006103891704697e+04])
+ expected_f = 1366860355.936367
+ expected_constraints = np.array([])
+ expected_nf = 51
+ run_problem('MGH10LS', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+@pytest.mark.order(1) # This test takes the longest
+@pytest.mark.xdist_group(name="tenbars1") # Separate group to make sure it gets a separate worker
+def test_tenbars1():
+ expected_x = np.array([
+ 1.9568934516948072542e+03, 3.3869509993142941084e+02,
+ 5.1084410783004557288e+02, 7.1451015340321816893e+02,
+ 6.7889550790383304957e+02, 5.5925898284379888992e+02,
+ 9.4664217112688754696e+02, 5.6527088845435753228e+02,
+ 8.5148703700913730419e-01, -1.3262297582844631005e+00,
+ -1.3262297582844631005e+00, -1.3262297582844631005e+00,
+ -1.3262297582844631005e+00, -6.2387954220169796263e-03,
+ -1.3262297582844631005e+00, -1.3262297582844631005e+00,
+ -1.3262297582844631005e+00, -1.3262297582844631005e+00])
+ expected_f = -30.27643654696821
+ expected_constraints = np.array([
+ -3.8949509993142942221e+02, -7.6531015340321812346e+02,
+ -6.1005898284379884444e+02, -6.1607088845435748681e+02,
+ -2.0648703700913728643e-01, 1.9712297582844631183e+00,
+ 1.9712297582844631183e+00, 1.9712297582844631183e+00,
+ 1.9712297582844631183e+00, 6.5123879542201701387e-01,
+ 1.9712297582844631183e+00, 1.9712297582844631183e+00,
+ 1.9712297582844631183e+00, 1.9712297582844631183e+00,
+ -3.9867505347178877173e+02, 1.9711816402637432066e+00,
+ 1.9712297463431205369e+00, 1.9712297462376682233e+00,
+ 1.9712419862444789942e+00, 1.9712293413148502808e+00,
+ 1.9712297449648037428e+00, 1.9712297458175953579e+00,
+ 1.9712419824544298308e+00, -1.9711816402637432066e+00,
+ -1.9712297463431205369e+00, -1.9712297462376682233e+00,
+ -1.9712419862444789942e+00, -1.9712293413148502808e+00,
+ -1.9712297449648037428e+00, -1.9712297458175953579e+00,
+ -1.9712419824544298308e+00])
+ expected_nf = 9000
+ run_problem('TENBARS1', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_biggs3():
+ expected_x = np.array([0.9999739462811082502, 9.9987085888009339385, 4.9992405910694346360])
+ expected_f = 1.0631133951128183e-08
+ expected_constraints = np.array([])
+ expected_nf = 1500
+ run_problem('BIGGS3', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_biggs6():
+ expected_x = np.array([1.2194245092803301933, 8.5197890909932567638, 1.1508892999691997527,
+ 3.8853547380197808181, 3.4765466485421625542, 2.4764750429775079787])
+ expected_f = 0.009233751892945407
+ expected_constraints = np.array([])
+ expected_nf = 3000
+ run_problem('BIGGS6', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_degenlpb():
+ expected_x = np.array([
+ 2.5048137222646543742e-01, 8.1652820831984397262e-04,
+ 2.8086877200736666549e-02, 9.9836359424439302668e-02,
+ 3.9729580203073201622e-06, 1.3602542024738497294e-04,
+ 4.8342827214562932199e-04, 5.6699356477458474901e-04,
+ 1.0247844713503989709e-03, 1.9602965525651544487e-01,
+ -2.5390800574482325636e-13, -2.5390800733077673568e-13,
+ -2.5390800458724593676e-13, -9.5814957530582223200e-14,
+ -2.5353478905985151653e-13, 1.9962073754867938440e-03,
+ -2.5390811415199054935e-13, 1.6586456462450486181e-06,
+ 3.9275644489467232551e-03, 1.2011160395190988333e-03])
+ expected_f = -30.731246817983664
+ expected_constraints = np.array([
+ -2.5048137222646543742e-01, -8.1652820831984397262e-04,
+ -2.8086877200736666549e-02, -9.9836359424439302668e-02,
+ -3.9729580203073201622e-06, -1.3602542024738497294e-04,
+ -4.8342827214562932199e-04, -5.6699356477458474901e-04,
+ -1.0247844713503989709e-03, -1.9602965525651544487e-01,
+ 2.5390800574482325636e-13, 2.5390800733077673568e-13,
+ 2.5390800458724593676e-13, 9.5814957530582223200e-14,
+ 2.5353478905985151653e-13, -1.9962073754867938440e-03,
+ 2.5390811415199054935e-13, -1.6586456462450486181e-06,
+ -3.9275644489467232551e-03, -1.2011160395190988333e-03,
+ -7.4951862777353461809e-01, -9.9918347179168021110e-01,
+ -9.7191312279926334039e-01, -9.0016364057556064182e-01,
+ -9.9999602704197965153e-01, -9.9986397457975262348e-01,
+ -9.9951657172785435268e-01, -9.9943300643522536841e-01,
+ -9.9897521552864965155e-01, -8.0397034474348449962e-01,
+ -1.0000000000002540190e+00, -1.0000000000002540190e+00,
+ -1.0000000000002537970e+00, -1.0000000000000959233e+00,
+ -1.0000000000002535749e+00, -9.9800379262451321960e-01,
+ -1.0000000000002540190e+00, -9.9999834135435372584e-01,
+ -9.9607243555105329236e-01, -9.9879888396048088772e-01,
+ -2.5413005033669833210e-13, -2.8724245204614362592e-13,
+ -2.4270863097086703419e-13, -2.5389065849701353272e-13,
+ -2.7571001037784981236e-13, -2.5407898441437426484e-13,
+ -2.5391320990220123122e-13, -2.4266699760744359082e-13,
+ 2.5390952361481478050e-13, -2.5402759323139845193e-13,
+ 2.5390774823375733549e-13, 2.5390795469244733799e-13,
+ -2.5390729730980910473e-13, -2.5379698342931078514e-13,
+ 2.5391494462567720802e-13, 2.5413005033669833210e-13,
+ 2.8724245204614362592e-13, 2.4270863097086703419e-13,
+ 2.5389065849701353272e-13, 2.7571001037784981236e-13,
+ 2.5407898441437426484e-13, 2.5391320990220123122e-13,
+ 2.4266699760744359082e-13, -2.5390952361481478050e-13,
+ 2.5402759323139845193e-13, -2.5390774823375733549e-13,
+ -2.5390795469244733799e-13, 2.5390729730980910473e-13,
+ 2.5379698342931078514e-13, -2.5391494462567720802e-13])
+ expected_nf = 97
+ run_problem('DEGENLPB', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_hs102():
+ # This is a very important test, because after nearly all of the math was switched
+ # to manual math, 99% of problems were identical except this one, and it was because
+ # of a non-math related bug in the code. It was a rather serious bug and it's surprising
+ # that it wasn't caught earlier, but it's a good reminder that these things happen, and
+ # also why it's important to check the math with precision algorithms instead of just
+ # ignoring issues when the differences appear close to machine epsilon.
+ expected_x = np.array([2.4754575633775468546, 0.5541250457494729664, 3.6444439553239513785,
+ 8.1630851993117925502, 1.5198804708746382897, 1.9600795356050697560,
+ 0.0196160573415092507])
+ expected_f = 3000.076360741547
+ expected_constraints = np.array([
+ -2.3754575633775467658e+00, -4.5412504574947298863e-01,
+ -3.5444439553239512897e+00, -8.0630851993117929055e+00,
+ -1.4198804708746382008e+00, -1.8600795356050696672e+00,
+ -9.6160573415092504695e-03, -7.5245424366224531454e+00,
+ -9.4458749542505273666e+00, -6.3555560446760486215e+00,
+ -1.8369148006882074498e+00, -8.4801195291253623765e+00,
+ -8.0399204643949300220e+00, -9.9803839426584914918e+00,
+ -6.0742857186875132136e-01, 7.6362201567565679561e-02,
+ 7.6362201333172202711e-02, -6.3020233552259341536e-01,
+ -2.9000763607415465231e+03, 7.6360741546495691789e-02,
+ ])
+ expected_nf = 3500
+ run_problem('HS102', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_misra1als():
+ # This test uncovered an issue with moderatef. We had been clipping incorrectly.
+ expected_x = np.array([5.0105555094407225E+002, 2.4181408013565766E-004])
+ expected_f = 1.9594518355352196E+001
+ expected_constraints = np.array([])
+ expected_nf = 39
+ run_problem('MISRA1ALS', expected_x, expected_f, expected_constraints, expected_nf)
+
+
+def test_polak3():
+ # This one, with these particular options, actually triggered the logic for taking
+ # the inverse of a non-triangular matrix. It happened via updatexfc, when the error
+ # between SIM and SIMI was too large.
+ expected_x = np.array([1.0000000000000000E+000, 1.0000000000000000E+000, 1.0000000000000000E+000,
+ 1.0000000000000000E+000, 1.0000000000000000E+000, 1.0000000000000000E+000,
+ 1.0000000000000000E+000, 1.0000000000000000E+000, 1.0000000000000000E+000,
+ 1.0000000000000000E+000, 1.0000000000000000E+000, 3.7182800000000000E+000])
+ expected_f = 3.71828
+ expected_constraints = np.array([1.7673936794898822E+001, 4.2552779271657478E+001, 3.3334527708058324E+001,
+ 6.8251421914926908E+001, 1.6633544068845666E+001, 2.8929015570937477E+001,
+ 1.6062115937829471E+001, 4.1328621950812334E+001, 2.8671452438715288E+001,
+ 7.1375489109225455E+001])
+ expected_nf = 3252
+ options = {
+ 'maxfev' : 271*len(expected_x),
+ 'rhobeg' : 2.71828,
+ 'rhoend' : 3.14e-4,
+ }
+ run_problem('POLAK3', expected_x, expected_f, expected_constraints, expected_nf, options)
diff --git a/pyprima/tests/test_threading.py b/pyprima/tests/test_threading.py
new file mode 100644
index 0000000000..83ebe973ba
--- /dev/null
+++ b/pyprima/tests/test_threading.py
@@ -0,0 +1,58 @@
+from scipy.optimize import rosen, Bounds, NonlinearConstraint, LinearConstraint
+from pyprima import minimize
+import numpy as np
+import concurrent.futures
+
+
+def test_threading():
+ # This is to validate that python cobyla does not require locks.
+ # We will solve a set of problems in a single threaded fashion and get their
+ # solutions, then we will solve the same problems in a thread pool, and validate
+ # that we get the same results.
+
+ problem1 = {
+ 'fun': rosen,
+ 'x0': np.linspace(3.4, 17.8, 10),
+ 'method': 'cobyla',
+ 'bounds': None,
+ 'constraints': None
+ }
+ problem2 = {
+ 'fun': rosen,
+ 'x0': np.linspace(-178, -23, 7),
+ 'method': 'cobyla',
+ 'bounds': None,
+ 'constraints': None
+ }
+ nlc = NonlinearConstraint(lambda x: x[0]**2 - 5.0, 0.0, np.inf)
+ problem3 = {
+ 'fun': rosen,
+ 'x0': np.linspace(-178, -23, 7),
+ 'method': 'cobyla',
+ 'bounds': None,
+ 'constraints': nlc
+ }
+ bounds = Bounds(np.array([0, 0]), np.array([10, 10]))
+ lc = LinearConstraint([1]*7, 50, 50) # Sum of optimum point is 50
+ problem4 = {
+ 'fun': rosen,
+ 'x0': np.linspace(-178, -23, 7),
+ 'method': 'cobyla',
+ 'bounds': bounds,
+ 'constraints': lc
+ }
+ problems = [problem1, problem2, problem3, problem4]
+
+ def run_problem(problem):
+ return minimize(**problem)
+
+ single_threaded_results = [run_problem(problem) for problem in problems]
+
+ with concurrent.futures.ThreadPoolExecutor() as executor:
+ multithreaded_results = executor.map(run_problem, problems)
+
+ for single_threaded_result, multithreaded_result in zip(single_threaded_results, multithreaded_results):
+ assert np.allclose(single_threaded_result.x, multithreaded_result.x)
+ assert np.allclose(single_threaded_result.constr, multithreaded_result.constr)
+ assert single_threaded_result.nf == multithreaded_result.nf
+ assert single_threaded_result.f == multithreaded_result.f
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000..a8fdeea31b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,54 @@
+[build-system]
+# scikit-build-core claims there's no need to explicitly specify Ninja,
+# as it will "automatically be downloaded if needed", but I don't know
+# how it determines "if needed", all I know is that we need it, particularly
+# for Windows.
+requires = ["scikit-build-core", "numpy", "ninja"]
+build-backend = "scikit_build_core.build"
+
+[project]
+name = "prima"
+dependencies = ["numpy", "scipy >= 1.9.0"]
+dynamic = ["version"]
+# The driving factor for our Python version is that we need a version of SciPy that
+# includes https://github.com/scipy/scipy/pull/15394. This was merged in SciPy 1.9.0,
+# and Python 3.7 only support up to SciPy 1.7.3 whereas 3.8 supports 1.10.1.
+requires-python = ">= 3.8"
+
+[tool.scikit-build]
+cmake.args = ["-G Ninja", "-DBUILD_SHARED_LIBS=OFF", "-DPRIMA_ENABLE_PYTHON=ON"]
+cmake.verbose = true
+logging.level = "INFO"
+metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"
+sdist.include = [".git-archival.txt"]
+install.components = ["Prima_Python_C_Extension"]
+
+[tool.setuptools_scm] # Section required
+write_to = "_version.txt"
+
+[tool.cibuildwheel]
+build-verbosity = 3
+test-command = "coverage run --branch --source=prima,{project} -m pytest -s {project}/python/tests && coverage html -d {project}/prima_htmlcov"
+test-requires = ["pytest", "coverage", "packaging"]
+# We need scipy and pdfo (which depends on scipy) for compatibility tests.
+# scipy is not available on all platforms we support, so we try to install it
+# if posssible, otherwise we skip it. The test will skip itself if it cannot
+# import scipy. "--only-binary" ensure we do not try to build scipy from
+# source (which requires special setup we have no intention of doing).
+before-test = "pip install --only-binary :all: scipy pdfo || true"
+skip = [
+ # On windows we get a complaint from CMake:
+ # "CMake Error at python/pybind11/tools/FindPythonLibsNew.cmake:191 (message):
+ # Python config failure: Python is 32-bit, chosen compiler is 64-bit"
+ # I do not see a way to install a 32-bit compiler with the setup-fortran action,
+ # so we will just build 64-bit wheels on windows.
+ "*-win32",
+ # Disable building PyPy wheels on all platforms. If there is interest in supporting PyPy
+ # we can look into it.
+ "pp*",
+ # Disable musllinux for the moment. It successfully built but there was an error when testing
+ "*musllinux*",
+ # SciPy does not provide binaries for 32-bit Python on Linux starting from 3.10.
+ # As such we can compile but we cannot test, so we skip those.
+ "cp31*-manylinux_i686",
+]
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
new file mode 100644
index 0000000000..a4edecaf30
--- /dev/null
+++ b/python/CMakeLists.txt
@@ -0,0 +1,61 @@
+# For Apple and Windows we need to make sure it doesn't try to find the system python.
+# Details: https://github.com/actions/setup-python/issues/121#issuecomment-777748504
+if ((APPLE OR WIN32) AND DEFINED ENV{CI})
+ if (POLICY CMP0094) # https://cmake.org/cmake/help/latest/policy/CMP0094.html
+ cmake_policy(SET CMP0094 NEW) # FindPython should return the first matching Python
+ endif ()
+ set(Python_FIND_FRAMEWORK "NEVER")
+ set(Python_FIND_REGISTRY "NEVER")
+endif ()
+
+set(PYBIND11_NEWPYTHON ON)
+add_subdirectory(pybind11)
+
+
+# numpy is needed since we use pybind11::array and array_t
+find_package (Python 3.6 REQUIRED COMPONENTS Interpreter)
+execute_process(COMMAND ${Python_EXECUTABLE} -c "import numpy" RESULT_VARIABLE _IMPORT_NUMPY)
+if (NOT _IMPORT_NUMPY EQUAL 0)
+ message(SEND_ERROR "numpy: NOT FOUND, not installing/testing Python bindings")
+ return()
+endif ()
+
+
+pybind11_add_module(_prima _prima.cpp)
+target_include_directories(_prima PRIVATE ${CMAKE_SOURCE_DIR}/c/include)
+target_link_libraries(_prima PRIVATE primac primaf)
+target_compile_definitions(_prima PRIVATE VERSION_INFO=${PRIMA_VERSION})
+
+# This section is cribbed from scipy/meson.build
+if(WIN32 AND (CMAKE_C_COMPILER_ID MATCHES "GNU"))
+ message(STATUS "MinGW detected, adding compiler and linker flags")
+ target_link_options(_prima PUBLIC -lucrt -static)
+ target_compile_options(_prima PUBLIC $<$:-mlong-double-64 -D__USE_MINGW_ANSI_STDIO=1>)
+ target_compile_options(_prima PUBLIC $<$:-D__USE_MINGW_ANSI_STDIO=1>)
+endif()
+
+install (TARGETS _prima DESTINATION prima/ COMPONENT Prima_Python_C_Extension)
+# The following are not added to the component because scikit-build-core automatically
+# detects files in python/ and adds them to the wheel. These commands
+# are here in case one is building via cmake directly and not via scikit-build-core
+file(GLOB SUPPORTING_PY_FILES "${CMAKE_CURRENT_SOURCE_DIR}/prima/*.py")
+install (FILES ${SUPPORTING_PY_FILES} DESTINATION prima)
+
+# We test the example files in CMake but not the test files. Those are tested by the Python
+# build system (specifically cibuildwheel).
+macro (prima_add_py_test name)
+ add_test (NAME ${name}_example_python COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/examples/${name}_example.py)
+ set_tests_properties (${name}_example_python PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_INSTALL_PREFIX}")
+ # if (WIN32)
+ # file(TO_NATIVE_PATH "${PROJECT_BINARY_DIR}/bin" _BIN_PATH)
+ # set_property(TEST ${name}_example_python APPEND PROPERTY ENVIRONMENT "PATH=${_BIN_PATH}\\;$ENV{PATH}")
+ # else ()
+ # set_property(TEST ${name}_example_python APPEND PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}/c:$ENV{LD_LIBRARY_PATH};DYLD_LIBRARY_PATH=${PROJECT_BINARY_DIR}/c:$ENV{DYLD_LIBRARY_PATH}")
+ # endif ()
+endmacro ()
+
+prima_add_py_test (cobyla)
+prima_add_py_test (bobyqa)
+prima_add_py_test (newuoa)
+prima_add_py_test (uobyqa)
+prima_add_py_test (lincoa)
diff --git a/python/README.md b/python/README.md
new file mode 100644
index 0000000000..89e5cd7fa5
--- /dev/null
+++ b/python/README.md
@@ -0,0 +1,50 @@
+# Developer notes
+
+## Normal workflow
+
+This workflow should be the most common. The only reason to use the detailed
+workflow is if you want to have more control over the CMake build.
+
+ 1. pip install build
+ 2. cd /path/to/repo && pyproject-build # This will generate wheels in the /path/to/repo/dist folder
+ 3. cd dist && pip install prima*whl # If you ran the build multiple times, there will be multiple wheels here, so select accordingly. You may also need to uninstall prima first.
+
+If you prefer, the first two commands can be replaced by running `pipx run build` from the repo root. This will install the `build` package to some temporary directory and run `pyproject-build` in the same directory from which `pipx` was invoked. `pipx run build` will leave your system untouched, see `pipx` documentation for more details.
+
+From here you might want to run the tests and generate coverage locally. Assuming you have `coverage` and `pytest` installed you would do the following:
+
+`coverage --branch --source=prima,$(git rev-parse --show-toplevel) -m pytest --capture=no /path/to/python/tests`
+
+Explanation:
+- `--branch` checks to make sure we're hitting all of the possible cases in if/elseif/else statements
+- `--source=prima,$(...)` directs `coverage` to only gather coverage data for the `prima` library and any files in the git directory (i.e. test files). This ensures that the subsequent coverage reports does not also include things like numpy, pytest, etc.
+- `--capture=no` is necessary so that certain tests which check for correct stdout from the library can obtain that stdout data
+
+After running the `coverage` command, you can run `coverage html` to generate an HTML report.
+
+## Editable workflow
+
+This is one more variant on the normal build which will install an editable version of prima, so that any changes do not have to go through the build-uninstall-reinstall loop in order to get tested. With this setup, anytime prima is imported it will be rebuilt.
+
+From [scikit-build-core docs](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#editable-installs), run the following from the repo root. You may get some errors about missing Python libraries that need to be installed. As of this writing you need `pyproject_metadata scikit_build_core pathspec setuptools_scm` as well as cmake and ninja, which can be installed on Linux via `apt install ninja-build cmake`.
+
+`pip install --no-build-isolation -Ceditable.rebuild=true -Cbuild-dir=build -ve .`
+
+From here you can run the coverage command as above.
+
+## Detailed workflow
+
+This bypasses the pyproject.toml and runs CMake directly.
+
+ cd /repo/root
+ cmake -B build -DPRIMA_ENABLE_PYTHON=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=$(pwd)/install
+ cmake --build build --target install -j8
+ # There should now be a prima folder in $(pwd)/install, set the PYTHONPATH in order to use it
+ export PYTHONPATH=$(pwd)/install/
+ # If the above instructions worked correctly this test should pass (it simply runs the examples)
+ ctest --test-dir build --tests-regex _example_python
+ # If you would like to run the python tests the following should work
+ pytest -s python/tests
+ # For coverage run the following
+ coverage run --branch --source=prima,$(pwd) -m pytest -s python/tests
+ coverage html
diff --git a/python/_prima.cpp b/python/_prima.cpp
new file mode 100644
index 0000000000..349c794c2a
--- /dev/null
+++ b/python/_prima.cpp
@@ -0,0 +1,345 @@
+// project includes
+#include
+// 3rd party includes
+#include
+#include
+#include
+// standard includes
+#include
+#include
+#include
+#include
+#include
+
+namespace py = pybind11;
+using namespace pybind11::literals;
+
+// The following class is necessary to ensure that the static variables used to
+// store the objective/constraint/callback functions are automatically reset
+// upon normal or abnormal exit from the _minimize function. If we don't do this,
+// we can get various errors in Python, from hanging, to warnings about the GIL, to
+// segfaults. Simply setting the static variables to py::none() is sufficient.
+class SelfCleaningPyObject {
+ py::object &obj;
+ public:
+ SelfCleaningPyObject(py::object &obj) : obj(obj) {}
+ ~SelfCleaningPyObject() {
+ obj = py::none();
+ }
+};
+
+struct PRIMAResult {
+ // Blank constructor for construction from Python
+ PRIMAResult() {}
+ // Construct PRIMAResult from prima_result_t
+ PRIMAResult(const prima_result_t& result, const int num_vars, const int num_constraints, const std::string method) :
+ x(num_vars, result.x),
+ success(result.success),
+ status(result.status),
+ message(result.message),
+ fun(result.f),
+ nfev(result.nf),
+ maxcv(result.cstrv),
+ nlconstr(num_constraints, result.nlconstr),
+ method(method) {}
+
+ std::string repr() const {
+ std::string repr = "PRIMAResult(";
+ repr = repr +
+ "x=" + std::string(pybind11::repr(x)) + ", " +
+ "success=" + std::to_string(success) + ", " +
+ "status=" + std::to_string(status) + ", " +
+ "message=" + "\'" + message + "\'" + ", " +
+ "fun=" + std::to_string(fun) + ", " +
+ "nfev=" + std::to_string(nfev) + ", " +
+ "maxcv=" + std::to_string(maxcv) + ", " +
+ "nlconstr=" + std::string(pybind11::repr(nlconstr)) + ", " +
+ "method=" + "\'" + method + "\'" +
+ ")";
+ return repr;
+ }
+
+ pybind11::array_t x; // final point
+ bool success; // whether the solver terminated with an error or not
+ int status; // exit code
+ std::string message; // error message
+ double fun; // objective value
+ int nfev; // number of objective function calls
+ double maxcv; // constraint violation (cobyla & lincoa)
+ pybind11::array_t nlconstr; // non-linear constraint values, of size m_nlcon (cobyla only)
+ std::string method; // optimization method
+};
+
+
+prima_algorithm_t pystr_method_to_algorithm(const pybind11::str& method_) {
+ const std::string method(method_);
+ if (method == "bobyqa") { return PRIMA_BOBYQA; }
+ else if (method == "cobyla") { return PRIMA_COBYLA; }
+ else if (method == "lincoa") { return PRIMA_LINCOA; }
+ else if (method == "newuoa") { return PRIMA_NEWUOA; }
+ else if (method == "uobyqa") { return PRIMA_UOBYQA; }
+ else { throw std::invalid_argument("Expected method to be one of BOBYQA, COBYLA, LINCOA, NEWUOA, or UOBYQA"); }
+}
+
+PYBIND11_MODULE(_prima, m) {
+#ifdef VERSION_INFO
+ #define STRINGIFY(x) #x
+ #define MACRO_STRINGIFY(x) STRINGIFY(x)
+ m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
+#else
+ m.attr("__version__") = "dev";
+#endif
+
+ py::class_(m, "PRIMAResult")
+ .def(py::init<>())
+ .def_readwrite("x", &PRIMAResult::x)
+ .def_readwrite("success", &PRIMAResult::success)
+ .def_readwrite("status", &PRIMAResult::status)
+ .def_readwrite("message", &PRIMAResult::message)
+ .def_readwrite("fun", &PRIMAResult::fun)
+ .def_readwrite("nfev", &PRIMAResult::nfev)
+ .def_readwrite("maxcv", &PRIMAResult::maxcv)
+ .def_readwrite("nlconstr", &PRIMAResult::nlconstr)
+ .def_readwrite("method", &PRIMAResult::method)
+ .def("__repr__", &PRIMAResult::repr);
+
+ py::enum_(m, "PRIMAMessage")
+ .value("NONE", PRIMA_MSG_NONE)
+ .value("EXIT", PRIMA_MSG_EXIT)
+ .value("RHO", PRIMA_MSG_RHO)
+ .value("FEVL", PRIMA_MSG_FEVL)
+ .export_values();
+
+
+ m.def("minimize", [](const py::function& python_objective_function,
+ py::array_t& py_x0,
+ const py::tuple& args,
+
+ // All arguments which accept None as a default value must be
+ // of type py::object as opposed to their natural type.
+ // https://github.com/pybind/pybind11/issues/4956
+ const py::object& method,
+ const py::object& lb,
+ const py::object& ub,
+ const py::object& A_eq,
+ const py::object& b_eq,
+ const py::object& A_ineq,
+ const py::object& b_ineq,
+ const py::object& nonlinear_constraint_function,
+ const py::object& python_callback_function,
+ const py::object& options_dict)
+ {
+ // Reference for how to go from a python function to a C style function pointer: https://stackoverflow.com/questions/74480093
+ // Basically, we need to create a function with a C signature that calls the python function, and
+ // so the python function needs to exist in a scope outside that function with the C signature but still
+ // be callable from within that function. Hence why we need to to be static and why we can't just capture
+ // it within the lambda below (lambdas that capture variables cannot decay into function pointers).
+ static py::function python_objective_function_holder;
+ python_objective_function_holder = std::move(python_objective_function);
+ auto cleaner_1 = SelfCleaningPyObject(python_objective_function_holder);
+ static py::object python_callback_function_holder;
+ python_callback_function_holder = std::move(python_callback_function);
+ auto cleaner_2 = SelfCleaningPyObject(python_callback_function_holder);
+ static py::object python_nonlinear_constraint_function_holder;
+ python_nonlinear_constraint_function_holder = std::move(nonlinear_constraint_function);
+ auto cleaner_3 = SelfCleaningPyObject(python_nonlinear_constraint_function_holder);
+ // Storing the shape lets us handle both scalar inputs for x0 as well as 1D arrays
+ static py::array::ShapeContainer x0_shape; // no cleaner required for this one since it's just a wrapped std::vector
+ if (py_x0.ndim() == 0) {
+ x0_shape = {};
+ } else if (py_x0.ndim() == 1) {
+ x0_shape = {py_x0.shape(0)};
+ } else {
+ throw std::invalid_argument("x0 must be a scalar or a 1D array");
+ }
+
+ // Initialize the problem
+ prima_problem_t problem;
+ prima_init_problem(&problem, py_x0.size());
+ problem.x0 = py_x0.mutable_data();
+ // In order to use automatic memory management, we create an std::vector here to store the initial
+ // value of the constraints. This way the memory is guaranteed to be freed in case we hit any of the
+ // exceptions.
+ std::vector nlconstr0;
+
+ // Process options.
+ prima_options_t options;
+ prima_init_options(&options);
+ if ( ! options_dict.is_none()) {
+ if(options_dict.contains("ctol")) { options.ctol = options_dict["ctol"].cast(); }
+ if(options_dict.contains("ftarget")) { options.ftarget = options_dict["ftarget"].cast(); }
+ if(options_dict.contains("iprint")) { options.iprint = options_dict["iprint"].cast(); }
+ if(options_dict.contains("maxfev")) { options.maxfun = options_dict["maxfev"].cast(); }
+ if(options_dict.contains("npt")) { options.npt = options_dict["npt"].cast(); }
+ if(options_dict.contains("rhobeg")) { options.rhobeg = options_dict["rhobeg"].cast(); }
+ if(options_dict.contains("rhoend")) { options.rhoend = options_dict["rhoend"].cast(); }
+ // The following are not options, but part of the problem. We are using the options dictionary
+ // as a convenient way to pass them to C++.
+ if(options_dict.contains("f0")) { problem.f0 = options_dict["f0"].cast(); }
+ if(options_dict.contains("m_nlcon")) { problem.m_nlcon = options_dict["m_nlcon"].cast(); }
+ if(options_dict.contains("nlconstr0")) {
+ try
+ {
+ py::buffer_info constr_list_buffer_info = options_dict["nlconstr0"].cast().request();
+ nlconstr0.resize(problem.m_nlcon);
+ memcpy(nlconstr0.data(), constr_list_buffer_info.ptr, constr_list_buffer_info.size * constr_list_buffer_info.itemsize);
+ problem.nlconstr0 = nlconstr0.data();
+ }
+ catch(const std::exception& e)
+ {
+ throw(std::invalid_argument("nonlinear_constraint_function must return a double or a double array"));
+ }
+ }
+ }
+ options.data = (void*)&args;
+
+ prima_algorithm_t algorithm = pystr_method_to_algorithm(method);
+
+ // We set up the C style objective function evaluator in such a way that it can be reused in the
+ // case of COBYLA, where we need to evaluate the constraints as well. Making it static lets it be
+ // captured by the COBYLA function.
+ static prima_obj_t calfun = nullptr;
+ calfun = [](const double x[], double *f, const void *data) {
+ // In order for xlist to not copy the data from x, we need to provide a dummy base object
+ // Ideally pybind11 would provide a facility to do this instead of us having to do this hacky
+ // thing but oh well. Reference: https://github.com/pybind/pybind11/issues/323#issuecomment-575717041
+ py::none dummybaseobject;
+ py::array_t xlist(x0_shape, x, dummybaseobject);
+ py::args args = *((py::args*)data);
+ double result;
+ if (args.size() > 0) {
+ result = python_objective_function_holder(xlist, args).cast();
+ } else {
+ result = python_objective_function_holder(xlist).cast();
+ }
+ *f = result;
+ };
+
+ if ( algorithm == PRIMA_COBYLA ) {
+ if (python_nonlinear_constraint_function_holder.is_none()) {
+ // If we don't have a nonlinear constraint function, we can just use calfun.
+ // This occurs when the user is trying to use COBYLA without any nonlinear constraints.
+ // We can't just set calcfc to calfun because we need to match the signature of calcfc.
+ problem.calcfc = [](const double x[], double *f, double constr[], const void *data) {
+ calfun(x, f, data);
+ };
+ } else {
+ // create the combined objective/constraint function
+ problem.calcfc = [](const double x[], double *f, double constr[], const void *data) {
+ calfun(x, f, data);
+
+ // In order for xlist to not copy the data from x, we need to provide a dummy base object
+ // Ideally pybind11 would provide a facility to do this instead of us having to do this hacky
+ // thing but oh well. Reference: https://github.com/pybind/pybind11/issues/323#issuecomment-575717041
+ py::none dummybaseobject;
+ py::array_t xlist(x0_shape, x, dummybaseobject);
+ py::object constraints = python_nonlinear_constraint_function_holder(xlist);
+ try
+ {
+ py::buffer_info constr_list_buffer_info = constraints.cast().request();
+
+ // We need to copy. We cannot set the pointer since we are not passed a pointer-to-pointer
+ for (int i = 0; i < constr_list_buffer_info.size; i++) {
+ constr[i] = ((double*)constr_list_buffer_info.ptr)[i];
+ }
+ }
+ catch(const std::exception& e)
+ {
+ throw(std::invalid_argument("nonlinear_constraint_function must return a double or a double array"));
+ }
+ };
+ }
+
+ } else {
+ // For all other algorithms just use calfun.
+ problem.calfun = calfun;
+ }
+
+ prima_callback_t cpp_callback_function_wrapper = nullptr;
+ if ( ! python_callback_function_holder.is_none()) {
+ cpp_callback_function_wrapper = [](const int n, const double x[], const double f, int nf, int tr,
+ const double cstrv, int m_nlcon, const double nlconstr[], bool *terminate) {
+ // In order for xlist to not copy the data from x, we need to provide a dummy base object
+ // Ideally pybind11 would provide a facility to do this instead of us having to do this hacky
+ // thing but oh well. Reference: https://github.com/pybind/pybind11/issues/323#issuecomment-575717041
+ py::none dummybaseobject;
+ py::array_t xlist(n, x, dummybaseobject);
+ py::array_t nlconstrlist(m_nlcon, nlconstr, dummybaseobject);
+ bool result = python_callback_function_holder(xlist, f, nf, tr, cstrv, nlconstrlist).cast();
+ *terminate = result;
+ };
+ }
+ options.callback = cpp_callback_function_wrapper;
+
+ //=====================
+ // Handle Bounds
+ //=====================
+ if (( ! lb.is_none()) && ( ! ub.is_none())) {
+ // Use the buffer protocol to avoid copying
+ py::buffer_info lb_buffer_info = lb.cast().request();
+ if (lb_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("lb must be a double array");
+ }
+ problem.xl = (double*) lb_buffer_info.ptr;
+ py::buffer_info ub_buffer_info = ub.cast().request();
+ if (ub_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("ub must be a double array");
+ }
+ problem.xu = (double*) ub_buffer_info.ptr;
+ }
+
+ //==============================
+ // Handle Linear Constraints
+ //==============================
+ if( ! A_eq.is_none()) {
+ py::buffer_info A_eq_buffer_info = A_eq.cast().request();
+ if (A_eq_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("A_eq must be a double array");
+ }
+ problem.m_eq = A_eq_buffer_info.shape[0];
+ problem.Aeq = (double*) A_eq_buffer_info.ptr;
+ py::buffer_info b_eq_buffer_info = b_eq.cast().request();
+ if (b_eq_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("b_eq must be a double array");
+ }
+ problem.beq = (double*) b_eq_buffer_info.ptr;
+ }
+ if( ! A_ineq.is_none()) {
+ py::buffer_info A_ineq_buffer_info = A_ineq.cast().request();
+ if (A_ineq_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("A_ineq must be a double array");
+ }
+ problem.m_ineq = A_ineq_buffer_info.shape[0];
+ problem.Aineq = (double*) A_ineq_buffer_info.ptr;
+ py::buffer_info b_ineq_buffer_info = b_ineq.cast().request();
+ if (b_ineq_buffer_info.format != "d")
+ {
+ throw std::invalid_argument("b_ineq must be a double array");
+ }
+ problem.bineq = (double*) b_ineq_buffer_info.ptr;
+ }
+
+
+ // Initialize the result, call the function, convert the return type, and return it.
+ prima_result_t result;
+ const prima_rc_t rc = prima_minimize(algorithm, problem, options, &result);
+ if (rc == PRIMA_RC_DFT) {
+ PRIMAResult result_copy(result, py_x0.size(), problem.m_nlcon, method.cast());
+ prima_free_result(&result);
+ return result_copy;
+ } else {
+ prima_free_result(&result);
+ throw std::runtime_error("PRIMA failed with error code " + std::to_string(rc));
+ }
+ }, "fun"_a, "x0"_a, "args"_a=py::tuple(), "method"_a=py::none(),
+ "lb"_a=py::none(), "ub"_a=py::none(), "A_eq"_a=py::none(), "b_eq"_a=py::none(),
+ "A_ineq"_a=py::none(), "b_ineq"_a=py::none(),
+ "nonlinear_constraint_function"_a=py::none(),
+ "callback"_a=nullptr, "options"_a=py::none()
+ );
+}
diff --git a/python/examples/bobyqa_example.py b/python/examples/bobyqa_example.py
new file mode 100644
index 0000000000..5f86a31b3b
--- /dev/null
+++ b/python/examples/bobyqa_example.py
@@ -0,0 +1,23 @@
+from prima import minimize
+from objective import fun
+
+x0 = [0.0] * 2
+
+# Unbounded the min is (5, 4), so we put a bound on the first
+# element to make sure we're exercising the bounding logic.
+bounds = [(None, 2.5), (-6, 6)] # x1 from -inf to 2.5, x2 from -6 to 6
+res = minimize(fun, x0, bounds=bounds)
+print(res)
+assert abs(res.x[0] - 2.5) < 2e-1 and abs(res.x[1] - 4.0) < 2e-1
+
+# Test not all bounds being provided
+bounds = [(None, 2.5)]
+res = minimize(fun, x0, method='BOBYQA', bounds=bounds)
+print(res)
+assert abs(res.x[0] - 2.5) < 2e-1 and abs(res.x[1] - 4.0) < 2e-1
+
+# Test bounding only the second variable
+bounds = [None, (None, 6)]
+res = minimize(fun, x0, method='BOBYQA', bounds=bounds)
+print(res)
+assert abs(res.x[0] - 5) < 2e-1 and abs(res.x[1] - 4.0) < 2e-1
diff --git a/python/examples/cobyla_example.py b/python/examples/cobyla_example.py
new file mode 100644
index 0000000000..d6654217dc
--- /dev/null
+++ b/python/examples/cobyla_example.py
@@ -0,0 +1,16 @@
+from prima import minimize, NonlinearConstraint as NLC
+from objective import fun
+import numpy as np # To set lb to -inf
+
+
+def f_con(x):
+ con = x[0]**2 - 9
+ return con
+
+
+x0 = [0.0] * 2
+myNLC = NLC(f_con, [-np.inf], [0])
+
+res = minimize(fun, x0, method='COBYLA', constraints=myNLC)
+print(res)
+assert abs(res.x[0] - 3) < 1e-2 and abs(res.x[1] - 4) < 1e-2 and abs(res.fun - 4) < 1e-2
diff --git a/python/examples/lincoa_example.py b/python/examples/lincoa_example.py
new file mode 100644
index 0000000000..bd729f9ba1
--- /dev/null
+++ b/python/examples/lincoa_example.py
@@ -0,0 +1,22 @@
+from prima import minimize, LinearConstraint as LC
+from objective import fun
+import numpy as np
+
+
+x0 = [0.0] * 2
+
+# x1 from -inf to 2.5, x2 from -6 to 6
+bounds = [(None, 2.5), (-6, 6)]
+
+Aineq = np.array([[1.0, 0.0],
+ [0.0, 1.0],
+ [1.0, 1.0]])
+bineq = np.array([2.7, # The bounds is below this, so it should take precedence
+ 1.5, # This is below the bounds, so it should take precedence
+ 10.0])
+
+constraint = LC(Aineq, -np.inf, bineq)
+
+res = minimize(fun, x0, method='LINCOA', bounds=bounds, constraints=constraint)
+print(res)
+assert abs(res.x[0] - 2.5) < 2e-1 and abs(res.x[1] - 1.5) < 2e-1
diff --git a/python/examples/newuoa_example.py b/python/examples/newuoa_example.py
new file mode 100644
index 0000000000..376574204c
--- /dev/null
+++ b/python/examples/newuoa_example.py
@@ -0,0 +1,19 @@
+from prima import minimize
+from objective import fun
+
+def callback(x, f, nf, tr, *args):
+ print(x, f, nf, tr)
+ return bool(x[0] > 3) # Testing terminate functionality
+
+x0 = [0.0] * 2
+
+# Test default arguments
+res = minimize(fun, x0, method='NEWUOA')
+print(res) # test repr
+assert fun.result_point_and_value_are_optimal(res)
+
+# Test callback and options
+options = {'rhobeg': 0.1}
+res = minimize(fun, x0, method='NEWUOA', callback=callback, options=options)
+print(res.message)
+assert not fun.result_point_and_value_are_optimal(res)
diff --git a/python/examples/objective.py b/python/examples/objective.py
new file mode 100644
index 0000000000..fa8625d5c3
--- /dev/null
+++ b/python/examples/objective.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+def fun(x):
+ x1, x2 = x
+ f = (x1 - 5) ** 2 + (x2 - 4.0) ** 2
+ return f
+
+fun.optimal_x = np.array((5, 4))
+fun.result_point_is_optimal = lambda result: np.isclose(0, np.linalg.norm(result.x - fun.optimal_x), atol=1e-6, rtol=1e-6)
+fun.optimal_f = fun(fun.optimal_x)
+fun.result_point_and_value_are_optimal = lambda result: fun.result_point_is_optimal(result) and np.isclose(result.fun, fun.optimal_f, atol=1e-6, rtol=1e-6)
diff --git a/python/examples/rosenbrock.py b/python/examples/rosenbrock.py
new file mode 100644
index 0000000000..4e2770bd07
--- /dev/null
+++ b/python/examples/rosenbrock.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+"""Illustration of how to use prima."""
+import numpy as np
+from prima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+
+
+# Print NumPy array nicely.
+np.set_printoptions(precision=4, threshold=7, edgeitems=3)
+
+
+def chrosen(x):
+ """Chained Rosenbrock function."""
+ return sum((1 - x[:-1]) ** 2 + 4 * (x[1:] - x[:-1] ** 2) ** 2)
+
+
+def nlc_ineq(x):
+ """Example of nonlinear inequality constraint function."""
+ return x[:-1] ** 2 - x[1:]
+
+
+def nlc_eq(x):
+ """Example of nonlinear equality constraint function."""
+ return sum(x ** 2) - 1
+
+
+if __name__ == '__main__':
+ print('Minimize the chained Rosenbrock function with three variables subject to various constraints:', end='\n\n')
+ x0 = np.array([0, 0, 0]) # starting point
+
+ print('1. Nonlinear constraints --- ||x||_2^2 = 1, x(i)^2 >= x(i+1) >= 0.5*x(i) >= 0 for i = 1, 2:', end='\n\n')
+ lb = [0, 0, 0]
+ ub = [np.inf, np.inf, np.inf]
+ bounds = Bounds(lb, ub) # bound constraints: lb <= x <= ub
+ A = [[0.5, -1, 0], [0, 0.5, -1]]
+ lin_lb = [-np.inf, -np.inf]
+ lin_ub = [0, 0]
+ lin_con = LinearConstraint(A, lin_lb, lin_ub) # inequality constraints: lin_lb <= A*x <= lin_ub
+ nonlinear_lb = [0, 0]
+ nonlinear_ub = [np.inf, np.inf]
+ nonlinear_con_ineq = NonlinearConstraint(nlc_ineq, nonlinear_lb, nonlinear_ub) # inequality constraints: nonlinear_lb <= nlc_ineq(x) <= nonlinear_ub
+ nonlinear_con_eq = NonlinearConstraint(nlc_eq, 0, 0) # equality constraint: nlc_eq(x) = 0
+ res = minimize(chrosen, x0, bounds=bounds, constraints=[lin_con, nonlinear_con_ineq, nonlinear_con_eq])
+ print(res, end='\n\n')
+
+ print('2. Linear constraints --- sum(x) = 1, x(i+1) <= x(i) <= 1 for i = 1, 2:', end='\n\n')
+ bounds = Bounds([-np.inf, -np.inf, -np.inf], [1, 1, 1])
+ A = [[-1, 1, 0], [0, -1, 1], [1, 1, 1]]
+ lin_con = LinearConstraint(A, [-np.inf, -np.inf, 1], [0, 0, 1])
+ res = minimize(chrosen, x0, bounds=bounds, constraints=lin_con)
+ print(res, end='\n\n')
+
+ print('3. Bound constraints --- -0.5 <= x(1) <= 0.5, 0 <= x(2) <= 0.25:', end='\n\n')
+ bounds = Bounds([-0.5, 0, -np.inf], [0.5, 0.25, np.inf])
+ res = minimize(chrosen, x0, bounds=bounds)
+ print(res, end='\n\n')
+
+ print('4. No constraints:', end='\n\n')
+ res = minimize(chrosen, x0)
+ print(res)
diff --git a/python/examples/uobyqa_example.py b/python/examples/uobyqa_example.py
new file mode 100644
index 0000000000..cf08579927
--- /dev/null
+++ b/python/examples/uobyqa_example.py
@@ -0,0 +1,10 @@
+from prima import minimize
+from objective import fun
+
+
+x0 = [0.0] * 2
+
+
+res = minimize(fun, x0, method='UOBYQA')
+print(res)
+assert fun.result_point_and_value_are_optimal(res)
diff --git a/python/prima/__init__.py b/python/prima/__init__.py
new file mode 100644
index 0000000000..a6eaf6ec78
--- /dev/null
+++ b/python/prima/__init__.py
@@ -0,0 +1,251 @@
+from ._prima import minimize as _minimize, __version__, PRIMAMessage, PRIMAResult
+# Bounds may appear unused in this file but we need to import it to make it available to the user
+from scipy.optimize import NonlinearConstraint, LinearConstraint, Bounds
+from ._nonlinear_constraints import process_nl_constraints
+from ._linear_constraints import (
+ combine_multiple_linear_constraints,
+ separate_LC_into_eq_and_ineq,
+)
+from ._bounds import process_bounds
+from enum import Enum
+import numpy as np
+from ._common import _project
+from ._common import get_arrays_tol
+from .infos import FIXED_SUCCESS
+
+
+class ConstraintType(Enum):
+ LINEAR_OBJECT = 5
+ NONLINEAR_OBJECT = 10
+ LINEAR_DICT = 15
+ NONLINEAR_DICT = 20
+
+
+def get_constraint_type(constraint):
+ if isinstance(constraint, dict) and ("A" in constraint) and ("lb" in constraint) and ("ub" in constraint):
+ return ConstraintType.LINEAR_DICT
+ elif isinstance(constraint, dict) and ("fun" in constraint) and ("lb" in constraint) and ("ub" in constraint):
+ return ConstraintType.NONLINEAR_DICT
+ elif hasattr(constraint, "A") and hasattr(constraint, "lb") and hasattr(constraint, "ub"):
+ return ConstraintType.LINEAR_OBJECT
+ elif hasattr(constraint, "fun") and hasattr(constraint, "lb") and hasattr(constraint, "ub"):
+ return ConstraintType.NONLINEAR_OBJECT
+ else:
+ raise ValueError("Constraint type not recognized")
+
+
+def process_constraints(constraints):
+ # First throw it back if it's an empty tuple
+ if not constraints:
+ return None, None
+ # Next figure out if it's a list of constraints or a single constraint
+ # If it's a single constraint, make it a list, and then the remaining logic
+ # doesn't have to change
+ if not isinstance(constraints, list):
+ constraints = [constraints]
+
+ # Separate out the linear and nonlinear constraints
+ linear_constraints = []
+ nonlinear_constraints = []
+ for constraint in constraints:
+ constraint_type = get_constraint_type(constraint)
+ if constraint_type is ConstraintType.LINEAR_OBJECT:
+ linear_constraints.append(constraint)
+ elif constraint_type is ConstraintType.NONLINEAR_OBJECT:
+ nonlinear_constraints.append(constraint)
+ elif constraint_type == ConstraintType.LINEAR_DICT:
+ linear_constraints.append(LinearConstraint(constraint["A"], constraint["lb"], constraint["ub"]))
+ elif constraint_type == ConstraintType.NONLINEAR_DICT:
+ nonlinear_constraints.append(NonlinearConstraint(constraint["fun"], constraint["lb"], constraint["ub"]))
+ else:
+ raise ValueError("Constraint type not recognized")
+
+ if len(nonlinear_constraints) > 0:
+ nonlinear_constraint_function = process_nl_constraints(nonlinear_constraints)
+ else:
+ nonlinear_constraint_function = None
+
+ # Determine if we have multiple linear constraints, just 1, or none, and process accordingly
+ if len(linear_constraints) > 1:
+ linear_constraint = combine_multiple_linear_constraints(linear_constraints)
+ elif len(linear_constraints) == 1:
+ linear_constraint = linear_constraints[0]
+ else:
+ linear_constraint = None
+
+ return linear_constraint, nonlinear_constraint_function
+
+
+def minimize(fun, x0, args=(), method=None, bounds=None, constraints=(), callback=None, options=None):
+
+ linear_constraint, nonlinear_constraint_function = process_constraints(constraints)
+
+ options = {'quiet': True} if options is None else options
+ quiet = options.get("quiet", True)
+
+ if method is None:
+ if nonlinear_constraint_function is not None:
+ if not quiet: print("Nonlinear constraints detected, applying COBYLA")
+ method = "cobyla"
+ elif linear_constraint is not None:
+ if not quiet: print("Linear constraints detected without nonlinear constraints, applying LINCOA")
+ method = "lincoa"
+ elif bounds is not None:
+ if not quiet: print("Bounds without linear or nonlinear constraints detected, applying BOBYQA")
+ method = "bobyqa"
+ else:
+ if not quiet: print("No bounds or constraints detected, applying NEWUOA")
+ method = "newuoa"
+ else:
+ # Raise some errors if methods were called with inappropriate options
+ method = method.lower()
+ if method not in ('newuoa', 'uobyqa', 'bobyqa', 'cobyla', 'lincoa'):
+ raise ValueError(f"Method must be one of NEWUOA, UOBYQA, BOBYQA, COBYLA, or LINCOA, not '{method}'")
+ if method != "cobyla" and nonlinear_constraint_function is not None:
+ raise ValueError("Nonlinear constraints were provided for an algorithm that cannot handle them")
+ if method not in ("cobyla", "lincoa") and linear_constraint is not None:
+ raise ValueError("Linear constraints were provided for an algorithm that cannot handle them")
+ if method not in ("cobyla", "bobyqa", "lincoa") and bounds is not None:
+ raise ValueError("Bounds were provided for an algorithm that cannot handle them")
+
+ try:
+ lenx0 = len(x0)
+ x0_is_scalar = False
+ except TypeError:
+ lenx0 = 1
+ x0_is_scalar = True
+
+ lb, ub = process_bounds(bounds, lenx0)
+
+ # Check which variables are fixed and eliminate them from the problem.
+ # Save the indices and values so that we can call the original function with
+ # an array of the appropriate size, and so that we can add the fixed values to the
+ # result when COBYLA returns.
+ tol = get_arrays_tol(lb, ub)
+ _fixed_idx = (
+ (lb <= ub)
+ & (ub <= lb + tol)
+ )
+ if any(_fixed_idx) and not all(_fixed_idx):
+ # We should NOT reduce the problem if all variables are fixed. Otherwise, Aineq would be [], and
+ # then bineq will be set to [] in the end. In this way, we lose completely the information in
+ # these constraints. Consequently, we cannot evaluate the constraint violation correctly when needed.
+ _fixed_values = 0.5 * (
+ lb[_fixed_idx] + ub[_fixed_idx]
+ )
+ _fixed_values = np.clip(
+ _fixed_values,
+ lb[_fixed_idx],
+ ub[_fixed_idx],
+ )
+ if isinstance(x0, np.ndarray):
+ x0 = np.array(x0)[~_fixed_idx]
+ else:
+ # In this case x is presumably a list, so we turn it into a numpy array
+ # for the convenience of indexing and then turn it back into a list
+ x0 = np.array(x0)[~_fixed_idx].tolist()
+ lb = lb[~_fixed_idx]
+ ub = ub[~_fixed_idx]
+ original_fun = fun
+ def fixed_fun(x):
+ newx = np.zeros(lenx0)
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = x
+ return original_fun(newx, *args)
+ fun = fixed_fun
+ # Adjust linear_constraint
+ if linear_constraint:
+ new_lb = linear_constraint.lb - linear_constraint.A[:, _fixed_idx] @ _fixed_values
+ new_ub = linear_constraint.ub - linear_constraint.A[:, _fixed_idx] @ _fixed_values
+ new_A = linear_constraint.A[:, ~_fixed_idx]
+ linear_constraint = LinearConstraint(new_A, new_lb, new_ub)
+ # Adjust nonlinear constraints
+ if nonlinear_constraint_function:
+ original_nlc_fun = nonlinear_constraint_function
+ def fixed_nlc_fun(x):
+ newx = np.zeros(lenx0)
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = x
+ return original_nlc_fun(newx, *args)
+ nonlinear_constraint_function = fixed_nlc_fun
+
+
+ # Project x0 onto the feasible set
+ if nonlinear_constraint_function is None:
+ result = _project(x0, lb, ub, {"linear": linear_constraint, "nonlinear": None})
+ x0 = result.x
+ # _project will upgrade x0 to a 1D array if it was a scalar, but the objective function
+ # might expect a scalar, so we downgrade it back to a scalar if that's what it was originally
+ if x0_is_scalar:
+ x0 = x0[0]
+
+ if linear_constraint is not None:
+ A_eq, b_eq, A_ineq, b_ineq = separate_LC_into_eq_and_ineq(linear_constraint)
+ else:
+ A_eq, b_eq, A_ineq, b_ineq = None, None, None, None
+
+ if nonlinear_constraint_function is not None and not all(_fixed_idx):
+ # If there is a nonlinear constraint function, we will call COBYLA, which needs the number of nonlinear
+ # constraints (m_nlcon). In order to get this number we need to evaluate the constraint function at x0.
+ # The constraint value at x0 (nlconstr0) is not discarded but passed down to the Fortran backend, as its
+ # evaluation is assumed to be expensive. We also evaluate the objective function at x0 and pass the result
+ # (f0) down to the Fortran backend, which expects nlconstr0 and f0 to be provided in sync.
+
+ f0 = fun(x0, *args)
+ nlconstr0 = nonlinear_constraint_function(x0)
+ options['f0'] = f0
+ options['nlconstr0'] = nlconstr0
+ options['m_nlcon'] = len(nlconstr0)
+
+ if all(_fixed_idx):
+ x = 0.5 * (
+ lb[_fixed_idx] + ub[_fixed_idx]
+ )
+ x = np.clip(
+ x,
+ lb[_fixed_idx],
+ ub[_fixed_idx],
+ )
+ success = True
+ status = FIXED_SUCCESS
+ message = "All variables were fixed by the provided bounds."
+ fun = fun(x)
+ nfev = 1
+ nlconstr = nonlinear_constraint_function(x) if nonlinear_constraint_function is not None else np.array([])
+ maxcv = max(max((A_ineq @ x) - b_ineq) if A_ineq is not None else 0,
+ max((abs((A_eq @ x) - b_eq))) if A_eq is not None else 0,
+ max(np.append(0, nlconstr)))
+ result = PRIMAResult()
+ result.x = x
+ result.success = success
+ result.status = status
+ result.message = message
+ result.fun = fun
+ result.nfev = nfev
+ result.maxcv = maxcv
+ result.nlconstr = nlconstr
+ result.method = method
+ return result
+
+ result = _minimize(
+ fun,
+ x0,
+ args,
+ method,
+ lb,
+ ub,
+ A_eq,
+ b_eq,
+ A_ineq,
+ b_ineq,
+ nonlinear_constraint_function,
+ callback,
+ options
+ )
+
+ if any(_fixed_idx):
+ newx = np.zeros(lenx0) + np.nan
+ newx[_fixed_idx] = _fixed_values
+ newx[~_fixed_idx] = result.x
+ result.x = newx
+ return result
diff --git a/python/prima/_bounds.py b/python/prima/_bounds.py
new file mode 100644
index 0000000000..a917a0bc1e
--- /dev/null
+++ b/python/prima/_bounds.py
@@ -0,0 +1,39 @@
+import numpy as np
+from scipy.optimize import Bounds
+
+def process_bounds(bounds, lenx0):
+ '''
+ `bounds` can either be an object with the properties lb and ub, or a list of tuples
+ indicating a lower bound and an upper bound for each variable. If the list contains
+ fewer entries than the length of x0, the remaining entries will generated as -/+ infinity.
+ Some examples of valid lists of tuple, assuming len(x0) == 3:
+ [(0, 1), (2, 3), (4, 5)] -> returns [0, 2, 4], [1, 3, 5]
+ [(0, 1), (None, 3)] -> returns [0, -inf, -inf], [1, 3, inf]
+ [(0, 1), (-np.inf, 3)] -> returns [0, -inf, -inf], [1, 3, inf]
+ '''
+
+ if bounds is None:
+ lb = np.array([-np.inf]*lenx0, dtype=np.float64)
+ ub = np.array([np.inf]*lenx0, dtype=np.float64)
+ return lb, ub
+
+ if isinstance(bounds, Bounds):
+ lb = np.array(bounds.lb, dtype=np.float64)
+ ub = np.array(bounds.ub, dtype=np.float64)
+ lb = np.concatenate((lb, -np.inf*np.ones(lenx0 - len(lb))))
+ ub = np.concatenate((ub, np.inf*np.ones(lenx0 - len(ub))))
+ return lb, ub
+
+ # If neither of the above conditions are true, we assume that bounds is a list of tuples
+ lb = np.array([bound[0] if bound[0] is not None else -np.inf for bound in bounds], dtype=np.float64)
+ ub = np.array([bound[1] if bound[1] is not None else np.inf for bound in bounds], dtype=np.float64)
+ # If there were fewer bounds than variables, pad the rest with -/+ infinity
+ lb = np.concatenate((lb, -np.inf*np.ones(lenx0 - len(lb))))
+ ub = np.concatenate((ub, np.inf*np.ones(lenx0 - len(ub))))
+
+ # Check the infeasibility of the bounds.
+ # TODO: Return a code instead of asserting because a feasibility check is a valid solution
+ infeasible = np.logical_not(lb <= ub)
+ assert not np.any(infeasible), f"Some of the provided bounds are infeasible. {infeasible=} {lb[infeasible]=}, {ub[infeasible]=}"
+
+ return lb, ub
diff --git a/python/prima/_common.py b/python/prima/_common.py
new file mode 100644
index 0000000000..f4a6a2f2fb
--- /dev/null
+++ b/python/prima/_common.py
@@ -0,0 +1,194 @@
+import numpy as np
+from ._linear_constraints import LinearConstraint
+from scipy.optimize import OptimizeResult
+
+# All the accepted scalar types; np.generic correspond to all NumPy types.
+scalar_types = (int, float, np.generic)
+eps = np.finfo(np.float64).eps
+
+def _project(x0, lb, ub, constraints):
+ """Projection of the initial guess onto the feasible set.
+
+ Parameters
+ ----------
+ x0: ndarray, shape (n,)
+ The same as in prepdfo.
+ lb: ndarray, shape (n,)
+ The same as in prepdfo.
+ ub: ndarray, shape (n,)
+ The same as in prepdfo.
+ constraints: dict
+ The general constraints of the problem, defined as a dictionary with
+ fields:
+ linear: LinearConstraint
+ The linear constraints of the problem.
+ nonlinear: dict
+ The nonlinear constraints of the problem. When ``_project`` is called, the nonlinear constraints are
+ None.
+
+ Returns
+ -------
+ result: OptimizeResult
+ The result of the projection.
+
+ Authors
+ -------
+ Tom M. RAGONNEAU (tom.ragonneau@gmail.com)
+ and Zaikun ZHANG (www.zhangzk.net)
+
+ Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
+ """
+ invoker = 'prima'
+
+ # Validate x0.
+ if isinstance(x0, scalar_types):
+ x0_c = [x0]
+ elif hasattr(x0, '__len__'):
+ x0_c = x0
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should be a vector.'.format(invoker))
+ try:
+ x0_c = np.asarray(x0_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should contain only scalars.'.format(invoker))
+ if len(x0_c.shape) != 1:
+ raise ValueError('{}: UNEXPECTED ERROR: x0 should be a vector.'.format(invoker))
+ lenx0 = x0_c.size
+
+ # Validate lb.
+ if isinstance(lb, scalar_types):
+ lb_c = [lb]
+ elif hasattr(lb, '__len__'):
+ lb_c = lb
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: lb should be a vector.'.format(invoker))
+ try:
+ lb_c = np.asarray(lb_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: lb should contain only scalars.'.format(invoker))
+ if len(lb_c.shape) != 1 or lb_c.size != lenx0:
+ raise ValueError('{}: UNEXPECTED ERROR: the size of lb is inconsistent with x0.'.format(invoker))
+
+ # Validate ub.
+ if isinstance(ub, scalar_types):
+ ub_c = [ub]
+ elif hasattr(ub, '__len__'):
+ ub_c = ub
+ else:
+ raise ValueError('{}: UNEXPECTED ERROR: ub should be a vector.'.format(invoker))
+ try:
+ ub_c = np.asarray(ub_c, dtype=np.float64)
+ except ValueError:
+ raise ValueError('{}: UNEXPECTED ERROR: ub should contain only scalars.'.format(invoker))
+ if len(ub_c.shape) != 1 or ub_c.size != lenx0:
+ raise ValueError('{}: UNEXPECTED ERROR: the size of ub is inconsistent with x0.'.format(invoker))
+
+ # Validate constraints.
+ if not isinstance(constraints, dict) or not ({'linear', 'nonlinear'} <= set(constraints.keys())) or \
+ not (isinstance(constraints['linear'], LinearConstraint) or constraints['linear'] is None):
+ # the nonlinear constraints will not be taken into account in this function and are, therefore, not validated
+ raise ValueError('{}: UNEXPECTED ERROR: The constraints are ill-defined.'.format(invoker))
+
+ max_con = 1e20 # Decide whether an inequality constraint can be ignored
+
+ # Project onto the feasible set.
+ if constraints['linear'] is None:
+ # Direct projection onto the bound constraints
+ x_proj = np.nanmin((np.nanmax((x0_c, lb_c), axis=0), ub_c), axis=0)
+ return OptimizeResult(x=x_proj)
+ elif all(np.less_equal(np.abs(constraints['linear'].ub - constraints['linear'].lb), eps)) and \
+ np.max(lb_c) <= -max_con and np.min(ub_c) >= max_con:
+ # The linear constraints are all equality constraints. The projection can therefore be done by solving the
+ # least-squares problem: min ||A*x - (b - A*x_0)||.
+ a = constraints['linear'].A
+ b = (constraints['linear'].lb + constraints['linear'].ub) / 2
+ xi, _, _, _ = np.linalg.lstsq(a, b - np.dot(a, x0_c), rcond=None)
+
+ # The problem is not bounded. However, if the least-square solver returned values bigger in absolute value
+ # than max_con, they will be reduced to this bound.
+ x_proj = np.nanmin((np.nanmax((x0_c + xi, lb_c), axis=0), ub_c), axis=0)
+
+ return OptimizeResult(x=x_proj)
+
+ if constraints['linear'] is not None:
+ try:
+ # Project the initial guess onto the linear constraints via SciPy.
+ from scipy.optimize import minimize
+ from scipy.optimize import Bounds as ScipyBounds
+ from scipy.optimize import LinearConstraint as ScipyLinearConstraint
+
+ linear = constraints['linear']
+
+ # To be more efficient, SciPy asks to separate the equality and the inequality constraints into two
+ # different LinearConstraint structures
+ pc_args_ineq, pc_args_eq = dict(), dict()
+ pc_args_ineq['A'], pc_args_eq['A'] = np.asarray([[]]), np.asarray([[]])
+ pc_args_ineq['A'] = pc_args_ineq['A'].reshape(0, linear.A.shape[1])
+ pc_args_eq['A'] = pc_args_eq['A'].reshape(0, linear.A.shape[1])
+ pc_args_ineq['lb'], pc_args_eq['lb'] = np.asarray([]), np.asarray([])
+ pc_args_ineq['ub'], pc_args_eq['ub'] = np.asarray([]), np.asarray([])
+
+ for i in range(linear.lb.size):
+ if linear.lb[i] != linear.ub[i]:
+ pc_args_ineq['A'] = np.concatenate((pc_args_ineq['A'], linear.A[i:i+1, :]), axis=0)
+ pc_args_ineq['lb'] = np.r_[pc_args_ineq['lb'], linear.lb[i]]
+ pc_args_ineq['ub'] = np.r_[pc_args_ineq['ub'], linear.ub[i]]
+ else:
+ pc_args_eq['A'] = np.concatenate((pc_args_eq['A'], linear.A[i:i+1, :]), axis=0)
+ pc_args_eq['lb'] = np.r_[pc_args_eq['lb'], linear.lb[i]]
+ pc_args_eq['ub'] = np.r_[pc_args_eq['ub'], linear.ub[i]]
+
+ if pc_args_ineq['A'].size > 0 and pc_args_ineq['lb'].size > 0 and pc_args_eq['lb'].size > 0:
+ project_constraints = [ScipyLinearConstraint(**pc_args_ineq), ScipyLinearConstraint(**pc_args_eq)]
+ elif pc_args_ineq['A'].size > 0 and pc_args_ineq['lb'].size > 0:
+ project_constraints = ScipyLinearConstraint(**pc_args_ineq)
+ elif pc_args_eq['A'].size > 0:
+ project_constraints = ScipyLinearConstraint(**pc_args_eq)
+ else:
+ project_constraints = ()
+
+ # Perform the actual projection.
+ ax_ineq = np.dot(pc_args_ineq['A'], x0_c)
+ ax_eq = np.dot(pc_args_eq['A'], x0_c)
+ if np.greater(ax_ineq, pc_args_ineq['ub']).any() or np.greater(pc_args_ineq['lb'], ax_ineq).any() or \
+ np.not_equal(ax_eq, pc_args_eq['lb']).any() or \
+ np.greater(x0_c, ub_c).any() or np.greater(lb_c, x0_c).any():
+ return minimize(lambda x: np.dot(x - x0_c, x - x0_c) / 2, x0_c, jac=lambda x: (x - x0_c),
+ bounds=ScipyBounds(lb_c, ub_c), constraints=project_constraints)
+ else:
+ # Do not perform any projection if the initial guess is feasible.
+ return OptimizeResult(x=x0_c)
+
+ except ImportError:
+ return OptimizeResult(x=x0_c)
+
+ return OptimizeResult(x=x0_c)
+
+
+def get_arrays_tol(*arrays):
+ """
+ Get a relative tolerance for a set of arrays. Borrowed from COBYQA
+
+ Parameters
+ ----------
+ *arrays: tuple
+ Set of `numpy.ndarray` to get the tolerance for.
+
+ Returns
+ -------
+ float
+ Relative tolerance for the set of arrays.
+
+ Raises
+ ------
+ ValueError
+ If no array is provided.
+ """
+ if len(arrays) == 0:
+ raise ValueError("At least one array must be provided.")
+ size = max(array.size for array in arrays)
+ weight = max(
+ np.max(np.abs(array[np.isfinite(array)]), initial=1.0)
+ for array in arrays
+ )
+ return 10.0 * eps * max(size, 1.0) * weight
diff --git a/python/prima/_linear_constraints.py b/python/prima/_linear_constraints.py
new file mode 100644
index 0000000000..be508dced7
--- /dev/null
+++ b/python/prima/_linear_constraints.py
@@ -0,0 +1,46 @@
+import numpy as np
+from scipy.optimize import LinearConstraint
+
+
+def combine_multiple_linear_constraints(constraints):
+ full_A = constraints[0].A
+ full_lb = constraints[0].lb
+ full_ub = constraints[0].ub
+ for constraint in constraints[1:]:
+ full_A = np.concatenate((full_A, constraint.A), axis=0)
+ full_lb = np.concatenate((full_lb, constraint.lb), axis=0)
+ full_ub = np.concatenate((full_ub, constraint.ub), axis=0)
+ return LinearConstraint(full_A, full_lb, full_ub)
+
+
+def separate_LC_into_eq_and_ineq(linear_constraint):
+ # The Python interface receives linear constraints lb <= A*x <= ub, but the
+ # Fortran backend of PRIMA expects that the linear constraints are specified
+ # as A_eq*x = b_eq, A_ineq*x <= b_ineq.
+ # As such, we must:
+ # 1. for constraints with lb == ub, rewrite them as A_eq*x = lb;
+ # 2. for constraints with lb < ub, rewrite them as A_ineq*x <= b_ineq.
+
+ # We suppose lb == ub if ub <= lb + 2*epsilon, assuming that the preprocessing
+ # ensures lb <= ub.
+ epsilon = np.finfo(np.float64).eps
+
+ eq_indices = (linear_constraint.ub <= (linear_constraint.lb + 2*epsilon))
+ A_eq = linear_constraint.A[eq_indices]
+ b_eq = (linear_constraint.lb[eq_indices] + linear_constraint.ub[eq_indices])/2.0
+
+ ineq_lb_indices = (linear_constraint.lb > -np.inf)
+ A_ineq_lb = -linear_constraint.A[~eq_indices & ineq_lb_indices]
+ b_ineq_lb = -linear_constraint.lb[~eq_indices & ineq_lb_indices]
+ ineq_ub_indices = (linear_constraint.ub < np.inf)
+ A_ineq_ub = linear_constraint.A[~eq_indices & ineq_ub_indices]
+ b_ineq_ub = linear_constraint.ub[~eq_indices & ineq_ub_indices]
+ A_ineq = np.concatenate((A_ineq_lb, A_ineq_ub))
+ b_ineq = np.concatenate((b_ineq_lb, b_ineq_ub))
+
+ # Ensure dtype is float64, or set to None if empty
+ A_eq = np.array(A_eq, dtype=np.float64) if len(A_eq) > 0 else None
+ b_eq = np.array(b_eq, dtype=np.float64) if len(b_eq) > 0 else None
+ A_ineq = np.array(A_ineq, dtype=np.float64) if len(A_ineq) > 0 else None
+ b_ineq = np.array(b_ineq, dtype=np.float64) if len(b_ineq) > 0 else None
+ return A_eq, b_eq, A_ineq, b_ineq
diff --git a/python/prima/_nonlinear_constraints.py b/python/prima/_nonlinear_constraints.py
new file mode 100644
index 0000000000..b248d4da8c
--- /dev/null
+++ b/python/prima/_nonlinear_constraints.py
@@ -0,0 +1,49 @@
+import numpy as np
+
+def transform_constraint_function(nlc):
+ '''
+ The Python interfaces receives the constraints as lb <= constraint(x) <= ub,
+ but the Fortran backend expects the nonlinear constraints to be constraint(x) <= 0.
+ Thus a conversion is needed.
+
+ In addition to the conversion, we add a check to ensure that the provided lower/upper bounds
+ have a shape consistent with the output of the constraint function.
+ '''
+
+ def newconstraint(x):
+ values = np.atleast_1d(np.array(nlc.fun(x), dtype=np.float64))
+
+ # Upgrade the lower/upper bounds to vectors if necessary
+ lb = nlc.lb
+ if not hasattr(lb, '__len__'):
+ lb = np.array([nlc.lb]*len(values), dtype=np.float64)
+ ub = nlc.ub
+ if not hasattr(ub, '__len__'):
+ ub = np.array([nlc.ub]*len(values), dtype=np.float64)
+
+
+ # Check the shapes and raise an exception if they do not match
+ if len(values) != len(lb):
+ raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the lower bound.")
+ if len(values) != len(ub):
+ raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the upper bound.")
+
+ # Combine the upper and lower bounds to transform the function into the form
+ # expected by the Fortran backend.
+ return np.concatenate(([lb_ii - vi for lb_ii, vi in zip(lb, values) if lb_ii > -np.inf],
+ [vi - ub_ii for ub_ii, vi in zip(ub, values) if ub_ii < np.inf],
+ ))
+ return newconstraint
+
+
+def process_nl_constraints(nlcs):
+ functions = []
+ for nlc in nlcs:
+ fun_i = transform_constraint_function(nlc)
+ functions.append(fun_i)
+ def constraint_function(x):
+ values = np.empty(0, dtype=np.float64)
+ for fun in functions:
+ values = np.concatenate((values, fun(x)))
+ return values
+ return constraint_function
diff --git a/python/prima/infos.py b/python/prima/infos.py
new file mode 100644
index 0000000000..77ffea2ca1
--- /dev/null
+++ b/python/prima/infos.py
@@ -0,0 +1,5 @@
+'''
+This module will be merged with pyprima infos.py in the near future
+'''
+
+FIXED_SUCCESS = 13
\ No newline at end of file
diff --git a/python/profiles/.gitignore b/python/profiles/.gitignore
new file mode 100644
index 0000000000..fa9567d1b0
--- /dev/null
+++ b/python/profiles/.gitignore
@@ -0,0 +1,2 @@
+pycutest_cache_holder
+out
diff --git a/python/profiles/README.md b/python/profiles/README.md
new file mode 100644
index 0000000000..d7960735da
--- /dev/null
+++ b/python/profiles/README.md
@@ -0,0 +1,7 @@
+This folder contains scripts and utilities to run performance profiles of the PRIMA Python bindings against the PRIMA MATLAB bindings.
+
+In general we expect the performance profiles to show a straight line for both the output based and history based profiles since the underlying code is the same. However since there are bound to be some differences in how MATLAB and Python handle floating point arithmetic we expect to see a few cases where one might perform slightly "better" than the other.
+
+In order to use this you will need Python and MATLAB and in the MATLAB GUI you should have run setup.m from the root of the repo. If that has been completed you should be able to run the profiles with simply `python profiles.py`. By default no profiles will be run, so you need to specify which algorithm you'd like to run profiles for using any combination of `-b` for BOBYQA, `-c` for COBYLA, `-l` for LINCOA, `-n` for NEWUOA, and `-u` for UOBYQA. You can also use `-j#` to change the number of jobs used to run the problems. For example to run both UOBYQA and NEWUOA with 4 jobs, you would run `python profiles.py -nuj4`.
+
+It is not necessary to have a MATLAB window open to run the profiles. Each job will create an instance of the MATLAB engine for Python. Please note that this instance takes up about 500MB of memory so you may want to limit the number of jobs accordingly.
\ No newline at end of file
diff --git a/python/profiles/bobyqa.txt b/python/profiles/bobyqa.txt
new file mode 100644
index 0000000000..95e470a484
--- /dev/null
+++ b/python/profiles/bobyqa.txt
@@ -0,0 +1,220 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EXP2
+EXP2B
+EXPFIT
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HS1
+HS110
+HS2
+HS25
+HS3
+HS38
+HS3MOD
+HS4
+HS45
+HS5
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LOGHAIRY
+LOGROS
+LSC1LS
+LSC2LS
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+S368
+SIM2BQP
+SIMBQP
+SINEVAL
+SISSER
+SNAIL
+SPECAN
+SSI
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
diff --git a/python/profiles/cobyla.txt b/python/profiles/cobyla.txt
new file mode 100644
index 0000000000..40cb534f87
--- /dev/null
+++ b/python/profiles/cobyla.txt
@@ -0,0 +1,654 @@
+AIRCRFTA
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITA
+ALLINITC
+ALLINITU
+ALSOTAME
+ARGAUSS
+AVGASA
+AVGASB
+BARD
+BARDNE
+BEALE
+BEALENE
+BENNETT5
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGS6NE
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOX3NE
+BOXBOD
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNBSNE
+BROWNDEN
+BROWNDENE
+BT1
+BT10
+BT11
+BT12
+BT13
+BT2
+BT3
+BT4
+BT5
+BT6
+BT7
+BT8
+BT9
+BYRDSPHR
+CAMEL6
+CANTILVR
+CB2
+CB3
+CERI651A
+CERI651ALS
+CERI651B
+CERI651BLS
+CERI651C
+CERI651CLS
+CERI651D
+CERI651DLS
+CERI651E
+CERI651ELS
+CHACONN1
+CHACONN2
+CHWIRUT1
+CHWIRUT1LS
+CHWIRUT2
+CHWIRUT2LS
+CLIFF
+CLUSTER
+CLUSTERLS
+CONCON
+CONGIGMZ
+COOLHANS
+COOLHANSLS
+CRESC100
+CRESC132
+CRESC4
+CRESC50
+CSFI1
+CSFI2
+CUBE
+CUBENE
+DANIWOOD
+DANIWOODLS
+DANWOOD
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DEMBO7
+DEMYMALO
+DENSCHNA
+DENSCHNB
+DENSCHNBNE
+DENSCHNC
+DENSCHNCNE
+DENSCHND
+DENSCHNDNE
+DENSCHNE
+DENSCHNENE
+DENSCHNF
+DENSCHNFNE
+DEVGLA1
+DEVGLA1B
+DEVGLA1NE
+DEVGLA2
+DEVGLA2B
+DEVGLA2NE
+DGOSPEC
+DIPIGRI
+DIXCHLNG
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+EGGCRATENE
+ELATTAR
+ELATVIDU
+ELATVIDUB
+ELATVIDUNE
+ENGVAL2
+ENGVAL2NE
+ENSO
+ENSOLS
+EQC
+ERRINBAR
+EXP2
+EXP2B
+EXP2NE
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXPFITNE
+EXTRASIM
+FBRAIN
+FBRAIN2
+FBRAIN2LS
+FBRAIN2NE
+FBRAIN3
+FBRAIN3LS
+FBRAINLS
+FBRAINNE
+FCCU
+FLETCHER
+FLT
+FREURONE
+GAUSS1
+GAUSS1LS
+GAUSS2
+GAUSS2LS
+GAUSS3
+GAUSS3LS
+GAUSSIAN
+GBRAIN
+GBRAINLS
+GENHS28
+GIGOMEZ1
+GIGOMEZ2
+GIGOMEZ3
+GOTTFR
+GROWTH
+GROWTHLS
+GULF
+GULFNE
+HAHN1
+HAHN1LS
+HAIFAS
+HAIRY
+HALDMADS
+HART6
+HATFLDA
+HATFLDANE
+HATFLDB
+HATFLDBNE
+HATFLDD
+HATFLDDNE
+HATFLDE
+HATFLDENE
+HATFLDF
+HATFLDFL
+HATFLDFLNE
+HATFLDFLS
+HATFLDH
+HEART6
+HEART6LS
+HEART8
+HEART8LS
+HELIX
+HELIXNE
+HET-Z
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBC
+HIMMELBCLS
+HIMMELBD
+HIMMELBE
+HIMMELBF
+HIMMELBFNE
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HIMMELP2
+HIMMELP3
+HIMMELP4
+HIMMELP5
+HIMMELP6
+HONG
+HS1
+HS10
+HS100
+HS100LNP
+HS100MOD
+HS101
+HS102
+HS103
+HS104
+HS105
+HS106
+HS107
+HS108
+HS109
+HS11
+HS110
+HS111
+HS111LNP
+HS112
+HS113
+HS114
+HS116
+HS117
+HS118
+HS119
+HS12
+HS13
+HS14
+HS15
+HS16
+HS17
+HS18
+HS19
+HS1NE
+HS2
+HS20
+HS21
+HS21MOD
+HS22
+HS23
+HS24
+HS25
+HS25NE
+HS26
+HS268
+HS27
+HS28
+HS29
+HS2NE
+HS3
+HS30
+HS31
+HS32
+HS33
+HS34
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS39
+HS3MOD
+HS4
+HS40
+HS41
+HS42
+HS43
+HS44
+HS44NEW
+HS45
+HS46
+HS47
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS56
+HS57
+HS59
+HS6
+HS60
+HS61
+HS62
+HS63
+HS64
+HS65
+HS66
+HS68
+HS69
+HS7
+HS70
+HS71
+HS72
+HS73
+HS74
+HS75
+HS76
+HS76I
+HS77
+HS78
+HS79
+HS8
+HS80
+HS81
+HS83
+HS84
+HS85
+HS86
+HS87
+HS88
+HS89
+HS9
+HS90
+HS91
+HS92
+HS93
+HS95
+HS96
+HS97
+HS98
+HS99
+HUBFIT
+HUMPS
+HYPCIR
+INTEQNE
+JENSMP
+JENSMPNE
+JUDGE
+JUDGEB
+JUDGENE
+KIRBY2
+KIRBY2LS
+KIWCRESC
+KOEBHELB
+KOEBHELBNE
+KOWOSB
+KOWOSBNE
+KSIP
+LANCZOS1
+LANCZOS1LS
+LANCZOS2
+LANCZOS2LS
+LANCZOS3
+LANCZOS3LS
+LEVYMONE10
+LEVYMONE5
+LEVYMONE6
+LEVYMONE7
+LEVYMONE8
+LEVYMONE9
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LEWISPOL
+LIN
+LOGHAIRY
+LOGROS
+LOOTSMA
+LOTSCHD
+LSC1
+LSC1LS
+LSC2
+LSC2LS
+LSNNODOC
+LSQFIT
+MADSEN
+MAKELA1
+MAKELA2
+MARATOS
+MARATOSB
+MATRIX2
+MAXLIKA
+MCONCON
+MDHOLE
+MEXHAT
+MEYER3
+MEYER3NE
+MGH09
+MGH09LS
+MGH10
+MGH10LS
+MGH10S
+MGH10SLS
+MGH17
+MGH17LS
+MGH17S
+MGH17SLS
+MIFFLIN1
+MIFFLIN2
+MINMAXBD
+MINMAXRB
+MISRA1A
+MISRA1ALS
+MISRA1B
+MISRA1BLS
+MISRA1C
+MISRA1CLS
+MISRA1D
+MISRA1DLS
+MISTAKE
+MOREBVNE
+MWRIGHT
+NELSON
+NELSONLS
+NYSTROM5
+NYSTROM5C
+ODFITS
+OET1
+OET2
+OET3
+OET4
+OET5
+OET6
+OET7
+OSBORNE1
+OSBORNE2
+OSBORNEA
+OSBORNEB
+OSCIPANE
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1ANE
+PALMER1B
+PALMER1BNE
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER1ENE
+PALMER1NE
+PALMER2
+PALMER2A
+PALMER2ANE
+PALMER2B
+PALMER2BNE
+PALMER2C
+PALMER2E
+PALMER2ENE
+PALMER2NE
+PALMER3
+PALMER3A
+PALMER3ANE
+PALMER3B
+PALMER3BNE
+PALMER3C
+PALMER3E
+PALMER3ENE
+PALMER3NE
+PALMER4
+PALMER4A
+PALMER4ANE
+PALMER4B
+PALMER4BNE
+PALMER4C
+PALMER4E
+PALMER4ENE
+PALMER4NE
+PALMER5A
+PALMER5ANE
+PALMER5B
+PALMER5BNE
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER5ENE
+PALMER6A
+PALMER6ANE
+PALMER6C
+PALMER6E
+PALMER6ENE
+PALMER7A
+PALMER7ANE
+PALMER7C
+PALMER7E
+PALMER7ENE
+PALMER8A
+PALMER8ANE
+PALMER8C
+PALMER8E
+PALMER8ENE
+PARKCH
+PENLT1NE
+PENLT2NE
+PENTAGON
+PFIT1
+PFIT1LS
+PFIT2
+PFIT2LS
+PFIT3
+PFIT3LS
+PFIT4
+PFIT4LS
+POLAK1
+POLAK2
+POLAK3
+POLAK4
+POLAK5
+POLAK6
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBS
+POWELLBSLS
+POWELLSE
+POWELLSQ
+POWELLSQLS
+POWERSUM
+POWERSUMB
+POWERSUMNE
+PRICE3
+PRICE3B
+PRICE3NE
+PRICE4
+PRICE4B
+PRICE4NE
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42
+RAT42LS
+RAT43
+RAT43LS
+RECIPE
+RECIPELS
+RES
+RK23
+ROBOT
+ROSENBR
+ROSENBRTU
+ROSENMMX
+ROSZMAN1
+ROSZMAN1LS
+RSNBRNE
+S268
+S277-280
+S308
+S308NE
+S316-322
+S365
+S365MOD
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SINVALNE
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SNAKE
+SPECAN
+SPECANNE
+SPIRAL
+SSI
+SSINE
+STANCMIN
+STRATEC
+STREG
+STREGNE
+STRTCHDV
+STRTCHDVB
+STRTCHDVNE
+SUPERSIM
+SYNTHES1
+SYNTHES2
+SYNTHES3
+TAME
+TENBARS1
+TENBARS2
+TENBARS3
+TENBARS4
+TFI1
+TFI2
+TFI3
+THURBER
+THURBERLS
+TRIGGER
+TRIGON1
+TRIGON1B
+TRIGON1NE
+TRIGON2
+TRIGON2B
+TRIGON2NE
+TRUSPYR1
+TRUSPYR2
+TRY-B
+TWOBARS
+VARDIMNE
+VESUVIA
+VESUVIALS
+VESUVIO
+VESUVIOLS
+VESUVIOU
+VESUVIOULS
+VIBRBEAM
+VIBRBEAMNE
+WACHBIEG
+WATSON
+WATSONNE
+WAYSEA1
+WAYSEA1B
+WAYSEA1NE
+WAYSEA2
+WAYSEA2B
+WAYSEA2NE
+WEEDS
+WEEDSNE
+WOMFLET
+YFIT
+YFITNE
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
+ZECEVIC3
+ZECEVIC4
+ZY2
diff --git a/python/profiles/excludelist.py b/python/profiles/excludelist.py
new file mode 100644
index 0000000000..d996a6ead4
--- /dev/null
+++ b/python/profiles/excludelist.py
@@ -0,0 +1,133 @@
+def excludelist(problem_type):
+ # As of 20230426, the objective function of HS67 takes infinite time to be evaluated at some
+ # points, e.g., [88.1351318; 12829.9219; 1.0e-5], maybe due to an infinite cycling.
+ excludelist = ['HS76']
+ # ARGLALE results in a segfault from slsqp when trying to project x0. We disable it for now
+ excludelist += ['ARGLALE', 'ARGLBLE', 'ARGLCLE']
+
+ if problem_type == 'unconstrained':
+ # With the following excludelist, there is no unconstrained problem in CUTEst (as of 20230130) with
+ # dimension between 51 and 100.
+ excludelist += [
+ 'ARGTRIGLS',
+ 'BA-L1LS',
+ 'BA-L1SPLS',
+ 'BROWNAL',
+ 'CHNROSNB',
+ 'CHNRSNBM',
+ 'DIAMON2DLS',
+ 'DIAMON3DLS',
+ 'DMN15102LS',
+ 'DMN15103LS',
+ 'DMN15332LS',
+ 'DMN15333LS',
+ 'DMN37142LS',
+ 'DMN37143LS',
+ 'ERRINROS',
+ 'ERRINRSM',
+ 'HYDC20LS',
+ 'LUKSAN11LS',
+ 'LUKSAN12LS',
+ 'LUKSAN13LS',
+ 'LUKSAN14LS',
+ 'LUKSAN15LS',
+ 'LUKSAN16LS',
+ 'LUKSAN17LS',
+ 'LUKSAN22LS',
+ 'LUKSAN21LS',
+ 'LRCOVTYPE',
+ 'MANCINO',
+ 'QING',
+ 'SENSORS',
+ 'TOINTGOR',
+ 'TOINTPSP',
+ 'VARDIM',
+ ]
+ elif problem_type == 'bobyqa':
+ # For the following problems, the classical bobyqa (single-precision) encounters SEGFAULT.
+ excludelist += ['MGH17LS']
+ elif problem_type == 'lincoa':
+ excludelist += [
+ 'DALLASM',
+ 'TARGUS',
+ ]
+ elif problem_type == 'cobyla':
+ # The following problems were observed to take excessive time during tests GitHub Actions and
+ # make the tests run overtime. Some of them may not be very time-consuming during a "plain"
+ # test but become more challenging with some perturbations or variations. The excessive time may
+ # be also a result of infinite cycling encountered by the classical version of cobyla.
+ # The number following the problem is the time in seconds taken by cobyla in a "plain" test on
+ # 20230130, which tested all linearly and nonlinearly constrained problems with at most 100
+ # variables and 10000 constraints. Bound-constrained or unconstrained problems were not tested.
+ excludelist += [
+ 'ACOPP30' ,
+ 'ACOPR30',
+ 'AIRPORT', # 73
+ 'BATCH', # 20
+ 'CHANDHEQ', # 17
+ 'CHEBYQADNE', # 546
+ 'CHNRSBNE', # 18
+ 'CHNRSNBMNE', # 32
+ 'CORE1', # 64
+ 'CRESC100',
+ 'CRESC132',
+ 'CVXQP1', # 54
+ 'DALLASS', # 3 (it takes a long time on GitHub Actions)
+ 'DECONVBNE',
+ 'DECONVC', # In a test on 20230328, the classical cobyla encountered infinite cycling.
+ 'DECONVNE',
+ 'DIAMON2D', # 1415
+ 'DIAMON3D', # 3703
+ 'DMN15102', # 887
+ 'DMN15103', # 3205
+ 'DMN15332', # 838
+ 'DMN15333', # 1441
+ 'DMN37142', # 857
+ 'DMN37143', # 2406
+ 'DUAL1', # 73
+ 'DUAL2', # 30
+ 'DUAL4',
+ 'ERRINRSMNE', # 16
+ 'FBRAIN2',
+ 'FBRAIN2NE',
+ 'FBRAIN3',
+ 'FEEDLOC',
+ 'GOULDQP1',
+ 'HAIFAM', # 173
+ 'HIMMELBI', # 100
+ 'HIMMELBJ',
+ 'HYDCAR20', # 648
+ 'HYDCAR6',
+ 'KISSING2',
+ 'LAKES', # 65
+ 'LEVYMONE', # 15
+ 'LHAIFAM',
+ 'LINSPANH', # 3 (it takes a long time on GitHub Actions)
+ 'LUKSAN11',
+ 'LUKSAN12', # 563
+ 'LUKSAN13', # 508
+ 'LUKSAN14', # 23
+ 'LUKSAN15', # 19
+ 'LUKSAN16', # 17
+ 'LUKSAN17', # 25
+ 'LUKSAN21', # 13
+ 'LUKSAN22', # 19
+ 'MANCINONE',
+ 'MSS1', # 39
+ 'OET5',
+ 'OET6',
+ 'OET7',
+ 'QINGNE',
+ 'QPCBLEND' ,
+ 'SPANHYD', # 15
+ 'SWOPF', # 10
+ 'TAX13322', # 5
+ 'TAXR13322', # 5
+ 'TRO4X4', # 30
+ 'VANDERM1', # 72
+ 'VANDERM2', # 72
+ 'VANDERM3', # 76
+ 'VESUVIO',
+ 'VESUVIOU',
+ ]
+ return excludelist
\ No newline at end of file
diff --git a/python/profiles/lincoa.txt b/python/profiles/lincoa.txt
new file mode 100644
index 0000000000..6388432a47
--- /dev/null
+++ b/python/profiles/lincoa.txt
@@ -0,0 +1,308 @@
+AIRCRFTB
+AKIVA
+ALLINIT
+ALLINITU
+AVGASA
+AVGASB
+BARD
+BEALE
+BENNETT5LS
+BIGGS3
+BIGGS5
+BIGGS6
+BIGGSC4
+BOOTH
+BOX2
+BOX3
+BOXBODLS
+BRANIN
+BRKMCC
+BROWNBS
+BROWNDEN
+BT3
+CAMEL6
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DEGENLPA
+DEGENLPB
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA1B
+DEVGLA2
+DEVGLA2B
+DGOSPEC
+DJTL
+DUALC1
+DUALC2
+DUALC5
+DUALC8
+ECKERLE4LS
+EG1
+EGGCRATE
+EGGCRATEB
+ELATVIDU
+ELATVIDUB
+ENGVAL2
+ENSOLS
+EQC
+EXP2
+EXP2B
+EXPFIT
+EXPFITA
+EXPFITB
+EXPFITC
+EXTRASIM
+FBRAIN2LS
+FBRAIN3LS
+FBRAINLS
+FCCU
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GENHS28
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HART6
+HATFLDA
+HATFLDB
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HATFLDH
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBA
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HIMMELP1
+HONG
+HS1
+HS105
+HS110
+HS112
+HS118
+HS119
+HS2
+HS21
+HS21MOD
+HS24
+HS25
+HS268
+HS28
+HS3
+HS35
+HS35I
+HS35MOD
+HS36
+HS37
+HS38
+HS3MOD
+HS4
+HS41
+HS44
+HS44NEW
+HS45
+HS48
+HS49
+HS5
+HS50
+HS51
+HS52
+HS53
+HS54
+HS55
+HS62
+HS76
+HS76I
+HS86
+HS9
+HUBFIT
+HUMPS
+JENSMP
+JUDGE
+JUDGEB
+KIRBY2LS
+KOEBHELB
+KOWOSB
+KSIP
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LEVYMONT10
+LEVYMONT5
+LEVYMONT6
+LEVYMONT7
+LEVYMONT8
+LEVYMONT9
+LIN
+LOGHAIRY
+LOGROS
+LOTSCHD
+LSC1LS
+LSC2LS
+LSNNODOC
+LSQFIT
+MARATOSB
+MAXLIKA
+MDHOLE
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+ODFITS
+OET1
+OET3
+OSBORNEA
+OSBORNEB
+OSLBQP
+PALMER1
+PALMER1A
+PALMER1B
+PALMER1C
+PALMER1D
+PALMER1E
+PALMER2
+PALMER2A
+PALMER2B
+PALMER2C
+PALMER2E
+PALMER3
+PALMER3A
+PALMER3B
+PALMER3C
+PALMER3E
+PALMER4
+PALMER4A
+PALMER4B
+PALMER4C
+PALMER4E
+PALMER5A
+PALMER5B
+PALMER5C
+PALMER5D
+PALMER5E
+PALMER6A
+PALMER6C
+PALMER6E
+PALMER7A
+PALMER7C
+PALMER7E
+PALMER8A
+PALMER8C
+PALMER8E
+PARKCH
+PENTAGON
+PFIT1LS
+PFIT2LS
+PFIT3LS
+PFIT4LS
+PORTFL1
+PORTFL2
+PORTFL3
+PORTFL4
+PORTFL6
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+POWERSUMB
+PRICE3
+PRICE3B
+PRICE4
+PRICE4B
+PSPDOC
+PT
+QC
+QCNEW
+QINGB
+RAT42LS
+RAT43LS
+RECIPELS
+RES
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S268
+S277-280
+S308
+S368
+SCW1
+SIM2BQP
+SIMBQP
+SIMPLLPA
+SIMPLLPB
+SINEVAL
+SIPOW1
+SIPOW1M
+SIPOW2
+SIPOW2M
+SIPOW3
+SIPOW4
+SISSER
+SNAIL
+SPECAN
+SSI
+STANCMIN
+STRATEC
+STREG
+STRTCHDV
+STRTCHDVB
+SUPERSIM
+TAME
+TFI2
+TFI3
+THURBERLS
+TRIGON1
+TRIGON1B
+TRIGON2
+TRIGON2B
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA1B
+WAYSEA2
+WAYSEA2B
+WEEDS
+YFIT
+YFITU
+ZANGWIL2
+ZANGWIL3
+ZECEVIC2
diff --git a/python/profiles/matlab_wrapper.m b/python/profiles/matlab_wrapper.m
new file mode 100644
index 0000000000..310e8d626a
--- /dev/null
+++ b/python/profiles/matlab_wrapper.m
@@ -0,0 +1,61 @@
+function [x, fx, flags, details] = matlab_wrapper(port, algorithm, options, x0, lb, ub, Aineq, bineq, Aeq, beq, ...
+ num_nl_ineq_con, num_nl_eq_con, port_nonlcon)
+arguments
+ port (1, 1) {mustBeGreaterThan(port, 1024), mustBeInteger} % Ports below 1024 are privileged.
+ algorithm (1, 1) string {mustBeMember(algorithm, {'bobyqa', 'cobyla', 'lincoa', 'newuoa', 'uobyqa'})}
+ options (1, 1) struct
+ x0 (:, 1) double
+ lb (:, 1) double = []
+ ub (:, 1) double = []
+ Aineq (:, :) double = []
+ bineq (:, 1) double = []
+ Aeq (:, :) double = []
+ beq (:, 1) double = []
+ num_nl_ineq_con (1, 1) {mustBeNonnegative} = 0
+ num_nl_eq_con (1, 1) {mustBeNonnegative} = 0
+ port_nonlcon (1, 1) {mustBeGreaterThan(port_nonlcon, 1024), mustBeInteger} = 8602 % Ports below 1024 are privileged.
+end
+
+obj_fun_conn = tcpclient('127.0.0.1', port);
+if algorithm == "bobyqa"
+ [x, fx, flags, details] = bobyqa(@obj_fun, x0, lb, ub, options);
+elseif algorithm == "cobyla"
+ nonlcon_fun_conn = tcpclient('127.0.0.1', port_nonlcon);
+ [x, fx, flags, details] = cobyla(@obj_fun, x0, Aineq, bineq, Aeq, beq, lb, ub, @nonlcon, options);
+ clear nonlcon_fun_conn; % Close the connection
+elseif algorithm == "lincoa"
+ [x, fx, flags, details] = lincoa(@obj_fun, x0, Aineq, bineq, Aeq, beq, lb, ub, options);
+elseif algorithm == "newuoa"
+ [x, fx, flags, details] = newuoa(@obj_fun, x0, options);
+elseif algorithm == "uobyqa"
+ [x, fx, flags, details] = uobyqa(@obj_fun, x0, options);
+end
+
+clear obj_fun_conn; % Close the connection
+
+function f = obj_fun(x)
+ % Write x to the socket
+ write(obj_fun_conn, x, 'double');
+ % Now Python should pick this up and run the function and write the result back to the socket
+ % We read the socket in a blocking manner
+ f = read(obj_fun_conn, 1, 'double');
+end
+
+function [cineq, ceq] = nonlcon(x)
+ % Write x to the socket
+ write(nonlcon_fun_conn, x, 'double');
+ % Now Python should pick this up and run the function and write the result back to the socket
+ % We read the socket in a blocking manner
+ if num_nl_ineq_con == 0
+ cineq = [];
+ else
+ cineq = read(nonlcon_fun_conn, num_nl_ineq_con, 'double');
+ end
+ if num_nl_eq_con == 0
+ ceq = [];
+ else
+ ceq = read(nonlcon_fun_conn, num_nl_eq_con, 'double');
+ end
+end
+
+end
diff --git a/python/profiles/profiles.py b/python/profiles/profiles.py
new file mode 100644
index 0000000000..a886a4c0c8
--- /dev/null
+++ b/python/profiles/profiles.py
@@ -0,0 +1,328 @@
+import numpy as np
+import sys
+import os
+from excludelist import excludelist
+from optiprofiler import set_cutest_problem_options, find_cutest_problems, run_benchmark
+import argparse
+from time import time
+import matlab.engine
+import threading
+import socket
+import struct
+
+
+nondefault_options = lambda n, f0: {
+ 'ftarget' : f0 - 314, # if this is doable, 3.14 otherwise
+ 'maxfev' : 271*n,
+ 'npt' : int(min(3.14*n, n**1.23)),
+ 'rhobeg' : 2.718,
+ 'rhoend' : 3.14*1.0e-4,
+}
+
+
+def get_matlab_options(n, f0):
+ if os.environ.get('NONDEFAULT_MATLAB') == 'True':
+ options = nondefault_options(n, f0)
+ # Change the option name
+ options['maxfun'] = matlab.int32(options.pop('maxfev'))
+ return options
+ return {}
+
+
+def get_python_options(n, f0):
+ if os.environ.get('NONDEFAULT_PYTHON') == 'True':
+ return nondefault_options(n, f0)
+ return {}
+
+
+def get_matlab_engine():
+ '''
+ This function is essentially following the singleton design pattern
+ (https://gameprogrammingpatterns.com/singleton.html).
+
+ Despite the above link recommending against this pattern we use it for
+ two reasons.
+
+ 1) The "lazy-loading" means that the MATLAB engine won't be started until
+ after OptiProfiler has created separate processes via the multiprocessing
+ module. If OptiProfiler stops using multiprocessing this approach may need
+ to be reviewed.
+
+ 2) Starting a MATLAB engine takes as much as 30s, so we only want to do it
+ once per process and keep the resulting engine. If future versions can
+ start up much more quickly then this might not be necessary.
+
+ '''
+ if not hasattr(get_matlab_engine, 'eng'):
+ get_matlab_engine.eng = matlab.engine.start_matlab()
+ return get_matlab_engine.eng
+
+
+def fun_wrapper(server, obj_fun, num_vars):
+ server.listen(1)
+ conn, _ = server.accept()
+ with conn:
+ while True:
+ bufsize = num_vars * 8 # 8 being sizeof(double)
+ flags = socket.MSG_WAITALL # This helps with large (n ~ 5000) problems, i.e. TESTQUAD
+ data = conn.recv(bufsize, flags)
+ if not data: break # This indicates the connection was closed
+ if len(data) < bufsize: raise ValueError('Received incomplete data')
+ x = struct.unpack(f'{num_vars}d', data)
+ x = np.array(x, dtype=np.float64)
+ fx = obj_fun(x)
+ conn.sendall(struct.pack('d', fx))
+
+
+def nl_constraints_wrapper(server, cineq_fun, ceq_fun, num_vars):
+ server.listen(1)
+ conn, _ = server.accept()
+ with conn:
+ while True:
+ bufsize = num_vars * 8 # 8 being sizeof(double)
+ flags = socket.MSG_WAITALL # This helps with large (n ~ 5000) problems
+ data = conn.recv(bufsize, flags)
+ if not data: break # This indicates the connection was closed
+ x = struct.unpack(f'{num_vars}d', data)
+ x = np.array(x, dtype=np.float64)
+ cineq = cineq_fun(x)
+ ceq = ceq_fun(x)
+ if len(cineq) > 0:
+ conn.sendall(struct.pack(f'{len(cineq)}d', *cineq))
+ if len(ceq) > 0:
+ conn.sendall(struct.pack(f'{len(ceq)}d', *ceq))
+
+
+def matlab_uobyqa(fun, x0):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ result = get_matlab_engine().matlab_wrapper(port, 'uobyqa', get_matlab_options(len(x0), f0), x0_m, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_uobyqa(fun, x0):
+ from prima import minimize
+ f0 = fun.__self__._fun(x0)
+ res = minimize(fun, x0, method='uobyqa', options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_newuoa(fun, x0):
+ # We bind to port 0 so that the OS automatically assigns an available port
+ # Then we extract the port number so that we can pass it to the MATLAB engine
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ # We need to reach into the internals of fun in order to call the original function object
+ # so that we don't add to the number of function evaluations. Otherwise OptiProfiler will
+ # end up throwing an exception as we hit max_eval. In the future when OptiProfiler offers
+ # the signature where it provides the problem itself we'll be able to do this without reaching
+ # into the internals like this.
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ result = get_matlab_engine().matlab_wrapper(port, 'newuoa', get_matlab_options(len(x0), f0), x0_m, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_newuoa(fun, x0):
+ from prima import minimize
+ f0 = fun.__self__._fun(x0)
+ res = minimize(fun, x0, method='newuoa', options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_bobyqa(fun, x0, lb, ub):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ result = get_matlab_engine().matlab_wrapper(port, 'bobyqa', get_matlab_options(len(x0), f0), x0_m, lb, ub, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_bobyqa(fun, x0, lb, ub):
+ from prima import minimize
+ from scipy.optimize import Bounds
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ res = minimize(fun, x0, method='bobyqa', bounds=bounds, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_lincoa(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ a_ub = matlab.double(a_ub)
+ b_ub = matlab.double(b_ub)
+ a_eq = matlab.double(a_eq)
+ b_eq = matlab.double(b_eq)
+ result = get_matlab_engine().matlab_wrapper(port, 'lincoa', get_matlab_options(len(x0), f0), x0_m, lb, ub, a_ub, b_ub, a_eq, b_eq, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_lincoa(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq):
+ from prima import minimize, Bounds, LinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ res = minimize(fun, x0, method='lincoa', bounds=bounds, constraints=constraints, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def matlab_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ineq, c_eq):
+ with socket.create_server(('localhost', 0)) as server:
+ port = server.getsockname()[1]
+ threading.Thread(target=fun_wrapper, args=(server, fun, len(x0))).start()
+ with socket.create_server(('localhost', 0)) as server_nonlcon:
+ port_nonlcon = server_nonlcon.getsockname()[1]
+ threading.Thread(target=nl_constraints_wrapper, args=(server_nonlcon, c_ineq, c_eq, len(x0))).start()
+ f0 = fun.__self__._fun(x0)
+ x0_m = matlab.double(x0)
+ lb = matlab.double(lb)
+ ub = matlab.double(ub)
+ a_ub = matlab.double(a_ub)
+ b_ub = matlab.double(b_ub)
+ a_eq = matlab.double(a_eq)
+ b_eq = matlab.double(b_eq)
+ c_ineq_x0 = c_ineq(x0)
+ m_c_ineq = c_ineq_x0.size
+ c_eq_x0 = c_eq(x0)
+ m_c_eq = c_eq_x0.size
+ result = get_matlab_engine().matlab_wrapper(port, 'cobyla', get_matlab_options(len(x0), f0), x0_m, lb, ub, a_ub, b_ub, a_eq, b_eq, m_c_ineq, m_c_eq, port_nonlcon, nargout=4)
+ x = np.array(matlab.double(result[0]).tomemoryview().tolist()) # Wrap result in matlab.double in case of n=1
+ return x
+
+
+def python_cobyla(fun, x0, lb, ub, a_ub, b_ub, a_eq, b_eq, c_ineq, c_eq):
+ from prima import minimize, Bounds, LinearConstraint, NonlinearConstraint
+
+ f0 = fun.__self__._fun(x0)
+ bounds = Bounds(lb, ub)
+ constraints = []
+ if b_ub.size > 0:
+ constraints.append(LinearConstraint(a_ub, -np.inf, b_ub))
+ if b_eq.size > 0:
+ constraints.append(LinearConstraint(a_eq, b_eq, b_eq))
+ c_ineq_x0 = c_ineq(x0)
+ if c_ineq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_ineq, -np.inf, np.zeros_like(c_ineq_x0)))
+ c_eq_x0 = c_eq(x0)
+ if c_eq_x0.size > 0:
+ constraints.append(NonlinearConstraint(c_eq, np.zeros_like(c_eq_x0), np.zeros_like(c_eq_x0)))
+ res = minimize(fun, x0, method='cobyla', bounds=bounds, constraints=constraints, options=get_python_options(len(x0), f0))
+ return res.x
+
+
+def get_problems(description):
+ cutest_problem_names = find_cutest_problems(description)
+ return list(filter(lambda x: x not in excludelist(description), cutest_problem_names))
+
+
+if __name__ == '__main__':
+ # If we run this script from a directory other than the one that contains it, pycutest's call to importlib will fail,
+ # unless we insert the current working directory into the path.
+ sys.path.insert(0, os.getcwd())
+ os.environ['PYCUTEST_CACHE'] = os.getcwd()
+
+ parser = argparse.ArgumentParser(description='Generate performance profiles comparing PRIMA MATLAB to PRIMA Python. '
+ 'By default no profiles are run. To run one or more profiles use the flags '
+ 'specified below. Multiple flags may be specified to run multiple profiles, '
+ 'but please note they will be run sequentially.')
+ parser.add_argument('-j', '--n_jobs', type=int, default=None, help='Number of jobs to run in parallel')
+ parser.add_argument('-n', '--newuoa', action='store_true', help='Run the NEWUOA benchmark')
+ parser.add_argument('-u', '--uobyqa', action='store_true', help='Run the UOBYQA benchmark')
+ parser.add_argument('-b', '--bobyqa', action='store_true', help='Run the BOBYQA benchmark')
+ parser.add_argument('-l', '--lincoa', action='store_true', help='Run the LINCOA benchmark')
+ parser.add_argument('-c', '--cobyla', action='store_true', help='Run the COBYLA benchmark')
+ parser.add_argument('--default_only', action='store_true', help='Run only the default options for both MATLAB and Python')
+ args = parser.parse_args()
+
+
+ def run_three_benchmarks(matlab_fun, python_fun, algorithm, cutest_problem_names, default_only, n_jobs):
+ '''
+ Proper validation of both default and nondefault options requires 3 runs: Both default, both nondefault, and
+ one default one nondefault. The first two should look identical, and so the third run confirms that our
+ experiment setup is valid (i.e. it rules out a scenario where even though options are provided, both algorithms
+ end up using default options anyway).
+ '''
+ # Sharing state with multiprocessing is hard when we can't control the function signature,
+ # so we resort to using the environment to pass options.
+ os.environ['NONDEFAULT_MATLAB'] = "False"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ algorithm = algorithm.lower()
+ ALGORITHM = algorithm.upper()
+ project_x0 = algorithm == 'lincoa'
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_default_options', n_jobs=n_jobs, project_x0=project_x0)
+ if not default_only:
+ os.environ['NONDEFAULT_MATLAB'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "True"
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_nondefault_options', n_jobs=n_jobs, project_x0=project_x0)
+ os.environ['NONDEFAULT_MATLAB'] = "True"
+ os.environ['NONDEFAULT_PYTHON'] = "False"
+ run_benchmark([matlab_fun, python_fun], [f'MATLAB-{ALGORITHM}', f'Python-{ALGORITHM}'], cutest_problem_names, benchmark_id=f'{algorithm}_different_options', n_jobs=n_jobs, project_x0=project_x0)
+
+ if args.newuoa:
+ start = time()
+ print("Running profiles for NEWUOA")
+ with open('uobyqa_newuoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('unconstrained'), cutest_problem_names))
+ run_three_benchmarks(matlab_newuoa, python_newuoa, 'newuoa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed NEWUOA profile in {time() - start:.2f} seconds')
+
+ if args.uobyqa:
+ start = time()
+ print("Running profiles for UOBYQA")
+ with open('uobyqa_newuoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('unconstrained'), cutest_problem_names))
+ run_three_benchmarks(matlab_uobyqa, python_uobyqa, 'uobyqa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed UOBYQA profile in {time() - start:.2f} seconds')
+
+ if args.bobyqa:
+ start = time()
+ print("Running profiles for BOBYQA")
+ with open('bobyqa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('bobyqa'), cutest_problem_names))
+ run_three_benchmarks(matlab_bobyqa, python_bobyqa, 'bobyqa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed BOBYQA profile in {time() - start:.2f} seconds')
+
+ if args.lincoa:
+ start = time()
+ print("Running profiles for LINCOA")
+ with open('lincoa.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('lincoa'), cutest_problem_names))
+ run_three_benchmarks(matlab_lincoa, python_lincoa, 'lincoa', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed LINCOA profile in {time() - start:.2f} seconds')
+
+ if args.cobyla:
+ start = time()
+ print("Running profiles for COBYLA")
+ with open('cobyla.txt') as f:
+ cutest_problem_names = f.read().splitlines()
+ cutest_problem_names = list(filter(lambda x: x not in excludelist('cobyla'), cutest_problem_names))
+ run_three_benchmarks(matlab_cobyla, python_cobyla, 'cobyla', cutest_problem_names, args.default_only, args.n_jobs)
+ print(f'Completed COBYLA profile in {time() - start:.2f} seconds')
diff --git a/python/profiles/uobyqa_newuoa.txt b/python/profiles/uobyqa_newuoa.txt
new file mode 100644
index 0000000000..8a3e876b3e
--- /dev/null
+++ b/python/profiles/uobyqa_newuoa.txt
@@ -0,0 +1,133 @@
+AKIVA
+ALLINITU
+BARD
+BEALE
+BENNETT5LS
+BIGGS6
+BOX3
+BOXBODLS
+BRKMCC
+BROWNBS
+BROWNDEN
+CERI651ALS
+CERI651BLS
+CERI651CLS
+CERI651DLS
+CERI651ELS
+CHWIRUT1LS
+CHWIRUT2LS
+CLIFF
+CLUSTERLS
+COOLHANSLS
+CUBE
+DANIWOODLS
+DANWOODLS
+DENSCHNA
+DENSCHNB
+DENSCHNC
+DENSCHND
+DENSCHNE
+DENSCHNF
+DEVGLA1
+DEVGLA2
+DJTL
+ECKERLE4LS
+EGGCRATE
+ELATVIDU
+ENGVAL2
+ENSOLS
+EXP2
+EXPFIT
+FBRAIN3LS
+GAUSS1LS
+GAUSS2LS
+GAUSS3LS
+GAUSSIAN
+GBRAINLS
+GROWTHLS
+GULF
+HAHN1LS
+HAIRY
+HATFLDD
+HATFLDE
+HATFLDFL
+HATFLDFLS
+HEART6LS
+HEART8LS
+HELIX
+HIELOW
+HILBERTA
+HILBERTB
+HIMMELBB
+HIMMELBCLS
+HIMMELBF
+HIMMELBG
+HIMMELBH
+HUMPS
+JENSMP
+JUDGE
+KIRBY2LS
+KOWOSB
+LANCZOS1LS
+LANCZOS2LS
+LANCZOS3LS
+LOGHAIRY
+LSC1LS
+LSC2LS
+MARATOSB
+MEXHAT
+MEYER3
+MGH09LS
+MGH10LS
+MGH10SLS
+MGH17LS
+MGH17SLS
+MISRA1ALS
+MISRA1BLS
+MISRA1CLS
+MISRA1DLS
+NELSONLS
+OSBORNEA
+OSBORNEB
+PALMER1C
+PALMER1D
+PALMER2C
+PALMER3C
+PALMER4C
+PALMER5C
+PALMER5D
+PALMER6C
+PALMER7C
+PALMER8C
+PARKCH
+POWELLBSLS
+POWELLSQLS
+POWERSUM
+PRICE3
+PRICE4
+RAT42LS
+RAT43LS
+RECIPELS
+ROSENBR
+ROSENBRTU
+ROSZMAN1LS
+S308
+SINEVAL
+SISSER
+SNAIL
+SSI
+STRATEC
+STREG
+STRTCHDV
+THURBERLS
+TRIGON1
+TRIGON2
+VESUVIALS
+VESUVIOLS
+VESUVIOULS
+VIBRBEAM
+WATSON
+WAYSEA1
+WAYSEA2
+YFITU
+ZANGWIL2
diff --git a/python/pybind11 b/python/pybind11
new file mode 160000
index 0000000000..45fab4087e
--- /dev/null
+++ b/python/pybind11
@@ -0,0 +1 @@
+Subproject commit 45fab4087eaaff234227a10cf7845e8b07f28a98
diff --git a/python/tests/README.md b/python/tests/README.md
new file mode 100644
index 0000000000..357602bd44
--- /dev/null
+++ b/python/tests/README.md
@@ -0,0 +1,99 @@
+Requirements
+==============
+
+These are high level requirements for the python package. Next to the requirement is the name of the test file and the name of the test
+within that file which implements it. The testfile/testname is written in such a way that it can be passed as an argument to `pytest` in
+order to run that test by itself.
+
+
+## Requirements for basic functionality, algorithm autoselection, and explicit algorithm selection
+- [x] providing nonlinear constraints alone calls COBYLA and the constraints are successfully applied - test_basic_functionality.py::test_provide_nonlinear_constraints_alone
+- [x] providing nonlinear constraints alone and selecting COBYLA calls COBYLA and the constraints are successfully applied - test_basic_functionality.py::test_provide_nonlinear_constraints_alone_and_select_COBYLA
+- [x] providing linear constraints alone calls LINCOA and the constraints are successfully applied - test_basic_functionality.py::test_provide_linear_constraints_alone
+- [x] providing linear constraints alone and selecting LINCOA calls LINCOA and the constraints are successfully applied - test_basic_functionality.py::test_provide_linear_constraints_alone_and_select_LINCOA
+- [x] providing bounds alone calls BOBYQA and the bounds are successfully applied - test_basic_functionality.py::test_provide_bounds_alone
+- [x] providing bounds alone and selecting BOBYQA calls BOBYQA and the bounds are successfully applied - test_basic_functionality.py::test_provide_bounds_alone_and_select_BOBYQA
+- [x] not providing any bounds, linear constraints, or nonlinear constraints calls NEWUOA and provides the optimal unbounded/unconstrained solution - test_basic_functionality.py::test_not_providing_bounds_linear_constraints_or_nonlinear_constraints
+- [x] not providing any bounds, linear constraints, or nonlinear constraints and selecting any algorithm calls that algorithm and provides the optimal unbounded/unconstrained solution - test_basic_functionality.py::test_not_providing_bounds_linear_constraints_or_nonlinear_constraints_and_selecting_any_algorithm
+
+
+## Requirements for combining constraints and bounds
+- [x] providing linear and nonlinear constraints together calls COBYLA and the constraints are successfully applied - test_combining_constraints.py::test_providing_linear_and_nonlinear_constraints
+- [x] providing bounds and linear constraints together calls LINCOA and the constraints/bounds are successfully applied - test_combining_constraints.py::test_providing_bounds_and_linear_constraints
+- [x] providing bounds and nonlinear constraints together calls COBYLA and the constraints/bounds are successfully applied - test_combining_constraints.py::test_providing_bounds_and_nonlinear_constraints
+- [x] providing bounds and linear and nonlinear constraints together calls COBYLA and the constraints/bounds are successfully applied - test_combining_constraints.py::test_providing_bounds_and_linear_and_nonlinear_constraints
+
+
+## Requirements for scalar/list/array
+- [x] providing a nonlinear constraint function returning a numpy array works without warnings or errors - test_data_types.py::test_constraint_function_returns_numpy_array
+- [x] providing a nonlinear constraint function returning a Python list works without warnings or errors - test_data_types.py::test_constraint_function_returns_list
+- [x] providing a nonlinear constraint function returning a scalar works without warnings or errors - test_data_types.py::test_constraint_function_returns_scalar
+- [x] providing x0 as numpy array works without warnings or errors - test_data_types.py::test_x0_as_list
+- [x] providing x0 as Python list works without warnings or errors - test_data_types.py::test_x0_as_array
+- [x] providing x0 as scalar works without warnings or errors - test_data_types.py::test_x0_as_scalar
+- [x] providing A as either a scalar, list, or numpy array, providing lb as either a scalar, list, or numpy array, and providing ub as either a scalar, list or numpy array works without warning or errors in all combinations - test_data_types.py::test_linear_constraint_data_types
+- [x] providing a nonlinear constraint with scalar lower bound and scalar upper bound works without warnings or errors - test_data_types.py::test_nonlinear_constraint_lb_scalar_ub_scalar
+- [x] providing a nonlinear constraint with scalar lower bound and list/array upper bound works without warnings or errors - test_data_types.py::test_nonlinear_constraint_lb_scalar_ub_not_scalar
+- [x] providing a nonlinear constraint with list/array lower bound and scalar upper bound works without warnings or errors - test_data_types.py::test_nonlinear_constraint_lb_not_scalar_ub_scalar
+- [x] providing a nonlinear constraint with list/array lower bound and list/array upper bound works without warnings or errors - test_data_types.py::test_nonlinear_constraint_lb_not_scalar_ub_not_scalar
+
+
+## Requirements for various options
+- [x] various options can be successfully provided
+ - [x] ftarget - test_options.py::test_ftarget
+ - [x] iprint - test_options.py::test_iprint
+ - [x] max function evaluations via maxfun (name used by SciPy) - test_options.py::test_maxfun
+ - [x] max function evaluations via maxfev (name used by PRIMA) - test_options.py::test_maxfev
+ - [x] npt - test_options.py::test_npt
+ - [x] rhobeg - test_options.py::test_rhobeg
+ - [x] rhoend - test_options.py::test_rhoend
+ - [x] quiet - test_options.py::test_quiet
+- [x] an objective function without args can be used successfully - test_options.py::test_normal_function
+- [x] an objective function with args can be used successfully - test_options.py::test_function_with_regular_args
+- [x] an objective function with *args can be used successfully - test_options.py::test_function_with_star_args
+- [x] providing a callback leads to the callback being successfully called - test_options.py::test_callback
+- [x] providing a callback that returns True leads to early termination - test_options.py::test_callback_early_termination
+
+
+## Requirements for compatibility with existing APIs
+- [x] compatible with scipy.optimize.minimize API - test_compatibility_scipy.py::test_scipy
+- [x] compatible with PDFO API - test_compatibility_pdfo.py::test_pdfo
+
+
+## Requirements for processing nonlinear constraints
+- [x] providing a list of nonlinear constraints with either scalar or vector for lb/ub correctly constructs the constraint function - test_process_nonlinear_constraints.py::test_multiple_nl_constraints_various_data_types
+- [x] providing a single nonlinear constraints with either scalar or vector for lb/ub correctly constructs the constraint function - test_process_nonlinear_constraints.py::test_single_nl_constraint
+- [x] providing a nonlinear constraint with upper and/or lower bounds as vectors of different length than the length of the output of the constraint function raises an exception - test_process_nonlinear_constraints.py::test_length_nlc_fun_not_equal_to_length_lb_ub
+- [x] providing a nonlinear constraint with upper and lower bound as vectors of where upper bounds has correct length and lower bounds does not as compared to the length of the output of the constraint function raises an exception - test_process_nonlinear_constraints.py::test_length_nlc_fun_ne_to_length_ub
+- [x] providing a nonlinear constraint with scalar lb at -inf and scalar ub at inf raises an exception - test_process_nonlinear_constraints.py::test_lb_neg_inf_ub_inf_raises
+- [x] providing a nonlinear constraint with scalar lb at -inf and vector ub containing at least 1 inf raises an exception - test_process_nonlinear_constraints.py::test_lb_neg_inf_ub_vector_w_inf_raises
+- [x] providing a nonlinear constraint with vector lb containing at least 1 -inf and scalar ub at inf raises an exception - test_process_nonlinear_constraints.py::test_lb_vector_with_neg_inf_ub_inf_raises
+- [x] providing a nonlinear constraint with vector lb and vector ub containing -inf and +inf at the same index raises an exception - test_process_nonlinear_constraints.py::test_lb_vector_with_neg_inf_ub_vector_w_inf_at_same_index_raises
+
+
+## Requirements for processing linear constraints
+- [x] providing a list of linear constraints leads to them being successfully combined - test_process_linear_constraints.py::test_multiple_linear_constraints_implementation
+- [x] providing a list of linear constraints leads to them being successfully combined and applied - test_process_linear_constraints.py::test_multiple_linear_constraints_high_level
+- [x] providing linear constraints that contain both equality and inequality constraints leads to a correct separation into A_eq/b_eq and A_ineq/b_ineq - test_process_linear_constraints.py::test_separate_LC_into_eq_and_ineq
+
+
+## Requirements for constraint types
+- [x] PRIMA's LinearConstraint is the same as SciPy's scipy.optimize.LinearConstraint - test_constraint_types.py::test_prima_lc_is_scipy_lc
+- [x] providing a linear constraint as a type scipy.optimize.LinearConstraint works without warnings or errors - test_constraint_types.py::test_linear_constraint_object
+- [x] providing a linear constraint as a dictionary with keys 'A', 'lb', and 'ub' works without warnings or errors - test_constraint_types.py::test_linear_constraint_dict
+- [x] PRIMA's NonlinearConstraint is the same as SciPy's scipy.optimize.NonlinearConstraint - test_constraint_types.py::test_prima_lc_is_scipy_lc
+- [x] providing a nonlinear constraint as a type scipy.optimize.NonlinearConstraint works without warnings or errors - test_constraint_types.py::test_nonlinear_constraint_object
+- [x] providing a nonlinear constraint as a dictionary with keys 'fun', 'lb', and 'ub' works without warnings or errors - test_constraint_types.py::test_nonlinear_constraint_dict
+- [x] providing an unsupported constraint type raises an exception - test_constraint_types.py::test_unsupported_type_raises_exception
+
+
+## Requirements for warnings and exceptions related to method selection
+- [x] providing a method that is not one of the 5 provided raises an exception - test_method_selection_warnings_and_exceptions.py::test_method_not_recognized
+- [x] providing nonlinear constraints to a method that cannot handle them raises an exception - test_method_selection_warnings_and_exceptions.py::test_providing_nonlinear_constraints_to_non_cobyla_method
+- [x] providing linear constraints to a method that cannot handle them raises an exception - test_method_selection_warnings_and_exceptions.py::test_providing_linear_constraints_to_non_cobyla_non_lincoa_method
+- [x] providing bounds to a method that cannot handle them raises an exception - test_method_selection_warnings_and_exceptions.py::test_providing_bounds_to_non_cobyla_non_lincoa_non_bobyqa_method
+
+## Requirements for regression tests
+These tests are for behavior observed during testing that we want to make sure remains fixed.
+- [x] providing anonymous lambda as: objective function, constraint function, callback works without warnings or errors - test_anonymous_lambda.py::test_anonymous_lambda
+- [x] calling with a method that is not available throws an exception and exits cleanly (i.e. no other warnings or error or hanging of the interpreter) (relates to the test regarding anonymous lambda functions) - test_anonymous_lambda.py::test_anonymous_lambda_unclean_exit
diff --git a/python/tests/objective.py b/python/tests/objective.py
new file mode 120000
index 0000000000..bd5bdf770b
--- /dev/null
+++ b/python/tests/objective.py
@@ -0,0 +1 @@
+../examples/objective.py
\ No newline at end of file
diff --git a/python/tests/test_anonymous_lambda.py b/python/tests/test_anonymous_lambda.py
new file mode 100644
index 0000000000..cc2ad79a47
--- /dev/null
+++ b/python/tests/test_anonymous_lambda.py
@@ -0,0 +1,26 @@
+# test with anonymous lambda as: objective function, callback function, nonlinear constraint function.
+from prima import minimize, NonlinearConstraint as NLC
+import numpy as np
+import pytest
+
+def test_anonymous_lambda():
+ '''
+ In previous iterations of these bindings, memory was not handled correctly in C++ and using anonymous lambdas
+ would cause issues including, but not limited to, segfaults and infinite hangs. This test is to ensure that
+ anonymous lambdas can be used without issue.
+ '''
+ myNLC = NLC(lambda x: x[0]**2 - 9, [-np.inf], [0])
+ res = minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='COBYLA', constraints=myNLC, callback=lambda x, *args: print(x))
+ assert abs(res.x[0] - 3) < 1e-2 and abs(res.x[1] - 4) < 1e-2 and abs(res.fun - 4) < 1e-2
+
+
+def test_anonymous_lambda_unclean_exit():
+ '''
+ Another potential issue with memory management is when the minimize function throws an exception instead of terminating
+ normally. This can be triggered by providing an invalid method. Another option might be to raise an exception within any
+ of the anonymous lambdas.
+ '''
+ myNLC = NLC(lambda x: x[0]**2 - 9, [-np.inf], [0])
+ with pytest.raises(ValueError) as e_info:
+ minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='GARBAGE', constraints=myNLC, callback=lambda x, *args: print(x))
+ assert e_info.match("Method must be one of NEWUOA, UOBYQA, BOBYQA, COBYLA, or LINCOA, not 'garbage'")
diff --git a/python/tests/test_basic_functionality.py b/python/tests/test_basic_functionality.py
new file mode 100644
index 0000000000..055efa83a6
--- /dev/null
+++ b/python/tests/test_basic_functionality.py
@@ -0,0 +1,76 @@
+from prima import minimize, NonlinearConstraint as NLC, LinearConstraint as LC
+import numpy as np
+import pytest
+from objective import fun
+
+
+def test_provide_nonlinear_constraints_alone():
+ nlc = NLC(lambda x: np.array([x[0]**2, x[1]**2]), lb=[25]*2, ub=[100]*2)
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
+ assert res.method == "cobyla"
+
+
+def test_provide_nonlinear_constraints_alone_and_select_COBYLA():
+ nlc = NLC(lambda x: np.array([x[0]**2, x[1]**2]), lb=[25]*2, ub=[100]*2)
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc, method="cobyla")
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
+ assert res.method == "cobyla"
+
+
+def test_provide_linear_constraints_alone():
+ lc = LC(np.array([[1, 1],[1, -1]]), lb=[0, 0], ub=[8, 2])
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=lc)
+ assert np.isclose(res.x[0], 4.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 0.5, atol=1e-6, rtol=1e-6)
+ assert res.method == "lincoa"
+
+def test_provide_linear_constraints_alone_and_select_LINCOA():
+ lc = LC(np.array([[1, 1],[1, -1]]), lb=[0, 0], ub=[8, 2])
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=lc, method="lincoa")
+ assert np.isclose(res.x[0], 4.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 0.5, atol=1e-6, rtol=1e-6)
+ assert res.method == "lincoa"
+
+
+def test_provide_bounds_alone():
+ x0 = [0, 0]
+ res = minimize(fun, x0, bounds=([0, 3], [0, 3]))
+ assert np.isclose(res.x[0], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 5, atol=1e-6, rtol=1e-6)
+ assert res.method == "bobyqa"
+
+
+def test_provide_bounds_alone_and_select_BOBYQA():
+ x0 = [0, 0]
+ res = minimize(fun, x0, bounds=([0, 3], [0, 3]), method="bobyqa")
+ assert np.isclose(res.x[0], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 5, atol=1e-6, rtol=1e-6)
+ assert res.method == "bobyqa"
+
+
+def test_not_providing_bounds_linear_constraints_or_nonlinear_constraints():
+ x0 = [0, 0]
+ res = minimize(fun, x0)
+ assert fun.result_point_and_value_are_optimal(res)
+ assert res.method == "newuoa"
+
+
+@pytest.mark.parametrize("method", ["newuoa", "uobyqa", "bobyqa", "lincoa", "cobyla"])
+def test_not_providing_bounds_linear_constraints_or_nonlinear_constraints_and_selecting_any_algorithm(method):
+ x0 = [0, 0]
+ res = minimize(fun, x0, method=method)
+ assert fun.result_point_and_value_are_optimal(res)
+ assert res.method == method.lower()
diff --git a/python/tests/test_bounds.py b/python/tests/test_bounds.py
new file mode 100644
index 0000000000..241223d312
--- /dev/null
+++ b/python/tests/test_bounds.py
@@ -0,0 +1,136 @@
+from prima import minimize, LinearConstraint as LC, NonlinearConstraint as NLC
+import numpy as np
+import pytest
+
+def test_eliminate_fixed_bounds():
+ # Test the logic for detecting and eliminating fixed bounds
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, 1, None, -0.5]
+ ub = [-0.5, -0.5, None, None, -0.5]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ res = minimize(f, x0=np.array([1, 2, 3, 4, 5]), bounds=bounds)
+ assert np.allclose(res.x, np.array([-0.5, -0.5, 1, 0, -0.5]), atol=1e-3)
+ assert np.allclose(res.fun, 1.75, atol=1e-3)
+
+
+def test_eliminate_fixed_bounds_with_linear_constraints():
+ # Ensure that the logic for fixed bounds also modifies linear constraints
+ # appropriately
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ # Internally, the algorithm should modify lc to be a 1x2 matrix instead of 1x3,
+ # since it will modify x0 and the objective function to eliminate the first
+ # variable. If it fails to modify lc, we will get shape mismatch errors.
+ lc = LC(np.array([1, 1, 1]), lb=9, ub=15)
+ res = minimize(f, x0=np.array([1, 1, 1]), constraints=lc, bounds=bounds)
+ assert np.isclose(res.x[0], -1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[2], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 51, atol=1e-6, rtol=1e-6)
+
+
+def test_eliminate_fixed_bounds_with_nonlinear_constraints():
+ # Ensure that the logic for fixed bounds also modifies the nonlinear constraint
+ # function appropriately
+
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ x0 = np.array([1, 1, 1])
+ # Have the nonlinear constraint function operate on the last element of x, but be
+ # explicit about the length of x. This ensures that the test is still valid if the
+ # fixed bound is removed. If we simply used x[-1] this test would pass but it
+ # wouldn't actually test if we had properly modified the nonlinear constraint
+ # function after removing the fixed bounds
+ nlc = NLC(lambda x: x[len(x0)-1]**2, lb=9, ub=15)
+ res = minimize(f, x0=x0, constraints=nlc, bounds=bounds)
+ assert np.isclose(res.x[0], -1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 0, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[2], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 10, atol=1e-6, rtol=1e-6)
+
+
+def test_infeasible_bounds():
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [1, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ with pytest.raises(AssertionError) as excinfo:
+ minimize(f, x0=np.array([1, 2, 3]), bounds=bounds)
+ assert str(excinfo.value) == "Some of the provided bounds are infeasible. infeasible=array([ True, False, False]) lb[infeasible]=array([1.]), ub[infeasible]=array([-1.])"
+
+
+def test_infeasible_bounds_nan():
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [np.nan, None, None]
+ ub = [-1, None, None]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ with pytest.raises(AssertionError) as excinfo:
+ minimize(f, x0=np.array([1, 2, 3]), bounds=bounds)
+ assert str(excinfo.value) == "Some of the provided bounds are infeasible. infeasible=array([ True, False, False]) lb[infeasible]=array([nan]), ub[infeasible]=array([-1.])"
+
+
+@pytest.mark.parametrize('with_Aineq', [False, True])
+@pytest.mark.parametrize('with_Aeq', [False, True])
+@pytest.mark.parametrize('with_nlcon', [False, True])
+def test_all_fixed(with_nlcon, with_Aeq, with_Aineq):
+ def f(x):
+ return np.sum(x**2)
+
+ lb = [-1, 2, 4]
+ ub = [-1, 2, 4]
+ bounds = [(a, b) for a, b in zip(lb, ub)]
+ nlc = NLC(lambda x: x[2]**2, lb=-np.inf, ub=0)
+ lc_eq = LC([0, 0, 1], lb=21, ub=21)
+ lc_ineq = LC([0, 0, 1], lb=22, ub=23)
+ constraints = []
+ if with_nlcon:
+ constraints.append(nlc)
+ if with_Aeq:
+ constraints.append(lc_eq)
+ if with_Aineq:
+ constraints.append(lc_ineq)
+
+ result = minimize(f, x0=np.array([1, 2, 3]), bounds=bounds, constraints=constraints)
+ assert all(result.x == [-1, 2, 4])
+ assert result.fun == 21
+ assert result.nfev == 1
+ if not with_nlcon and not with_Aeq and not with_Aineq:
+ assert np.array_equal(result.nlconstr, [])
+ assert result.maxcv == 0
+ if not with_nlcon and not with_Aeq and with_Aineq:
+ assert np.array_equal(result.nlconstr, [])
+ assert result.maxcv == 18
+ if not with_nlcon and with_Aeq and not with_Aineq:
+ assert np.array_equal(result.nlconstr, [])
+ assert result.maxcv == 17
+ if not with_nlcon and with_Aeq and with_Aineq:
+ assert np.array_equal(result.nlconstr, [])
+ assert result.maxcv == 18
+ if with_nlcon and not with_Aeq and not with_Aineq:
+ assert np.array_equal(result.nlconstr, [16])
+ assert result.maxcv == 16
+ if with_nlcon and not with_Aeq and with_Aineq:
+ assert np.array_equal(result.nlconstr, [16])
+ assert result.maxcv == 18
+ if with_nlcon and with_Aeq and not with_Aineq:
+ assert np.array_equal(result.nlconstr, [16])
+ assert result.maxcv == 17
+ if with_nlcon and with_Aeq and with_Aineq:
+ assert np.array_equal(result.nlconstr, [16])
+ assert result.maxcv == 18
diff --git a/python/tests/test_combining_constraints.py b/python/tests/test_combining_constraints.py
new file mode 100644
index 0000000000..db7a1606b2
--- /dev/null
+++ b/python/tests/test_combining_constraints.py
@@ -0,0 +1,61 @@
+from prima import minimize as prima_minimize, NonlinearConstraint as prima_NLC, LinearConstraint as prima_LC, Bounds as prima_Bounds
+import numpy as np
+from objective import fun
+
+
+def test_providing_linear_and_nonlinear_constraints():
+ nlc = prima_NLC(lambda x: x[0]**2, lb=[25], ub=[100])
+ lc = prima_LC(np.array([1,1]), lb=10, ub=15)
+ x0 = [0, 0]
+ res = prima_minimize(fun, x0, constraints=[nlc, lc])
+ assert np.isclose(res.x[0], 5.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 4.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 0.5, atol=1e-6, rtol=1e-6)
+ assert res.method == "cobyla"
+
+
+def test_providing_bounds_and_linear_constraints():
+ lc = prima_LC(np.array([1,1]), lb=10, ub=15)
+ bounds = prima_Bounds(1, 1)
+ x0 = [0, 0]
+ res = prima_minimize(fun, x0, constraints=lc, bounds=bounds)
+ assert np.isclose(res.x[0], 1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 9, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 41, atol=1e-6, rtol=1e-6)
+ assert res.method == "lincoa"
+
+
+def test_providing_bounds_and_nonlinear_constraints():
+ nlc = prima_NLC(lambda x: x[0]**2, lb=[25], ub=[100])
+ bounds = prima_Bounds([None, 1], [None, 1])
+ x0 = [6, 1] # Unfortunately the test is very fragile if we do not start near the optimal point
+ res = prima_minimize(fun, x0, constraints=nlc, bounds=bounds)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 9, atol=1e-6, rtol=1e-6)
+ assert res.method == "cobyla"
+
+
+# This test is re-used for the compatibility tests, hence the extra arguments and their
+# default values
+def test_providing_bounds_and_linear_and_nonlinear_constraints(minimize=prima_minimize, NLC=prima_NLC, LC=prima_LC, Bounds=prima_Bounds, package='prima'):
+ # This test needs a 3 variable objective function so that we can check that the
+ # bounds and constraints are all active
+ def newfun(x):
+ return fun(x[0:2]) + (x[2] - 3)**2
+ nlc = NLC(lambda x: x[0]**2, lb=[25], ub=[100])
+ bounds = Bounds([-np.inf, 1, -np.inf], [np.inf, 1, np.inf])
+ lc = LC(np.array([1,1,1]), lb=10, ub=15)
+ x0 = [6, 1, 3.5] # The test becomes very fragile if we do not start near the optimal point
+ # PDFO and PRIMA will select COBYLA but SciPy may select something else, so we tell it to select COBYLA
+ method = 'COBYLA' if package == 'scipy' else None
+ res = minimize(newfun, x0, method=method, constraints=[nlc, lc], bounds=bounds)
+
+ # 32 bit builds of PRIMA reach the optimal solution with the same level of precision as 64 bit builds
+ # so we lower the atol/rtol to 1e-3 so that 32 bit builds will pass.
+ assert np.isclose(res.x[0], 5.5, atol=1e-3, rtol=1e-3)
+ assert np.isclose(res.x[1], 1, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[2], 3.5, atol=1e-3, rtol=1e-3)
+ assert np.isclose(res.fun, 9.5, atol=1e-3, rtol=1e-3)
+ if package == 'prima' or package == 'pdfo':
+ assert res.method == "cobyla"
diff --git a/python/tests/test_compatibility_pdfo.py b/python/tests/test_compatibility_pdfo.py
new file mode 100644
index 0000000000..9ecfc90c06
--- /dev/null
+++ b/python/tests/test_compatibility_pdfo.py
@@ -0,0 +1,29 @@
+# On some platforms in CI we are not able to install scipy, and in that
+# case we should skip this test. Note that pdfo depends on scipy.
+import pytest
+from packaging import version
+from test_combining_constraints import test_providing_bounds_and_linear_and_nonlinear_constraints
+
+
+def test_prima():
+ from prima import minimize, NonlinearConstraint as NLC, LinearConstraint as LC, Bounds
+ test_providing_bounds_and_linear_and_nonlinear_constraints(minimize, NLC, LC, Bounds)
+
+
+# Despite the fact that we are using the pdfo function, we still get this warning because the pdfo
+# function itself calls the cobyla function. For now we suppress this warning.
+@pytest.mark.filterwarnings("ignore:The `cobyla` function is deprecated. Use the `pdfo` function")
+def test_pdfo():
+ pdfo = pytest.importorskip("pdfo")
+ if version.parse(pdfo.__version__) < version.parse("2.0.0"):
+ pytest.skip("pdfo version too old for this test (it does not accept radius_init and radius_final as options)")
+ scipy = pytest.importorskip("scipy")
+ if version.parse(scipy.__version__) < version.parse("1.11.0"):
+ pytest.skip("scipy version too old for this test (its version of COBYLA does not accept bounds)")
+ numpy = pytest.importorskip("numpy")
+ if version.parse(numpy.__version__) >= version.parse("2.0.0"):
+ pytest.skip("numpy version too new for this test (pdfo does not yet support numpy v2)")
+
+ from pdfo import pdfo
+ from scipy.optimize import NonlinearConstraint as NLC, LinearConstraint as LC, Bounds
+ test_providing_bounds_and_linear_and_nonlinear_constraints(pdfo, NLC, LC, Bounds, package='pdfo')
diff --git a/python/tests/test_compatibility_scipy.py b/python/tests/test_compatibility_scipy.py
new file mode 100644
index 0000000000..459cf969f9
--- /dev/null
+++ b/python/tests/test_compatibility_scipy.py
@@ -0,0 +1,18 @@
+# On some platforms in CI we are not able to install scipy, and in that
+# case we should skip this test. Note that pdfo depends on scipy.
+import pytest
+from packaging import version
+
+from test_combining_constraints import test_providing_bounds_and_linear_and_nonlinear_constraints
+
+def test_prima():
+ from prima import minimize, NonlinearConstraint as NLC, LinearConstraint as LC, Bounds
+ test_providing_bounds_and_linear_and_nonlinear_constraints(minimize, NLC, LC, Bounds)
+
+
+def test_scipy():
+ scipy = pytest.importorskip("scipy")
+ if version.parse(scipy.__version__) < version.parse("1.11.0"):
+ pytest.skip("scipy version too old for this test (its version of COBYLA does not accept bounds)")
+ from scipy.optimize import minimize, NonlinearConstraint as NLC, LinearConstraint as LC, Bounds
+ test_providing_bounds_and_linear_and_nonlinear_constraints(minimize, NLC, LC, Bounds, package="scipy")
diff --git a/python/tests/test_constraint_types.py b/python/tests/test_constraint_types.py
new file mode 100644
index 0000000000..ed2dd1b260
--- /dev/null
+++ b/python/tests/test_constraint_types.py
@@ -0,0 +1,58 @@
+from prima import minimize, LinearConstraint as LC, NonlinearConstraint as NLC
+from scipy.optimize import LinearConstraint as ScipyLC, NonlinearConstraint as ScipyNLC
+import numpy as np
+from objective import fun
+import pytest
+
+def test_prima_lc_is_scipy_lc():
+ assert LC is ScipyLC
+
+
+def test_linear_constraint_object():
+ lc = LC(np.array([[1, 1],[1, -1]]), lb=[0, 0], ub=[8, 2])
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=lc)
+ assert np.isclose(res.x[0], 4.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 0.5, atol=1e-6, rtol=1e-6)
+
+
+def test_linear_constraint_dict():
+ lc = {'A': np.array([[1, 1],[1, -1]]), 'lb':[0, 0], 'ub':[8, 2]}
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=lc)
+ assert np.isclose(res.x[0], 4.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3.5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 0.5, atol=1e-6, rtol=1e-6)
+
+
+def test_prima_nlc_is_scipy_nlc():
+ assert NLC is ScipyNLC
+
+
+def test_nonlinear_constraint_object():
+ nlc = NLC(lambda x: np.array([x[0]**2, x[1]**2]), lb=[25]*2, ub=[100]*2)
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
+
+
+def test_nonlinear_constraint_dict():
+ nlc = {'fun': lambda x: np.array([x[0]**2, x[1]**2]), 'lb':[25]*2, 'ub':[100]*2}
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
+
+
+def test_unsupported_type_raises_exception():
+ # By not providing ub this becomes an unsupported type
+ lc = {'A': np.array([1, 1]), 'lb': 0}
+ x0 = [0, 0]
+
+ with pytest.raises(ValueError) as e_info:
+ minimize(fun, x0, constraints=lc)
+ assert e_info.match('Constraint type not recognized')
diff --git a/python/tests/test_data_types.py b/python/tests/test_data_types.py
new file mode 100644
index 0000000000..3a7a85f07b
--- /dev/null
+++ b/python/tests/test_data_types.py
@@ -0,0 +1,97 @@
+from prima import minimize, NonlinearConstraint as NLC, LinearConstraint as LC
+from objective import fun
+import numpy as np
+import pytest
+
+
+def test_x0_as_list():
+ x0 = [0.0] * 2
+ res = minimize(fun, x0)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_x0_as_array():
+ x0 = np.array([0.0] * 2)
+ res = minimize(fun, x0)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_x0_as_scalar():
+ x0 = 0.0
+ # We need a custom function since the default objective function we're
+ # using for tests expects something that can be unpacked into two variables.
+ res = minimize(lambda x: (x-5)**2, x0)
+ assert np.isclose(res.x, 5.0, rtol=1e-6)
+ assert np.isclose(res.fun, 0.0, rtol=1e-6)
+
+
+def test_constraint_function_returns_numpy_array():
+ nlc = NLC(lambda x: np.array([x[0], x[1]]), lb=[-np.inf]*2, ub=[10]*2)
+ x0 = [0, 0]
+ res = minimize(fun, x0, method='COBYLA', constraints=nlc)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_constraint_function_returns_list():
+ nlc = NLC(lambda x: [x[0], x[1]], lb=[-np.inf]*2, ub=[10]*2)
+ x0 = [0, 0]
+ res = minimize(fun, x0, method='COBYLA', constraints=nlc)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_constraint_function_returns_scalar():
+ nlc = NLC(lambda x: float(np.linalg.norm(x) - np.linalg.norm(fun.optimal_x)), lb=[-np.inf], ub=[0])
+ x0 = [0, 0]
+ res = minimize(fun, x0, method='COBYLA', constraints=nlc)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+
+@pytest.mark.parametrize("A", (1, [1], np.array([1])))
+@pytest.mark.parametrize("lb", (0, [0], np.array([0])))
+@pytest.mark.parametrize("ub", (4, [4], np.array([4])))
+def test_linear_constraint_data_types(A, lb, ub):
+ myLC = LC(A=A, lb=lb, ub=ub)
+ # If A is scalar, x must have dimension 1, so we need a univariate function for that
+ scalar_fun = lambda x: (x - 5)**2
+ x0 = 0
+ res = minimize(scalar_fun, x0, method='LINCOA', constraints=myLC)
+ assert np.isclose(res.x, 4, rtol=1e-2)
+
+
+def test_nonlinear_constraint_lb_scalar_ub_scalar():
+ nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=25, ub=100)
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
+
+
+def test_nonlinear_constraint_lb_scalar_ub_not_scalar():
+ nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=9, ub=[9, 16])
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 3, atol=1e-6, rtol=1e-6)
+ # cp38-manylinux_i686 didn't quite get to 4 with a tol of e-6,
+ # so we lower the tolerance a little bit.
+ assert np.isclose(res.x[1], 4, atol=1e-5, rtol=1e-5)
+ assert np.isclose(res.fun, 4, atol=1e-6, rtol=1e-6)
+
+
+def test_nonlinear_constraint_lb_not_scalar_ub_scalar():
+ nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=[25, 36], ub=100)
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 6, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 4, atol=1e-6, rtol=1e-6)
+
+
+def test_nonlinear_constraint_lb_not_scalar_ub_not_scalar():
+ nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=[4, 4], ub=[4, 9])
+ x0 = [0, 0]
+ res = minimize(fun, x0, constraints=nlc)
+ assert np.isclose(res.x[0], 2, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.x[1], 3, atol=1e-6, rtol=1e-6)
+ assert np.isclose(res.fun, 10, atol=1e-6, rtol=1e-6)
diff --git a/python/tests/test_method_selection_warnings_and_exceptions.py b/python/tests/test_method_selection_warnings_and_exceptions.py
new file mode 100644
index 0000000000..fa84e7bb2b
--- /dev/null
+++ b/python/tests/test_method_selection_warnings_and_exceptions.py
@@ -0,0 +1,29 @@
+from prima import minimize, LinearConstraint, NonlinearConstraint, Bounds
+import pytest
+
+
+def test_method_not_recognized():
+ with pytest.raises(ValueError) as e_info:
+ minimize(lambda x: x, [0.0], method="not a method")
+ assert e_info.match("Method must be one of NEWUOA, UOBYQA, BOBYQA, COBYLA, or LINCOA, not 'not a method'")
+
+
+@pytest.mark.parametrize("method", ["newuoa", "uobyqa", "bobyqa", "lincoa"])
+def test_providing_nonlinear_constraints_to_non_cobyla_method(method):
+ with pytest.raises(ValueError) as e_info:
+ minimize(lambda x: x, [0.0], method=method, constraints=NonlinearConstraint(lambda x: x, 0, 1))
+ assert e_info.match("Nonlinear constraints were provided for an algorithm that cannot handle them")
+
+
+@ pytest.mark.parametrize("method", ["newuoa", "uobyqa", "bobyqa"])
+def test_providing_linear_constraints_to_non_cobyla_non_lincoa_method(method):
+ with pytest.raises(ValueError) as e_info:
+ minimize(lambda x: x, [0.0], method=method, constraints=LinearConstraint([1], 0, 1))
+ assert e_info.match("Linear constraints were provided for an algorithm that cannot handle them")
+
+
+@pytest.mark.parametrize("method", ["newuoa", "uobyqa"])
+def test_providing_bounds_to_non_cobyla_non_lincoa_non_bobyqa_method(method):
+ with pytest.raises(ValueError) as e_info:
+ minimize(lambda x: x, [0.0], method=method, bounds=Bounds([0], [1]))
+ assert e_info.match("Bounds were provided for an algorithm that cannot handle them")
diff --git a/python/tests/test_options.py b/python/tests/test_options.py
new file mode 100644
index 0000000000..c41e9e6ce4
--- /dev/null
+++ b/python/tests/test_options.py
@@ -0,0 +1,148 @@
+from prima import minimize, LinearConstraint as LC, NonlinearConstraint as NLC, PRIMAMessage
+from objective import fun
+import numpy as np
+import pytest
+from sys import platform
+
+
+def fun_with_star_args(x, *args):
+ return fun(x) + args[0][0]
+
+
+def fun_with_regular_args(x, args):
+ return fun(x) + args[0]
+
+
+def test_normal_function():
+ x0 = [0.0] * 2
+ res = minimize(fun, x0)
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_function_with_star_args():
+ x0 = np.array([0.0] * 2)
+ myargs = (5,)
+ res = minimize(fun_with_star_args, x0, args=myargs)
+ assert fun.result_point_is_optimal(res)
+ assert np.isclose(res.fun, fun.optimal_f + myargs[0], rtol=1e-6)
+
+
+def test_function_with_regular_args():
+ x0 = np.array([0.0] * 2)
+ myargs = (6,)
+ res = minimize(fun_with_regular_args, x0, args=myargs)
+ assert fun.result_point_is_optimal(res)
+ assert np.isclose(res.fun, fun.optimal_f + myargs[0], rtol=1e-6)
+
+
+def test_callback():
+ x0 = [0.0] * 2
+ nlc = NLC(lambda x: np.array([x[0], x[1]]), lb=[-np.inf]*2, ub=[10]*2)
+ callback_called = False
+ def callback(x, f, nf, tr, cstrv, nlconstr):
+ nonlocal callback_called # this makes sure we reference the variable in the parent scope
+ callback_called = True
+ res = minimize(fun, x0, method="COBYLA", constraints=nlc, callback=callback)
+ assert callback_called
+ assert fun.result_point_and_value_are_optimal(res)
+
+
+def test_callback_early_termination():
+ x0 = [0.0] * 2
+ def callback(x, *args):
+ if x[0] > 1:
+ return True
+ res = minimize(fun, x0, callback=callback)
+ # Assert that the results are not optimal since we terminated early
+ assert not fun.result_point_and_value_are_optimal(res)
+
+
+def test_ftarget():
+ x0 = [0.0] * 2
+ options = {'ftarget': 20}
+ res = minimize(fun, x0, options=options)
+ # Assert that the results are not optimal since we're stopping
+ # at ftarget=20 instead of the natural minimum of 0.
+ assert not fun.result_point_and_value_are_optimal(res)
+
+
+@pytest.mark.skipif(platform == "win32", reason="Windows outputs some strange characters, probably \\r\\n")
+def test_iprint(capfd):
+ x0 = [0.0] * 2
+ options = {'iprint': PRIMAMessage.EXIT}
+ res = minimize(fun, x0, options=options)
+ assert fun.result_point_and_value_are_optimal(res)
+ outerr = capfd.readouterr()
+ # In order to account for some machines (i.e. 32bit machines we test on) providing
+ # slightly different values, we use this formatting function to get the numbers in
+ # the right format, whatever they may be.
+ fmt = lambda x: np.format_float_scientific(x, precision=16, unique=False, exp_digits=3).upper()
+ assert outerr.out == f'''
+Return from NEWUOA because the trust region radius reaches its lower bound.
+Number of function values = {res.nfev} Least value of F = {fmt(res.fun)}
+The corresponding X is: {fmt(res.x[0])} {fmt(res.x[1])}
+'''
+ assert outerr.err == ''
+
+
+def test_maxfev():
+ x0 = [0.0] * 2
+ options = {'maxfev': 10}
+ res = minimize(fun, x0, options=options)
+ assert res.nfev == 10
+
+
+def test_npt():
+ x0 = [0.0] * 2
+ options = {'npt': 4}
+ res_with_npt = minimize(fun, x0, options=options)
+ res_without_npt = minimize(fun, x0)
+ assert res_with_npt.nfev != res_without_npt.nfev
+
+
+def test_rhobeg():
+ x0 = [0.0] * 2
+ options = {'rhobeg': 1e-3}
+ res_with_rhobeg = minimize(fun, x0, options=options)
+ res_without_rhobeg = minimize(fun, x0)
+ # With a smaller rhobeg, we're taking smaller steps at first
+ # and so we should converge more slowly
+ assert res_with_rhobeg.nfev > res_without_rhobeg.nfev
+
+
+def test_rhoend():
+ x0 = [0.0] * 2
+ options = {'rhoend': 1e-2}
+ res_with_rhoend = minimize(fun, x0, options=options)
+ res_without_rhoend = minimize(fun, x0)
+ # With a smaller rhoend we have a larger tolerance for the final
+ # trust region radius, and so we should converge more quickly
+ assert res_with_rhoend.nfev < res_without_rhoend.nfev
+
+
+@pytest.mark.parametrize("method", ["NEWUOA", "BOBYQA", "LINCOA", "COBYLA"])
+def test_quiet(capfd, method):
+ nlc = NLC(lambda x: np.array([x[0]**2, x[1]**2]), lb=[25]*2, ub=[100]*2)
+ lc = LC(np.array([[1, 1],[1, -1]]), lb=[0, 0], ub=[8, 2])
+ bounds = ([0, 3], [0, 3])
+ x0 = [0.0] * 2
+ options = {'quiet': False}
+ if method == "NEWUOA":
+ res = minimize(fun, x0, options=options)
+ elif method == "BOBYQA":
+ res = minimize(fun, x0, bounds=bounds, options=options)
+ elif method == "LINCOA":
+ res = minimize(fun, x0, constraints=lc, options=options)
+ elif method == "COBYLA":
+ res = minimize(fun, x0, constraints=nlc, options=options)
+ outerr = capfd.readouterr()
+ if method == "NEWUOA":
+ assert outerr.out == "No bounds or constraints detected, applying NEWUOA\n"
+ elif method == "BOBYQA":
+ assert outerr.out == "Bounds without linear or nonlinear constraints detected, applying BOBYQA\n"
+ elif method == "LINCOA":
+ assert outerr.out == "Linear constraints detected without nonlinear constraints, applying LINCOA\n"
+ elif method == "COBYLA":
+ assert outerr.out == "Nonlinear constraints detected, applying COBYLA\n"
+ assert outerr.err == ''
+
\ No newline at end of file
diff --git a/python/tests/test_process_linear_constraints.py b/python/tests/test_process_linear_constraints.py
new file mode 100644
index 0000000000..220ea945c7
--- /dev/null
+++ b/python/tests/test_process_linear_constraints.py
@@ -0,0 +1,31 @@
+import numpy as np
+from prima import LinearConstraint, combine_multiple_linear_constraints, separate_LC_into_eq_and_ineq, minimize
+from objective import fun
+
+
+def test_multiple_linear_constraints_implementation():
+ constraints = [LinearConstraint(A=np.array([[1, 2], [3, 4]]), lb=[5, 6], ub=[7, 8]),
+ LinearConstraint(A=np.array([[9, 10], [11, 12]]), lb=[13, 14], ub=[15, 16])]
+ processed_constraint = combine_multiple_linear_constraints(constraints)
+ assert (processed_constraint.A == np.array([[1, 2], [3, 4], [9, 10], [11, 12]])).all()
+ assert all(processed_constraint.lb == [5, 6, 13, 14])
+ assert all(processed_constraint.ub == [7, 8, 15, 16])
+
+
+def test_multiple_linear_constraints_high_level():
+ constraints = [LinearConstraint(A=np.array([1, 0]), lb=5, ub=7),
+ LinearConstraint(A=np.array([0, 1]), lb=6, ub=8)]
+ x0 = [0.0, 0.0]
+ res = minimize(fun, x0, constraints=constraints)
+ assert np.isclose(res.x[0], 5.0, rtol=1e-6)
+ assert np.isclose(res.x[1], 6.0, rtol=1e-6)
+ assert np.isclose(res.fun, 4.0, rtol=1e-6)
+
+
+def test_separate_LC_into_eq_and_ineq():
+ linear_constraint = LinearConstraint(A=np.array([[1, 2], [3, 4]]), lb=[5, 6], ub=[5, 8])
+ A_eq, b_eq, A_ineq, b_ineq = separate_LC_into_eq_and_ineq(linear_constraint)
+ assert (A_eq == np.array([[1, 2]])).all()
+ assert all(b_eq == [5])
+ assert (A_ineq == np.array([[-3, -4], [3, 4]])).all()
+ assert all(b_ineq == [-6, 8])
diff --git a/python/tests/test_process_nonlinear_constraints.py b/python/tests/test_process_nonlinear_constraints.py
new file mode 100644
index 0000000000..fd95f1bdad
--- /dev/null
+++ b/python/tests/test_process_nonlinear_constraints.py
@@ -0,0 +1,50 @@
+import numpy as np
+from prima import NonlinearConstraint, process_nl_constraints, minimize
+import pytest
+
+
+@pytest.mark.parametrize("lb1", (-np.inf, [-np.inf], np.array([-np.inf])))
+@pytest.mark.parametrize("lb2", (-np.inf, [-np.inf]*2, np.array([-np.inf]*2)))
+@pytest.mark.parametrize("ub1", (0, [0], np.array([0])))
+@pytest.mark.parametrize("ub2", (0, [0]*2, np.array([0]*2)))
+def test_multiple_nl_constraints_various_data_types(lb1, ub1, lb2, ub2):
+ nlc1 = NonlinearConstraint(lambda x: x, lb=lb1, ub=ub1)
+ nlc2 = NonlinearConstraint(lambda x: [x, x], lb=lb2, ub=ub2)
+ nlcs = [nlc1, nlc2]
+ constr_func = process_nl_constraints(nlcs)
+ assert len(constr_func(0)) == 3
+ assert all(constr_func(0) == [0, 0, 0])
+
+
+@pytest.mark.parametrize("lb", (-np.inf, [-np.inf]*2, np.array([-np.inf]*2)))
+@pytest.mark.parametrize("ub", (0, [0]*2, np.array([0]*2)))
+def test_single_nl_constraint(lb, ub):
+ nlc = NonlinearConstraint(lambda x: [x, x], lb=lb, ub=ub)
+ x0 = 2.1
+ constr_func = process_nl_constraints([nlc])
+ assert len(constr_func(0)) == 2
+ assert all(constr_func(x0) == [x0, x0])
+
+@pytest.mark.parametrize("lb", (-np.inf, [-np.inf]))
+@pytest.mark.parametrize("ub", (0.0, [0.0]))
+def test_length_nlc_fun_not_equal_to_length_lb_ub(lb, ub):
+ if lb == -np.inf and ub == 0.0:
+ return # No exception here
+ nlc = NonlinearConstraint(lambda x: [x, x], lb=lb, ub=ub)
+ constr_fun = process_nl_constraints([nlc])
+ with pytest.raises(ValueError) as e_info:
+ constr_fun(0)
+ if lb == -np.inf:
+ assert e_info.match("The number of elements in the constraint function's output does not match the number of elements in the upper bound.")
+ else:
+ assert e_info.match("The number of elements in the constraint function's output does not match the number of elements in the lower bound.")
+
+
+def test_length_nlc_fun_ne_to_length_ub():
+ # This specific test is for the case that both lb and ub are vectors, and lb has the correct
+ # length but ub does not.
+ nlc = NonlinearConstraint(lambda x: [x, x], lb=[-np.inf, 0], ub=[0])
+ constr_fun = process_nl_constraints([nlc])
+ with pytest.raises(ValueError) as e_info:
+ constr_fun(0)
+ assert e_info.match("The number of elements in the constraint function's output does not match the number of elements in the upper bound.")
diff --git a/setup.m b/setup.m
index c5c8cc678d..5ffdfe3a93 100644
--- a/setup.m
+++ b/setup.m
@@ -18,6 +18,7 @@ function setup(varargin)
% setup(solver_name, options) % Compile a solver with `options`
%
% Possible options for compilation:
+% - half: whether to compile the half precision of the Fortran solvers (default: false)
% - single: whether to compile the single precision of the Fortran solvers (default: true)
% - quadruple: whether to compile the quadruple precision of the Fortran solvers (default: false)
% - classical: whether to compile the classical variant of the Fortran solvers (default: true)
@@ -42,10 +43,9 @@ function setup(varargin)
% contains this script and its subdirectories.
%
% ***********************************************************************
-% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
-% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
-% Department of Applied Mathematics,
-% The Hong Kong Polytechnic University.
+% Author: Zaikun ZHANG (www.zhangzk.net)
+% School of Mathematics,
+% Sun Yat-sen University, China.
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
%
@@ -63,8 +63,14 @@ function setup(varargin)
package_name = 'prima';
% Check the version of MATLAB.
-if verLessThan('matlab', '9.4') % MATLAB R2018a = MATLAB 9.4
- fprintf('\nSorry, this package does not support MATLAB R2017b or earlier releases.\n\n');
+oldest_supported_release = 'R2020b';
+try
+ matlab_too_old = isMATLABReleaseOlderThan(oldest_supported_release);
+catch
+ matlab_too_old = true; % isMATLABReleaseOlderThan was introduced in MATLAB R2020b.
+end
+if matlab_too_old
+ fprintf('\nSorry, this package does not support MATLAB %s or earlier releases.\n\n', oldest_supported_release);
return
end
@@ -106,7 +112,7 @@ function setup(varargin)
% Remove the compiled MEX files (and the files generated along side) if requested.
if strcmp(action, 'clean')
- clean_mex(mexdir, 'verbose');
+ clean_mex(mexdir, true); % verbose = true
clean_generated_files(fortd_interform, gateways_interform, tools, mexdir);
rmpath(tools);
return
@@ -115,15 +121,17 @@ function setup(varargin)
% Uninstall the package if requested.
if strcmp(action, 'uninstall')
uninstall_prima(package_name);
- % `rmpath(tools)` is not needed, as `uninstall_prima` already does it.
+ rmpath(tools);
return
end
% Add the path and return if requested.
if strcmp(action, 'path')
add_save_path(interfaces, package_name);
+ % Add the tests directory to the path, so that the user can run `testprima`.
+ addpath(tests);
% Create `all_precisions.m` and `all_variants.m` under `tools` according to the content of
- % `mexdir`. They reflect the precisions ('double', 'single', 'quadruple') and variants
+ % `mexdir`. They reflect the precisions ('half', 'single', 'double', 'quadruple') and variants
% ('modern', 'classical') of the solvers available under `mexdir`.
create_all_precisions(mexdir);
create_all_variants(mexdir);
@@ -145,7 +153,7 @@ function setup(varargin)
% Create `all_precisions.m` and `all_variants.m` under `tools` according to `options`.
-% They reflect the precisions ('double', 'single', 'quadruple') and variants ('modern','classical')
+% They reflect the precisions ('half', 'single', 'double', 'quadruple') and variants ('modern','classical')
% of the solvers available after the compilation.
create_all_precisions(options);
create_all_variants(options);
@@ -156,12 +164,14 @@ function setup(varargin)
% Check whether MEX is properly configured.
fprintf('\nVerifying the setup of MEX ... \n');
language = 'Fortran'; % Language to compile
-mex_well_conf = try_mex_setup(language);
+verbose = (isfield(options, 'verbose') && islogicalscalar(options.verbose) && options.verbose);
+mex_well_conf = try_mex_setup(language, verbose);
if mex_well_conf == 0
- fprintf('\nVerification FAILED.\n')
- fprintf('\nThe MEX of your MATLAB is not properly configured for compiling Fortran.');
- fprintf('\nPlease configure MEX before using this package. Try ''help mex'' for more information.\n\n');
+ fprintf('\nMATLAB needs you to set MEX up for Fortran.');
+ fprintf('\nTry ''help mex'' or ask a MATLAB expert about MEX setup.');
+ fprintf('\nNote: MEX is a product of MathWorks. Its configuration is not part of this package.\n\n');
return
+
elseif mex_well_conf == -1
fprintf('\nmex(''-setup'', ''%s'') runs successfully but we cannot verify that MEX works properly.', language);
fprintf('\nWe will try to continue.\n\n');
@@ -172,6 +182,34 @@ function setup(varargin)
% Generate the intersection-form Fortran source code
% We need to do this because MEX accepts only the (obsolescent) fixed-form Fortran code on Windows.
% Intersection-form Fortran code can be compiled both as free form and as fixed form.
+%
+% Zaikun 20260428: Starting from R2026a, MATLAB officially supports free-form Fortran code (see
+% https://www.mathworks.com/help/matlab/ref/mex.html and
+% https://fortran-lang.discourse.group/t/matlab-mex-free-form-fortran-finally ). So this
+% can be skipped when we believe that most of our users have upgraded to R2026a or later.
+% At that time, we should at least do the following.
+% - in this script, remove the definition of `fortd_interform` and `gateways_interform`
+% - in this script, replace 'fortd_interform' and 'gateways_interform' with 'fortd' and 'gateways'
+% - in matlab/setup_tools/clean_generated_files.m, remove the input arguments `fortd_interform` and
+% `gateways_interform` and the code that deletes the files under these two directories
+% - in matlab/setup_tools/compile.m, replace '.f' with '.f90' and '.F' with '.F90'
+% - in matlab/setup_tools/compile.m, remove the following comment:
+% % N.B.: The .*90 files have become .* after the code refactoring in setup.m.
+% - in matlab/setup_tools/official_mex_example.m, replace 'timestwo.F' with 'timestwo.F90' (prior to
+% R2026a, the official MEX example is timestwo.F; R2026a provides both timestwo.F and timestwo.F90)
+% - in the root directory of the project, run the bash command
+% grep -RiIn 'interform' --exclude-dir={benchmark,.development,tests,.github,.git}
+% to search for 'interform' in the whole package and remove the code related to it
+% - remove matlab/setup_tools/interform.m
+% - update oldest_supported_release to R2026a
+% - update matlab/setup_tools/try_mex_setup.m concerning `isenv` and `unsetenv`, which have been
+% available since R2022b
+% - update matlab/setup_tools/compile.m regarding the support for Fortran internal procedures in
+% MATLAB R2025a+
+% - in the root directory of the project, run the bash command
+% grep -RiIn 'R20' --exclude-dir={benchmark,.development,tests,.github,.git}
+% grep -RiIn 'verLessThan\|matlabRelease\|isMATLABReleaseOlderThan' --exclude-dir={benchmark,.development,tests,.github,.git}
+% to search for 'R20' in the whole package and update the code related to the MATLAB release
fprintf('Refactoring the Fortran code ... ');
interform(fortd);
interform(gateways);
@@ -258,7 +296,7 @@ function setup(varargin)
addpath(path_string);
% Try saving the path in the system path-defining file at sys_pathdef. If the user does not have
-% writing permission for this file, then the path will not saved.
+% writing permission for this file, then the path will not be saved.
% N.B. Do not save the path to the pathdef.m file under userpath. This file is not loaded by default
% at startup. See
% https://www.mathworks.com/matlabcentral/answers/269482-is-userdata-pathdef-m-for-local-path-additions-supported-on-linux
@@ -276,7 +314,7 @@ function setup(varargin)
if ~path_saved && numel(userpath) > 0
user_startup = fullfile(userpath, 'startup.m');
add_path_string = sprintf('addpath(''%s'');', path_string);
- full_add_path_string = sprintf('%s %s %s', add_path_string, '%', path_string_stamp);
+ full_add_path_string = sprintf('%s %s %s', add_path_string, '%', path_string_stamp);
% First, check whether full_add_path_string already exists in user_startup or not.
if exist(user_startup, 'file')
diff --git a/tests.md b/tests.md
new file mode 100644
index 0000000000..4b2f0e199d
--- /dev/null
+++ b/tests.md
@@ -0,0 +1,102 @@
+**Code must be battle-tested before becoming software.**
+The development of PRIMA is driven by [intensive and extensive tests](https://github.com/orgs/libprima/discussions/39)
+automated by [GitHub Actions](https://docs.github.com/en/actions).
+
+Since each GitHub Team account can only run at most 60 GitHub Actions workflows concurrently, I have
+to distribute this large amount of tests to multiple Team accounts as follows.
+
+- [Tests](https://github.com/libprima/prima/actions) at [libprima/prima](https://github.com/libprima/prima)
+
+ - [](https://github.com/libprima/prima/actions/workflows/cmake.yml)
+ - [](https://github.com/libprima/prima/actions/workflows/build_python.yml)
+ - [](https://github.com/libprima/prima/actions/workflows/compile_mex.yml)
+ - [](https://github.com/libprima/prima/actions/workflows/actionlint.yml)
+ - [](https://github.com/libprima/prima/actions/workflows/spelling.yml)
+ - [](https://cirrus-ci.com/github/libprima/prima)
+
+
+
+
+
+
+
+
+
+
+
+
+- [Tests](https://github.com/libsprima/prima/actions) at [libsprima/prima](https://github.com/libsprima/prima)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_small.yml)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_big.yml)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_large.yml)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_archiva_nova.yml)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_archiva_media.yml)
+ - [](https://github.com/libsprima/prima/actions/workflows/verify_archiva_antiqua.yml)
+
+- [Tests](https://github.com/primapack/prima/actions) at [primapack/prima](https://github.com/primapack/prima)
+
+ - [](https://github.com/primapack/prima/actions/workflows/profile_cobyla_small.yml)
+ - [](https://github.com/primapack/prima/actions/workflows/profile_uobyqa_small.yml)
+ - [](https://github.com/primapack/prima/actions/workflows/profile_newuoa_small.yml)
+ - [](https://github.com/primapack/prima/actions/workflows/profile_bobyqa_small.yml)
+ - [](https://github.com/primapack/prima/actions/workflows/profile_lincoa_small.yml)
+
+- [Tests](https://github.com/fortlab/prima/actions) at [fortlab/prima](https://github.com/fortlab/prima)
+
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_all_sq.yml)
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_cobyla_small_sq.yml)
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_uobyqa_small_sq.yml)
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_newuoa_small_sq.yml)
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_bobyqa_small_sq.yml)
+ - [](https://github.com/fortlab/prima/actions/workflows/profile_lincoa_small_sq.yml)
+
+- [Tests](https://github.com/primalib/prima/actions) at [primalib/prima](https://github.com/primalib/prima)
+
+ - [](https://github.com/primalib/prima/actions/workflows/profile_prima_small.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_all.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_single.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_quadruple.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_integer_kind.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_infnan.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_compiler_options.yml)
+ - [](https://github.com/primalib/prima/actions/workflows/profile_npt.yml)
+
+- [Tests](https://github.com/opt4ai/prima/actions) at [opt4ai/prima](https://github.com/opt4ai/prima)
+
+ - [](https://github.com/opt4ai/prima/actions/workflows/test_matlab.yml)
+ - [](https://github.com/opt4ai/prima/actions/workflows/test_matlab_linux.yml)
+ - [](https://github.com/opt4ai/prima/actions/workflows/test_pyprima.yml)
+
+- [Tests](https://github.com/opt2ai/prima/actions) at [opt2ai/prima](https://github.com/opt2ai/prima)
+
+ - [](https://github.com/opt2ai/prima/actions/workflows/test_matlab_mac_intel.yml)
+ - [](https://github.com/opt2ai/prima/actions/workflows/test_matlab_windows.yml)
+ - [](https://github.com/opt2ai/prima/actions/workflows/profile_intrinsic_linalg.yml)
+
+- [Tests](https://github.com/sprimalib/prima/actions) at [sprimalib/prima](https://github.com/sprimalib/prima)
+
+ - [](https://github.com/sprimalib/prima/actions/workflows/stress_test_matlab.yml)
+ - [](https://github.com/sprimalib/prima/actions/workflows/stress_test_fortran.yml)
+
+- [Tests](https://github.com/zequipe/prima/actions) at [zequipe/prima](https://github.com/zequipe/prima)
+
+ - [](https://github.com/zequipe/prima/actions/workflows/parallel_test_matlab.yml)
+ - [](https://github.com/zequipe/prima/actions/workflows/recursive_test_matlab.yml)
+ - [](https://github.com/zequipe/prima/actions/workflows/test_sunf95.yml)
+ - [](https://github.com/zequipe/prima/actions/workflows/test_g95.yml)
+ - [](https://github.com/zequipe/prima/actions/workflows/profile_rescue_idz_classical.yml)
+ - [](https://github.com/zequipe/prima/actions/workflows/profile_rescue_idz_modernized.yml)
+
+- [Tests](https://github.com/equipez/prima/actions) at [equipez/prima](https://github.com/equipez/prima)
+
+ - [](https://github.com/equipez/prima/actions/workflows/test_nvfortran.yml)
+ - [](https://github.com/equipez/prima/actions/workflows/test_flang.yml)
+ - [](https://github.com/equipez/prima/actions/workflows/test_aflang.yml)
+ - [](https://github.com/equipez/prima/actions/workflows/test_armflang.yml)
+
+- [Tests](https://github.com/s-prima/prima/actions) at [s-prima/prima](https://github.com/s-prima/prima)
+
+ - [](https://github.com/s-prima/prima/actions/workflows/test_gfortran.yml)
+ - [](https://github.com/s-prima/prima/actions/workflows/test_ifort.yml)
+ - [](https://github.com/s-prima/prima/actions/workflows/test_ifx.yml)
+ - [](https://github.com/s-prima/prima/actions/workflows/lint_hosted.yml)
diff --git a/todo/TODO b/todo/TODO
index a0c2afdb36..4026892082 100644
--- a/todo/TODO
+++ b/todo/TODO
@@ -53,3 +53,32 @@ In addition, we may use statehist and radhist to improve fmsg, invoking it unifo
```
To this end, we will need to modify fmsg so that it can translate the state index (0, 1, 2, 3) to
the state name as mentioned above.
+
+13. Try scaling the matrices H and PQ to avoid huge or tiny values.
+
+14. Instead of requiring that each component of X0 is either on the boundary or is away from the
+boundary by at least RHOBEG, we require that each component is away from one boundary by at least
+RHOBEG or away from the other boundary by at least 2*RHOBEG. In other words,
+
+(XU - X0 >= RHOBEG .OR. X0 - XL >= 2*RHOBEG) .AND. (X0 - XL >= RHOBEG .OR. XU - X0 >= 2*RHOBEG) .AND.
+
+X0 - XL >= 2*RHOBEG .OR. XU - X0 >= 2*RHOBEG .OR. X0 - XL >= 2*RHOBEG .AND. XU - X0 >= 2*RHOBEG
+
+
+When HONOUR_X0 is TRUE, instead of setting
+
+RHOBEG = MINVAL([X-XL, XU-X]),
+
+use
+
+RHOBEG = MINVAL([MAX(X-XL, (XU-X) / 2), MAX(XU-X, (X-XL) / 2)]).
+
+15. Fortran test: implement semi_circle and semi_ellipse to test problems with both linear and
+nonlinear constraints, to see whether the SEGFAULT of AOCC Flang on big problems is due to the
+presence of both linear and nonlinear constraints.
+
+16. Fix profiling of various NPT and compiler options.
+
+17. Fix CVAL in LINCOA.
+
+18. Recalculate the model if non-finite values appear.
diff --git a/todo/TODO_for b/todo/TODO_for
new file mode 100644
index 0000000000..1da6d065c5
--- /dev/null
+++ b/todo/TODO_for
@@ -0,0 +1,7 @@
+1. Implement the following for LINCOA, BOBYQA. It has been done for COBYLA, NEWUOA, UOBYQA.
+See cobylb.f90, newuob.f90, uobyqb.f90 for reference.
+If X is close to one of the points in the interpolation set, then we do not evaluate the
+objective and constraints at X, assuming them to have the values at the closest point.
+N.B.: If this happens, do NOT include X into the filter, as F and CONSTR are inaccurate.
+
+After this, turn on the checking that "XHIST does not contain a repeating segment". See history.f90.