introduce Isomorphic/Anisomorphic conductors into the CPU side
this is needed for the future Spirv optimizations to be possible.
This commit is contained in:
@@ -337,9 +337,9 @@ fn run_sim(id: u32, p: Params, g: Geometries) -> Results {
|
|||||||
let feat_vol = p.geom.feat_size * p.geom.feat_size * p.geom.feat_size;
|
let feat_vol = p.geom.feat_size * p.geom.feat_size * p.geom.feat_size;
|
||||||
|
|
||||||
// mu_r=881.33, starting at H=25 to H=75.
|
// mu_r=881.33, starting at H=25 to H=75.
|
||||||
let ferro_mat = mat::MHPgram::new(25.0, 881.33, 44000.0);
|
let ferro_mat = mat::Ferroxcube3R1MH::new();
|
||||||
// let ferro_mat = mat::db::conductor(wire_conductivity);
|
// let ferro_mat = mat::db::conductor(wire_conductivity);
|
||||||
let wire_mat = mat::db::conductor(p.wire_conductivity);
|
let wire_mat = mat::IsomorphicConductor::new(p.wire_conductivity);
|
||||||
|
|
||||||
let mut driver: Driver<_> = Driver::new_spirv(g.dim, p.geom.feat_size);
|
let mut driver: Driver<_> = Driver::new_spirv(g.dim, p.geom.feat_size);
|
||||||
driver.set_steps_per_stim(1000);
|
driver.set_steps_per_stim(1000);
|
||||||
|
@@ -265,8 +265,8 @@ impl<S: MaterialSim> Driver<S> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_classical_boundary<C: Coord, R: Real>(&mut self, thickness: C)
|
pub fn add_classical_boundary<C: Coord>(&mut self, thickness: C)
|
||||||
where S::Material: From<mat::Conductor<R>>
|
where S::Material: From<mat::IsomorphicConductor<f32>>
|
||||||
{
|
{
|
||||||
let timestep = self.state.timestep();
|
let timestep = self.state.timestep();
|
||||||
self.state.fill_boundary_using(thickness, |boundary_ness| {
|
self.state.fill_boundary_using(thickness, |boundary_ness| {
|
||||||
@@ -274,7 +274,7 @@ impl<S: MaterialSim> Driver<S> {
|
|||||||
let cond = b * (0.5 / timestep);
|
let cond = b * (0.5 / timestep);
|
||||||
|
|
||||||
let iso_cond = cond.x() + cond.y() + cond.z();
|
let iso_cond = cond.x() + cond.y() + cond.z();
|
||||||
let iso_conductor = mat::db::conductor(iso_cond);
|
let iso_conductor = mat::IsomorphicConductor::new(iso_cond);
|
||||||
iso_conductor
|
iso_conductor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
//! database of common materials
|
//! database of common materials
|
||||||
|
|
||||||
use crate::geom::Vec3;
|
use crate::geom::Vec3;
|
||||||
use crate::mat::{Conductor, LinearMagnet, Ferroxcube3R1, MinimalSquare};
|
use crate::mat::{AnisomorphicConductor, IsomorphicConductor, LinearMagnet, Ferroxcube3R1, MinimalSquare};
|
||||||
use crate::real::Real;
|
use crate::real::Real;
|
||||||
|
|
||||||
pub fn conductor<R: Real, R2: Real>(conductivity: R2) -> Conductor<R> {
|
pub fn conductor<R: Real, R2: Real>(conductivity: R2) -> IsomorphicConductor<R> {
|
||||||
Conductor::new(conductivity)
|
IsomorphicConductor::new(conductivity.cast())
|
||||||
}
|
}
|
||||||
pub fn anisotropic_conductor<R: Real, R2: Real>(conductivity: Vec3<R2>) -> Conductor<R> {
|
pub fn anisotropic_conductor<R>(conductivity: Vec3<R>) -> AnisomorphicConductor<R> {
|
||||||
Conductor::new_anisotropic(conductivity)
|
AnisomorphicConductor::new(conductivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copper<R: Real>() -> Conductor<R> {
|
pub fn copper<R: Real>() -> IsomorphicConductor<R> {
|
||||||
Conductor::new(50_000_000.0)
|
conductor(50_000_000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://en.wikipedia.org/wiki/Permeability_(electromagnetism)#Values_for_some_common_materials
|
// See https://en.wikipedia.org/wiki/Permeability_(electromagnetism)#Values_for_some_common_materials
|
||||||
|
@@ -8,29 +8,47 @@ use serde::{Serialize, Deserialize};
|
|||||||
|
|
||||||
/// Material which has a conductivity parameter, but cannot be magnetized
|
/// Material which has a conductivity parameter, but cannot be magnetized
|
||||||
#[derive(Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Conductor<R> {
|
pub struct Conductor<V> {
|
||||||
conductivity: Vec3<R>,
|
conductivity: V,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Real> Conductor<R> {
|
pub type IsomorphicConductor<R> = Conductor<R>;
|
||||||
pub fn new<R2: Real>(conductivity: R2) -> Self {
|
pub type AnisomorphicConductor<R> = Conductor<Vec3<R>>;
|
||||||
|
|
||||||
|
impl<V> Conductor<V> {
|
||||||
|
pub fn new(conductivity: V) -> Self {
|
||||||
Self {
|
Self {
|
||||||
conductivity: Vec3::uniform(conductivity).cast()
|
conductivity
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn new_anisotropic<R2: Real>(conductivity: Vec3<R2>) -> Self {
|
|
||||||
Self {
|
|
||||||
conductivity: conductivity.cast(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Real> Material<R> for Conductor<R> {
|
impl<R: Real> Into<AnisomorphicConductor<R>> for IsomorphicConductor<R> {
|
||||||
|
fn into(self) -> AnisomorphicConductor<R> {
|
||||||
|
AnisomorphicConductor::new(Vec3::uniform(self.conductivity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Real> AnisomorphicConductor<R> {
|
||||||
|
pub fn new_anisotropic<R2: Real>(c: Vec3<R2>) -> Self {
|
||||||
|
Self {
|
||||||
|
conductivity: Vec3::new(c.x().cast(), c.y().cast(), c.z().cast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Real> Material<R> for AnisomorphicConductor<R> {
|
||||||
fn step_parameters_mut<'a>(&'a mut self) -> StepParametersMut<'a, R> {
|
fn step_parameters_mut<'a>(&'a mut self) -> StepParametersMut<'a, R> {
|
||||||
StepParametersMut::default().with_conductivity(self.conductivity)
|
StepParametersMut::default().with_conductivity(self.conductivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Real> Material<R> for IsomorphicConductor<R> {
|
||||||
|
fn step_parameters_mut<'a>(&'a mut self) -> StepParametersMut<'a, R> {
|
||||||
|
StepParametersMut::default().with_conductivity(Vec3::uniform(self.conductivity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Material which can be magnetized, but has no hysteresis and no coercivity.
|
/// Material which can be magnetized, but has no hysteresis and no coercivity.
|
||||||
#[derive(Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct LinearMagnet<R> {
|
pub struct LinearMagnet<R> {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use crate::real::Real;
|
use crate::real::Real;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MHPgram<R> {
|
pub struct MHPgram<R> {
|
||||||
/// X coordinate at which M is always zero.
|
/// X coordinate at which M is always zero.
|
||||||
pub h_intercept: R,
|
pub h_intercept: R,
|
||||||
@@ -16,3 +16,18 @@ impl<R: Real> MHPgram<R> {
|
|||||||
Self { h_intercept, mu_r, max_m }
|
Self { h_intercept, mu_r, max_m }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Ferroxcube3R1MH;
|
||||||
|
|
||||||
|
impl Ferroxcube3R1MH {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Real> Into<MHPgram<R>> for Ferroxcube3R1MH {
|
||||||
|
fn into(self) -> MHPgram<R> {
|
||||||
|
MHPgram::new(R::from_f32(25.0), R::from_f32(881.33), R::from_f32(44000.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -110,7 +110,7 @@ impl<R: Real> Material<R> for Pml<R> {
|
|||||||
// #[enum_dispatch(Material)]
|
// #[enum_dispatch(Material)]
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum GenericMaterial<R> {
|
pub enum GenericMaterial<R> {
|
||||||
Conductor(Conductor<R>),
|
Conductor(AnisomorphicConductor<R>),
|
||||||
LinearMagnet(LinearMagnet<R>),
|
LinearMagnet(LinearMagnet<R>),
|
||||||
Pml(Pml<R>),
|
Pml(Pml<R>),
|
||||||
MBFerromagnet(MBFerromagnet<R>),
|
MBFerromagnet(MBFerromagnet<R>),
|
||||||
@@ -120,16 +120,22 @@ pub enum GenericMaterial<R> {
|
|||||||
|
|
||||||
impl<R: Real> Default for GenericMaterial<R> {
|
impl<R: Real> Default for GenericMaterial<R> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Conductor::default().into()
|
Self::Conductor(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> From<Conductor<R>> for GenericMaterial<R> {
|
impl<R> From<AnisomorphicConductor<R>> for GenericMaterial<R> {
|
||||||
fn from(inner: Conductor<R>) -> Self {
|
fn from(inner: AnisomorphicConductor<R>) -> Self {
|
||||||
Self::Conductor(inner)
|
Self::Conductor(inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Real> From<IsomorphicConductor<R>> for GenericMaterial<R> {
|
||||||
|
fn from(inner: IsomorphicConductor<R>) -> Self {
|
||||||
|
Self::Conductor(inner.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<R> From<LinearMagnet<R>> for GenericMaterial<R> {
|
impl<R> From<LinearMagnet<R>> for GenericMaterial<R> {
|
||||||
fn from(inner: LinearMagnet<R>) -> Self {
|
fn from(inner: LinearMagnet<R>) -> Self {
|
||||||
Self::LinearMagnet(inner)
|
Self::LinearMagnet(inner)
|
||||||
|
@@ -1679,7 +1679,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn conductor_dissipates_energy() {
|
fn conductor_dissipates_energy() {
|
||||||
let (energy_0, energy_1) = conductor_test(Conductor::new(1e3));
|
let (energy_0, energy_1) = conductor_test(Conductor::new(R64::from_f32(1e3)));
|
||||||
assert_float_eq!(energy_1/energy_0, 0.0, abs <= 1e-6);
|
assert_float_eq!(energy_1/energy_0, 0.0, abs <= 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1734,8 +1734,8 @@ mod test {
|
|||||||
let timestep = state.timestep();
|
let timestep = state.timestep();
|
||||||
state.fill_boundary_using(size/4, |boundary_ness| {
|
state.fill_boundary_using(size/4, |boundary_ness| {
|
||||||
let b = boundary_ness.elem_pow(3.0);
|
let b = boundary_ness.elem_pow(3.0);
|
||||||
let conductivity = Vec3::uniform(f32::eps0() * b.mag() * 0.5 / timestep);
|
let conductivity = f32::eps0() * b.mag() * 0.5 / timestep;
|
||||||
Conductor::new_anisotropic(conductivity)
|
Conductor::new(conductivity.cast())
|
||||||
});
|
});
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,14 @@ use serde::de::Deserializer;
|
|||||||
use serde::ser::Serializer;
|
use serde::ser::Serializer;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::mat::{Conductor, MaterialExt as _, MBFerromagnet, MBPgram, MHPgram, Static};
|
use crate::mat::{AnisomorphicConductor, IsomorphicConductor, Ferroxcube3R1MH, MaterialExt as _, MBFerromagnet, MBPgram, MHPgram, Static};
|
||||||
use crate::geom::{Index, Vec3, Vec3u};
|
use crate::geom::{Index, Vec3, Vec3u};
|
||||||
|
|
||||||
/// hide the actual spirv backend structures inside a submodule to make their use/boundary clear.
|
/// hide the actual spirv backend structures inside a submodule to make their use/boundary clear.
|
||||||
mod ffi {
|
mod ffi {
|
||||||
pub use spirv_backend_lib::sim::SerializedSimMeta;
|
pub use spirv_backend_lib::sim::SerializedSimMeta;
|
||||||
pub use spirv_backend_lib::support::{Optional, Vec3Std, UVec3Std};
|
pub use spirv_backend_lib::support::{Optional, Vec3Std, UVec3Std};
|
||||||
pub use spirv_backend_lib::mat::{FullyGenericMaterial, Material, MBPgram, MHPgram};
|
pub use spirv_backend_lib::mat::{Ferroxcube3R1MH, FullyGenericMaterial, Material, MBPgram, MHPgram};
|
||||||
}
|
}
|
||||||
|
|
||||||
// conversion traits for types defined cross-lib
|
// conversion traits for types defined cross-lib
|
||||||
@@ -100,6 +100,28 @@ impl IntoLib for ffi::MHPgram {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoFfi for Ferroxcube3R1MH {
|
||||||
|
type Ffi = ffi::Ferroxcube3R1MH;
|
||||||
|
fn into_ffi(self) -> Self::Ffi {
|
||||||
|
let ffi_self = ffi::Ferroxcube3R1MH;
|
||||||
|
let ffi_curve: ffi::MHPgram = ffi_self.into();
|
||||||
|
let lib_curve: MHPgram<f32> = self.into();
|
||||||
|
assert_eq!(lib_curve.into_ffi(), ffi_curve);
|
||||||
|
ffi_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLib for ffi::Ferroxcube3R1MH {
|
||||||
|
type Lib = Ferroxcube3R1MH;
|
||||||
|
fn into_lib(self) -> Self::Lib {
|
||||||
|
let lib_self = Ferroxcube3R1MH;
|
||||||
|
let lib_curve: MHPgram<f32> = lib_self.into();
|
||||||
|
let ffi_curve: ffi::MHPgram = self.into();
|
||||||
|
assert_eq!(ffi_curve.into_lib(), lib_curve);
|
||||||
|
lib_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FullyGenericMaterial {
|
pub struct FullyGenericMaterial {
|
||||||
pub conductivity: Vec3<f32>,
|
pub conductivity: Vec3<f32>,
|
||||||
@@ -138,8 +160,17 @@ impl From<Static<f32>> for FullyGenericMaterial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Conductor<f32>> for FullyGenericMaterial {
|
impl From<AnisomorphicConductor<f32>> for FullyGenericMaterial {
|
||||||
fn from(m: Conductor<f32>) -> Self {
|
fn from(m: AnisomorphicConductor<f32>) -> Self {
|
||||||
|
FullyGenericMaterial {
|
||||||
|
conductivity: m.conductivity(),
|
||||||
|
.. Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IsomorphicConductor<f32>> for FullyGenericMaterial {
|
||||||
|
fn from(m: IsomorphicConductor<f32>) -> Self {
|
||||||
FullyGenericMaterial {
|
FullyGenericMaterial {
|
||||||
conductivity: m.conductivity(),
|
conductivity: m.conductivity(),
|
||||||
.. Default::default()
|
.. Default::default()
|
||||||
@@ -165,6 +196,13 @@ impl From<MHPgram<f32>> for FullyGenericMaterial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Ferroxcube3R1MH> for FullyGenericMaterial {
|
||||||
|
fn from(m: Ferroxcube3R1MH) -> Self {
|
||||||
|
let curve: MHPgram<f32> = m.into();
|
||||||
|
curve.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct SimMeta {
|
pub struct SimMeta {
|
||||||
pub(crate) dim: Index,
|
pub(crate) dim: Index,
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
register_attr(spirv),
|
register_attr(spirv),
|
||||||
no_std
|
no_std
|
||||||
)]
|
)]
|
||||||
|
#![feature(const_fn_floating_point_arithmetic)]
|
||||||
|
|
||||||
extern crate spirv_std;
|
extern crate spirv_std;
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
use crate::support::{Optional, Vec3Std};
|
use crate::support::{Optional, Vec3Std};
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
pub trait Material: Sized {
|
pub trait Material: Sized {
|
||||||
fn conductivity(&self) -> Vec3Std {
|
fn conductivity(&self) -> Vec3Std {
|
||||||
Default::default()
|
Default::default()
|
||||||
@@ -88,7 +90,7 @@ impl Material for MBPgram {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
pub struct MHPgram {
|
pub struct MHPgram {
|
||||||
/// optimized form of mu_0^-1 * (1-mu_r^-1)
|
/// optimized form of mu_0^-1 * (1-mu_r^-1)
|
||||||
b_mult: f32,
|
b_mult: f32,
|
||||||
@@ -101,7 +103,7 @@ pub struct MHPgram {
|
|||||||
impl MHPgram {
|
impl MHPgram {
|
||||||
/// h_intercept: X coordinate at which M is always zero.
|
/// h_intercept: X coordinate at which M is always zero.
|
||||||
/// mu_r: relative mu value along the non-flat edges of the parallelogram.
|
/// mu_r: relative mu value along the non-flat edges of the parallelogram.
|
||||||
pub fn new(h_intercept: f32, mu_r: f32, max_m: f32) -> Self {
|
pub const fn new(h_intercept: f32, mu_r: f32, max_m: f32) -> Self {
|
||||||
const MU0_INV: f32 = 795774.715025073;
|
const MU0_INV: f32 = 795774.715025073;
|
||||||
let one_minus_mu_r_inv = 1.0 - 1.0/mu_r;
|
let one_minus_mu_r_inv = 1.0 - 1.0/mu_r;
|
||||||
Self {
|
Self {
|
||||||
@@ -198,6 +200,61 @@ impl Material for FullyGenericMaterial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MHPgram that's vaguely similar to Ferroxcube's 3R1 material
|
||||||
|
#[derive(Copy, Clone, Default, PartialEq)]
|
||||||
|
pub struct Ferroxcube3R1MH;
|
||||||
|
|
||||||
|
impl Into<MHPgram> for Ferroxcube3R1MH {
|
||||||
|
fn into(self) -> MHPgram {
|
||||||
|
const CURVE: MHPgram = MHPgram::new(25.0, 881.33, 44000.0);
|
||||||
|
CURVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material for Ferroxcube3R1MH {
|
||||||
|
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
|
||||||
|
let curve: MHPgram = (*self).into();
|
||||||
|
curve.move_b_vec(m, target_b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optimized material aimed at saving space for the common case of a simulation that contains
|
||||||
|
/// isomorphic conductors plus one exception material.
|
||||||
|
#[derive(Copy, Clone, Default, PartialEq)]
|
||||||
|
pub struct IsoConductorOr<M> {
|
||||||
|
/// conductivity, if >= 0, else ignored & used to signal the other material
|
||||||
|
value: f32,
|
||||||
|
_mat: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> IsoConductorOr<M> {
|
||||||
|
pub fn new_conductor(conductivity: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
value: conductivity,
|
||||||
|
_mat: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new_other() -> Self {
|
||||||
|
Self {
|
||||||
|
value: -1.0,
|
||||||
|
_mat: Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Material + Default> Material for IsoConductorOr<M> {
|
||||||
|
fn conductivity(&self) -> Vec3Std {
|
||||||
|
Vec3Std::uniform(self.value.max(0.0))
|
||||||
|
}
|
||||||
|
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
|
||||||
|
if self.value < 0.0 {
|
||||||
|
M::default().move_b_vec(m, target_b)
|
||||||
|
} else {
|
||||||
|
m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Reference in New Issue
Block a user