1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
use std::fmt::Display;
use allocative::Allocative;
use serde::Serialize;
use crate::model::{label::label_model_error::LabelModelError, network::VertexId};
/// The required length for OS-aligned state vectors.
/// This is the word size of the target architecture.
#[cfg(target_pointer_width = "32")]
pub const OS_ALIGNED_STATE_LEN: usize = 4;
#[cfg(target_pointer_width = "64")]
pub const OS_ALIGNED_STATE_LEN: usize = 8;
#[derive(PartialEq, Eq, Hash, Debug, Clone, Serialize, Allocative)]
pub struct IntStateVec(pub Vec<usize>);
#[derive(PartialEq, Eq, Hash, Debug, Clone, Serialize, Allocative)]
pub struct U8StateVec {
pub state: Vec<u8>,
pub state_len: u8,
}
#[derive(PartialEq, Eq, Hash, Debug, Clone, Serialize, Allocative)]
pub enum Label {
Vertex(VertexId),
VertexWithIntState {
vertex_id: VertexId,
state: usize,
},
VertexWithIntStateVec {
vertex_id: VertexId,
state: Box<IntStateVec>,
},
/// Store u8 state data. more efficient memory layout for smaller
/// numbers or categorical data with 256 or fewer categories.
///
/// The state vector is stored on the heap to keep the enum size small.
///
/// For memory alignment, the Vec<u8> will be extended to the
/// nearest integer multiple of OS_ALIGNED_STATE_LEN that covers
/// the provided state values.
///
/// In order to ensure reading the state value produces a slice
/// of the same length as the Vec<u8> used to construct this
/// Label, we also store a state_len: u8 value. This limits to
/// state sizes up to 256 elements. This is guaranteed when using
/// the get_u8_state method for retrieval.
VertexWithU8StateVec {
vertex_id: VertexId,
state: Box<U8StateVec>,
},
}
impl Label {
/// Creates a new VertexWithU8StateVec with validation.
///
/// # Arguments
///
/// * `vertex_id` - The vertex identifier
/// * `state` - The u8 state vector, must be exactly OS_ALIGNED_STATE_LEN bytes long
///
/// # Returns
///
/// A Result containing the Label or a LabelModelError if the state vector length exceeds u8::MAX.
///
/// # Example
///
/// ```rust
/// # use routee_compass_core::model::label::label_enum::{Label, OS_ALIGNED_STATE_LEN};
/// # use routee_compass_core::model::network::VertexId;
/// let vertex_id = VertexId(42);
/// let state = vec![1u8, 2u8, 3u8, 4u8];
/// let label = Label::new_u8_state(vertex_id, &state).unwrap();
/// let out_state = label.get_u8_state().unwrap();
/// assert_eq!(state.as_slice(), out_state);
/// ```
pub fn new_u8_state(vertex_id: VertexId, state: &[u8]) -> Result<Self, LabelModelError> {
let mut label_state = state.to_vec();
let state_len: u8 = state
.len()
.try_into()
.map_err(|_| LabelModelError::BadLabelVecSize(state.len(), u8::MAX as usize))?;
// Calculate total memory needed: state data + 1 byte for state_len
let total_data_len = label_state.len() + 1;
let remainder = total_data_len % OS_ALIGNED_STATE_LEN;
if remainder != 0 {
let padding_needed = OS_ALIGNED_STATE_LEN - remainder;
label_state.extend(vec![0u8; padding_needed]);
}
Ok(Label::VertexWithU8StateVec {
vertex_id,
state: Box::new(U8StateVec {
state: label_state,
state_len,
}),
})
}
/// Gets the OS-aligned state if this label contains one.
///
/// # Returns
///
/// Some reference to the state vector if this is a VertexWithU8StateVec, None otherwise
pub fn get_u8_state(&self) -> Option<&[u8]> {
match self {
Label::VertexWithU8StateVec { state, .. } => {
let len: usize = state.state_len.into();
Some(&state.state[0..len])
}
_ => None,
}
}
pub fn vertex_id(&self) -> &VertexId {
match self {
Label::Vertex(vertex_id) => vertex_id,
Label::VertexWithIntState { vertex_id, .. } => vertex_id,
Label::VertexWithIntStateVec { vertex_id, .. } => vertex_id,
Label::VertexWithU8StateVec { vertex_id, .. } => vertex_id,
}
}
/// Returns true if this label variant should be stored in the vertex->labels mapping
/// of the SearchTree.
///
/// Label::Vertex is excluded because it's redundant - the vertex ID is already
/// the key, so storing Label::Vertex(id) under key `id` provides no additional value.
pub fn needs_vertex_map_storage(&self) -> bool {
!matches!(self, Label::Vertex(_))
}
/// returns true if this label variant is not a bijection to the vertex set.
/// if not, then its type has a greater cardinality than the vertex set and so
/// we will want to prune any dominated labels with matching VertexId.
pub fn does_not_require_pruning(&self) -> bool {
matches!(self, Label::Vertex(_))
}
}
impl Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Label::Vertex(vertex_id) => write!(f, "Vertex({vertex_id})"),
Label::VertexWithIntState { vertex_id, state } => {
write!(f, "VertexWithIntState({vertex_id}, {state})")
}
Label::VertexWithIntStateVec { vertex_id, state } => {
write!(f, "VertexWithIntStateVec({vertex_id}, {:?})", state.0)
}
Label::VertexWithU8StateVec { vertex_id, state } => {
write!(
f,
"VertexWithU8StateVec({vertex_id}, {}, {:?})",
state.state_len, state.state
)
}
}
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use super::*;
#[test]
fn test_e2e_display_trip() {
let modes = ["walk", "bike", "drive", "tnc", "transit"];
let trip_sequence = [0, 2, 4, 0, 4, 3];
let vertex_id = VertexId(1234);
let label = Label::new_u8_state(vertex_id, &trip_sequence).unwrap();
println!("label storage: {}", label);
let out = label.get_u8_state().unwrap();
let trip_modes = out
.iter()
.map(|idx| modes[*idx as usize].to_string())
.join(",");
println!("[{}]", trip_modes);
assert_eq!(
trip_modes,
"walk,drive,transit,walk,transit,tnc".to_string()
);
}
#[test]
fn test_new_u8_state_valid() {
let vertex_id = VertexId(42);
let state = vec![0u8; OS_ALIGNED_STATE_LEN];
let label = Label::new_u8_state(vertex_id, &state).expect("test failed");
assert_eq!(label.vertex_id(), &vertex_id);
assert_eq!(label.get_u8_state(), Some(state.as_slice()));
}
#[test]
fn test_new_u8_state_aligned() {
let vertex_id = VertexId(42);
let state = vec![1, 2, 3];
let label = Label::new_u8_state(vertex_id, &state).expect("test failed");
match label {
Label::VertexWithU8StateVec {
state: inner_state, ..
} => {
// should pad to OS_ALIGNED_STATE_LEN - 1
// (minus one due to storing state_len value)
assert_eq!(inner_state.state.len(), super::OS_ALIGNED_STATE_LEN - 1);
}
_ => panic!("wrong label variant!"),
};
}
#[test]
fn test_u8_state_none_for_other_variants() {
let vertex_id = VertexId(42);
let vertex_label = Label::Vertex(vertex_id);
assert_eq!(vertex_label.get_u8_state(), None);
let int_state_label = Label::VertexWithIntState {
vertex_id,
state: 123,
};
assert_eq!(int_state_label.get_u8_state(), None);
// Test that VertexWithU8StateVec does return the state
let valid_state = vec![1, 2, 3];
let u8_vec_label = Label::new_u8_state(vertex_id, &valid_state).expect("test failed");
let result = u8_vec_label.get_u8_state().unwrap();
let expected = [1, 2, 3];
assert_eq!(result, &expected);
}
#[test]
fn test_u8_state_label_display() {
let vertex_id = VertexId(42);
let state = vec![1u8; OS_ALIGNED_STATE_LEN];
let label = Label::new_u8_state(vertex_id, &state).expect("test failed");
let display_string = format!("{}", label);
assert!(display_string.contains("VertexWithU8StateVec"));
assert!(display_string.contains("42"));
}
}