Flat White Vitriol - An incremental solver protocol for combinatorial solving using shared objects

feat: add first draft of the protocol

dekker.one 3f173e76

+21
.gitignore
··· 1 + # Generated by Cargo 2 + # will have compiled files and executables 3 + debug/ 4 + target/ 5 + 6 + # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 + # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 + Cargo.lock 9 + 10 + # These are backup files generated by rustfmt 11 + **/*.rs.bk 12 + 13 + # MSVC Windows builds of rustc generate these, which store debugging information 14 + *.pdb 15 + 16 + # RustRover 17 + # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 + # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 + # and can be added to the global gitignore or merged into this file. For a more nuclear 20 + # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 + #.idea/
+59
Cargo.toml
··· 1 + [workspace] 2 + 3 + members = ["crates/ipdos_protocol"] 4 + 5 + resolver = "2" 6 + 7 + [workspace.package] 8 + repository = "https://tangled.sh/@dekker.one/ipdos" 9 + version = "0.1.0" 10 + authors = ["Jip J. Dekker <jip@dekker.one>"] 11 + edition = "2024" 12 + license = "" 13 + 14 + [workspace.lints.rust] 15 + missing_debug_implementations = "warn" 16 + missing_docs = "warn" 17 + non_ascii_idents = "deny" 18 + trivial_casts = "warn" 19 + trivial_numeric_casts = "warn" 20 + unit_bindings = "warn" 21 + # unnameable_types = "warn" 22 + unreachable_pub = "warn" 23 + unused_crate_dependencies = "deny" 24 + unused_import_braces = "warn" 25 + unused_lifetimes = "warn" 26 + unused_macro_rules = "warn" 27 + unused_qualifications = "warn" 28 + unused_results = "warn" 29 + variant_size_differences = "warn" 30 + 31 + [workspace.lints.clippy] 32 + allow_attributes_without_reason = "deny" 33 + # cargo_common_metadata = "warn" 34 + clone_on_ref_ptr = "warn" 35 + default_union_representation = "deny" 36 + missing_docs_in_private_items = "warn" 37 + mixed_read_write_in_expression = "deny" 38 + # multiple_crate_versions = "warn" 39 + negative_feature_names = "deny" 40 + rc_buffer = "warn" 41 + rc_mutex = "warn" 42 + redundant_feature_names = "warn" 43 + redundant_type_annotations = "warn" 44 + rest_pat_in_fully_bound_structs = "warn" 45 + # same_name_method = "warn" 46 + semicolon_if_nothing_returned = "warn" 47 + str_to_string = "warn" 48 + string_add = "warn" 49 + string_add_assign = "warn" 50 + string_lit_chars_any = "warn" 51 + string_to_string = "warn" 52 + tests_outside_test_module = "warn" 53 + try_err = "warn" 54 + undocumented_unsafe_blocks = "warn" 55 + unnecessary_safety_comment = "warn" 56 + unseparated_literal_suffix = "warn" 57 + unnecessary_safety_doc = "warn" 58 + wildcard_dependencies = "warn" 59 + wrong_self_convention = "warn"
+44
README.md
··· 1 + # IPDOS: An Incremental Protocol for Decision and Optimization Solvers 2 + 3 + ## Introduction 4 + 5 + ## Common Functionality 6 + 7 + Although solvers are able to define their own functionality using the IPDOS protocol, we advocate for the following common functionality to be implemented by different solvers. 8 + This will allow for a more consistent user experience when interacting with different solvers. 9 + Even if a solver does not implement all of these functions, we recommend that the solvers do not use the same name for different functionality. 10 + 11 + ### Common Options 12 + 13 + A solver will expose its available options through `ipdos_option_list`. 14 + We recommend that solvers eagerly implement the following options: 15 + 16 + - `all_optimal` (`bool`, default: `false`): If set to `true`, the solver will after finding an optimal solution, continue to search for other solutions with the same objective value. 17 + - `fixed_search` (`bool`, default: `false`): If set to `true`, the solver will strictly follow the search order defined by the user. 18 + - `intermediate` (`bool`, default: `false`): If set to `true` for a problem with an objective strategy set, the solver will trigger its `on_solution` callback when it finds an intermediate solution. 19 + Afterward, the solver will continue the search until it finds the next solution, or it proves that no better solutions exist (returning the `IpdosComplete` status). 20 + - `threads` (`int`, default: `1`): For multithreaded solvers, this option will set the number of threads to use. 21 + - `time_limit` (`int`, default: `-1`): If set to a positive integer, the solver will abandon the search after the specified number of milliseconds. 22 + - `random_seed` (`opt int`, default: `<>`): If set to a positive integer, the solver will use the given value as the seed for its random number generator. 23 + Solvers are encouraged to use a variable seed for the default value. 24 + - `verbose` (`bool`, default: `false`): If set to `true`, the solver will print additional information about its process to `stderr`. 25 + 26 + ### Common Constraints 27 + 28 + A solver will expose the constraints that can be used in a `IpdosModel` through `ipdos_constraint_list`. 29 + We encourage solvers to support the constraints using the names and definitions from the [FlatZinc Builtins](https://docs.minizinc.dev/en/stable/lib-flatzinc.html). 30 + Other MiniZinc (global) constraints are also encouraged to be implemented, using the `fzn_` prefix. 31 + 32 + ### Common Objective Strategies 33 + 34 + A solver will expose the objective strategies that can be used in a `IpdosModel` through `ipdos_objective_list`. 35 + We encourage solvers to support the following objective strategies if possible: 36 + 37 + - `lex_maximize_int` (`list of var int`) | `lex_maximize_float` (`list of var float`): The solver will maximize the list of decision variables in lexicographical order, i.e., the first variable is maximized first, then the second variable, etc. 38 + - `lex_minimize_int` (`list of var int`) | `lex_minimize_float` (`list of var float`): The solver will minimize the list of decision variables in lexicographical order, i.e., the first variable is minimized first, then the second variable, etc. 39 + - `maximize_int` (`var int`) | `maximize_float` (`var float`): The solver will maximize the given decision variable. 40 + - `minimize_int` (`var int`) | `minimize_float` (`var float`): The solver will minimize the given decision variable. 41 + - `pareto_maximize_int` (`list of var int`) | `pareto_maximize_float` (`list of 42 + var float`): The solver will output solutions where at least one decision variable is assigned a higher value than it was assigned in all previous solutions. 43 + 44 + Note that if no objective is set, then the solver is expected to find any valid assignment of the decision variables that satisfies the constraints added to the solver.
+1
codebook.toml
··· 1 + words = ["ipdos"]
+1
crates/ipdos_protocol/.gitignore
··· 1 + /target
+12
crates/ipdos_protocol/Cargo.toml
··· 1 + [package] 2 + name = "ipdos_protocol" 3 + repository.workspace = true 4 + version.workspace = true 5 + authors.workspace = true 6 + edition.workspace = true 7 + license.workspace = true 8 + 9 + [dependencies] 10 + 11 + [lints] 12 + workspace = true
+5
crates/ipdos_protocol/cbindgen.toml
··· 1 + language = "C" 2 + 3 + # Wrapping header contents 4 + include_guard = "ipdos_protocol_h" 5 + documentation_style = "c99"
+372
crates/ipdos_protocol/ipdos_protocol.h
··· 1 + #ifndef ipdos_protocol_h 2 + #define ipdos_protocol_h 3 + 4 + #include <stdarg.h> 5 + #include <stdbool.h> 6 + #include <stdint.h> 7 + #include <stdlib.h> 8 + 9 + // The status returned by `ipdos_solver_run`, indicating whether the solver 10 + // completed its search. 11 + typedef enum IpdosStatus { 12 + // The solver explored the full search space and yielded all relevant 13 + // solutions. 14 + IpdosComplete, 15 + // The solver did not explore the full search space due to a timeout or 16 + // other termination condition. Additional (better) solutions might be 17 + // possible. 18 + IpdosIncomplete, 19 + // An error occurred during the solver's execution. 20 + // 21 + // `ipdos_solver_read_error` can be used to retrieve the error message. 22 + IpdosError, 23 + } IpdosStatus; 24 + 25 + // Representation of the base type of a value. 26 + typedef enum IpdosTypeBase { 27 + // Boolean type 28 + IpdosTypeBaseBool, 29 + // Integer numeric type 30 + IpdosTypeBaseInt, 31 + // Floating point numeric type 32 + IpdosTypeBaseFloat, 33 + // Character string type 34 + IpdosTypeBaseString, 35 + } IpdosTypeBase; 36 + 37 + // Enumerated type used to mark the kind of [`IpdosValue`]. This is used to 38 + // determine which field in the [`IpdosValueContent`] union to access. 39 + typedef enum IpdosValueKind { 40 + // No value is stored. 41 + IpdosValueAbsent, 42 + // The value is stored in `decision_index`. 43 + IpdosValueDecision, 44 + // The value is stored in `boolean_value`. 45 + IpdosValueBoolean, 46 + // The value is stored in `integer_value`. 47 + IpdosValueInteger, 48 + // The value is stored in `float_value`. 49 + IpdosValueFloat, 50 + // The value is stored in `string_value`. 51 + IpdosValueString, 52 + // The value is stored in `list_value`. 53 + IpdosValueList, 54 + } IpdosValueKind; 55 + 56 + // Representation of a type to signal and check whether an argument takes the 57 + // correct type. 58 + typedef struct IpdosType { 59 + // Whether the type is a list of values. 60 + bool list_of; 61 + // Whether the argument can be or contain decision variables (represented as 62 + // decision indexes). 63 + bool decision; 64 + // Whether expected type is an set of values of the base type. 65 + bool set_of; 66 + // The expected base type of the argument. 67 + enum IpdosTypeBase base; 68 + } IpdosType; 69 + 70 + // Representation of a type of constraint, discerned by its identifier and the 71 + // types of its arguments. 72 + typedef struct IpdosConstraintType { 73 + // The identifier of the constraint type. 74 + const char *ident; 75 + // The number of expected arguments for the constraint type. 76 + uintptr_t arg_len; 77 + // The types of the expected arguments for the constraint type. 78 + const struct IpdosType *arg_types; 79 + } IpdosConstraintType; 80 + 81 + // A list of [`IpdosConstraintType`]s. 82 + // 83 + // This type is for example used to return from [`ipdos_constraint_list`]. 84 + typedef struct IpdosConstraintList { 85 + // The number of elements in the `constraints` array. 86 + uintptr_t len; 87 + // An array of constraint types. 88 + const struct IpdosConstraintType *constraints; 89 + } IpdosConstraintList; 90 + 91 + // A list of [`IpdosType`]s. 92 + // 93 + // This type is for example used to return from [`ipdos_decision_list`]. 94 + typedef struct IpdosTypeList { 95 + // The number of elements in the `constraints` array. 96 + uintptr_t len; 97 + // An array of constraint types. 98 + const struct IpdosType *types; 99 + } IpdosTypeList; 100 + 101 + // Representation of a type of objective strategies, discerned by its 102 + // identifier and the type of its argument. 103 + typedef struct IpdosObjective { 104 + // The identifier of the objective type. 105 + const char *ident; 106 + // The type of the expected argument for the constraint type. 107 + struct IpdosType arg_type; 108 + } IpdosObjective; 109 + 110 + // A list of [`IpdosObjective`]s. 111 + // 112 + // This type is, for example, used to return from [`ipdos_objective_list`]. 113 + typedef struct IpdosObjectiveList { 114 + // The number of elements in the `options` array. 115 + uintptr_t len; 116 + // An array of option definitions. 117 + const struct IpdosObjective *options; 118 + } IpdosObjectiveList; 119 + 120 + // The storage of the value content of a [`IpdosValue`]. 121 + typedef union IpdosValueContent { 122 + // Decision index storage 123 + uintptr_t decision_index; 124 + // Boolean value storage 125 + bool bool_value; 126 + // Integer value storage 127 + int64_t integer_value; 128 + // Float value storage 129 + double float_value; 130 + // Set of integers value storage 131 + // 132 + // The set of integers is represented using a range list. The array of 133 + // `i64` values should be interpreted as a list of inclusive ranges, 134 + // where each range is represented by two values. 135 + // 136 + // Note that the number of `i64` values is stored in the `len` field. Since 137 + // each range is represented by two values, it can be assumed that `len mod 138 + // 2 == 0`. 139 + const int64_t *set_of_int_value; 140 + // Set of floating point value storage 141 + // 142 + // The set of floating point values is represented using a range list. The 143 + // array of `f64` values should be interpreted as a list of inclusive 144 + // ranges, where each range is represented by two values. 145 + // 146 + // Note that the number of `f64` values is stored in the `len` field. Since 147 + // each range is represented by two values, it can be assumed that `len mod 148 + // 2 == 0`. 149 + const int64_t *set_of_float_value; 150 + // String value storage 151 + // 152 + // Note that the `string_value` field is intended to be interpreted as a 153 + // C-string. It should be \0 terminated, use UTF-8 encoding, and be valid 154 + // for the lifetime of the value. 155 + const char *string_value; 156 + // List value storage 157 + // 158 + // Note that the the number of elements in the list is stored in the `len` 159 + // field. 160 + const struct IpdosValue *list_value; 161 + } IpdosValueContent; 162 + 163 + // The value representation used for values assigned to decision variables in 164 + // solution, as constraint/annotation arguments, and option parameters. 165 + typedef struct IpdosValue { 166 + // The kind of the value 167 + // 168 + // This field is used to determine what field in the `value` union is 169 + // allowed to be accessed. 170 + enum IpdosValueKind kind; 171 + // A field containing the size of the value. 172 + // 173 + // This field is used when the value is a list, set of float/int, and 174 + // string, to determine the number of elements in the C array type. 175 + uint32_t len; 176 + // The storage of the actual data of the value. 177 + union IpdosValueContent content; 178 + } IpdosValue; 179 + 180 + // The definition of an option that is available to be set for the solver. 181 + typedef struct IpdosOption { 182 + // The identifier used to set the option or get the current value of the option. 183 + const char *ident; 184 + // The type of value that is expected for this option. 185 + struct IpdosValue arg_ty; 186 + // The default value for this option. 187 + struct IpdosType arg_def; 188 + } IpdosOption; 189 + 190 + // A list of [`IpdosOption`]s. 191 + // 192 + // This type is, for example, used to return from [`ipdos_option_list`]. 193 + typedef struct IpdosOptionList { 194 + // The number of elements in the `options` array. 195 + uintptr_t len; 196 + // An array of option definitions. 197 + const struct IpdosOption *options; 198 + } IpdosOptionList; 199 + 200 + // The handle to a solver instance. 201 + // 202 + // This type is opaque to the user. Only pointers of this type are ever used. 203 + // 204 + // In implementation of the IPDOS interface, a pointer is generally cast to 205 + // this type, i.e. `(IpdosSolver*) my_solver`. A similar cast can be used to 206 + // cast the pointer back to the original type, e.g. `(MySolverType*) 207 + // ipdos_solver`. 208 + typedef void IpdosSolver; 209 + 210 + // The handle to a the data of a model instance. 211 + // 212 + // This type is opaque to the user. Only pointers of this type are ever used. 213 + // 214 + // In implementation of the IPDOS interface, a pointer is generally cast to 215 + // this type, i.e. `(IpdosModelData*) my_model`. A similar cast can be used to 216 + // cast the pointer back to the original type, e.g. `(MyModel*) ipdos_model`. 217 + typedef void IpdosModelData; 218 + 219 + // An interface to a model instance used to communicate with the solver. 220 + // 221 + // The solver can use the included function callbacks to interact with the 222 + // model. 223 + typedef struct IpdosModel { 224 + // The handle to the data of the model instance. 225 + // 226 + // This handle is passed to all the different function callbacks to 227 + // interact with the model. 228 + const IpdosModelData *data; 229 + // Returns the current number of model layers currently contained in the 230 + // model. 231 + // 232 + // Note that it is required that the solver calls [`ipdos_solver_push`] and 233 + // [`ipdos_solver_pop`] whenever a layer is added or removed. 234 + uintptr_t (*layers)(const IpdosModelData *model); 235 + // Retrieve the number of constraints currently contained in the model. 236 + uintptr_t (*constraint_len)(const IpdosModelData *model); 237 + // Retrieve the constraint index of the first new constraint in the layer. 238 + // 239 + // This can be equivalent to `constraint_len` if the layer does not add any 240 + // new constraints. 241 + uintptr_t (*constraint_layer_start)(const IpdosModelData *model); 242 + // Retrieve the identifier of a constraint. 243 + // 244 + // The returned pointer can be assumed to have the the same lifetime as the 245 + // constraint reference and must be valid UTF8. 246 + const char *(*constraint_ident)(const IpdosModelData *model, uintptr_t constraint); 247 + // Retrieve the number of arguments of a constraint. 248 + uintptr_t (*constraint_argument_len)(const IpdosModelData *model, uintptr_t constraint); 249 + // Retrieve the value of the constraint's argument at the given index. 250 + // 251 + // The `index` argument must be less than the value returned by 252 + // `constraint_argument_len` for the given constraint. 253 + struct IpdosValue (*constraint_argument)(const IpdosModelData *model, 254 + uintptr_t constraint, 255 + uintptr_t index); 256 + // Retrieve the number of decisions currently contained in the model. 257 + uintptr_t (*decision_len)(const IpdosModelData *model); 258 + // Retrieve the decision index of the first new decision in the layer. 259 + // 260 + // This can be equivalent to `decision_len` if the layer does not add any 261 + // new decisions. 262 + uintptr_t (*decision_layer_start)(const IpdosModelData *model); 263 + // Retrieve the domain of the given decision. 264 + // 265 + // Note that the the value might have [`IpdosValueKind::IpdosValueAbsent`] if 266 + // the decision variable does not have an explicit domain. 267 + struct IpdosValue (*decision_domain)(const IpdosModelData *model, uintptr_t decision); 268 + // Retrieve the name of a decision variable, if it exists. 269 + // 270 + // Note that names are only available for debugging purposes. Decisions are 271 + // identified using their index in the model. If the decision variable does 272 + // not have a name, this function returns a null pointer. 273 + const char *(*decision_name)(const IpdosModelData *model, uintptr_t decision); 274 + // Check whether the decision variable is functionally defined by a 275 + // constraint. 276 + bool (*decision_defined)(const IpdosModelData *model, uintptr_t decision); 277 + // Request the identifier of the type of objective strategy to be used when 278 + // solving the model. 279 + // 280 + // Note that the function can return a null pointer if the model does not 281 + // have an objective strategy. 282 + const char *(*objective_ident)(const IpdosModelData *model); 283 + // Retrieve the argument of the objective strategy. 284 + struct IpdosValue (*objective_arg)(const IpdosModelData *model); 285 + } IpdosModel; 286 + 287 + // The handle to a the data of a solution instance. 288 + // 289 + // This type is opaque to the user. Only pointers of this type are ever used. 290 + // 291 + // In implementation of the IPDOS interface, a pointer is generally cast to 292 + // this type, i.e. `(IpdosSolutionData*) my_solution`. A similar cast can be 293 + // used to cast the pointer back to the original type, e.g. `(MySolution*) 294 + // ipdos_solution`. 295 + typedef void IpdosSolutionData; 296 + 297 + // Structure used to represent a solution emitted by the solver. 298 + // 299 + // The caller can use the `get_value` function to retrieve the value of the 300 + // used decision variables. 301 + typedef struct IpdosSolution { 302 + // The data pointer to be the first argument of `get_value`. 303 + const IpdosSolutionData *data; 304 + // Function callback to retrieve the value assigned to a decision variable 305 + // in the solution. 306 + struct IpdosValue (*get_value)(const IpdosSolutionData *data, uintptr_t decision_index); 307 + } IpdosSolution; 308 + 309 + // Returns the list of available constraint that can be added to the solver. 310 + struct IpdosConstraintList ipdos_constraint_list(void); 311 + 312 + // Returns the list of types for which decision variable can be created by the 313 + // solver. 314 + struct IpdosTypeList ipdos_decision_list(void); 315 + 316 + // Returns the list of available objective that can be achieved by the solver. 317 + struct IpdosObjectiveList ipdos_objective_list(void); 318 + 319 + // Returns the list of available options that can be set of the solver. 320 + struct IpdosOptionList ipdos_option_list(void); 321 + 322 + // Create a new solver instance 323 + IpdosSolver *ipdos_solver_create(void); 324 + 325 + // Free a solver instance, releasing all resources associated with it. 326 + // 327 + // The pointer to the solver instance will be invalid after this function has 328 + // been called. 329 + void ipdos_solver_free(IpdosSolver *solver); 330 + 331 + // Get the current value of an option for the solver. 332 + // 333 + // Note that this is only valid to be called with options named by 334 + // `ipdos_option_list`. 335 + struct IpdosValue ipdos_solver_option_get(const IpdosSolver *solver, const char *ident); 336 + 337 + // Set the current value of an option for the solver. 338 + // 339 + // Note that this is only valid to be called with options named by 340 + // `ipdos_option_list`, and the value must be of the correct type. 341 + bool ipdos_solver_option_set(IpdosSolver *solver, const char *ident, struct IpdosValue value); 342 + 343 + // Remove a model layer from the solver. 344 + // 345 + // It is required that `model.layers` is less than the number of layers in the 346 + // last call to `ipdos_solver_push` or `ipdos_solver_pop`. It is allowed to 347 + // remove multiple model layers at the same time. 348 + void ipdos_solver_pop(IpdosSolver *solver, struct IpdosModel model); 349 + 350 + // Add a new model layer to the solver. 351 + // 352 + // It is required that `model.layers` is exactly one larger than the previous 353 + // call to `ipdos_solver_push` or `ipdos_solver_pop`. It is NOT allowed to add 354 + // multiple model layers at the same time. 355 + bool ipdos_solver_push(IpdosSolver *solver, struct IpdosModel model); 356 + 357 + // Read an error message from the solver. 358 + // 359 + // This function is expected to be called after solver interactions signal an 360 + // error has occurred. For example, if [`ipdos_solver_run`] returns 361 + // [`IpdosError`] or [`ipdos_solver_push`] returns `false`. 362 + void ipdos_solver_read_error(IpdosSolver *solver, 363 + void *context, 364 + void (*read_error)(void *context, const char *error)); 365 + 366 + // Run the solver with the given model 367 + enum IpdosStatus ipdos_solver_run(IpdosSolver *solver, 368 + void *context, 369 + void (*on_solution)(void *context, 370 + const struct IpdosSolution *solution)); 371 + 372 + #endif /* ipdos_protocol_h */
+482
crates/ipdos_protocol/src/lib.rs
··· 1 + //! IPDOS Protocol 2 + //! 3 + //! This crate defines the IPDOS protocol, which allows the incremental usage of 4 + //! solvers that can solve decision and optimization problems. The goal of the 5 + //! interface is to easily use different solvers in a unified way. 6 + #![allow(missing_debug_implementations)] 7 + 8 + use std::{ffi, marker::PhantomData}; 9 + 10 + #[repr(C)] 11 + /// Representation of a type of constraint, discerned by its identifier and the 12 + /// types of its arguments. 13 + pub struct IpdosConstraintType<'a> { 14 + /// The identifier of the constraint type. 15 + pub ident: *const ffi::c_char, 16 + /// The number of expected arguments for the constraint type. 17 + pub arg_len: usize, 18 + /// The types of the expected arguments for the constraint type. 19 + pub arg_types: *const IpdosType, 20 + /// The lifetime for which the `ident` and `arg_types` attributes are 21 + /// allocated. 22 + pub lifetime: PhantomData<&'a ()>, 23 + } 24 + 25 + #[repr(C)] 26 + /// A list of [`IpdosConstraintType`]s. 27 + /// 28 + /// This type is for example used to return from [`ipdos_constraint_list`]. 29 + pub struct IpdosConstraintList<'a> { 30 + /// The number of elements in the `constraints` array. 31 + pub len: usize, 32 + /// An array of constraint types. 33 + pub constraints: *const IpdosConstraintType<'a>, 34 + /// The lifetime for which the `constraints` attribute is allocated. 35 + pub lifetime: PhantomData<&'a ()>, 36 + } 37 + 38 + #[repr(C)] 39 + /// An interface to a model instance used to communicate with the solver. 40 + /// 41 + /// The solver can use the included function callbacks to interact with the 42 + /// model. 43 + pub struct IpdosModel<'a> { 44 + /// The handle to the data of the model instance. 45 + /// 46 + /// This handle is passed to all the different function callbacks to 47 + /// interact with the model. 48 + data: &'a IpdosModelData, 49 + /// Returns the current number of model layers currently contained in the 50 + /// model. 51 + /// 52 + /// Note that it is required that the solver calls [`ipdos_solver_push`] and 53 + /// [`ipdos_solver_pop`] whenever a layer is added or removed. 54 + layers: extern "C" fn(model: &IpdosModelData) -> usize, 55 + 56 + /// Retrieve the number of constraints currently contained in the model. 57 + constraint_len: extern "C" fn(model: &IpdosModelData) -> usize, 58 + /// Retrieve the constraint index of the first new constraint in the layer. 59 + /// 60 + /// This can be equivalent to `constraint_len` if the layer does not add any 61 + /// new constraints. 62 + constraint_layer_start: extern "C" fn(model: &IpdosModelData) -> usize, 63 + /// Retrieve the identifier of a constraint. 64 + /// 65 + /// The returned pointer can be assumed to have the the same lifetime as the 66 + /// constraint reference and must be valid UTF8. 67 + constraint_ident: 68 + extern "C" fn(model: &IpdosModelData, constraint: usize) -> *const ffi::c_char, 69 + /// Retrieve the number of arguments of a constraint. 70 + constraint_argument_len: extern "C" fn(model: &IpdosModelData, constraint: usize) -> usize, 71 + /// Retrieve the value of the constraint's argument at the given index. 72 + /// 73 + /// The `index` argument must be less than the value returned by 74 + /// `constraint_argument_len` for the given constraint. 75 + constraint_argument: 76 + extern "C" fn(model: &IpdosModelData, constraint: usize, index: usize) -> IpdosValue<'_>, 77 + 78 + /// Retrieve the number of decisions currently contained in the model. 79 + decision_len: extern "C" fn(model: &IpdosModelData) -> usize, 80 + /// Retrieve the decision index of the first new decision in the layer. 81 + /// 82 + /// This can be equivalent to `decision_len` if the layer does not add any 83 + /// new decisions. 84 + decision_layer_start: extern "C" fn(model: &IpdosModelData) -> usize, 85 + /// Retrieve the domain of the given decision. 86 + /// 87 + /// Note that the the value might have [`IpdosValueKind::IpdosValueAbsent`] if 88 + /// the decision variable does not have an explicit domain. 89 + decision_domain: extern "C" fn(model: &IpdosModelData, decision: usize) -> IpdosValue<'_>, 90 + /// Retrieve the name of a decision variable, if it exists. 91 + /// 92 + /// Note that names are only available for debugging purposes. Decisions are 93 + /// identified using their index in the model. If the decision variable does 94 + /// not have a name, this function returns a null pointer. 95 + decision_name: extern "C" fn(model: &IpdosModelData, decision: usize) -> *const ffi::c_char, 96 + /// Check whether the decision variable is functionally defined by a 97 + /// constraint. 98 + decision_defined: extern "C" fn(model: &IpdosModelData, decision: usize) -> bool, 99 + 100 + /// Request the identifier of the type of objective strategy to be used when 101 + /// solving the model. 102 + /// 103 + /// Note that the function can return a null pointer if the model does not 104 + /// have an objective strategy. 105 + objective_ident: extern "C" fn(model: &IpdosModelData) -> *const ffi::c_char, 106 + /// Retrieve the argument of the objective strategy. 107 + objective_arg: extern "C" fn(model: &IpdosModelData) -> IpdosValue<'_>, 108 + } 109 + 110 + #[repr(transparent)] 111 + /// The handle to a the data of a model instance. 112 + /// 113 + /// This type is opaque to the user. Only pointers of this type are ever used. 114 + /// 115 + /// In implementation of the IPDOS interface, a pointer is generally cast to 116 + /// this type, i.e. `(IpdosModelData*) my_model`. A similar cast can be used to 117 + /// cast the pointer back to the original type, e.g. `(MyModel*) ipdos_model`. 118 + pub struct IpdosModelData(ffi::c_void); 119 + 120 + #[repr(C)] 121 + /// Representation of a type of objective strategies, discerned by its 122 + /// identifier and the type of its argument. 123 + pub struct IpdosObjective<'a> { 124 + /// The identifier of the objective type. 125 + pub ident: *const ffi::c_char, 126 + /// The type of the expected argument for the constraint type. 127 + pub arg_type: IpdosType, 128 + /// The lifetime for which the `ident` attribute is allocated. 129 + pub lifetime: PhantomData<&'a ()>, 130 + } 131 + 132 + #[repr(C)] 133 + /// A list of [`IpdosObjective`]s. 134 + /// 135 + /// This type is, for example, used to return from [`ipdos_objective_list`]. 136 + pub struct IpdosObjectiveList<'a> { 137 + /// The number of elements in the `options` array. 138 + pub len: usize, 139 + /// An array of option definitions. 140 + pub options: *const IpdosObjective<'a>, 141 + /// The lifetime of the `options` attribute. 142 + pub lifetime: PhantomData<&'a ()>, 143 + } 144 + 145 + #[repr(C)] 146 + /// The definition of an option that is available to be set for the solver. 147 + pub struct IpdosOption<'a> { 148 + /// The identifier used to set the option or get the current value of the option. 149 + pub ident: *const ffi::c_char, 150 + /// The type of value that is expected for this option. 151 + pub arg_ty: IpdosValue<'a>, 152 + /// The default value for this option. 153 + pub arg_def: IpdosType, 154 + /// The lifetime of the `ident` attribute. 155 + pub lifetime: PhantomData<&'a ()>, 156 + } 157 + 158 + #[repr(C)] 159 + /// A list of [`IpdosOption`]s. 160 + /// 161 + /// This type is, for example, used to return from [`ipdos_option_list`]. 162 + pub struct IpdosOptionList<'a> { 163 + /// The number of elements in the `options` array. 164 + pub len: usize, 165 + /// An array of option definitions. 166 + pub options: *const IpdosOption<'a>, 167 + /// The lifetime of the `options` attribute. 168 + pub lifetime: PhantomData<&'a ()>, 169 + } 170 + 171 + #[repr(C)] 172 + /// Structure used to represent a solution emitted by the solver. 173 + /// 174 + /// The caller can use the `get_value` function to retrieve the value of the 175 + /// used decision variables. 176 + pub struct IpdosSolution<'a> { 177 + /// The data pointer to be the first argument of `get_value`. 178 + data: &'a IpdosSolutionData, 179 + /// Function callback to retrieve the value assigned to a decision variable 180 + /// in the solution. 181 + get_value: extern "C" fn(data: &IpdosSolutionData, decision_index: usize) -> IpdosValue<'_>, 182 + } 183 + 184 + #[repr(transparent)] 185 + /// The handle to a the data of a solution instance. 186 + /// 187 + /// This type is opaque to the user. Only pointers of this type are ever used. 188 + /// 189 + /// In implementation of the IPDOS interface, a pointer is generally cast to 190 + /// this type, i.e. `(IpdosSolutionData*) my_solution`. A similar cast can be 191 + /// used to cast the pointer back to the original type, e.g. `(MySolution*) 192 + /// ipdos_solution`. 193 + pub struct IpdosSolutionData(ffi::c_void); 194 + 195 + #[repr(transparent)] 196 + /// The handle to a solver instance. 197 + /// 198 + /// This type is opaque to the user. Only pointers of this type are ever used. 199 + /// 200 + /// In implementation of the IPDOS interface, a pointer is generally cast to 201 + /// this type, i.e. `(IpdosSolver*) my_solver`. A similar cast can be used to 202 + /// cast the pointer back to the original type, e.g. `(MySolverType*) 203 + /// ipdos_solver`. 204 + pub struct IpdosSolver(ffi::c_void); 205 + 206 + #[repr(C)] 207 + /// The status returned by `ipdos_solver_run`, indicating whether the solver 208 + /// completed its search. 209 + pub enum IpdosStatus { 210 + /// The solver explored the full search space and yielded all relevant 211 + /// solutions. 212 + IpdosComplete, 213 + /// The solver did not explore the full search space due to a timeout or 214 + /// other termination condition. Additional (better) solutions might be 215 + /// possible. 216 + IpdosIncomplete, 217 + /// An error occurred during the solver's execution. 218 + /// 219 + /// `ipdos_solver_read_error` can be used to retrieve the error message. 220 + IpdosError, 221 + } 222 + 223 + #[repr(C)] 224 + /// Representation of a type to signal and check whether an argument takes the 225 + /// correct type. 226 + pub struct IpdosType { 227 + /// Whether the type is a list of values. 228 + pub list_of: bool, 229 + /// Whether the argument can be or contain decision variables (represented as 230 + /// decision indexes). 231 + pub decision: bool, 232 + /// Whether expected type is an set of values of the base type. 233 + pub set_of: bool, 234 + /// The expected base type of the argument. 235 + pub base: IpdosTypeBase, 236 + } 237 + 238 + #[repr(C)] 239 + /// Representation of the base type of a value. 240 + pub enum IpdosTypeBase { 241 + /// Boolean type 242 + IpdosTypeBaseBool, 243 + /// Integer numeric type 244 + IpdosTypeBaseInt, 245 + /// Floating point numeric type 246 + IpdosTypeBaseFloat, 247 + /// Character string type 248 + IpdosTypeBaseString, 249 + } 250 + 251 + #[repr(C)] 252 + /// A list of [`IpdosType`]s. 253 + /// 254 + /// This type is for example used to return from [`ipdos_decision_list`]. 255 + pub struct IpdosTypeList<'a> { 256 + /// The number of elements in the `constraints` array. 257 + pub len: usize, 258 + /// An array of constraint types. 259 + pub types: *const IpdosType, 260 + /// The lifetime for which the `types` attribute is allocated. 261 + pub lifetime: PhantomData<&'a ()>, 262 + } 263 + 264 + #[repr(C)] 265 + /// The value representation used for values assigned to decision variables in 266 + /// solution, as constraint/annotation arguments, and option parameters. 267 + pub struct IpdosValue<'a> { 268 + /// The kind of the value 269 + /// 270 + /// This field is used to determine what field in the `value` union is 271 + /// allowed to be accessed. 272 + pub kind: IpdosValueKind, 273 + /// A field containing the size of the value. 274 + /// 275 + /// This field is used when the value is a list, set of float/int, and 276 + /// string, to determine the number of elements in the C array type. 277 + pub len: u32, 278 + /// The storage of the actual data of the value. 279 + pub content: IpdosValueContent<'a>, 280 + /// Lifetime of the value (only relevant in Rust). 281 + pub lifetime: PhantomData<&'a ()>, 282 + } 283 + 284 + #[repr(C)] 285 + /// The storage of the value content of a [`IpdosValue`]. 286 + pub union IpdosValueContent<'a> { 287 + /// Decision index storage 288 + pub decision_index: usize, 289 + /// Boolean value storage 290 + pub bool_value: bool, 291 + /// Integer value storage 292 + pub integer_value: i64, 293 + /// Float value storage 294 + pub float_value: f64, 295 + /// Set of integers value storage 296 + /// 297 + /// The set of integers is represented using a range list. The array of 298 + /// `i64` values should be interpreted as a list of inclusive ranges, 299 + /// where each range is represented by two values. 300 + /// 301 + /// Note that the number of `i64` values is stored in the `len` field. Since 302 + /// each range is represented by two values, it can be assumed that `len mod 303 + /// 2 == 0`. 304 + pub set_of_int_value: *const i64, 305 + /// Set of floating point value storage 306 + /// 307 + /// The set of floating point values is represented using a range list. The 308 + /// array of `f64` values should be interpreted as a list of inclusive 309 + /// ranges, where each range is represented by two values. 310 + /// 311 + /// Note that the number of `f64` values is stored in the `len` field. Since 312 + /// each range is represented by two values, it can be assumed that `len mod 313 + /// 2 == 0`. 314 + pub set_of_float_value: *const i64, 315 + /// String value storage 316 + /// 317 + /// Note that the `string_value` field is intended to be interpreted as a 318 + /// C-string. It should be \0 terminated, use UTF-8 encoding, and be valid 319 + /// for the lifetime of the value. 320 + pub string_value: *const ffi::c_char, 321 + /// List value storage 322 + /// 323 + /// Note that the the number of elements in the list is stored in the `len` 324 + /// field. 325 + pub list_value: *const IpdosValue<'a>, 326 + } 327 + 328 + #[repr(C)] 329 + /// Enumerated type used to mark the kind of [`IpdosValue`]. This is used to 330 + /// determine which field in the [`IpdosValueContent`] union to access. 331 + pub enum IpdosValueKind { 332 + /// No value is stored. 333 + IpdosValueAbsent, 334 + /// The value is stored in `decision_index`. 335 + IpdosValueDecision, 336 + /// The value is stored in `boolean_value`. 337 + IpdosValueBoolean, 338 + /// The value is stored in `integer_value`. 339 + IpdosValueInteger, 340 + /// The value is stored in `float_value`. 341 + IpdosValueFloat, 342 + /// The value is stored in `string_value`. 343 + IpdosValueString, 344 + /// The value is stored in `list_value`. 345 + IpdosValueList, 346 + } 347 + 348 + #[unsafe(no_mangle)] 349 + /// Returns the list of available constraint that can be added to the solver. 350 + pub extern "C" fn ipdos_constraint_list() -> IpdosConstraintList<'static> { 351 + unimplemented!() 352 + } 353 + 354 + #[unsafe(no_mangle)] 355 + /// Returns the list of types for which decision variable can be created by the 356 + /// solver. 357 + pub extern "C" fn ipdos_decision_list() -> IpdosTypeList<'static> { 358 + unimplemented!() 359 + } 360 + 361 + #[unsafe(no_mangle)] 362 + /// Returns the list of available objective that can be achieved by the solver. 363 + pub extern "C" fn ipdos_objective_list() -> IpdosObjectiveList<'static> { 364 + unimplemented!() 365 + } 366 + 367 + #[unsafe(no_mangle)] 368 + /// Returns the list of available options that can be set of the solver. 369 + pub extern "C" fn ipdos_option_list() -> IpdosOptionList<'static> { 370 + unimplemented!() 371 + } 372 + 373 + #[unsafe(no_mangle)] 374 + /// Create a new solver instance 375 + pub extern "C" fn ipdos_solver_create() -> Box<IpdosSolver> { 376 + unimplemented!() 377 + } 378 + 379 + /// Free a solver instance, releasing all resources associated with it. 380 + /// 381 + /// The pointer to the solver instance will be invalid after this function has 382 + /// been called. 383 + #[unsafe(no_mangle)] 384 + pub extern "C" fn ipdos_solver_free(solver: Box<IpdosSolver>) { 385 + let _ = solver; 386 + unimplemented!() 387 + } 388 + 389 + #[unsafe(no_mangle)] 390 + /// Get the current value of an option for the solver. 391 + /// 392 + /// Note that this is only valid to be called with options named by 393 + /// `ipdos_option_list`. 394 + pub extern "C" fn ipdos_solver_option_get( 395 + solver: &IpdosSolver, 396 + ident: *const ffi::c_char, 397 + ) -> IpdosValue<'_> { 398 + let _ = solver; 399 + let _ = ident; 400 + unimplemented!() 401 + } 402 + 403 + #[unsafe(no_mangle)] 404 + /// Set the current value of an option for the solver. 405 + /// 406 + /// Note that this is only valid to be called with options named by 407 + /// `ipdos_option_list`, and the value must be of the correct type. 408 + pub extern "C" fn ipdos_solver_option_set( 409 + solver: &mut IpdosSolver, 410 + ident: *const ffi::c_char, 411 + value: IpdosValue<'_>, 412 + ) -> bool { 413 + let _ = solver; 414 + let _ = ident; 415 + let _ = value; 416 + unimplemented!() 417 + } 418 + 419 + #[unsafe(no_mangle)] 420 + /// Remove a model layer from the solver. 421 + /// 422 + /// It is required that `model.layers` is less than the number of layers in the 423 + /// last call to `ipdos_solver_push` or `ipdos_solver_pop`. It is allowed to 424 + /// remove multiple model layers at the same time. 425 + pub extern "C" fn ipdos_solver_pop(solver: &mut IpdosSolver, model: IpdosModel) { 426 + let _ = solver; 427 + let _ = model; 428 + unimplemented!() 429 + } 430 + 431 + #[unsafe(no_mangle)] 432 + /// Add a new model layer to the solver. 433 + /// 434 + /// It is required that `model.layers` is exactly one larger than the previous 435 + /// call to `ipdos_solver_push` or `ipdos_solver_pop`. It is NOT allowed to add 436 + /// multiple model layers at the same time. 437 + pub extern "C" fn ipdos_solver_push(solver: &mut IpdosSolver, model: IpdosModel) -> bool { 438 + let _ = solver; 439 + let _ = model; 440 + unimplemented!() 441 + } 442 + 443 + #[unsafe(no_mangle)] 444 + /// Read an error message from the solver. 445 + /// 446 + /// This function is expected to be called after solver interactions signal an 447 + /// error has occurred. For example, if [`ipdos_solver_run`] returns 448 + /// [`IpdosError`] or [`ipdos_solver_push`] returns `false`. 449 + pub extern "C" fn ipdos_solver_read_error( 450 + solver: &mut IpdosSolver, 451 + context: &mut ffi::c_void, 452 + read_error: extern "C" fn(context: &mut ffi::c_void, error: *const ffi::c_char), 453 + ) { 454 + let _ = solver; 455 + let _ = context; 456 + let _ = read_error; 457 + unimplemented!() 458 + } 459 + 460 + #[unsafe(no_mangle)] 461 + /// Run the solver with the given model 462 + pub extern "C" fn ipdos_solver_run( 463 + solver: &mut IpdosSolver, 464 + context: &mut ffi::c_void, 465 + on_solution: extern "C" fn(context: &mut ffi::c_void, solution: &IpdosSolution), 466 + ) -> IpdosStatus { 467 + let _ = solver; 468 + let _ = context; 469 + let _ = on_solution; 470 + unimplemented!() 471 + } 472 + 473 + #[cfg(test)] 474 + mod tests { 475 + use crate::{IpdosType, IpdosValue}; 476 + 477 + #[test] 478 + fn memory() { 479 + assert_eq!(size_of::<IpdosValue>(), size_of::<u128>()); 480 + assert_eq!(size_of::<IpdosType>(), size_of::<u64>()); 481 + } 482 + }
+4
rustfmt.toml
··· 1 + group_imports = "StdExternalCrate" 2 + hard_tabs = true 3 + imports_granularity = "Crate" 4 + wrap_comments = true