Fortran
Uno's Fortran interface: how to use Uno from Fortran
Uno provides a Fortran interface built on top of the Uno C API using the module iso_c_binding.
To balance completeness and Fortran usability, the interface is split into two files:
The file uno_c.f90 defines low-level C bindings for all routines that do not involve C strings, together with all Uno constants, enumerations, and type definitions.
Functions whose signatures contain char * arguments or return values are intentionally excluded.
The file uno_fortran.f90 complements this layer by providing Fortran-friendly wrappers for all string-based C API routines.
These wrappers handle the conversion between Fortran character variables and null-terminated C strings, as well as the associated memory management.
Used together, uno_c.f90 and uno_fortran.f90 provide complete access to the Uno C API.
Both files must be included when using Uno from Fortran and linked against the Uno library (libuno).
An example program is available in example_uno.f90.
Basic usage
Start by including the Fortran interface:
Using the interface as a Fortran module (optional)
You can also wrap the interface in a module for cleaner use statements:
Then simply use it in your program:
This approach avoids include statements scattered in your code and allows standard module scoping.
Remark: We provide the Fortran interface as include files rather than a precompiled module to maximize portability.
Fortran .mod files are compiler-specific and can cause issues when cross-compiling or using different compilers.
By including the source directly, users avoid these problems and can build the interface consistently across platforms.
Building an optimization model
Building an optimization model is incremental and starts with the variables:
type(c_ptr) :: model
model = uno_create_model(problem_type, number_variables, &
variables_lower_bounds, variables_upper_bounds, &
base_indexing)
The following optional elements can be added or set to the model separately: * lower bounds for the variables:
-
upper bounds for the variables:
-
a lower bound for a given variable:
-
an upper bound for a given variable:
-
the objective function and its gradient:
logical(c_bool) :: success
success = uno_set_objective(model, optimization_sense, &
objective_function, objective_gradient)
- constraint functions and their Jacobian:
logical(c_bool) :: success
success = uno_set_constraints(model, number_constraints, constraint_functions, &
constraints_lower_bounds, constraints_upper_bounds, &
number_jacobian_nonzeros, jacobian_row_indices, &
jacobian_column_indices, jacobian)
-
lower bounds for the constraints:
-
upper bounds for the constraints:
-
a lower bound for a given constraint:
-
an upper bound for a given constraint:
-
the Lagrangian Hessian:
logical(c_bool) :: success
success = uno_set_lagrangian_hessian(model, number_hessian_nonzeros, &
hessian_triangular_part, &
hessian_row_indices, hessian_column_indices, &
lagrangian_hessian)
- a Jacobian operator:
- a Jacobian-transposed operator:
logical(c_bool) :: success
success = uno_set_jacobian_transposed_operator(model, jacobian_transposed_operator)
- a Hessian operator:
logical(c_bool) :: success
success = uno_set_lagrangian_hessian_operator(model, lagrangian_hessian_operator)
- the Lagrangian sign convention (default is
UNO_MULTIPLIER_NEGATIVE):
logical(c_bool) :: success
success = uno_set_lagrangian_sign_convention(model, lagrangian_sign_convention)
- user-defined data:
- an initial primal point:
- an initial dual point:
The memory for the model is allocated by Uno and must be freed with:
Creating an instance of the Uno solver
Create a solver instance with:
The memory for the solver is allocated by Uno must be freed with:
Passing options to the Uno solver
Options can be passed individually:
logical(c_bool) :: success
integer(uno_int) :: max_iterations = 1000
real(c_double) :: primal_tolerance = 1.0d-6
logical(c_bool) :: print_solution = .true.
character(len=*) :: hessian_model = "exact"
success = uno_set_solver_integer_option(solver, "max_iterations", max_iterations)
success = uno_set_solver_double_option(solver, "primal_tolerance", primal_tolerance)
success = uno_set_solver_bool_option(solver, "print_solution", print_solution)
success = uno_set_solver_string_option(solver, "hessian_model", hessian_model)
Loading options from a file:
logical(c_bool) :: success
character(len=*) :: option_file = "uno.opt"
success = uno_load_solver_option_file(solver, option_file)
Getting option values:
integer(uno_int) :: max_iterations
real(c_double) :: primal_tolerance
logical(c_bool) :: print_solution
max_iterations = uno_get_solver_integer_option(solver, "max_iterations")
primal_tolerance = uno_get_solver_double_option(solver, "primal_tolerance")
print_solution = uno_get_solver_bool_option(solver, "print_solution")
Setting a preset:
Solving the model
Solve the optimization problem with:
Inspecting the result
The following routines allow you to inspect the solution:
- optimization status:
- solution status:
- objective value:
- primal solution:
real(c_double), dimension(number_variables) :: primal_solution
call uno_get_primal_solution(solver, primal_solution)
- dual solutions:
real(c_double), dimension(number_variables) :: lower_bound_dual_solution, upper_bound_dual_solution
real(c_double), dimension(number_constraints) :: constraint_dual_solution
call uno_get_constraint_dual_solution(solver, constraint_dual_solution)
call uno_get_lower_bound_dual_solution(solver, lower_bound_dual_solution)
call uno_get_upper_bound_dual_solution(solver, upper_bound_dual_solution)
- optimality measures:
real(c_double) :: primal_feasibility, stationarity, complementarity
primal_feasibility = uno_get_solution_primal_feasibility(solver)
stationarity = uno_get_solution_stationarity(solver)
complementarity = uno_get_solution_complementarity(solver)
- solver statistics: