1extern crate float_cmp;
2extern crate byteorder;
3extern crate rand;
4extern crate cpu_time;
5
6use std::error::Error;
7use std::fs::{File, OpenOptions, remove_file};
8use std::io::{BufReader, BufWriter};
9use std::io::prelude::*;
10use std::path::Path;
11use std::str;
12use std::io;
13use std::fmt;
14
15use cpu_time::ProcessTime;
16use std::time::Duration;
17use byteorder::{ByteOrder, LittleEndian};
18use rand::{SeedableRng, StdRng, Rng};
19use float_cmp::ApproxEqUlps;
20
21const HEADER_LENGTH: usize = 25;
22const MAX_RECORD_LENGTH: usize = 32;
23const BUFFER_CAPACITY: usize = 1 * 1024 * 1024;
24const MODE_LENGTH: usize = 5;
25
26#[derive(Debug, Copy, Clone)]
27pub struct Header {
28 pub mode: [u8; 5],
29 pub total_particles: i32,
30 pub total_photons: i32,
31 pub min_energy: f32,
32 pub max_energy: f32,
33 pub total_particles_in_source: f32,
34 pub record_size: u64,
35 pub using_zlast: bool,
36}
37
38#[derive(Debug, Copy, Clone)]
39pub struct Record {
40 pub latch: u32,
41 total_energy: f32,
42 pub x_cm: f32,
43 pub y_cm: f32,
44 pub x_cos: f32,
45 pub y_cos: f32,
46 pub weight: f32, pub zlast: Option<f32>,
48}
49
50#[derive(Debug)]
51pub struct Transform;
52
53#[derive(Debug)]
54pub enum EGSError {
55 Io(io::Error),
56 BadMode,
57 BadLength,
58 ModeMismatch,
59 HeaderMismatch,
60 RecordMismatch,
61}
62
63pub type EGSResult<T> = Result<T, EGSError>;
64
65
66impl From<io::Error> for EGSError {
67 fn from(err: io::Error) -> EGSError {
68 EGSError::Io(err)
69 }
70}
71
72impl fmt::Display for EGSError {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 match *self {
75 EGSError::Io(ref err) => err.fmt(f),
76 EGSError::BadMode => {
77 write!(f,
78 "First 5 bytes of file are invalid, must be MODE0 or MODE2")
79 }
80 EGSError::BadLength => {
81 write!(f,
82 "Number of total particles does notmatch byte length of file")
83 }
84 EGSError::ModeMismatch => write!(f, "Input file MODE0/MODE2 do not match"),
85 EGSError::HeaderMismatch => write!(f, "Headers are different"),
86 EGSError::RecordMismatch => write!(f, "Records are different"),
87 }
88 }
89}
90
91impl Error for EGSError {
92 fn description(&self) -> &str {
93 match *self {
94 EGSError::Io(ref err) => err.description(),
95 EGSError::BadMode => "invalid mode",
96 EGSError::BadLength => "bad file length",
97 EGSError::ModeMismatch => "mode mismatch",
98 EGSError::HeaderMismatch => "header mismatch",
99 EGSError::RecordMismatch => "record mismatch",
100 }
101 }
102
103 fn cause(&self) -> Option<&dyn Error> {
104 match *self {
105 EGSError::Io(ref err) => Some(err),
106 EGSError::BadMode => None,
107 EGSError::BadLength => None,
108 EGSError::ModeMismatch => None,
109 EGSError::HeaderMismatch => None,
110 EGSError::RecordMismatch => None,
111 }
112 }
113}
114
115pub struct PHSPReader {
116 reader: BufReader<File>,
117 pub header: Header,
118 next_record: u64,
119}
120
121pub struct PHSPWriter {
122 writer: BufWriter<File>,
123 pub header: Header,
124}
125
126
127impl PHSPReader {
128 pub fn from(file: File) -> EGSResult<PHSPReader> {
129 let actual_size = file.metadata()?.len();
130 let mut reader = BufReader::with_capacity(BUFFER_CAPACITY, file);
131 let mut buffer = [0; HEADER_LENGTH];
132 reader.read_exact(&mut buffer)?;
133 let mut mode = [0; MODE_LENGTH];
134 mode.clone_from_slice(&buffer[0..5]);
135 let header = Header {
136 mode: mode,
137 total_particles: LittleEndian::read_i32(&buffer[5..9]),
138 total_photons: LittleEndian::read_i32(&buffer[9..13]),
139 max_energy: LittleEndian::read_f32(&buffer[13..17]),
140 min_energy: LittleEndian::read_f32(&buffer[17..21]),
141 total_particles_in_source: LittleEndian::read_f32(&buffer[21..25]),
142 using_zlast: &mode == b"MODE2",
143 record_size: if &mode == b"MODE0" {
144 28
145 } else if &mode == b"MODE2" {
146 32
147 } else {
148 return Err(EGSError::BadMode);
149 },
150 };
151 if actual_size != header.expected_size() as u64 {
152 writeln!(&mut std::io::stderr(),
153 "Expected {} bytes in file, not {}",
154 header.expected_size(),
155 actual_size)
156 .unwrap();
157 }
159 reader.consume(header.record_size as usize - HEADER_LENGTH);
160 Ok(PHSPReader {
161 reader: reader,
162 header: header,
163 next_record: 0,
164 })
165 }
166}
167
168impl Iterator for PHSPReader {
169 type Item = EGSResult<Record>;
170 fn next(&mut self) -> Option<EGSResult<Record>> {
171 if self.next_record >= self.header.total_particles as u64 {
172 return None;
173 }
174 let mut buffer = [0; MAX_RECORD_LENGTH];
175 match self.reader.read_exact(&mut buffer[..self.header.record_size as usize]) {
176 Ok(()) => (),
177 Err(err) => return Some(Err(EGSError::Io(err))),
178 };
179 self.next_record += 1;
180 Some(Ok(Record {
181 latch: LittleEndian::read_u32(&buffer[0..4]),
182 total_energy: LittleEndian::read_f32(&buffer[4..8]),
183 x_cm: LittleEndian::read_f32(&buffer[8..12]),
184 y_cm: LittleEndian::read_f32(&buffer[12..16]),
185 x_cos: LittleEndian::read_f32(&buffer[16..20]),
186 y_cos: LittleEndian::read_f32(&buffer[20..24]),
187 weight: LittleEndian::read_f32(&buffer[24..28]),
188 zlast: if self.header.using_zlast {
189 Some(LittleEndian::read_f32(&buffer[28..32]))
190 } else {
191 None
192 },
193 }))
194 }
195}
196
197impl PHSPWriter {
198 pub fn from(file: File, header: &Header) -> EGSResult<PHSPWriter> {
199 let mut writer = BufWriter::with_capacity(BUFFER_CAPACITY, file);
200 let mut buffer = [0; MAX_RECORD_LENGTH];
201 buffer[0..5].clone_from_slice(&header.mode);
202 LittleEndian::write_i32(&mut buffer[5..9], header.total_particles);
203 LittleEndian::write_i32(&mut buffer[9..13], header.total_photons);
204 LittleEndian::write_f32(&mut buffer[13..17], header.max_energy);
205 LittleEndian::write_f32(&mut buffer[17..21], header.min_energy);
206 LittleEndian::write_f32(&mut buffer[21..25], header.total_particles_in_source);
207 writer.write_all(&buffer[..header.record_size as usize])?;
208 Ok(PHSPWriter {
209 header: *header,
210 writer: writer,
211 })
212 }
213
214 pub fn write(&mut self, record: &Record) -> EGSResult<()> {
215 let mut buffer = [0; 32];
216 LittleEndian::write_u32(&mut buffer[0..4], record.latch);
217 LittleEndian::write_f32(&mut buffer[4..8], record.total_energy);
218 LittleEndian::write_f32(&mut buffer[8..12], record.x_cm);
219 LittleEndian::write_f32(&mut buffer[12..16], record.y_cm);
220 LittleEndian::write_f32(&mut buffer[16..20], record.x_cos);
221 LittleEndian::write_f32(&mut buffer[20..24], record.y_cos);
222 LittleEndian::write_f32(&mut buffer[24..28], record.weight);
223 if self.header.using_zlast {
224 LittleEndian::write_f32(&mut buffer[28..32], record.weight);
225 }
226 self.writer.write_all(&buffer[..self.header.record_size as usize])?;
227 Ok(())
228 }
229}
230
231impl Header {
232 fn expected_size(&self) -> usize {
233 (self.total_particles as usize + 1) * self.record_size as usize
234 }
235 pub fn similar_to(&self, other: &Header) -> bool {
236 self.mode == other.mode && self.total_particles == other.total_particles &&
237 self.total_photons == other.total_photons &&
238 self.max_energy.approx_eq_ulps(&other.max_energy, 10) &&
239 self.min_energy.approx_eq_ulps(&other.min_energy, 10) &&
240 self.total_particles_in_source.approx_eq_ulps(&other.total_particles_in_source, 2)
241 }
242 fn merge(&mut self, other: &Header) {
243 assert!(&self.mode == &other.mode, "Merge mode mismatch");
244 self.total_particles = self.total_particles
245 .checked_add(other.total_particles)
246 .expect("Too many particles, i32 overflow");
247 self.total_photons += other.total_photons;
248 self.min_energy = self.min_energy.min(other.min_energy);
249 self.max_energy = self.max_energy.max(other.max_energy);
250 self.total_particles_in_source += other.total_particles_in_source;
251 }
252}
253
254
255impl Record {
256 pub fn similar_to(&self, other: &Record) -> bool {
257 self.latch == other.latch && self.total_energy() - other.total_energy() < 0.01 &&
258 self.x_cm - other.x_cm < 0.01 && self.y_cm - other.y_cm < 0.01 &&
259 self.x_cos - other.x_cos < 0.01 && self.y_cos - other.y_cos < 0.01 &&
260 self.weight - other.weight < 0.01 && self.zlast == other.zlast
261 }
262 pub fn bremsstrahlung_or_annihilation(&self) -> bool {
263 self.latch & 1 != 0
264 }
265 pub fn bit_region(&self) -> u32 {
266 self.latch & 0xfffffe
267 }
268 pub fn region_number(&self) -> u32 {
269 self.latch & 0xf000000
270 }
271 pub fn b29(&self) -> bool {
272 self.latch & (1 << 29) != 0
273 }
274 pub fn charged(&self) -> bool {
275 self.latch & (1 << 30) != 0
276 }
277 pub fn crossed_multiple(&self) -> bool {
278 self.latch & (1 << 30) != 0
279 }
280 pub fn get_weight(&self) -> f32 {
281 self.weight.abs()
282 }
283 pub fn set_weight(&mut self, new_weight: f32) {
284 self.weight = new_weight * self.weight.signum();
285 }
286 pub fn total_energy(&self) -> f32 {
287 self.total_energy.abs()
288 }
289 pub fn z_positive(&self) -> bool {
290 self.weight.is_sign_positive()
291 }
292 pub fn z_cos(&self) -> f32 {
293 (1.0 - (self.x_cos * self.x_cos + self.y_cos * self.y_cos)).sqrt()
294 }
295 pub fn first_scored_by_primary_history(&self) -> bool {
296 return self.total_energy.is_sign_negative();
297 }
298
299 fn transform(&mut self, matrix: &[[f32; 3]; 3]) {
300 let x_cm = self.x_cm;
301 let y_cm = self.y_cm;
302 self.x_cm = matrix[0][0] * x_cm + matrix[0][1] * y_cm + matrix[0][2] * 1.0;
303 self.y_cm = matrix[1][0] * x_cm + matrix[1][1] * y_cm + matrix[1][2] * 1.0;
304 let x_cos = self.x_cos;
305 let y_cos = self.y_cos;
306 self.x_cos = matrix[0][0] * x_cos + matrix[0][1] * y_cos + matrix[0][2] * self.z_cos();
307 self.y_cos = matrix[1][0] * x_cos + matrix[1][1] * y_cos + matrix[1][2] * self.z_cos();
308 }
309}
310
311impl Transform {
312 pub fn rotation(matrix: &mut [[f32; 3]; 3], theta: f32) {
313 *matrix =
314 [[theta.cos(), -theta.sin(), 0.0], [theta.sin(), theta.cos(), 0.0], [0.0, 0.0, 1.0]];
315 }
316}
317
318
319
320pub fn combine(input_paths: &[&Path], output_path: &Path, delete: bool) -> EGSResult<()> {
321 assert!(input_paths.len() > 0, "Cannot combine zero files");
322 let start = ProcessTime::now();
323 let reader = PHSPReader::from(File::open(input_paths[0])?)?;
324 let mut final_header = reader.header;
325 for path in input_paths[1..].iter() {
326 let reader = PHSPReader::from(File::open(path)?)?;
327 final_header.merge(&reader.header);
328 }
329 println!("");
330 println!("Final header: {:?}", final_header);
331 println!("");
332 let ofile = File::create(output_path)?;
333 let mut writer = PHSPWriter::from(ofile, &final_header)?;
334 for path in input_paths.iter() {
335 let reader = PHSPReader::from(File::open(path)?)?;
336 for record in reader {
337 writer.write(&record.unwrap())?
338 }
339 if delete {
340 remove_file(path)?;
341 }
342 }
343 let cpu_time: Duration = start.elapsed();
344 println!("CPU time: {:?}", cpu_time);
345 Ok(())
346}
347
348pub fn sample(ipaths: &[&Path], opath: &Path, rate: u32, seed: &[usize]) -> EGSResult<()> {
349 assert!(ipaths.len() > 0, "Cannot combine zero files");
350 let mut rng: StdRng = SeedableRng::from_seed(seed);
351 let mut header = Header {
352 mode: *b"MODE0",
353 record_size: 28,
354 using_zlast: false,
355 total_particles: 0,
356 total_photons: 0,
357 min_energy: 1000.0,
358 max_energy: 0.0,
359 total_particles_in_source: 0.0,
360 };
361 let mut writer = PHSPWriter::from(File::create(opath)?, &header)?;
362 for path in ipaths.iter() {
363 let reader = PHSPReader::from(File::open(path)?)?;
364 assert!(!reader.header.using_zlast);
365 println!("Found {} particles", reader.header.total_particles);
366 header.total_particles_in_source += reader.header.total_particles_in_source;
367 let records = reader.filter(|_| rng.gen_weighted_bool(rate));
368 for record in records.map(|r| r.unwrap()) {
369 header.total_particles =
370 header.total_particles.checked_add(1).expect("Total particles overflow");
371 if !record.charged() {
372 header.total_photons += 1;
373 }
374 if record.total_energy > 0.0 {
375 header.min_energy = header.min_energy.min(record.total_energy);
376 header.max_energy = header.max_energy.max(record.total_energy);
377 }
378 writer.write(&record)?;
379 }
380 println!("Now have {} particles", header.total_particles);
381 }
382 header.total_particles_in_source /= rate as f32;
383 drop(writer);
384 let ofile = OpenOptions::new().write(true).create(true).open(opath)?;
386 PHSPWriter::from(ofile, &header)?;
387 Ok(())
388}
389
390pub fn transform(input_path: &Path, output_path: &Path, matrix: &[[f32; 3]; 3]) -> EGSResult<()> {
391 let ifile = File::open(input_path)?;
392 let reader = PHSPReader::from(ifile)?;
393 let ofile;
394 if input_path == output_path {
395 println!("Transforming {} in place", input_path.display());
396 ofile = OpenOptions::new().write(true).create(true).open(output_path)?;
397 } else {
398 println!("Transforming {} and saving to {}",
400 input_path.display(),
401 output_path.display());
402 ofile = File::create(output_path)?;
403 }
404 let mut writer = PHSPWriter::from(ofile, &reader.header)?;
405 let n_particles = reader.header.total_particles;
406 let mut records_transformed = 0;
407 for mut record in reader.map(|r| r.unwrap()) {
408 record.transform(&matrix);
409 writer.write(&record)?;
410 records_transformed += 1;
411 }
412 println!("Transformed {} records, expected {}",
413 records_transformed,
414 n_particles);
415 Ok(())
416}