Skip to main content

crfsuite/
lib.rs

1#![allow(unknown_lints)]
2#![allow(clippy::useless_transmute)]
3#![allow(clippy::transmute_ptr_to_ref)]
4#![allow(clippy::transmute_ptr_to_ptr)]
5use std::ffi::{CStr, CString};
6use std::fs::File;
7use std::io::{Cursor, Read, Seek, SeekFrom};
8#[cfg(unix)]
9use std::os::unix::io::{IntoRawFd, RawFd};
10#[cfg(windows)]
11use std::os::windows::io::{IntoRawHandle, RawHandle};
12use std::path::Path;
13use std::{error, fmt, mem, ptr, slice};
14
15use crfsuite_sys::*;
16#[cfg(not(windows))]
17use libc::{c_char, c_int, c_uint};
18use libc::{c_void, fclose, fdopen};
19
20/// Errors from crfsuite ffi functions
21#[derive(Debug, Clone, PartialEq)]
22pub enum CrfSuiteError {
23    /// Incompatible data
24    Incompatible,
25    /// Internal error
26    InternalLogic,
27    /// Not implemented
28    NotImplemented,
29    /// Unsupported operation
30    NotSupported,
31    /// Insufficient memory
32    OutOfMemory,
33    /// Overflow
34    Overflow,
35    /// Unknown error occurred
36    Unknown,
37}
38
39impl error::Error for CrfSuiteError {}
40
41impl fmt::Display for CrfSuiteError {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        let desc = match *self {
44            CrfSuiteError::Incompatible => "Incompatible data",
45            CrfSuiteError::InternalLogic => "Internal error",
46            CrfSuiteError::NotImplemented => "Not implemented",
47            CrfSuiteError::NotSupported => "Unsupported operation",
48            CrfSuiteError::OutOfMemory => "Insufficient memory",
49            CrfSuiteError::Overflow => "Overflow",
50            CrfSuiteError::Unknown => "Unknown error occurred",
51        };
52        write!(f, "{}", desc)
53    }
54}
55
56impl From<libc::c_int> for CrfSuiteError {
57    fn from(code: libc::c_int) -> Self {
58        match code {
59            CRFSUITEERR_INCOMPATIBLE => CrfSuiteError::Incompatible,
60            CRFSUITEERR_INTERNAL_LOGIC => CrfSuiteError::InternalLogic,
61            CRFSUITEERR_NOTIMPLEMENTED => CrfSuiteError::NotImplemented,
62            CRFSUITEERR_NOTSUPPORTED => CrfSuiteError::NotSupported,
63            CRFSUITEERR_OUTOFMEMORY => CrfSuiteError::OutOfMemory,
64            CRFSUITEERR_OVERFLOW => CrfSuiteError::Overflow,
65            CRFSUITEERR_UNKNOWN => CrfSuiteError::Unknown,
66            _ => unreachable!(),
67        }
68    }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub enum CrfError {
73    /// Errors from crfsuite ffi functions
74    CrfSuiteError(CrfSuiteError),
75    /// Create instance error
76    CreateInstanceError(String),
77    /// Parameter not found
78    ParamNotFound(String),
79    /// Trainer algorithm not selected
80    AlgorithmNotSelected,
81    /// Trainer data is empty
82    EmptyData,
83    /// Invalid argument
84    InvalidArgument(String),
85    /// Invalid value
86    ValueError(String),
87    /// Invalid model
88    InvalidModel(String),
89}
90
91impl error::Error for CrfError {}
92
93impl fmt::Display for CrfError {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        match *self {
96            CrfError::CrfSuiteError(ref err) => err.fmt(f),
97            CrfError::ParamNotFound(ref name) => write!(f, "Parameter {} not found", name),
98            CrfError::AlgorithmNotSelected => write!(
99                f,
100                "The trainer is not initialized. Call Trainer::select before Trainer::train."
101            ),
102            CrfError::EmptyData => write!(
103                f,
104                "The data is empty. Call Trainer::append before Trainer::train."
105            ),
106            CrfError::CreateInstanceError(ref err)
107            | CrfError::InvalidArgument(ref err)
108            | CrfError::ValueError(ref err)
109            | CrfError::InvalidModel(ref err) => err.fmt(f),
110        }
111    }
112}
113
114pub type Result<T> = ::std::result::Result<T, CrfError>;
115
116/// Tuple of attribute and its value.
117#[derive(Debug, Clone, PartialEq)]
118pub struct Attribute {
119    /// Attribute name
120    pub name: String,
121    /// Attribute value
122    pub value: f64,
123}
124
125/// Type of an item (equivalent to an attribute vector) in a sequence
126pub type Item = Vec<Attribute>;
127
128impl Attribute {
129    #[inline]
130    pub fn new<T: Into<String>>(name: T, value: f64) -> Self {
131        Self {
132            name: name.into(),
133            value,
134        }
135    }
136}
137
138impl From<String> for Attribute {
139    #[inline]
140    fn from(t: String) -> Self {
141        Self {
142            name: t,
143            value: 1.0,
144        }
145    }
146}
147
148impl<'a> From<&'a str> for Attribute {
149    #[inline]
150    fn from(t: &'a str) -> Self {
151        Self {
152            name: t.to_string(),
153            value: 1.0,
154        }
155    }
156}
157
158impl<T: Into<String>> From<(T, f64)> for Attribute {
159    #[inline]
160    fn from(t: (T, f64)) -> Self {
161        let (name, value) = t;
162        Self {
163            name: name.into(),
164            value,
165        }
166    }
167}
168
169/// The training algorithm
170#[derive(Debug, Clone, Copy, PartialEq)]
171pub enum Algorithm {
172    /// Gradient descent using the L-BFGS method
173    LBFGS,
174    /// Stochastic Gradient Descent with L2 regularization term
175    L2SGD,
176    /// Averaged Perceptron
177    AP,
178    /// Passive Aggressive
179    PA,
180    /// Adaptive Regularization Of Weight Vector
181    AROW,
182}
183
184impl fmt::Display for Algorithm {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        let desc = match *self {
187            Algorithm::LBFGS => "lbfgs",
188            Algorithm::L2SGD => "l2sgd",
189            Algorithm::AP => "averaged-perceptron",
190            Algorithm::PA => "passive-aggressive",
191            Algorithm::AROW => "arow",
192        };
193        write!(f, "{}", desc)
194    }
195}
196
197impl ::std::str::FromStr for Algorithm {
198    type Err = CrfError;
199
200    fn from_str(s: &str) -> Result<Self> {
201        match s {
202            "lbfgs" => Ok(Algorithm::LBFGS),
203            "l2sgd" => Ok(Algorithm::L2SGD),
204            "ap" | "averaged-perceptron" => Ok(Algorithm::AP),
205            "pa" | "passive-aggressive" => Ok(Algorithm::PA),
206            "arow" => Ok(Algorithm::AROW),
207            _ => Err(CrfError::InvalidArgument(s.to_string())),
208        }
209    }
210}
211
212/// The graphical model
213#[derive(Debug, Clone, Copy, PartialEq)]
214pub enum GraphicalModel {
215    /// The 1st-order Markov CRF with state and transition features (dyad features).
216    /// State features are conditioned on combinations of attributes and labels,
217    /// and transition features are conditioned on label bigrams.
218    CRF1D,
219}
220
221impl fmt::Display for GraphicalModel {
222    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223        let desc = match *self {
224            GraphicalModel::CRF1D => "crf1d",
225        };
226        write!(f, "{}", desc)
227    }
228}
229
230impl ::std::str::FromStr for GraphicalModel {
231    type Err = CrfError;
232
233    fn from_str(s: &str) -> Result<Self> {
234        match s {
235            "1d" | "crf1d" => Ok(GraphicalModel::CRF1D),
236            _ => Err(CrfError::InvalidArgument(s.to_string())),
237        }
238    }
239}
240
241/// The trainer
242/// It maintains a data set for training, and provides an interface
243/// to various graphical models and training algorithms.
244#[derive(Debug)]
245pub struct Trainer {
246    data: *mut crfsuite_data_t,
247    trainer: *mut crfsuite_trainer_t,
248    #[allow(dead_code)]
249    verbose: bool,
250}
251
252impl Default for Trainer {
253    fn default() -> Self {
254        Trainer::new(false)
255    }
256}
257
258#[cfg(not(windows))]
259extern "C" {
260    fn vsnprintf(buf: *mut c_char, size: c_uint, fmt: *const c_char, va_list: *mut c_void);
261}
262
263#[cfg(not(windows))]
264extern "C" fn logging_callback(
265    user: *mut c_void,
266    format: *const c_char,
267    args: *mut __va_list_tag,
268) -> c_int {
269    let trainer: &Trainer = unsafe { mem::transmute(user) };
270    if !trainer.verbose {
271        return 0;
272    }
273    unsafe {
274        let mut buf = mem::MaybeUninit::<[c_char; 65535]>::uninit();
275        let buf = {
276            vsnprintf(buf.as_mut_ptr() as _, 65534, format, mem::transmute(args));
277            buf.assume_init()
278        };
279        let message = CStr::from_ptr(buf.as_ptr()).to_str().unwrap();
280        print!("{}", message);
281    }
282    0
283}
284
285impl Trainer {
286    /// Construct a trainer
287    pub fn new(verbose: bool) -> Self {
288        unsafe {
289            let data_ptr = libc::malloc(mem::size_of::<crfsuite_data_t>()) as *mut crfsuite_data_t;
290            if !data_ptr.is_null() {
291                crfsuite_data_init(data_ptr);
292            }
293            Self {
294                data: data_ptr,
295                trainer: ptr::null_mut(),
296                verbose,
297            }
298        }
299    }
300
301    fn init(&mut self) -> Result<()> {
302        unsafe {
303            let interface = CString::new("dictionary").unwrap();
304            if (*self.data).labels.is_null() {
305                let ret = crfsuite_create_instance(
306                    interface.as_ptr() as *const _,
307                    &mut (*self.data).attrs as *mut *mut _ as *mut *mut _,
308                );
309                // ret is c bool
310                if ret == 0 {
311                    return Err(CrfError::CreateInstanceError(
312                        "Failed to create a dictionary instance for attributes.".to_string(),
313                    ));
314                }
315            }
316            if (*self.data).labels.is_null() {
317                let ret = crfsuite_create_instance(
318                    interface.as_ptr() as *const _,
319                    &mut (*self.data).labels as *mut *mut _ as *mut *mut _,
320                );
321                // ret is c bool
322                if ret == 0 {
323                    return Err(CrfError::CreateInstanceError(
324                        "Failed to create a dictionary instance for labels.".to_string(),
325                    ));
326                }
327            }
328        }
329        #[cfg(not(windows))]
330        {
331            self.set_message_callback();
332        }
333        Ok(())
334    }
335
336    /// Remove all instances in the data set
337    pub fn clear(&mut self) -> Result<()> {
338        if self.data.is_null() {
339            return Ok(());
340        }
341        unsafe {
342            if !(*self.data).attrs.is_null() {
343                (*(*self.data).attrs)
344                    .release
345                    .map(|release| release((*self.data).attrs))
346                    .unwrap();
347                (*self.data).attrs = ptr::null_mut();
348            }
349            if !(*self.data).labels.is_null() {
350                (*(*self.data).labels)
351                    .release
352                    .map(|release| release((*self.data).labels))
353                    .unwrap();
354                (*self.data).labels = ptr::null_mut();
355            }
356            crfsuite_data_finish(self.data);
357            crfsuite_data_init(self.data);
358        }
359        Ok(())
360    }
361
362    /// Append an instance (item/label sequence) to the data set.
363    ///
364    /// ## Parameters
365    ///
366    /// `xseq`: a sequence of item features, The item sequence of the instance.
367    ///
368    /// `yseq`: a sequence of strings, The label sequence of the instance.
369    ///
370    /// `group`: The group number of the instance. Group numbers are used to select subset of data
371    /// for heldout evaluation.
372    pub fn append<T: AsRef<str>>(&mut self, xseq: &[Item], yseq: &[T], group: i32) -> Result<()> {
373        unsafe {
374            if (*self.data).attrs.is_null() || (*self.data).labels.is_null() {
375                self.init()?;
376            }
377            let xseq_len = xseq.len();
378            assert_eq!(xseq_len, yseq.len());
379            let mut instance = mem::MaybeUninit::<crfsuite_instance_t>::uninit();
380            let mut instance = {
381                crfsuite_instance_init_n(instance.as_mut_ptr(), xseq_len as i32);
382                instance.assume_init()
383            };
384            let crf_items = slice::from_raw_parts_mut(instance.items, instance.num_items as usize);
385            let crf_labels =
386                slice::from_raw_parts_mut(instance.labels, instance.num_items as usize);
387            for t in 0..xseq_len {
388                let items = &xseq[t];
389                let crf_item = &mut crf_items[t];
390                // Set the attributes in the item
391                crfsuite_item_init_n(crf_item, items.len() as i32);
392                let contents =
393                    slice::from_raw_parts_mut(crf_item.contents, crf_item.num_contents as usize);
394                for (content, item) in contents.iter_mut().zip(items) {
395                    let name_cstr = CString::new(&item.name[..]).unwrap();
396                    let aid = (*(*self.data).attrs)
397                        .get
398                        .map(|f| f((*self.data).attrs, name_cstr.as_ptr()))
399                        .unwrap();
400                    (*content).aid = aid;
401                    (*content).value = item.value;
402                }
403                // Set the label of the item
404                let y_value = yseq[t].as_ref();
405                let y_cstr = CString::new(y_value).unwrap();
406                let label = (*(*self.data).labels)
407                    .get
408                    .map(|f| f((*self.data).labels, y_cstr.as_ptr()))
409                    .unwrap();
410                crf_labels[t] = label;
411            }
412            instance.group = group;
413            // Append the instance to the training set
414            crfsuite_data_append(self.data, &instance);
415            // Finish the instance
416            crfsuite_instance_finish(&mut instance);
417        }
418        Ok(())
419    }
420
421    /// Initialize the training algorithm.
422    pub fn select(&mut self, algorithm: Algorithm, typ: GraphicalModel) -> Result<()> {
423        unsafe {
424            // Release the trainer if it is already initialzed
425            if !self.trainer.is_null() {
426                (*self.trainer).release.map(|f| f(self.trainer)).unwrap();
427                self.trainer = ptr::null_mut();
428            }
429            let mut tid = String::from("train/");
430            tid.push_str(&typ.to_string());
431            tid.push_str("/");
432            tid.push_str(&algorithm.to_string());
433            let tid_cstr = CString::new(tid).unwrap();
434            let ret = crfsuite_create_instance(
435                tid_cstr.as_ptr(),
436                &mut self.trainer as *mut *mut _ as *mut *mut _,
437            );
438            // ret is c bool
439            if ret == 0 {
440                return Err(CrfError::CreateInstanceError(
441                    "Failed to create a instance for trainer.".to_string(),
442                ));
443            }
444        }
445        Ok(())
446    }
447
448    /// Run the training algorithm.
449    ///
450    /// This function starts the training algorithm with the data set given
451    /// by `append()` function.
452    ///
453    /// ## Parameters
454    ///
455    /// `model`: The filename to which the trained model is stored
456    ///
457    /// `holdout`: The group number of holdout evaluation.
458    /// the instances with this group number will not be used
459    /// for training, but for holdout evaluation.
460    /// -1 meaning "use all instances for training".
461    pub fn train(&mut self, model: &str, holdout: i32) -> Result<()> {
462        if self.trainer.is_null() {
463            return Err(CrfError::AlgorithmNotSelected);
464        }
465        unsafe {
466            if (*self.data).attrs.is_null() || (*self.data).labels.is_null() {
467                return Err(CrfError::EmptyData);
468            }
469            let model_cstr = CString::new(model).unwrap();
470            let ret = (*self.trainer)
471                .train
472                .map(|f| f(self.trainer, self.data, model_cstr.as_ptr(), holdout))
473                .unwrap();
474            if ret != 0 {
475                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
476            }
477        }
478        Ok(())
479    }
480
481    /// Obtain the list of parameters.
482    ///
483    /// This function returns the list of parameter names available for the
484    /// graphical model and training algorithm specified by `select()` function.
485    pub fn params(&self) -> Vec<String> {
486        unsafe {
487            let pms = (*self.trainer).params.map(|f| f(self.trainer)).unwrap();
488            let n = (*pms).num.map(|f| f(pms)).unwrap();
489            let mut ret = Vec::with_capacity(n as usize);
490            for i in 0..n {
491                let mut name: *mut libc::c_char = ptr::null_mut();
492                (*pms).name.map(|f| f(pms, i, &mut name)).unwrap();
493                let c_str = CStr::from_ptr(name);
494                ret.push(c_str.to_string_lossy().into_owned());
495                (*pms).free.map(|f| f(pms, name)).unwrap();
496            }
497            (*pms).release.map(|f| f(pms)).unwrap();
498            ret
499        }
500    }
501
502    /// Set a training parameter.
503    ///
504    /// This function sets a parameter value for the graphical model and
505    /// training algorithm specified by `select()` function.
506    pub fn set(&mut self, name: &str, value: &str) -> Result<()> {
507        let name_cstr = CString::new(name).unwrap();
508        let value_cstr = CString::new(value).unwrap();
509        unsafe {
510            let pms = (*self.trainer).params.map(|f| f(self.trainer)).unwrap();
511            if (*pms)
512                .set
513                .map(|f| f(pms, name_cstr.as_ptr(), value_cstr.as_ptr()))
514                .unwrap()
515                != 0
516            {
517                (*pms).release.map(|f| f(pms)).unwrap();
518                return Err(CrfError::ParamNotFound(name.to_string()));
519            }
520            (*pms).release.map(|f| f(pms)).unwrap();
521        }
522        Ok(())
523    }
524
525    /// Get the value of a training parameter.
526    ///
527    /// This function gets a parameter value for the graphical model and
528    /// training algorithm specified by `select()` function.
529    pub fn get(&self, name: &str) -> Result<String> {
530        let name_cstr = CString::new(name).unwrap();
531        let value;
532        unsafe {
533            let mut value_ptr: *mut libc::c_char = ptr::null_mut();
534            let pms = (*self.trainer).params.map(|f| f(self.trainer)).unwrap();
535            if (*pms)
536                .get
537                .map(|f| f(pms, name_cstr.as_ptr(), &mut value_ptr))
538                .unwrap()
539                != 0
540            {
541                (*pms).release.map(|f| f(pms)).unwrap();
542                return Err(CrfError::ParamNotFound(name.to_string()));
543            }
544            value = CStr::from_ptr(value_ptr).to_string_lossy().into_owned();
545            (*pms).free.map(|f| f(pms, value_ptr)).unwrap();
546            (*pms).release.map(|f| f(pms)).unwrap();
547        }
548        Ok(value)
549    }
550
551    /// Get the description of a training parameter.
552    ///
553    /// This function obtains the help message for the parameter specified
554    /// by the name. The graphical model and training algorithm must be
555    /// selected by `select()` function before calling this function.
556    pub fn help(&self, name: &str) -> Result<String> {
557        let name_cstr = CString::new(name).unwrap();
558        let value;
559        unsafe {
560            let mut value_ptr: *mut libc::c_char = ptr::null_mut();
561            let pms = (*self.trainer).params.map(|f| f(self.trainer)).unwrap();
562            if (*pms)
563                .help
564                .map(|f| f(pms, name_cstr.as_ptr(), ptr::null_mut(), &mut value_ptr))
565                .unwrap()
566                != 0
567            {
568                (*pms).release.map(|f| f(pms)).unwrap();
569                return Err(CrfError::ParamNotFound(name.to_string()));
570            }
571            value = CStr::from_ptr(value_ptr).to_string_lossy().into_owned();
572            (*pms).free.map(|f| f(pms, value_ptr)).unwrap();
573            (*pms).release.map(|f| f(pms)).unwrap();
574        }
575        Ok(value)
576    }
577
578    #[cfg(not(windows))]
579    /// Set the callback function and user-defined data
580    // XXX: make it a public API?
581    fn set_message_callback(&mut self) {
582        unsafe {
583            (*self.trainer)
584                .set_message_callback
585                .map(|f| f(self.trainer, mem::transmute(self), Some(logging_callback)))
586                .unwrap();
587        }
588    }
589}
590
591impl Drop for Trainer {
592    fn drop(&mut self) {
593        unsafe {
594            if !self.data.is_null() {
595                self.clear().unwrap();
596                libc::free(self.data as *mut _);
597                self.data = ptr::null_mut();
598            }
599            if !self.trainer.is_null() {
600                (*self.trainer).release.map(|f| f(self.trainer)).unwrap();
601                self.trainer = ptr::null_mut();
602            }
603        }
604    }
605}
606
607/// The model
608#[derive(Debug)]
609pub struct Model(*mut crfsuite_model_t);
610
611/// The tagger
612/// provides the functionality for predicting label sequences for input sequences using a model.
613#[derive(Debug)]
614pub struct Tagger<'a> {
615    model: &'a Model,
616    tagger: *mut crfsuite_tagger_t,
617}
618
619impl Model {
620    #[inline]
621    fn new() -> Self {
622        Model(ptr::null_mut())
623    }
624
625    /// Open a model file
626    pub fn from_file(name: &str) -> Result<Self> {
627        let mut file = File::open(name)
628            .map_err(|err| CrfError::InvalidModel(format!("Failed to open model: {}", err)))?;
629        Self::validate_model(&mut file)?;
630        drop(file); // Close file
631
632        let mut model = Model::new();
633        model.open(name)?;
634        Ok(model)
635    }
636
637    /// Create an instance of a model object from a model in memory
638    pub fn from_memory(bytes: &[u8]) -> Result<Self> {
639        let mut cdr = Cursor::new(bytes);
640        Self::validate_model(&mut cdr)?;
641        let mut instance = ptr::null_mut();
642        unsafe {
643            let ret = crfsuite_create_instance_from_memory(
644                bytes.as_ptr() as *const c_void,
645                bytes.len(),
646                &mut instance,
647            );
648            if ret != 0 {
649                return Err(CrfError::CreateInstanceError(
650                    "Failed to create a model instance.".to_string(),
651                ));
652            }
653        }
654        let model: *mut crfsuite_sys::crfsuite_model_t = unsafe { mem::transmute(instance) };
655        Ok(Model(model))
656    }
657
658    /// Validate model
659    ///
660    /// See https://github.com/chokkan/crfsuite/pull/24
661    fn validate_model<R: Read + Seek>(reader: &mut R) -> Result<()> {
662        // Check that file magic is correct
663        let mut magic = [0; 4];
664        reader.read_exact(&mut magic).map_err(|err| {
665            CrfError::InvalidModel(format!("Failed to read model file magic: {}", err))
666        })?;
667        if &magic != b"lCRF" {
668            return Err(CrfError::InvalidModel(
669                "Invalid model file magic".to_string(),
670            ));
671        }
672        // Make sure crfsuite won't read past allocated memory in case of incomplete header
673        let pos = reader
674            .seek(SeekFrom::End(0))
675            .map_err(|err| CrfError::InvalidModel(format!("Invalid model: {}", err)))?;
676        if pos <= 48 {
677            // header size
678            return Err(CrfError::InvalidModel(
679                "Invalid model file header".to_string(),
680            ));
681        }
682        Ok(())
683    }
684
685    /// Open a model file
686    fn open(&mut self, name: &str) -> Result<()> {
687        let name_cstr = CString::new(name).unwrap();
688        unsafe {
689            let ret = crfsuite_create_instance_from_file(
690                name_cstr.as_ptr(),
691                &mut self.0 as *mut *mut _ as *mut *mut _,
692            );
693            if ret != 0 {
694                return Err(CrfError::CreateInstanceError(
695                    "Failed to create a model instance.".to_string(),
696                ));
697            }
698        }
699        Ok(())
700    }
701
702    /// Close the model
703    fn close(&mut self) {
704        unsafe {
705            if !self.0.is_null() {
706                (*self.0).release.map(|f| f(self.0)).unwrap();
707            }
708        }
709    }
710
711    pub fn tagger(&self) -> Result<Tagger> {
712        unsafe {
713            let mut tagger = ptr::null_mut();
714            let ret = (*self.0)
715                .get_tagger
716                .map(|f| f(self.0, &mut tagger))
717                .unwrap();
718            if ret != 0 {
719                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
720            }
721            Ok(Tagger {
722                model: self,
723                tagger,
724            })
725        }
726    }
727
728    #[cfg(unix)]
729    /// Print the model in human-readable format
730    ///
731    /// ## Parameters
732    ///
733    /// `file`: Something convertable to file descriptor
734    ///
735    pub fn dump(&self, fd: RawFd) -> Result<()> {
736        let c_mode = CString::new("w").unwrap();
737        unsafe {
738            let file = fdopen(fd, c_mode.as_ptr());
739            if file.is_null() {
740                panic!("fdopen failed");
741            }
742            let ret = (*self.0).dump.map(|f| f(self.0, file)).unwrap();
743            if ret != 0 {
744                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
745            }
746            fclose(file);
747        }
748        Ok(())
749    }
750
751    #[cfg(windows)]
752    /// Print the model in human-readable format
753    ///
754    /// ## Parameters
755    ///
756    /// `file`: Something convertable to file descriptor
757    ///
758    pub fn dump(&self, fd: RawHandle) -> Result<()> {
759        unsafe {
760            let fd = libc::open_osfhandle(fd as _, libc::O_RDWR);
761            if fd == -1 {
762                panic!("open_osfhandle failed");
763            }
764            let c_mode = CString::new("w").unwrap();
765            let file = fdopen(fd, c_mode.as_ptr());
766            if file.is_null() {
767                panic!("fdopen failed");
768            }
769            let ret = (*self.0).dump.map(|f| f(self.0, file)).unwrap();
770            if ret != 0 {
771                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
772            }
773            fclose(file);
774        }
775        Ok(())
776    }
777
778    #[cfg(unix)]
779    /// Print the model in human-readable format to file
780    ///
781    /// ## Parameters
782    ///
783    /// `path`: Dump file path
784    ///
785    pub fn dump_file<T: AsRef<Path>>(&self, path: T) -> Result<()> {
786        let file = File::create(path).expect("create file failed");
787        self.dump(file.into_raw_fd())
788    }
789
790    #[cfg(windows)]
791    /// Print the model in human-readable format to file
792    ///
793    /// ## Parameters
794    ///
795    /// `path`: Dump file path
796    ///
797    pub fn dump_file<T: AsRef<Path>>(&self, path: T) -> Result<()> {
798        let file = File::create(path).expect("create file failed");
799        self.dump(file.into_raw_handle())
800    }
801
802    unsafe fn get_attrs(&self) -> Result<*mut crfsuite_dictionary_t> {
803        let mut attrs: *mut crfsuite_dictionary_t = ptr::null_mut();
804        let ret = (*self.0).get_attrs.map(|f| f(self.0, &mut attrs)).unwrap();
805        if ret != 0 {
806            return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
807        }
808        Ok(attrs)
809    }
810
811    unsafe fn get_labels(&self) -> Result<*mut crfsuite_dictionary_t> {
812        let mut labels: *mut crfsuite_dictionary_t = ptr::null_mut();
813        let ret = (*self.0)
814            .get_labels
815            .map(|f| f(self.0, &mut labels))
816            .unwrap();
817        if ret != 0 {
818            return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
819        }
820        Ok(labels)
821    }
822}
823
824impl Drop for Model {
825    fn drop(&mut self) {
826        self.close();
827    }
828}
829
830unsafe impl Send for Model {}
831unsafe impl Sync for Model {}
832
833impl<'a> Drop for Tagger<'a> {
834    fn drop(&mut self) {
835        unsafe {
836            (*self.tagger).release.map(|f| f(self.tagger)).unwrap();
837        }
838    }
839}
840
841impl<'a> Tagger<'a> {
842    /// Obtain the list of labels
843    pub fn labels(&self) -> Result<Vec<String>> {
844        unsafe {
845            let labels = self.model.get_labels()?;
846            let length = (*labels).num.map(|f| f(labels)).unwrap();
847            let mut lseq = Vec::with_capacity(length as usize);
848            for i in 0..length {
849                let mut label: *mut libc::c_char = ptr::null_mut();
850                let ret = (*labels)
851                    .to_string
852                    .map(|f| f(labels, i, &mut label as *mut *mut _ as *mut *const _))
853                    .unwrap();
854                if ret != 0 {
855                    (*labels).release.map(|f| f(labels)).unwrap();
856                    return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
857                }
858                lseq.push(CStr::from_ptr(label).to_string_lossy().into_owned());
859                (*labels).free.map(|f| f(labels, label)).unwrap();
860            }
861            (*labels).release.map(|f| f(labels)).unwrap();
862            Ok(lseq)
863        }
864    }
865
866    /// Predict the label sequence for the item sequence.
867    pub fn tag(&mut self, xseq: &[Item]) -> Result<Vec<String>> {
868        self.set(xseq)?;
869        self.viterbi()
870    }
871
872    /// Set an item sequence.
873    fn set(&mut self, xseq: &[Item]) -> Result<()> {
874        unsafe {
875            let attrs = self.model.get_attrs()?;
876            let xseq_len = xseq.len();
877            let mut instance = mem::MaybeUninit::<crfsuite_instance_t>::uninit();
878            let mut instance = {
879                crfsuite_instance_init_n(instance.as_mut_ptr(), xseq_len as i32);
880                instance.assume_init()
881            };
882            let crf_items = slice::from_raw_parts_mut(instance.items, instance.num_items as usize);
883            for t in 0..xseq_len {
884                let items = &xseq[t];
885                let crf_item = &mut crf_items[t];
886                // Set the attributes in the item
887                crfsuite_item_init(crf_item);
888                for attr in items.iter() {
889                    let name_cstr = CString::new(&attr.name[..]).unwrap();
890                    let aid = (*attrs)
891                        .to_id
892                        .map(|f| f(attrs, name_cstr.as_ptr()))
893                        .unwrap();
894                    if aid >= 0 {
895                        let mut cont = mem::MaybeUninit::<crfsuite_attribute_t>::uninit();
896                        let cont = {
897                            crfsuite_attribute_set(cont.as_mut_ptr(), aid, attr.value);
898                            cont.assume_init()
899                        };
900                        crfsuite_item_append_attribute(crf_item, &cont);
901                    }
902                }
903            }
904
905            // Set the instance to the tagger
906            let ret = (*self.tagger)
907                .set
908                .map(|f| f(self.tagger, &mut instance))
909                .unwrap();
910            if ret != 0 {
911                (*attrs).release.map(|f| f(attrs)).unwrap();
912                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
913            }
914            crfsuite_instance_finish(&mut instance);
915            (*attrs).release.map(|f| f(attrs)).unwrap();
916        }
917        Ok(())
918    }
919
920    /// Find the Viterbi label sequence for the item sequence.
921    pub fn viterbi(&self) -> Result<Vec<String>> {
922        unsafe {
923            // Make sure that the current instance is not empty
924            let length = (*self.tagger).length.map(|f| f(self.tagger)).unwrap();
925            if length <= 0 {
926                return Ok(Vec::new());
927            }
928            let labels = self.model.get_labels()?;
929            // Run the Viterbi algorithm
930            let mut score: floatval_t = 0.0;
931            let mut paths: Vec<libc::c_int> = Vec::with_capacity(length as usize);
932            let ret = (*self.tagger)
933                .viterbi
934                .map(|f| f(self.tagger, paths.as_mut_ptr(), &mut score))
935                .unwrap();
936            if ret != 0 {
937                (*labels).release.map(|f| f(labels)).unwrap();
938                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
939            }
940            paths.set_len(length as usize);
941            let mut yseq = Vec::with_capacity(length as usize);
942            // Convert the Viterbi path to a label sequence
943            for path in paths {
944                let mut label: *mut libc::c_char = ptr::null_mut();
945                let ret = (*labels)
946                    .to_string
947                    .map(|f| f(labels, path, &mut label as *mut *mut _ as *mut *const _))
948                    .unwrap();
949                if ret != 0 {
950                    (*labels).release.map(|f| f(labels)).unwrap();
951                    return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
952                }
953                yseq.push(CStr::from_ptr(label).to_string_lossy().into_owned());
954                (*labels).free.map(|f| f(labels, label)).unwrap();
955            }
956            (*labels).release.map(|f| f(labels)).unwrap();
957            Ok(yseq)
958        }
959    }
960
961    /// Compute the probability of the label sequence.
962    pub fn probability<T: AsRef<str>>(&self, yseq: &[T]) -> Result<f64> {
963        let mut score: floatval_t = 0.0;
964        unsafe {
965            // Make sure that the current instance is not empty
966            let length = (*self.tagger).length.map(|f| f(self.tagger)).unwrap() as usize;
967            if length == 0 {
968                return Ok(score);
969            }
970            // Make sure |y| == |x|
971            if length != yseq.len() {
972                return Err(CrfError::InvalidArgument(format!(
973                    "The numbers of items and labels differ: |x| = {}, |y| = {}",
974                    length,
975                    yseq.len()
976                )));
977            }
978            // Obtain the dictionary interface representing the labels in the model.
979            let labels = self.model.get_labels()?;
980            // Convert string labels into label IDs.
981            let mut paths: Vec<libc::c_int> = Vec::with_capacity(length);
982            for y in yseq.iter() {
983                let y_cstr = CString::new(y.as_ref()).unwrap();
984                let l = (*labels).to_id.map(|f| f(labels, y_cstr.as_ptr())).unwrap();
985                if l < 0 {
986                    (*labels).release.map(|f| f(labels)).unwrap();
987                    return Err(CrfError::ValueError(format!(
988                        "Failed to convert into label identifier: {}",
989                        y.as_ref()
990                    )));
991                }
992                paths.push(l);
993            }
994            // Compute the score of the path.
995            let ret = (*self.tagger)
996                .score
997                .map(|f| f(self.tagger, paths.as_mut_ptr(), &mut score))
998                .unwrap();
999            if ret != 0 {
1000                (*labels).release.map(|f| f(labels)).unwrap();
1001                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
1002            }
1003            // Compute the partition factor.
1004            let mut lognorm: floatval_t = 0.0;
1005            let ret = (*self.tagger)
1006                .lognorm
1007                .map(|f| f(self.tagger, &mut lognorm))
1008                .unwrap();
1009            (*labels).release.map(|f| f(labels)).unwrap();
1010            if ret != 0 {
1011                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
1012            }
1013            Ok((score - lognorm).exp())
1014        }
1015    }
1016
1017    /// Compute the marginal probability of the label.
1018    pub fn marginal(&self, label: &str, position: i32) -> Result<f64> {
1019        let mut prob: floatval_t = 0.0;
1020        unsafe {
1021            // Make sure that the current instance is not empty
1022            let length = (*self.tagger).length.map(|f| f(self.tagger)).unwrap() as usize;
1023            if length == 0 {
1024                return Ok(prob);
1025            }
1026            // Make sure that 0 <= position < |x|.
1027            if position < 0 || length <= position as usize {
1028                return Err(CrfError::InvalidArgument(format!(
1029                    "The position {} is out of range of {}",
1030                    position, length
1031                )));
1032            }
1033            // Obtain the dictionary interface representing the labels in the model.
1034            let labels = self.model.get_labels()?;
1035            // Convert string labels into label IDs.
1036            let y_cstr = CString::new(label).unwrap();
1037            let l = (*labels).to_id.map(|f| f(labels, y_cstr.as_ptr())).unwrap();
1038            if l < 0 {
1039                (*labels).release.map(|f| f(labels)).unwrap();
1040                return Err(CrfError::ValueError(format!(
1041                    "Failed to convert into label identifier: {}",
1042                    label
1043                )));
1044            }
1045            // Compute the score of the path.
1046            let ret = (*self.tagger)
1047                .marginal_point
1048                .map(|f| f(self.tagger, l, position, &mut prob))
1049                .unwrap();
1050            (*labels).release.map(|f| f(labels)).unwrap();
1051            if ret != 0 {
1052                return Err(CrfError::CrfSuiteError(CrfSuiteError::from(ret)));
1053            }
1054            Ok(prob)
1055        }
1056    }
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061    use super::{Algorithm, Attribute, GraphicalModel, Result};
1062
1063    #[test]
1064    fn test_str_to_algorithm_enum() {
1065        let algo: Algorithm = "lbfgs".parse().unwrap();
1066        assert_eq!(algo, Algorithm::LBFGS);
1067
1068        let algo: Algorithm = "l2sgd".parse().unwrap();
1069        assert_eq!(algo, Algorithm::L2SGD);
1070
1071        let algo: Algorithm = "ap".parse().unwrap();
1072        assert_eq!(algo, Algorithm::AP);
1073        let algo: Algorithm = "averaged-perceptron".parse().unwrap();
1074        assert_eq!(algo, Algorithm::AP);
1075
1076        let algo: Algorithm = "pa".parse().unwrap();
1077        assert_eq!(algo, Algorithm::PA);
1078        let algo: Algorithm = "passive-aggressive".parse().unwrap();
1079        assert_eq!(algo, Algorithm::PA);
1080
1081        let algo: Algorithm = "arow".parse().unwrap();
1082        assert_eq!(algo, Algorithm::AROW);
1083
1084        let algo: Result<Algorithm> = "foo".parse();
1085        assert!(algo.is_err());
1086    }
1087
1088    #[test]
1089    fn test_algorithm_enum_to_str() {
1090        assert_eq!("lbfgs", &Algorithm::LBFGS.to_string());
1091        assert_eq!("l2sgd", &Algorithm::L2SGD.to_string());
1092        assert_eq!("averaged-perceptron", &Algorithm::AP.to_string());
1093        assert_eq!("passive-aggressive", &Algorithm::PA.to_string());
1094        assert_eq!("arow", &Algorithm::AROW.to_string());
1095    }
1096
1097    #[test]
1098    fn test_str_to_graphical_model_enum() {
1099        let model: GraphicalModel = "1d".parse().unwrap();
1100        assert_eq!(model, GraphicalModel::CRF1D);
1101        let model: GraphicalModel = "crf1d".parse().unwrap();
1102        assert_eq!(model, GraphicalModel::CRF1D);
1103
1104        let model: Result<GraphicalModel> = "foo".parse();
1105        assert!(model.is_err());
1106    }
1107
1108    #[test]
1109    fn test_attribute() {
1110        Attribute::new("foo", 1.0);
1111        Attribute::from(("foo", 1.0));
1112        assert_eq!(Attribute::from("foo"), Attribute::from(("foo", 1.0)));
1113    }
1114}