Skip to main content

dtrees_rs/parsers/
examples.rs

1//! Command line arguments and result files shared by the anytime examples.
2
3use crate::algorithms::common::types::{OptimalDepth2Policy, SearchHeuristic, SearchStepStrategy};
4use crate::caching::Caching;
5use crate::parsers::CacheType;
6use crate::tree::Tree;
7use clap::Parser;
8use serde::{Deserialize, Serialize};
9use std::fs;
10use std::fs::{remove_file, File};
11use std::io::{BufReader, BufWriter, Write};
12use std::path::PathBuf;
13
14/// Command line arguments of the anytime examples.
15#[derive(Debug, Parser)]
16#[clap(name = "dt-trees", version, author, about)]
17pub struct ExampleParser {
18    /// Dataset file: one instance per line, label first, binary features
19    #[clap(short, long, value_parser)]
20    pub input: PathBuf,
21
22    /// Minimum number of instances in each leaf
23    #[arg(short, long, default_value_t = 5)]
24    pub support: usize,
25
26    /// Maximum depth of the tree
27    #[arg(short, long)]
28    pub depth: usize,
29
30    /// Time limit in seconds
31    #[arg(short, long, default_value_t = 300.0)]
32    pub timeout: f64,
33
34    /// Not used by the current examples
35    #[arg(short, long, default_value_t = 1.0)]
36    pub metric: f64,
37
38    /// Length in seconds of one pass of the restart and iterative examples;
39    /// 0 runs the search uninterrupted and records every improvement
40    #[arg(long, default_value_t = 1.0)]
41    pub restart_interval: f64,
42
43    /// Initial upper bound on the tree error: only strictly better trees are
44    /// searched. Used by the restart example (warm start from a known tree)
45    #[arg(long, default_value_t = f64::INFINITY)]
46    pub max_error: f64,
47
48    /// Top-k example: halve k at each level of the tree (Top-k*)
49    #[arg(long, default_value_t = false)]
50    pub dynamic: bool,
51
52    /// Top-k example: stop relaxing at this k instead of the number of
53    /// features (the incomplete Top-k baseline)
54    #[arg(long)]
55    pub max_k: Option<usize>,
56
57    /// Step of the gain and purity rules
58    #[arg(long, default_value_t = 0.002)]
59    pub epsilon: f64,
60
61    /// Whether to use the depth-2 solver
62    #[arg(long, value_enum, default_value_t = OptimalDepth2Policy::Enabled)]
63    pub fast_d2: OptimalDepth2Policy,
64
65    /// Cache of the subproblems
66    #[arg(long, value_enum, default_value_t = CacheType::Trie)]
67    pub cache: CacheType,
68
69    /// Print the search statistics
70    #[arg(long, default_value_t = false)]
71    pub print_stats: bool,
72
73    /// Sort the features by the heuristic at every node
74    #[arg(long, default_value_t = true)]
75    pub always_sort: bool,
76
77    /// Heuristic used to order the features
78    #[arg(long, value_enum, default_value_t = SearchHeuristic::NoHeuristic)]
79    pub heuristic: SearchHeuristic,
80
81    /// How the budget of the search rule grows between passes
82    #[arg(long, value_enum, default_value_t = SearchStepStrategy::Monotonic)]
83    pub step: SearchStepStrategy,
84
85    /// Directory where the JSON results are written
86    #[arg(short, long)]
87    pub result: PathBuf,
88
89    /// Print the tree
90    #[arg(long, default_value_t = false)]
91    pub print_tree: bool,
92
93    /// Overwrite existing results
94    #[arg(long, default_value_t = false)]
95    pub overwrite: bool,
96}
97
98impl ExampleParser {
99    /// A new cache of the chosen type.
100    pub fn new_cache(&self) -> Box<dyn Caching> {
101        self.cache.new_cache()
102    }
103
104    /// `method`, followed by `-cover` with the cover cache so that the
105    /// results of both caches are kept apart.
106    pub fn method_name(&self, method: String) -> String {
107        match self.cache {
108            CacheType::Trie => method,
109            CacheType::Cover => format!("{method}-cover"),
110        }
111    }
112}
113
114/// The results of one run, saved as JSON.
115#[derive(Serialize, Deserialize, Clone)]
116pub struct Res {
117    pub name: String,
118    pub method: String,
119    pub depth: usize,
120    pub support: usize,
121    pub completed: bool,
122    pub one_time_sort: bool,
123    pub fast_d2: bool,
124    pub metric: Vec<f64>,
125    pub runtimes: Vec<f64>,
126    pub errors: Vec<f64>,
127    pub cache: Vec<usize>,
128    pub tree: Tree,
129}
130
131/// Writes `result` to `result_path`, creating its directory.
132pub fn save_results(result: &Res, result_path: &PathBuf) -> std::io::Result<()> {
133    if let Some(parent) = result_path.parent() {
134        fs::create_dir_all(parent)?;
135    }
136
137    let file = File::create(result_path)?;
138    let mut writer = BufWriter::new(file);
139    serde_json::to_writer_pretty(&mut writer, result)?;
140    writer.flush()
141}
142
143/// Reads results saved earlier, if the file exists and parses.
144pub fn load_results(result_path: &PathBuf) -> Option<Res> {
145    if !result_path.exists() {
146        return None;
147    }
148
149    File::open(result_path).ok().and_then(|file| {
150        let reader = BufReader::new(file);
151        serde_json::from_reader(reader).ok()
152    })
153}
154
155/// Deletes a result file if it exists.
156pub fn remove_results(result_path: &PathBuf) -> std::io::Result<()> {
157    if result_path.exists() {
158        remove_file(result_path)?
159    }
160    Ok(())
161}