Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
47929da
Renamed UNO_OPTION_TYPE_INT to UNO_OPTION_TYPE_INTEGER
stefphd Oct 15, 2025
873c2df
Moved stream callbacks to UserModel.hpp
stefphd Oct 15, 2025
a4ae849
Added Vector(begin, end) constructor
stefphd Oct 15, 2025
c74073a
Added typed option getters
stefphd Oct 15, 2025
ce7ece0
Added Matlab MEX interface
stefphd Oct 15, 2025
3bb120a
Create README for Matlab interface
stefphd Oct 15, 2025
ff6f04c
Added Matlab example
stefphd Oct 15, 2025
c322a01
Fixed missing include
stefphd Oct 15, 2025
c79b6bf
Matlab libut not required
stefphd Oct 15, 2025
571f82b
Added explicit sparsity patterns in matlab interface
stefphd Oct 15, 2025
ea06c51
Updated matlab example to use explicit sparsity
stefphd Oct 15, 2025
d6474ec
Updated matlab README
stefphd Oct 15, 2025
9f1a5b2
Simplified struct to option conversion
stefphd Oct 15, 2025
ef5fb87
Added conversion of matlab string to std::string
stefphd Oct 15, 2025
2ade211
Accept matlab string in option preset
stefphd Oct 15, 2025
e731182
Merge branch 'main' of https://github.com/cvanaret/Uno
stefphd Oct 16, 2025
28f2af1
Removed redundant callbacks in matlab interface
stefphd Oct 16, 2025
f671410
Merge branch 'main' of https://github.com/cvanaret/Uno
stefphd Oct 16, 2025
a7cdf6c
Updated matlab with number model evaluations
stefphd Oct 16, 2025
35c8b36
Updated the matlab example
stefphd Oct 17, 2025
bdfb559
Added function argument validation
stefphd Oct 17, 2025
e364415
Reorganized matlab interface sources
stefphd Oct 18, 2025
e5c888c
Link matlab interface to uno default lib
stefphd Oct 19, 2025
568e59b
Validate user callback outputs during optimization
stefphd Oct 19, 2025
577b605
Removed unused functions
stefphd Oct 19, 2025
c83e3f5
Update example_hs015.m
stefphd Oct 19, 2025
d0b7d8b
Added matlab Polak5 example
stefphd Oct 19, 2025
252f39f
Flip multiplier sign back before eval (data are copied)
stefphd Oct 19, 2025
95bda52
Update example_polak5.m
stefphd Oct 19, 2025
fd716f3
Renamed validate_matlab_handle to validate_matlab_handle_field
stefphd Oct 19, 2025
5e516dd
Added high-level Matlab interface
stefphd Oct 23, 2025
0668266
Fixed missing (:) in uno.m
stefphd Oct 23, 2025
f333c0b
Fixed typo in Matlab readme
stefphd Oct 23, 2025
26bade9
Added Test Matlab interface workflow
stefphd Oct 23, 2025
7fe875b
Fixed typo in Matlab readme
stefphd Oct 23, 2025
71b1b3d
Exclude auto build of Matlab interface
stefphd Oct 23, 2025
9a4d24d
Try install Matlab files
stefphd Oct 23, 2025
f17e229
Added explicit error messages in Uno matlab interface
stefphd Oct 23, 2025
d9101e2
Merge branch 'main' of https://github.com/cvanaret/Uno
stefphd Oct 23, 2025
9843722
Fixed unomex_optimize hessian and problem type
stefphd Oct 23, 2025
09e3a74
Clear uno_optimize after calling uno
stefphd Oct 23, 2025
3f5a38a
Merge branch 'main' of https://github.com/cvanaret/Uno
stefphd Oct 24, 2025
799876c
Merged C API with main changes
stefphd Oct 24, 2025
cc4329a
unomex_* renamed to uno_
stefphd Oct 24, 2025
16ae619
Added validate_string_field
stefphd Oct 24, 2025
eb36f1e
Added missing return
stefphd Oct 24, 2025
88abf78
Updated Matlab examples with new problem_type
stefphd Oct 24, 2025
390bd6b
Link Matlab libut in unomex-options target
stefphd Oct 24, 2025
08bbf4f
Divided C++ classes for Matlab interface into multiple files
stefphd Oct 24, 2025
6f6eca7
Added Matlab test example on MacOS
stefphd Oct 24, 2025
0ddef43
Fixed platform in github workflow
stefphd Oct 24, 2025
b0f5d1d
Try adding fortran compiler in macod workflow
stefphd Oct 24, 2025
2842a66
Fixed wrong slash in include
stefphd Oct 24, 2025
1988033
Fixed wrong slash in include (missing)
stefphd Oct 24, 2025
0fc34b5
Merge branch 'main' of https://github.com/cvanaret/Uno
stefphd Oct 27, 2025
49165f3
Updated multiplier sign convention in Materlab interface
stefphd Oct 27, 2025
752601d
compile the Unomex source files once
stefphd Oct 27, 2025
8cce827
Try fix rpath for macos Matlab interface
stefphd Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/workflows/matlab-test-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Test the Matlab interface on the example

on:
push:
branches: [ "main" ]
paths-ignore:
- '*.md'
- 'LICENSE'
- '*.cff'
pull_request:
branches: [ "main" ]
paths-ignore:
- '*.md'
- 'LICENSE'
- '*.cff'

env:
BUILD_TYPE: Release

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: windows-latest
addpath: install/bin
- os: macos-14
addpath: install/lib

steps:
- uses: actions/checkout@v4

- name: Set up MATLAB
uses: matlab-actions/setup-matlab@v2

- name: Download dependency (BQPD)
shell: bash
run: |
VERSION_BQPD="1.0.0"
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
PLATFORM="x86_64-w64-mingw32"
choco install wget
else
PLATFORM="aarch64-apple-darwin"
fi
wget https://github.com/leyffer/BQPD_jll.jl/releases/download/BQPD-v${VERSION_BQPD}%2B0/BQPD.v${VERSION_BQPD}.$PLATFORM-libgfortran5.tar.gz
mkdir deps
tar -xzvf BQPD.v${VERSION_BQPD}.$PLATFORM-libgfortran5.tar.gz -C deps

- name: Add MinGW64 to PATH (Windows)
if: matrix.os == 'windows-latest'
run: echo "C:\tools\msys64\mingw64\bin" >> "$GITHUB_PATH"

- name: Install Fortran compiler (MacOS)
if: matrix.os != 'windows-latest'
uses: fortran-lang/setup-fortran@main
with:
compiler: 'gcc'
version: '13'

- name: Configure CMake
shell: bash
run: |
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
cmake -G "MinGW Makefiles" -B "${{github.workspace}}/build" \
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-DBQPD="${{github.workspace}}/deps/lib/libbqpd.a"
else
cmake -B "${{github.workspace}}/build" \
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-DBQPD="${{github.workspace}}/deps/lib/libbqpd.a"
fi

- name: Build uno
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j4

- name: Build unomex
run: cmake --build ${{github.workspace}}/build --target unomex --config ${{env.BUILD_TYPE}} -j4

- name: Install unomex
run: cmake --install ${{github.workspace}}/build --prefix ${{github.workspace}}/install

- name: Run example (high-level interface)
uses: matlab-actions/run-command@v2
with:
command: |
addpath('${{ matrix.addpath }}');
run('interfaces/Matlab/example/example_uno.m');

- name: Run example (low-level interface)
uses: matlab-actions/run-command@v2
with:
command: |
addpath('${{ matrix.addpath }}');
run('interfaces/Matlab/example/example_hs015.m');

57 changes: 57 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,58 @@ if(pybind11_DIR)
target_include_directories(unopy PUBLIC ${DIRECTORIES})
endif()

############################
# optional Matlab bindings #
############################

find_package(Matlab COMPONENTS MAIN_PROGRAM MEX_COMPILER)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you've verified this works with a particular matlab version I'd recommend pinning it on that version. There have been several minor changes in the mex behaviour over the past couple of versions that could cause unnecessary heartache.

if(Matlab_FOUND)
message(STATUS "Found Matlab")

file(GLOB MATLAB_SOURCE_FILES
interfaces/Matlab/cpp_classes/*.cpp
interfaces/Matlab/unomex/*.cpp
)

# libut matlab library
# cf. https://undocumentedmatlab.com/articles/mex-ctrl-c-interrupt#:~:text=The%20relevant%20functions%20seem%20to,utLongjmpIfInterruptPending(?)
get_filename_component(Matlab_MX_LIBRARY_PATH ${Matlab_MX_LIBRARY} DIRECTORY )
find_library(MXLIBUT NAMES libut ut
PATHS ${Matlab_MX_LIBRARY_PATH})
if(MXLIBUT)
add_definitions("-D HAS_MXLIBUT")
endif()

# compile the Unomex source files once
add_library(compiled_unomex_files EXCLUDE_FROM_ALL OBJECT ${MATLAB_SOURCE_FILES})
set_property(TARGET compiled_unomex_files PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(compiled_unomex_files PUBLIC ${DIRECTORIES} ${Matlab_INCLUDE_DIRS})

# uno_optimize
matlab_add_mex(NAME unomex-optimize SRC $<TARGET_OBJECTS:compiled_unomex_files> interfaces/Matlab/uno_optimize.cpp
EXCLUDE_FROM_ALL
OUTPUT_NAME uno_optimize
LINK_TO ${DEFAULT_UNO_LIB} ${LIBRARIES} ${FORTRAN_LIBS} ${MXLIBUT})
target_include_directories(unomex-optimize PUBLIC ${DIRECTORIES})

# uno_options
matlab_add_mex(NAME unomex-options SRC $<TARGET_OBJECTS:compiled_unomex_files> interfaces/Matlab/uno_options.cpp
EXCLUDE_FROM_ALL
OUTPUT_NAME uno_options
LINK_TO ${DEFAULT_UNO_LIB} ${LIBRARIES} ${FORTRAN_LIBS} ${MXLIBUT})
target_include_directories(unomex-options PUBLIC ${DIRECTORIES})

# logical target to compile both mex
add_custom_target(unomex DEPENDS unomex-optimize unomex-options)

# cf. https://github.com/cvanaret/Uno/pull/360#issuecomment-3431367949
if(APPLE)
set(MEX_RPATH "@executable_path/../extern/bin/maci64;@executable_path/../extern/bin/maca64;@executable_path/../../extern/bin/maci64;@executable_path/../../extern/bin/maca64")
set_target_properties(unomex-optimize PROPERTIES BUILD_RPATH "${MEX_RPATH};${CMAKE_BUILD_RPATH}" INSTALL_RPATH "${MEX_RPATH};${CMAKE_INSTALL_RPATH}")
set_target_properties(unomex-options PROPERTIES BUILD_RPATH "${MEX_RPATH};${CMAKE_BUILD_RPATH}" INSTALL_RPATH "${MEX_RPATH};${CMAKE_INSTALL_RPATH}")
endif()
endif()

##################################
# optional GoogleTest unit tests #
##################################
Expand Down Expand Up @@ -327,6 +379,11 @@ endif()
if(TARGET unopy)
list(APPEND INSTALL_TARGETS unopy)
endif()
if(TARGET unomex)
list(APPEND INSTALL_TARGETS unomex-optimize unomex-options)
set(MATLAB_INSTALL_FILES "interfaces/Matlab/uno.m;interfaces/Matlab/uno_optimize.m;interfaces/Matlab/uno_options.m")
install(FILES ${MATLAB_INSTALL_FILES} DESTINATION $<IF:$<BOOL:${WIN32}>,bin,lib>)
endif()
install(TARGETS ${INSTALL_TARGETS}
COMPONENT libuno
EXPORT UnoTargets
Expand Down
89 changes: 22 additions & 67 deletions interfaces/C/Uno_C_API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iostream>
#include <streambuf>
#include "Uno_C_API.h"
#include "../UserModel.hpp"
#include "Uno.hpp"
Expand Down Expand Up @@ -371,75 +369,28 @@ class CUserCallbacks: public UserCallbacks {
void* user_data;
};

// std::streambuf wrapper around LoggerStreamUserCallback
class CStreamBuffer : public std::streambuf {
public:
explicit CStreamBuffer(LoggerStreamUserCallback logger_stream_callback, void* user_data, std::size_t buffer_size) :
logger_stream_callback(logger_stream_callback), user_data(user_data) {
// allocate output buffer and set stream buffer pointer
this->buffer = new char[buffer_size];
this->setp(this->buffer, this->buffer + buffer_size - 1);
}
~CStreamBuffer() override {
// flush remaining data and release buffer memory
this->sync();
delete[] this->buffer;
}
class CStreamCallback : public UserStreamCallback {
public:
CStreamCallback(LoggerStreamUserCallback logger_stream_callback, void* user_data) :
UserStreamCallback(), logger_stream_callback(logger_stream_callback), user_data(user_data) { }
~CStreamCallback() override { }

protected:
// called on buffer overflow
int overflow(int character) override {
if (character != EOF) {
// insert the character into the buffer
*this->pptr() = traits_type::to_char_type(character);
this->pbump(1);
int32_t operator()(const char* buf, int32_t len) const override {
if (this->logger_stream_callback) {
return this->logger_stream_callback(buf, len, this->user_data);
}
else {
return 0;
}
// return EOF for error
return (this->flush_buffer() == 0) ? character : EOF;
}

int sync() override {
return this->flush_buffer();
}

private:
LoggerStreamUserCallback logger_stream_callback;
void* user_data;
char* buffer;

// flush buffer to the logger callback
int flush_buffer() {
// check for invalid stream callback
if (!this->logger_stream_callback) {
return -1;
}
std::ptrdiff_t current_used_buffer_size = this->pptr() - this->pbase();
if (current_used_buffer_size > 0) {
// call user logger callback
const int32_t callback_result = this->logger_stream_callback(this->pbase(), static_cast<int32_t>(current_used_buffer_size),
this->user_data);
if (callback_result != static_cast<int32_t>(current_used_buffer_size)) {
return -1;
}
// move buffer pointer
this->pbump(static_cast<int>(-current_used_buffer_size));
}
return 0;
}
};

// std::ostream wrapper around LoggerStreamUserCallback
class COStream : public std::ostream {
public:
COStream(LoggerStreamUserCallback logger_stream_callback, void* user_data, std::size_t buffer_size = 1024) : // 1024 default buffer size
std::ostream(&this->buffer), buffer(logger_stream_callback, user_data, buffer_size) { }

private:
// internal stream buffer that sends output to the LoggerStreamUserCallback
CStreamBuffer buffer;
};

COStream* c_ostream = nullptr;
CStreamCallback* c_stream_callback = nullptr;
UserOStream* ostream = nullptr;

struct Solver {
Uno* solver;
Expand Down Expand Up @@ -759,15 +710,19 @@ bool uno_set_solver_callbacks(void* solver, NotifyAcceptableIterateUserCallback
}

bool uno_set_logger_stream_callback(LoggerStreamUserCallback logger_stream_callback, void* user_data) {
delete c_ostream;
c_ostream = new COStream(logger_stream_callback, user_data);
Logger::set_stream(*c_ostream);
delete c_stream_callback;
delete ostream;
c_stream_callback = new CStreamCallback(logger_stream_callback, user_data);
ostream = new UserOStream(c_stream_callback);
Logger::set_stream(*ostream);
return true;
}

bool uno_reset_logger_stream() {
delete c_ostream;
c_ostream = nullptr;
delete ostream;
delete c_stream_callback;
c_stream_callback = nullptr;
ostream = nullptr;
Logger::set_stream(std::cout);
return true;
}
Expand Down
Loading
Loading