use std::{borrow::Cow, sync::Arc};
use stem_material::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_mosaic::serialize_arc_link;
use compare_variables::compare_variables;
use planar_geo::prelude::*;
use super::*;
use crate::magnet::Magnet;
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Options for defining an arc magnet][drawing_arc_parallel_enums]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"drawing_arc_parallel_enums",
"docs/img/drawing_arc_parallel_enums.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum AngleOrWidth {
Angle(f64),
Width(Length),
}
impl AngleOrWidth {
pub fn width(&self, radius: Length) -> Length {
match self {
AngleOrWidth::Angle(angle) => 2.0 * radius.abs() * (0.5 * *angle).sin(),
AngleOrWidth::Width(width) => *width,
}
}
pub fn angle(&self, radius: Length) -> f64 {
match self {
AngleOrWidth::Angle(angle) => *angle,
AngleOrWidth::Width(width) => {
2.0 * (0.5 * (*width / radius.abs()).get::<ratio>()).asin()
}
}
}
}
impl From<f64> for AngleOrWidth {
fn from(value: f64) -> Self {
return AngleOrWidth::Angle(value);
}
}
impl From<Length> for AngleOrWidth {
fn from(value: Length) -> Self {
return AngleOrWidth::Width(value);
}
}
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Options for defining an arc magnet][drawing_arc_parallel_enums]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"drawing_arc_parallel_enums",
"docs/img/drawing_arc_parallel_enums.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SideHeightOrThickness {
Height(Length),
Thickness(Length),
}
impl SideHeightOrThickness {
pub fn height(&self, core_radius: Length, angle_or_width: AngleOrWidth) -> Length {
match self {
SideHeightOrThickness::Height(height) => *height,
SideHeightOrThickness::Thickness(thickness) => {
use uom::typenum::P2;
let width = angle_or_width.width(core_radius);
let angle = angle_or_width.angle(core_radius);
let cos_ag_r = ((core_radius + *thickness).powi(P2::new())
- (0.5 * width).powi(P2::new()))
.sqrt();
(cos_ag_r - core_radius.abs() * (0.5 * angle).cos()).abs()
}
}
}
pub fn thickness(&self, core_radius: Length, angle_or_width: AngleOrWidth) -> Length {
match self {
SideHeightOrThickness::Height(height) => {
use uom::typenum::P2;
let width = angle_or_width.width(core_radius);
let angle = angle_or_width.angle(core_radius);
let cos_ag_r = core_radius * (0.5 * angle).cos() + *height;
((cos_ag_r.powi(P2::new()) + (0.5 * width).powi(P2::new())).sqrt()
- core_radius.abs())
.abs()
}
SideHeightOrThickness::Thickness(thickness) => *thickness,
}
}
}
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Arc parallel magnet definitions][drawing_arc_parallel_magnet]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"drawing_arc_parallel_magnet",
"docs/img/drawing_arc_parallel_magnet.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Magnet form dependence on radius][drawing_arc_segment_magnet_pos_neg_radii]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"drawing_arc_segment_magnet_pos_neg_radii",
"docs/img/drawing_arc_segment_magnet_pos_neg_radii.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Different arc parallel shapes][arc_parallel_vary_air_gap_radius]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"arc_parallel_vary_air_gap_radius",
"docs/img/arc_parallel_vary_air_gap_radius.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct ArcParallelMagnet {
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
length: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
side_height: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
core_radius: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
air_gap_radius: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
width: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arc_link",))]
material: Arc<Material>,
#[cfg_attr(feature = "serde", serde(skip))]
center_thickness: Length,
#[cfg_attr(feature = "serde", serde(skip))]
shape: Shape,
#[cfg_attr(feature = "serde", serde(skip))]
north_shape: Shape,
#[cfg_attr(feature = "serde", serde(skip))]
south_shape: Shape,
}
impl ArcParallelMagnet {
pub fn new(
length: Length,
core_radius: Length,
air_gap_radius: Length,
side_height: Length,
angle_or_width: AngleOrWidth,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
let zero = Length::new::<meter>(0.0);
compare_variables!(val zero < length)?;
compare_variables!(val zero <= side_height)?;
let width = angle_or_width.width(core_radius.abs());
let angle = angle_or_width.angle(core_radius.abs());
let offset = [0.0, side_height.get::<meter>()];
let [core_arc, air_gap_arc] =
core_and_air_gap_from_air_gap_arc_radius(core_radius, air_gap_radius, offset, angle)?;
let center_thickness =
Length::new::<meter>(air_gap_arc.segment_point(0.5)[1]) - core_radius;
let [shape, north_shape, south_shape] = shapes(
core_arc,
air_gap_arc,
offset,
core_radius.is_sign_positive(),
)?;
return Ok(Self {
length,
core_radius,
air_gap_radius,
width,
side_height,
material,
shape,
center_thickness,
north_shape,
south_shape,
});
}
pub fn with_const_thickness(
length: Length,
core_radius: Length,
side_height_or_thickness: SideHeightOrThickness,
angle_or_width: AngleOrWidth,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
let side_height = side_height_or_thickness.height(core_radius, angle_or_width);
let thickness = side_height_or_thickness.thickness(core_radius, angle_or_width);
let air_gap_radius = core_radius + thickness;
return Self::new(
length,
core_radius,
air_gap_radius,
side_height,
angle_or_width,
material,
);
}
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Different magnet shapes from center thickness][arc_parallel_vary_center_thickness]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"arc_parallel_vary_center_thickness",
"docs/img/arc_parallel_vary_center_thickness.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
pub fn with_center_thickness(
length: Length,
core_radius: Length,
side_height: Length,
center_thickness: Length,
angle_or_width: AngleOrWidth,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
let zero = Length::new::<meter>(0.0);
compare_variables!(val zero < center_thickness)?;
let offset = [0.0, side_height.get::<meter>()];
let [core_arc, air_gap_arc] = core_and_air_gap_arc_from_center_thickness(
core_radius,
center_thickness,
offset,
angle_or_width.angle(core_radius.abs()),
)?;
let sign = if air_gap_arc.is_positive() {
if core_radius.is_sign_positive() {
-1.0
} else {
1.0
}
} else {
if core_radius.is_sign_positive() {
1.0
} else {
-1.0
}
};
let air_gap_radius = Length::new::<meter>(air_gap_arc.radius() * sign);
let [shape, north_shape, south_shape] = shapes(
core_arc,
air_gap_arc,
offset,
core_radius.is_sign_positive(),
)?;
return Ok(Self {
length,
core_radius,
air_gap_radius,
width: angle_or_width.width(core_radius.abs()),
side_height,
material,
shape,
center_thickness,
north_shape,
south_shape,
});
}
pub fn core_radius(&self) -> Length {
return self.core_radius;
}
pub fn air_gap_radius(&self) -> Length {
return self.air_gap_radius;
}
pub fn angle(&self) -> f64 {
return 2.0 * ((self.width / (2.0 * self.core_radius.abs())).get::<ratio>()).asin();
}
pub fn center_thickness(&self) -> Length {
return self.center_thickness;
}
pub fn side_thickness(&self) -> Length {
return SideHeightOrThickness::Height(self.side_height)
.thickness(self.core_radius, self.angle().into());
}
pub fn side_height(&self) -> Length {
return self.side_height;
}
}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Magnet for ArcParallelMagnet {
fn width(&self) -> Length {
return self.width;
}
fn length(&self) -> Length {
return self.length;
}
fn thickness(&self) -> Length {
return 0.5 * (self.center_thickness + self.side_thickness());
}
fn material(&self) -> &Material {
return &*self.material;
}
fn material_arc(&self) -> Arc<Material> {
return self.material.clone();
}
fn shape(&self) -> Cow<'_, Shape> {
return Cow::Borrowed(&self.shape);
}
fn north_south_shapes(&self) -> [Cow<'_, Shape>; 2] {
return [
Cow::Borrowed(&self.north_shape),
Cow::Borrowed(&self.south_shape),
];
}
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde_mosaic::deserialize_arc_link;
use stem_material::prelude::{deserialize_opt_quantity, deserialize_quantity};
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithAirGapRadiusSideHeightAndWidth {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_height: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(default, deserialize_with = "deserialize_opt_quantity")]
air_gap_radius: Option<Length>,
#[serde(deserialize_with = "deserialize_quantity")]
width: Length,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithAirGapRadiusSideHeightAndAngle {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_height: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(default, deserialize_with = "deserialize_opt_quantity")]
air_gap_radius: Option<Length>,
#[serde(deserialize_with = "deserialize_angle")]
angle: f64,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithConstThicknessAndAngle {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(deserialize_with = "deserialize_angle")]
angle: f64,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithConstThicknessAndWidth {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(deserialize_with = "deserialize_quantity")]
width: Length,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithCenterThicknessAndWidth {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_height: Length,
#[serde(deserialize_with = "deserialize_quantity")]
center_thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(deserialize_with = "deserialize_quantity")]
width: Length,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithCenterThicknessAndAngle {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_height: Length,
#[serde(deserialize_with = "deserialize_quantity")]
center_thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
core_radius: Length,
#[serde(deserialize_with = "deserialize_angle")]
angle: f64,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(deserialize_untagged_verbose_error::DeserializeUntaggedVerboseError)]
enum MagnetEnum {
WithAirGapRadiusSideHeightAndWidth(WithAirGapRadiusSideHeightAndWidth),
WithAirGapRadiusSideHeightAndAngle(WithAirGapRadiusSideHeightAndAngle),
WithConstThicknessAndWidth(WithConstThicknessAndWidth),
WithConstThicknessAndAngle(WithConstThicknessAndAngle),
WithCenterThicknessAndAngle(WithCenterThicknessAndAngle),
WithCenterThicknessAndWidth(WithCenterThicknessAndWidth),
}
impl<'de> Deserialize<'de> for ArcParallelMagnet {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let m = MagnetEnum::deserialize(deserializer)?;
match m {
MagnetEnum::WithAirGapRadiusSideHeightAndWidth(m) => match m.air_gap_radius {
Some(r) => {
return Self::new(
m.length,
m.core_radius,
r,
m.side_height,
AngleOrWidth::Width(m.width),
m.material,
)
.map_err(serde::de::Error::custom);
}
None => {
return Self::with_const_thickness(
m.length,
m.core_radius,
SideHeightOrThickness::Height(m.side_height),
AngleOrWidth::Width(m.width),
m.material,
)
.map_err(serde::de::Error::custom);
}
},
MagnetEnum::WithAirGapRadiusSideHeightAndAngle(m) => match m.air_gap_radius {
Some(r) => {
return Self::new(
m.length,
m.core_radius,
r,
m.side_height,
AngleOrWidth::Angle(m.angle),
m.material,
)
.map_err(serde::de::Error::custom);
}
None => {
return Self::with_const_thickness(
m.length,
m.core_radius,
SideHeightOrThickness::Height(m.side_height),
AngleOrWidth::Angle(m.angle),
m.material,
)
.map_err(serde::de::Error::custom);
}
},
MagnetEnum::WithConstThicknessAndWidth(m) => {
return Self::with_const_thickness(
m.length,
m.core_radius,
SideHeightOrThickness::Thickness(m.thickness),
AngleOrWidth::Width(m.width),
m.material,
)
.map_err(serde::de::Error::custom);
}
MagnetEnum::WithConstThicknessAndAngle(m) => {
return Self::with_const_thickness(
m.length,
m.core_radius,
SideHeightOrThickness::Thickness(m.thickness),
AngleOrWidth::Angle(m.angle),
m.material,
)
.map_err(serde::de::Error::custom);
}
MagnetEnum::WithCenterThicknessAndAngle(m) => {
return Self::with_center_thickness(
m.length,
m.core_radius,
m.side_height,
m.center_thickness,
AngleOrWidth::Angle(m.angle),
m.material,
)
.map_err(serde::de::Error::custom);
}
MagnetEnum::WithCenterThicknessAndWidth(m) => {
return Self::with_center_thickness(
m.length,
m.core_radius,
m.side_height,
m.center_thickness,
AngleOrWidth::Width(m.width),
m.material,
)
.map_err(serde::de::Error::custom);
}
}
}
}
}