-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathquasi_inverse.rs
220 lines (191 loc) · 7.15 KB
/
quasi_inverse.rs
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
use super::Matrix;
use crate::prime::ValidPrime;
use crate::vector::{FpVector, Slice, SliceMut};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Write};
/// Given a matrix M, a quasi-inverse Q is a map from the co-domain to the domain such that xQM = x
/// for all x in the image (recall our matrices act on the right).
///
/// # Fields
/// * `image` - The image of the original matrix. If the image is omitted, it is assumed to be
/// everything (with the standard basis).
/// * `preimage` - The actual quasi-inverse, where the basis of the image is that given by
/// `image`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuasiInverse {
image: Option<Vec<isize>>,
preimage: Matrix,
}
impl QuasiInverse {
pub fn new(image: Option<Vec<isize>>, preimage: Matrix) -> Self {
Self { image, preimage }
}
pub fn image_dimension(&self) -> usize {
self.preimage.rows()
}
pub fn source_dimension(&self) -> usize {
self.preimage.columns()
}
pub fn target_dimension(&self) -> usize {
match self.image.as_ref() {
Some(v) => v.len(),
None => self.image_dimension(),
}
}
pub fn to_bytes(&self, buffer: &mut impl Write) -> std::io::Result<()> {
buffer.write_u64::<LittleEndian>(self.source_dimension() as u64)?;
buffer.write_u64::<LittleEndian>(self.target_dimension() as u64)?;
buffer.write_u64::<LittleEndian>(self.image_dimension() as u64)?;
match self.image.as_ref() {
None => {
for i in 0..self.preimage.rows() {
buffer.write_i64::<LittleEndian>(i as i64)?;
}
}
Some(v) => {
Matrix::write_pivot(v, buffer)?;
}
}
self.preimage.to_bytes(buffer)
}
pub fn from_bytes(p: ValidPrime, data: &mut impl Read) -> std::io::Result<Self> {
let source_dim = data.read_u64::<LittleEndian>()? as usize;
let target_dim = data.read_u64::<LittleEndian>()? as usize;
let image_dim = data.read_u64::<LittleEndian>()? as usize;
let image = Matrix::read_pivot(target_dim, data)?;
let preimage = Matrix::from_bytes(p, image_dim, source_dim, data)?;
Ok(Self {
image: Some(image),
preimage,
})
}
/// Given a data file containing a quasi-inverse, apply it to all the vectors in `input`
/// and write the results to the corresponding vectors in `results`. This reads in the
/// quasi-inverse row by row to minimize memory usage.
pub fn stream_quasi_inverse<T, S>(
p: ValidPrime,
data: &mut impl Read,
results: &mut [T],
inputs: &[S],
) -> std::io::Result<()>
where
for<'a> &'a mut T: Into<SliceMut<'a>>,
for<'a> &'a S: Into<Slice<'a>>,
{
use crate::limb::Limb;
let source_dim = data.read_u64::<LittleEndian>()? as usize;
let target_dim = data.read_u64::<LittleEndian>()? as usize;
let _image_dim = data.read_u64::<LittleEndian>()? as usize;
let image = Matrix::read_pivot(target_dim, data)?;
let mut v = FpVector::new(p, source_dim);
assert_eq!(results.len(), inputs.len());
for result in &mut *results {
assert_eq!(result.into().as_slice().len(), source_dim);
}
for input in inputs {
// The quasi-inverse might be computed with a smaller target dimension when computing
// through stem.
assert!(input.into().len() >= target_dim);
}
let num_limbs = FpVector::num_limbs(p, source_dim);
for (i, r) in image.into_iter().enumerate() {
if r < 0 {
continue;
}
let limbs = v.limbs_mut();
// Read in data
cfg_if::cfg_if! {
if #[cfg(target_endian = "little")] {
let num_bytes = num_limbs * std::mem::size_of::<Limb>();
unsafe {
let buf: &mut [u8] = std::slice::from_raw_parts_mut(limbs.as_mut_ptr() as *mut u8, num_bytes);
data.read_exact(buf).unwrap();
}
} else {
for limb in limbs.iter_mut() {
let mut bytes: [u8; size_of::<Limb>()] = [0; size_of::<Limb>()];
data.read_exact(&mut bytes)?;
*limb = Limb::from_le_bytes(bytes);
}
}
};
println!("{v}");
for (input, result) in inputs.iter().zip(&mut *results) {
result.into().add(v.as_slice(), input.into().entry(i));
}
}
Ok(())
}
pub fn preimage(&self) -> &Matrix {
&self.preimage
}
pub fn pivots(&self) -> Option<&[isize]> {
self.image.as_deref()
}
pub fn prime(&self) -> ValidPrime {
self.preimage.prime()
}
/// Apply the quasi-inverse to an input vector and add a constant multiple of the result
/// to an output vector
///
/// # Arguments
/// * `target` - The output vector
/// * `coeff` - The constant multiple above
/// * `input` - The input vector, expressed in the basis of the ambient space
pub fn apply(&self, mut target: SliceMut, coeff: u32, input: Slice) {
let p = self.prime();
let mut row = 0;
for (i, c) in input.iter().enumerate() {
if let Some(pivots) = self.pivots() {
if i >= pivots.len() || pivots[i] < 0 {
continue;
}
}
if c != 0 {
target.add(self.preimage[row].as_slice(), (coeff * c) % *p);
}
row += 1;
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_stream_qi() {
let p = ValidPrime::new(2);
let qi = QuasiInverse {
image: Some(vec![0, -1, 1, -1, 2, 3]),
preimage: Matrix::from_vec(
p,
&[
vec![1, 0, 1, 1],
vec![1, 1, 0, 0],
vec![0, 1, 0, 1],
vec![1, 1, 1, 0],
],
),
};
let v0 = FpVector::from_slice(p, &[1, 1, 0, 0, 1, 0]);
let v1 = FpVector::from_slice(p, &[0, 0, 1, 0, 1, 1]);
let mut out0 = FpVector::new(p, 4);
let mut out1 = FpVector::new(p, 4);
let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
qi.to_bytes(&mut cursor).unwrap();
cursor.set_position(0);
QuasiInverse::stream_quasi_inverse(
p,
&mut cursor,
&mut [out0.as_slice_mut(), out1.as_slice_mut()],
&[v0.as_slice(), v1.as_slice()],
)
.unwrap();
let mut bench0 = FpVector::new(p, 4);
let mut bench1 = FpVector::new(p, 4);
qi.apply(bench0.as_slice_mut(), 1, v0.as_slice());
qi.apply(bench1.as_slice_mut(), 1, v1.as_slice());
assert_eq!(out0, bench0, "{out0} != {bench0}");
assert_eq!(out1, bench1, "{out1} != {bench1}");
assert_eq!(cursor.position() as usize, cursor.get_ref().len());
}
}