extern crate nalgebra;
use std::marker::PhantomData;
use super::emit::AtomNumberToEmit;
use super::precalc::{MaxwellBoltzmannSource, PrecalculatedSpeciesInformation};
use super::species::{AtomCreator};
use crate::constant;
use crate::constant::PI;
use crate::initiate::*;
use super::VelocityCap;
use super::WeightedProbabilityDistribution;
use rand;
use rand::distributions::Distribution;
use rand::Rng;
extern crate specs;
use crate::atom::*;
use nalgebra::Vector3;
use specs::{Component, Entities, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System};
fn velocity_generate(
v_mag: f64,
new_dir: &Vector3<f64>,
theta_distribution: &WeightedProbabilityDistribution,
) -> (Vector3<f64>, f64) {
let dir = &new_dir.normalize();
let dir_1 = new_dir.cross(&Vector3::new(2.0, 1.0, 0.5)).normalize();
let dir_2 = new_dir.cross(&dir_1).normalize();
let mut rng = rand::thread_rng();
let theta = theta_distribution.sample(&mut rng);
let phi = rng.gen_range(0.0..2.0 * PI);
let dir_div = dir_1 * theta.sin() * phi.cos() + dir_2 * theta.sin() * phi.sin();
let dirf = dir * theta.cos() + dir_div;
let v_out = dirf * v_mag;
(v_out, theta)
}
#[derive(Copy, Clone)]
pub enum OvenAperture {
Cubic { size: [f64; 3] },
Circular { radius: f64, thickness: f64 },
}
pub struct OvenBuilder<T> where T : AtomCreator {
temperature: f64,
aperture: OvenAperture,
direction: Vector3<f64>,
microchannel_radius: f64,
microchannel_length: f64,
max_theta: f64,
phantom: PhantomData<T>
}
impl<T> OvenBuilder<T> where T : AtomCreator {
pub fn new(temperature_kelvin: f64, direction: Vector3<f64>) -> Self {
Self {
temperature: temperature_kelvin,
aperture: OvenAperture::Circular {
radius: 3.0e-3,
thickness: 1.0e-3,
},
direction: direction.normalize(),
microchannel_length: 4e-3,
microchannel_radius: 0.2e-3,
max_theta: PI / 2.0,
phantom: PhantomData
}
}
pub fn with_microchannels(
&mut self,
microchannel_length: f64,
microchannel_radius: f64,
) -> &mut Self {
self.microchannel_length = microchannel_length;
self.microchannel_radius = microchannel_radius;
self
}
pub fn with_lip(&mut self, lip_length: f64, lip_radius: f64) -> &mut Self {
self.max_theta = (lip_radius / lip_length).atan();
self
}
pub fn with_aperture(&mut self, aperture: OvenAperture) -> &mut Self {
self.aperture = aperture;
self
}
pub fn build(&self) -> Oven<T> {
Oven {
temperature: self.temperature,
aperture: self.aperture,
direction: self.direction.normalize(),
theta_distribution: create_jtheta_distribution(
self.microchannel_radius,
self.microchannel_length,
),
max_theta: self.max_theta,
phantom: PhantomData
}
}
}
pub struct Oven<T> where T : AtomCreator {
pub temperature: f64,
pub aperture: OvenAperture,
pub direction: Vector3<f64>,
theta_distribution: WeightedProbabilityDistribution,
pub max_theta: f64,
phantom : PhantomData<T>
}
impl<T> MaxwellBoltzmannSource for Oven<T> where T : AtomCreator {
fn get_temperature(&self) -> f64 {
self.temperature
}
fn get_v_dist_power(&self) -> f64 {
3.0
}
}
impl<T> Component for Oven<T> where T : AtomCreator + 'static {
type Storage = HashMapStorage<Self>;
}
impl<T> Oven<T> where T : AtomCreator {
pub fn get_random_spawn_position(&self) -> Vector3<f64> {
let mut rng = rand::thread_rng();
match self.aperture {
OvenAperture::Cubic { size } => {
let size = size;
let pos1 = rng.gen_range(-0.5 * size[0]..0.5 * size[0]);
let pos2 = rng.gen_range(-0.5 * size[1]..0.5 * size[1]);
let pos3 = rng.gen_range(-0.5 * size[2]..0.5 * size[2]);
Vector3::new(pos1, pos2, pos3)
}
OvenAperture::Circular { radius, thickness } => {
let dir = self.direction.normalize();
let dir_1 = dir.cross(&Vector3::new(2.0, 1.0, 0.5)).normalize();
let dir_2 = dir.cross(&dir_1).normalize();
let theta = rng.gen_range(0.0..2. * constant::PI);
let r = rng.gen_range(0.0..radius);
let h = rng.gen_range(-0.5 * thickness..0.5 * thickness);
dir * h + r * dir_1 * theta.sin() + r * dir_2 * theta.cos()
}
}
}
}
#[derive(Default)]
pub struct OvenCreateAtomsSystem<T>(PhantomData<T>) where T : AtomCreator;
impl<'a, T> System<'a> for OvenCreateAtomsSystem<T> where T : AtomCreator + 'static {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Oven<T>>,
ReadStorage<'a, AtomNumberToEmit>,
ReadStorage<'a, Position>,
ReadStorage<'a, PrecalculatedSpeciesInformation>,
Option<Read<'a, VelocityCap>>,
Read<'a, LazyUpdate>,
);
fn run(
&mut self,
(entities, oven, numbers_to_emit, pos, precalcs, velocity_cap, updater): Self::SystemData,
) {
let max_vel = match velocity_cap {
Some(cap) => cap.value,
None => std::f64::MAX,
};
let mut rng = rand::thread_rng();
for (oven, number_to_emit, oven_position, precalcs) in
(&oven, &numbers_to_emit, &pos, &precalcs).join()
{
for _i in 0..number_to_emit.number {
let (mass, speed) = precalcs.generate_random_mass_v(&mut rng);
if speed > max_vel {
continue;
}
let new_atom = entities.create();
let (new_vel, theta) =
velocity_generate(speed, &oven.direction, &oven.theta_distribution);
if theta > oven.max_theta {
continue;
}
let start_position = oven_position.pos + oven.get_random_spawn_position();
updater.insert(
new_atom,
Position {
pos: start_position,
},
);
updater.insert(
new_atom,
Velocity {
vel: new_vel,
},
);
updater.insert(new_atom, Force::new());
updater.insert(new_atom, Mass { value: mass });
updater.insert(new_atom, Atom);
updater.insert(new_atom, InitialVelocity { vel: new_vel });
updater.insert(new_atom, NewlyCreated);
T::mutate(&updater, new_atom);
}
}
}
}
pub fn jtheta(theta: f64, channel_radius: f64, channel_length: f64) -> f64 {
let beta = 2.0 * channel_radius / channel_length; let q = theta.tan() / beta; let alpha = 0.5 - 1.0 / (3.0 * beta.powf(2.0))
* (1.0 - 2.0 * beta.powf(3.0)
+ (2.0 * beta.powf(2.0) - 1.0) * (1.0 + beta.powf(2.0)).powf(0.5))
/ ((1.0 + beta.powf(2.0)).powf(0.5) - beta.powf(2.0) * (1.0 / beta).asinh());
let j_theta;
if q <= 1.0 {
let r_q = q.acos() - q * (1.0 - q.powf(2.0)).powf(0.5); j_theta = alpha * theta.cos()
+ (2.0 / PI)
* theta.cos() * ((1.0 - alpha) * r_q
+ 2.0 / (3.0 * q) * (1.0 - 2.0 * alpha) * (1.0 - (1.0 - q.powf(2.0)).powf(1.5)))
} else {
j_theta = alpha * theta.cos() + 4.0 / (3.0 * PI * q) * (1.0 - 2.0 * alpha) * theta.cos();
}
j_theta
}
fn create_jtheta_distribution(
channel_radius: f64,
channel_length: f64,
) -> WeightedProbabilityDistribution {
let mut thetas = Vec::<f64>::new();
let mut weights = Vec::<f64>::new();
let n = 1000; for i in 0..n {
let theta = (i as f64 + 0.5) / (n as f64 + 1.0) * PI / 2.0;
let weight = jtheta(theta, channel_radius, channel_length) * theta.sin();
thetas.push(theta);
weights.push(weight);
}
WeightedProbabilityDistribution::new(thetas, weights)
}