1extern crate byteorder;
2extern crate float_cmp;
3extern crate rand;
4
5use std::fmt;
6use std::fs::{remove_file, File, OpenOptions};
7use std::io;
8use std::io::prelude::*;
9use std::io::{BufReader, BufWriter};
10use std::path::Path;
11
12use byteorder::{ByteOrder, LittleEndian};
13use float_cmp::ApproxEqUlps;
14use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
15
16const HEADER_LENGTH: usize = 25;
17const MAX_RECORD_LENGTH: usize = 32;
18const BUFFER_CAPACITY: usize = 1 * 1024 * 1024;
19const MODE_LENGTH: usize = 5;
20const BATCHES: usize = 128; #[derive(Debug, Copy, Clone)]
23pub struct Header {
24 pub mode: [u8; 5],
25 pub total_particles: i32,
26 pub total_photons: i32,
27 pub min_energy: f32,
28 pub max_energy: f32,
29 pub total_particles_in_source: f32,
30 pub record_size: u64,
31 pub using_zlast: bool,
32}
33
34#[derive(Debug, Copy, Clone)]
35pub struct Record {
36 pub latch: u32,
37 total_energy: f32,
38 pub x_cm: f32,
39 pub y_cm: f32,
40 pub x_cos: f32, pub y_cos: f32,
42 pub weight: f32, pub zlast: Option<f32>,
44}
45
46#[derive(Debug)]
47pub struct Transform;
48
49#[derive(Debug)]
50pub enum EGSError {
51 Io(io::Error),
52 BadMode,
53 BadLength,
54 ModeMismatch,
55 HeaderMismatch,
56 RecordMismatch,
57}
58
59pub type EGSResult<T> = Result<T, EGSError>;
60
61impl From<io::Error> for EGSError {
62 fn from(err: io::Error) -> EGSError {
63 EGSError::Io(err)
64 }
65}
66
67impl fmt::Display for EGSError {
68 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 match *self {
70 EGSError::Io(ref err) => err.fmt(f),
71 EGSError::BadMode => {
72 write!(
73 f,
74 "First 5 bytes of file are invalid, must be MODE0 or MODE2"
75 )
76 }
77 EGSError::BadLength => {
78 write!(
79 f,
80 "Number of total particles does notmatch byte length of file"
81 )
82 }
83 EGSError::ModeMismatch => write!(f, "Input file MODE0/MODE2 do not match"),
84 EGSError::HeaderMismatch => write!(f, "Headers are different"),
85 EGSError::RecordMismatch => write!(f, "Records are different"),
86 }
87 }
88}
89
90pub struct PHSPReader {
91 reader: BufReader<File>,
92 pub header: Header,
93 next_record: u64,
94}
95
96pub struct PHSPWriter {
97 writer: BufWriter<File>,
98 pub header: Header,
99}
100
101impl PHSPReader {
102 pub fn from(file: File) -> EGSResult<PHSPReader> {
103 let actual_size = (file.metadata()?).len();
104 let mut reader = BufReader::with_capacity(BUFFER_CAPACITY, file);
105 let mut buffer = [0; HEADER_LENGTH];
106 reader.read_exact(&mut buffer)?;
107 let mut mode = [0; MODE_LENGTH];
108 mode.clone_from_slice(&buffer[0..5]);
109 let header = Header {
110 mode: mode,
111 total_particles: LittleEndian::read_i32(&buffer[5..9]),
112 total_photons: LittleEndian::read_i32(&buffer[9..13]),
113 max_energy: LittleEndian::read_f32(&buffer[13..17]),
114 min_energy: LittleEndian::read_f32(&buffer[17..21]),
115 total_particles_in_source: LittleEndian::read_f32(&buffer[21..25]),
116 using_zlast: &mode == b"MODE2",
117 record_size: if &mode == b"MODE0" {
118 28
119 } else if &mode == b"MODE2" {
120 32
121 } else {
122 return Err(EGSError::BadMode);
123 },
124 };
125 if actual_size != header.expected_size() as u64 {
126 writeln!(
127 &mut std::io::stderr(),
128 "Expected {} bytes in file, not {}",
129 header.expected_size(),
130 actual_size
131 )
132 .unwrap();
133 }
135 reader.consume(header.record_size as usize - HEADER_LENGTH);
136 Ok(PHSPReader {
137 reader: reader,
138 header: header,
139 next_record: 0,
140 })
141 }
142 fn exhausted(&self) -> bool {
143 self.next_record >= self.header.total_particles as u64
144 }
145}
146
147impl Iterator for PHSPReader {
148 type Item = EGSResult<Record>;
149 fn next(&mut self) -> Option<EGSResult<Record>> {
150 if self.next_record >= self.header.total_particles as u64 {
151 return None;
152 }
153 let mut buffer = [0; MAX_RECORD_LENGTH];
154 match self
155 .reader
156 .read_exact(&mut buffer[..self.header.record_size as usize])
157 {
158 Ok(()) => (),
159 Err(err) => return Some(Err(EGSError::Io(err))),
160 };
161 self.next_record += 1;
162 Some(Ok(Record {
163 latch: LittleEndian::read_u32(&buffer[0..4]),
164 total_energy: LittleEndian::read_f32(&buffer[4..8]),
165 x_cm: LittleEndian::read_f32(&buffer[8..12]),
166 y_cm: LittleEndian::read_f32(&buffer[12..16]),
167 x_cos: LittleEndian::read_f32(&buffer[16..20]),
168 y_cos: LittleEndian::read_f32(&buffer[20..24]),
169 weight: LittleEndian::read_f32(&buffer[24..28]),
170 zlast: if self.header.using_zlast {
171 Some(LittleEndian::read_f32(&buffer[28..32]))
172 } else {
173 None
174 },
175 }))
176 }
177}
178
179impl PHSPWriter {
180 pub fn from(file: File, header: &Header) -> EGSResult<PHSPWriter> {
181 let mut writer = BufWriter::with_capacity(BUFFER_CAPACITY, file);
182 let mut buffer = [0; MAX_RECORD_LENGTH];
183 buffer[0..5].clone_from_slice(&header.mode);
184 LittleEndian::write_i32(&mut buffer[5..9], header.total_particles);
185 LittleEndian::write_i32(&mut buffer[9..13], header.total_photons);
186 LittleEndian::write_f32(&mut buffer[13..17], header.max_energy);
187 LittleEndian::write_f32(&mut buffer[17..21], header.min_energy);
188 LittleEndian::write_f32(&mut buffer[21..25], header.total_particles_in_source);
189 writer.write_all(&buffer[..header.record_size as usize])?;
190 Ok(PHSPWriter {
191 header: *header,
192 writer: writer,
193 })
194 }
195
196 pub fn write(&mut self, record: &Record) -> EGSResult<()> {
197 let mut buffer = [0; 32];
198 LittleEndian::write_u32(&mut buffer[0..4], record.latch);
199 LittleEndian::write_f32(&mut buffer[4..8], record.total_energy);
200 LittleEndian::write_f32(&mut buffer[8..12], record.x_cm);
201 LittleEndian::write_f32(&mut buffer[12..16], record.y_cm);
202 LittleEndian::write_f32(&mut buffer[16..20], record.x_cos);
203 LittleEndian::write_f32(&mut buffer[20..24], record.y_cos);
204 LittleEndian::write_f32(&mut buffer[24..28], record.weight);
205 if self.header.using_zlast {
206 LittleEndian::write_f32(&mut buffer[28..32], record.weight);
207 }
208 self.writer
209 .write_all(&buffer[..self.header.record_size as usize])?;
210 Ok(())
211 }
212}
213
214impl Header {
215 fn expected_size(&self) -> usize {
216 (self.total_particles as usize + 1) * self.record_size as usize
217 }
218 pub fn similar_to(&self, other: &Header) -> bool {
219 self.mode == other.mode
220 && self.total_particles == other.total_particles
221 && self.total_photons == other.total_photons
222 && self.max_energy.approx_eq_ulps(&other.max_energy, 10)
223 && self.min_energy.approx_eq_ulps(&other.min_energy, 10)
224 && self
225 .total_particles_in_source
226 .approx_eq_ulps(&other.total_particles_in_source, 2)
227 }
228 fn merge(&mut self, other: &Header) {
229 assert!(&self.mode == &other.mode, "Merge mode mismatch");
230 self.total_particles = self
231 .total_particles
232 .checked_add(other.total_particles)
233 .expect("Too many particles, i32 overflow");
234 self.total_photons += other.total_photons;
235 self.min_energy = self.min_energy.min(other.min_energy);
236 self.max_energy = self.max_energy.max(other.max_energy);
237 self.total_particles_in_source += other.total_particles_in_source;
238 }
239}
240
241impl Record {
242 pub fn similar_to(&self, other: &Record) -> bool {
243 self.latch == other.latch
244 && self.total_energy() - other.total_energy() < 0.01
245 && self.x_cm - other.x_cm < 0.01
246 && self.y_cm - other.y_cm < 0.01
247 && self.x_cos - other.x_cos < 0.01
248 && self.y_cos - other.y_cos < 0.01
249 && self.weight - other.weight < 0.01
250 && self.zlast == other.zlast
251 }
252 pub fn bremsstrahlung_or_annihilation(&self) -> bool {
253 self.latch & 1 != 0
254 }
255 pub fn bit_region(&self) -> u32 {
256 self.latch & 0xfffffe
257 }
258 pub fn region_number(&self) -> u32 {
259 self.latch & 0xf000000
260 }
261 pub fn b29(&self) -> bool {
262 self.latch & (1 << 29) != 0
263 }
264 pub fn charged(&self) -> bool {
265 self.latch & (1 << 30) != 0
266 }
267 pub fn crossed_multiple(&self) -> bool {
268 self.latch & (1 << 30) != 0
269 }
270 pub fn get_weight(&self) -> f32 {
271 self.weight.abs()
272 }
273 pub fn set_weight(&mut self, new_weight: f32) {
274 self.weight = new_weight * self.weight.signum();
275 }
276 pub fn total_energy(&self) -> f32 {
277 self.total_energy.abs()
278 }
279 pub fn z_positive(&self) -> bool {
280 self.weight.is_sign_positive()
281 }
282 pub fn z_cos(&self) -> f32 {
283 (1.0 - (self.x_cos * self.x_cos + self.y_cos * self.y_cos)).sqrt()
284 }
285 pub fn first_scored_by_primary_history(&self) -> bool {
286 return self.total_energy.is_sign_negative();
287 }
288
289 fn translate(&mut self, x: f32, y: f32) {
290 self.x_cm += x;
291 self.y_cm += y;
292 }
293
294 fn transform(&mut self, matrix: &[[f32; 3]; 3]) {
295 let x_cm = self.x_cm;
296 let y_cm = self.y_cm;
297 self.x_cm = matrix[0][0] * x_cm + matrix[0][1] * y_cm + matrix[0][2] * 1.0;
298 self.y_cm = matrix[1][0] * x_cm + matrix[1][1] * y_cm + matrix[1][2] * 1.0;
299 let x_cos = self.x_cos;
300 let y_cos = self.y_cos;
301 self.x_cos = matrix[0][0] * x_cos + matrix[0][1] * y_cos + matrix[0][2] * self.z_cos();
302 self.y_cos = matrix[1][0] * x_cos + matrix[1][1] * y_cos + matrix[1][2] * self.z_cos();
303 }
304}
305
306impl Transform {
307 pub fn reflection(matrix: &mut [[f32; 3]; 3], x_raw: f32, y_raw: f32) {
308 let norm = (x_raw * x_raw + y_raw * y_raw).sqrt();
309 let x = x_raw / norm;
310 let y = y_raw / norm;
311 *matrix = [
312 [x * x - y * y, 2.0 * x * y, 0.0],
313 [2.0 * x * y, y * y - x * x, 0.0],
314 [0.0, 0.0, 1.0],
315 ];
316 }
317 pub fn rotation(matrix: &mut [[f32; 3]; 3], theta: f32) {
318 *matrix = [
319 [theta.cos(), -theta.sin(), 0.0],
320 [theta.sin(), theta.cos(), 0.0],
321 [0.0, 0.0, 1.0],
322 ];
323 }
324}
325
326pub fn randomize(path: &Path, seed: u64) -> EGSResult<()> {
327 let mut rng = StdRng::seed_from_u64(seed);
328 let ifile = File::open(path)?;
329 let mut reader = PHSPReader::from(ifile)?;
330 let header = reader.header;
331 let max_per_batch = reader.header.total_particles as usize / BATCHES + 1;
332 let mut batch_paths = Vec::with_capacity(BATCHES);
333 for i in 0..BATCHES {
334 let mut batch_path = path.to_path_buf();
335 batch_path.set_extension(format!("rand{}", i));
336 batch_paths.push(batch_path);
337 }
338 let mut records = Vec::with_capacity(max_per_batch);
339 for path in batch_paths.iter() {
340 for _ in 0..max_per_batch {
341 match reader.next() {
342 Some(record) => records.push(record.unwrap()),
343 None => (),
344 }
345 }
346 records.shuffle(&mut rng);
349
350 let header = Header {
351 mode: reader.header.mode,
352 total_particles: records.len() as i32,
353 total_photons: 0,
354 max_energy: 0.0,
355 min_energy: 0.0,
356 total_particles_in_source: 0.0,
357 using_zlast: &reader.header.mode == b"MODE2",
358 record_size: reader.header.record_size,
359 };
360 let ofile = File::create(&path)?;
361 let mut writer = PHSPWriter::from(ofile, &header)?;
362 for record in records.iter() {
363 writer.write(&record)?;
364 }
365 records.clear();
366 }
367 drop(records);
368 let mut readers = Vec::with_capacity(BATCHES);
369 for path in batch_paths.iter() {
370 let ifile = File::open(&path)?;
371 readers.push(PHSPReader::from(ifile)?);
372 }
373
374 let ofile = File::create(path)?;
375 let mut writer = PHSPWriter::from(ofile, &header)?;
376 while readers.len() != 0 {
377 readers.shuffle(&mut rng);
378 for reader in readers.iter_mut() {
379 match reader.next() {
380 Some(record) => writer.write(&record.unwrap())?,
381 None => (),
382 }
383 }
384 readers.retain(|r| !r.exhausted());
385 }
386 for path in batch_paths.iter() {
387 remove_file(path)?;
388 }
389 Ok(())
390}
391
392pub fn combine(input_paths: &[&Path], output_path: &Path, delete: bool) -> EGSResult<()> {
393 assert!(input_paths.len() > 0, "Cannot combine zero files");
394 let reader = PHSPReader::from(File::open(input_paths[0])?)?;
395 let mut final_header = reader.header;
396 for path in input_paths[1..].iter() {
397 let reader = PHSPReader::from(File::open(path)?)?;
398 final_header.merge(&reader.header);
399 }
400 println!("Final header: {:?}", final_header);
401 let ofile = File::create(output_path)?;
402 let mut writer = PHSPWriter::from(ofile, &final_header)?;
403 for path in input_paths.iter() {
404 let reader = PHSPReader::from(File::open(path)?)?;
405 for record in reader {
406 writer.write(&record.unwrap())?
407 }
408 if delete {
409 remove_file(path)?;
410 }
411 }
412 Ok(())
413}
414
415pub fn compare(path1: &Path, path2: &Path) -> EGSResult<()> {
416 let ifile1 = File::open(path1)?;
417 let ifile2 = File::open(path2)?;
418 let reader1 = PHSPReader::from(ifile1)?;
419 let reader2 = PHSPReader::from(ifile2)?;
420 println!(" First\t\tSecond");
421 println!(
422 "Total particles: {0: <10}\t\t{1:}",
423 reader1.header.total_particles, reader2.header.total_particles
424 );
425 println!(
426 "Total photons: {0: <10}\t\t{1}",
427 reader1.header.total_photons, reader2.header.total_photons
428 );
429 println!(
430 "Minimum energy: {0: <10}\t\t{1}",
431 reader1.header.min_energy, reader2.header.min_energy
432 );
433 println!(
434 "Maximum energy: {0: <10}\t\t{1}",
435 reader1.header.max_energy, reader2.header.max_energy
436 );
437 println!(
438 "Source particles: {0: <10}\t\t{1}",
439 reader1.header.total_particles_in_source, reader2.header.total_particles_in_source
440 );
441 if !reader1.header.similar_to(&reader2.header) {
442 println!("Headers different");
443 return Err(EGSError::HeaderMismatch);
444 } else {
445 for (record1, record2) in reader1.zip(reader2) {
446 let r1 = record1.unwrap();
447 let r2 = record2.unwrap();
448 if !r1.similar_to(&r2) {
449 println!("{:?} != {:?}", r1, r2);
450 return Err(EGSError::RecordMismatch);
451 }
452 }
453 }
454 Ok(())
455}
456
457pub fn sample_combine(ipaths: &[&Path], opath: &Path, rate: f64, seed: u64) -> EGSResult<()> {
458 assert!(ipaths.len() > 0, "Cannot combine zero files");
459 let mut rng = StdRng::seed_from_u64(seed);
460 let mut header = Header {
461 mode: *b"MODE0",
462 record_size: 28,
463 using_zlast: false,
464 total_particles: 0,
465 total_photons: 0,
466 min_energy: 1000.0,
467 max_energy: 0.0,
468 total_particles_in_source: 0.0,
469 };
470 let mut writer = PHSPWriter::from(File::create(opath)?, &header)?;
471 for path in ipaths.iter() {
472 let reader = PHSPReader::from(File::open(path)?)?;
473 assert!(!reader.header.using_zlast);
474 println!("Found {} particles", reader.header.total_particles);
475 header.total_particles_in_source += reader.header.total_particles_in_source;
476 let records = reader.filter(|_| rng.random_bool(rate));
477 for record in records.map(|r| r.unwrap()) {
478 header.total_particles = header
479 .total_particles
480 .checked_add(1)
481 .expect("Total particles overflow");
482 if !record.charged() {
483 header.total_photons += 1;
484 }
485 if record.total_energy > 0.0 {
486 header.min_energy = header.min_energy.min(record.total_energy);
487 header.max_energy = header.max_energy.max(record.total_energy);
488 }
489 writer.write(&record)?;
490 }
491 println!("Now have {} particles", header.total_particles);
492 }
493 header.total_particles_in_source *= rate as f32;
494 drop(writer);
495 let ofile = OpenOptions::new().write(true).create(true).open(opath)?;
497 PHSPWriter::from(ofile, &header)?;
498 Ok(())
499}
500
501pub fn translate(input_path: &Path, output_path: &Path, x: f32, y: f32) -> EGSResult<()> {
502 let ifile = File::open(input_path)?;
503 let reader = PHSPReader::from(ifile)?;
504 let ofile;
505 if input_path == output_path {
506 println!(
507 "Translating {} in place by ({}, {})",
508 input_path.display(),
509 x,
510 y
511 );
512 ofile = OpenOptions::new()
513 .write(true)
514 .create(true)
515 .open(output_path)?;
516 } else {
517 println!(
518 "Translating {} by ({}, {}) and saving to {}",
519 input_path.display(),
520 x,
521 y,
522 output_path.display()
523 );
524 ofile = File::create(output_path)?;
525 }
526 let mut writer = PHSPWriter::from(ofile, &reader.header)?;
527 let n_particles = reader.header.total_particles;
528 let mut records_translated = 0;
529 for mut record in reader.map(|r| r.unwrap()) {
530 record.translate(x, y);
531 writer.write(&record)?;
532 records_translated += 1;
533 }
534 println!(
535 "Translated {} records, expected {}",
536 records_translated, n_particles
537 );
538 Ok(())
539}
540
541pub fn transform(input_path: &Path, output_path: &Path, matrix: &[[f32; 3]; 3]) -> EGSResult<()> {
542 let ifile = File::open(input_path)?;
543 let reader = PHSPReader::from(ifile)?;
544 let ofile;
545 if input_path == output_path {
546 println!("Transforming {} in place", input_path.display());
547 ofile = OpenOptions::new()
548 .write(true)
549 .create(true)
550 .open(output_path)?;
551 } else {
552 println!(
554 "Transforming {} and saving to {}",
555 input_path.display(),
556 output_path.display()
557 );
558 ofile = File::create(output_path)?;
559 }
560 let mut writer = PHSPWriter::from(ofile, &reader.header)?;
561 let n_particles = reader.header.total_particles;
562 let mut records_transformed = 0;
563 for mut record in reader.map(|r| r.unwrap()) {
564 record.transform(&matrix);
565 writer.write(&record)?;
566 records_transformed += 1;
567 }
568 println!(
569 "Transformed {} records, expected {}",
570 records_transformed, n_particles
571 );
572 Ok(())
573}
574
575pub fn reweight(
576 input_path: &Path,
577 output_path: &Path,
578 f: &dyn Fn(f32) -> f32,
579 number_bins: usize,
580 max_radius: f32,
581) -> EGSResult<()> {
582 let input_file = File::open(input_path)?;
583 let output_file;
584 if input_path == output_path {
585 println!("Reweighting in-place");
586 output_file = OpenOptions::new()
587 .write(true)
588 .create(true)
589 .open(output_path)?;
590 } else {
591 println!("Rewighting and saving to {}", output_path.display());
592 output_file = File::create(output_path)?;
593 }
594 let reader1 = PHSPReader::from(input_file)?;
595 let mut writer1 = PHSPWriter::from(output_file, &reader1.header)?;
596 let bin_size = max_radius / number_bins as f32;
597 println!("Bin size is {}", bin_size);
598 let mut sum_old_weight = 0.0;
599 let mut sum_new_weight = 0.0;
600 for mut record in reader1.map(|r| r.unwrap()) {
601 sum_old_weight += record.weight;
602 let r = (record.x_cm * record.x_cm + record.y_cm * record.y_cm).sqrt();
603 record.weight *= f(r);
604 sum_new_weight += record.weight;
605 writer1.write(&record)?;
606 }
607 drop(writer1);
608 let ifile2 = File::open(input_path)?;
609 let ofile2;
610 if input_path == output_path {
611 ofile2 = OpenOptions::new()
612 .write(true)
613 .create(true)
614 .open(output_path)?;
615 } else {
616 ofile2 = File::create(output_path)?;
617 }
618 let reader2 = PHSPReader::from(ifile2)?;
619 let mut writer2 = PHSPWriter::from(ofile2, &reader2.header)?;
620 let factor = sum_old_weight / sum_new_weight;
621 for mut record in reader2.map(|r| r.unwrap()) {
622 record.weight *= factor;
623 writer2.write(&record)?;
624 }
625 Ok(())
626}