Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ edition = "2021"
exclude = ["data/test/*"]

[features]
default = ["datastore"]
bundled = ["scip-sys/bundled"]
from-source = ["scip-sys/from-source"]
datastore = ["anymap"]

[dependencies]
scip-sys = "0.1.21"
anymap = { version = "0.12.0", optional = true }

[dev-dependencies]
rayon = "1.5.1"
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ To add a custom plugin to a SCIP `Model` instance, you should implement its trai
`include_{PLUGIN_NAME}` method. For examples on implementing the specific plugin trait you can check the tests in the
corresponding files.

## Attaching custom data to SCIP instance
This is enabled with the help of the `anymap` crate. You can attach any data to the `Model` instance using the
`set_data` method, and retrieve it using the `get_data` and `get_data_mut` methods.
This is useful for communicating data between plugins, or storing other representations of the
variables/constraints in the model.

```rust


## Contributing

Thinking about contributing to _russcip_? First of all thank you! You can check our
Expand Down
18 changes: 18 additions & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,24 @@ impl<T> Model<T> {
pub fn eps(&self) -> f64 {
unsafe { ffi::SCIPepsilon(self.scip.raw) }
}

#[cfg(feature = "datastore")]
/// Set generic data attached to the model
pub fn set_data<D: 'static>(&mut self, data: D) {
self.scip.set_store(data).expect("Failed to set data");
}

#[cfg(feature = "datastore")]
/// Retrieves a reference to a generic data type attached to the model
pub fn get_data<D: 'static>(&self) -> Option<&D> {
self.scip.get_store::<D>().expect("Failed to get data")
}

#[cfg(feature = "datastore")]
/// Returns a mutable reference to generic data attached to the model
pub fn get_data_mut<D: 'static>(&mut self) -> Option<&mut D> {
self.scip.get_mut_store::<D>().expect("Failed to get data")
}
}

/// The default implementation for a `Model` instance in the `ProblemCreated` state.
Expand Down
179 changes: 177 additions & 2 deletions src/scip.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "datastore")]
use anymap::AnyMap;

use crate::branchrule::{BranchRule, BranchingCandidate};
use crate::node::Node;
use crate::pricer::{Pricer, PricerResultState};
Expand Down Expand Up @@ -760,7 +763,7 @@ impl ScipPtr {
let c_desc = CString::new(desc).unwrap();
let eventhdlr_ptr = Box::into_raw(Box::new(eventhdlr));

unsafe {
scip_call! {
ffi::SCIPincludeEventhdlr(
self.raw,
c_name.as_ptr(),
Expand All @@ -774,7 +777,7 @@ impl ScipPtr {
None,
Some(eventhdlrexec),
eventhdlr_ptr as *mut ffi::SCIP_EVENTHDLRDATA,
);
)
}

Ok(())
Expand Down Expand Up @@ -1570,6 +1573,114 @@ impl ScipPtr {
));
Ok(infeasible != 0)
}

#[cfg(feature = "datastore")]
// Initializes an anymap as a generic datastore, and keeps a reference to it on an
// unused plugin (eventhdlr)
fn init_datastore(&self) -> Result<(), Retcode> {
unsafe extern "C" fn eventhdlrfree(
_scip: *mut ffi::SCIP,
eventhdlr: *mut ffi::SCIP_EVENTHDLR,
) -> ffi::SCIP_Retcode {
let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) };
assert!(!data_ptr.is_null());
let eventhdlr_ptr = data_ptr as *mut AnyMap;
drop(unsafe { Box::from_raw(eventhdlr_ptr) });
Retcode::Okay.into()
}

extern "C" fn eventhdlrexec(
_scip: *mut ffi::SCIP,
_eventhdlr: *mut ffi::SCIP_EVENTHDLR,
_event: *mut ffi::SCIP_EVENT,
_event_data: *mut ffi::SCIP_EVENTDATA,
) -> ffi::SCIP_Retcode {
Retcode::Okay.into()
}

extern "C" fn eventhdlrinit(
_scip: *mut ffi::SCIP,
_eventhdlr: *mut ffi::SCIP_EVENTHDLR,
) -> ffi::SCIP_Retcode {
Retcode::Okay.into()
}

let c_name = CString::new("russcip_datastore").unwrap();
let c_desc = CString::new("").unwrap();
let map = AnyMap::new();
let data = Box::new(map);
let eventhdlr_ptr = Box::into_raw(data);

scip_call! {
ffi::SCIPincludeEventhdlr(
self.raw,
c_name.as_ptr(),
c_desc.as_ptr(),
None,
Some(eventhdlrfree),
Some(eventhdlrinit),
None,
None,
None,
None,
Some(eventhdlrexec),
eventhdlr_ptr as *mut ffi::SCIP_EVENTHDLRDATA,
)
}

Ok(())
}

#[cfg(feature = "datastore")]
pub(crate) fn get_store<T: 'static>(&self) -> Result<Option<&T>, Retcode> {
let name = CString::new("russcip_datastore").unwrap();
let mut eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
if eventhdlr.is_null() {
self.init_datastore()?;
eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
}

let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) };
assert!(!data_ptr.is_null());
let eventhdlr_ptr = data_ptr as *mut AnyMap;
let map = unsafe { &*eventhdlr_ptr };
let thing = map.get::<T>();
Ok(thing)
}

#[cfg(feature = "datastore")]
pub(crate) fn get_mut_store<T: 'static>(&self) -> Result<Option<&mut T>, Retcode> {
let name = CString::new("russcip_datastore").unwrap();
let mut eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
if eventhdlr.is_null() {
self.init_datastore()?;
eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
}

let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) };
assert!(!data_ptr.is_null());
let eventhdlr_ptr = data_ptr as *mut AnyMap;
let map = unsafe { &mut *eventhdlr_ptr };
let thing = map.get_mut::<T>();
Ok(thing)
}

#[cfg(feature = "datastore")]
pub(crate) fn set_store<T: 'static>(&self, thing: T) -> Result<(), Retcode> {
let name = CString::new("russcip_datastore").unwrap();
let mut eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
if eventhdlr.is_null() {
self.init_datastore()?;
eventhdlr = unsafe { ffi::SCIPfindEventhdlr(self.raw, name.as_ptr()) };
}

let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) };
assert!(!data_ptr.is_null());
let eventhdlr_ptr = data_ptr as *mut AnyMap;
let map = unsafe { &mut *eventhdlr_ptr };
map.insert(thing);
Ok(())
}
}

impl Drop for ScipPtr {
Expand Down Expand Up @@ -1620,3 +1731,67 @@ impl Drop for ScipPtr {
unsafe { ffi::SCIPfree(&mut self.raw) };
}
}

#[cfg(test)]
mod tests {
#[cfg(feature = "datastore")]
#[test]
fn test_datastore() {
use crate::scip::ScipPtr;

let scip = ScipPtr::new();
assert!(!scip.raw.is_null());

// Test with a simple integer
scip.set_store(5).unwrap();
let data = scip.get_store::<i32>().unwrap();
assert_eq!(data, Some(&5));
let data = scip.get_mut_store::<i32>().unwrap();
assert_eq!(data, Some(&mut 5));
*data.unwrap() = 10;
let data = scip.get_store::<i32>().unwrap();
assert_eq!(data, Some(&10));

// Test with a custom struct
#[derive(Debug, PartialEq)]
struct MyData {
a: i32,
b: String,
}

let my_data = MyData {
a: 42,
b: "Hello".to_string(),
};

scip.set_store(my_data).unwrap();
let data = scip.get_store::<MyData>().unwrap();
assert_eq!(
data,
Some(&MyData {
a: 42,
b: "Hello".to_string()
})
);
let data = scip.get_mut_store::<MyData>().unwrap();
assert_eq!(
data,
Some(&mut MyData {
a: 42,
b: "Hello".to_string()
})
);
*data.unwrap() = MyData {
a: 100,
b: "World".to_string(),
};
let data = scip.get_store::<MyData>().unwrap();
assert_eq!(
data,
Some(&MyData {
a: 100,
b: "World".to_string()
})
);
}
}
Loading