use serde::{Deserialize, Serialize};
use crate::copper::{CopperWeight, EtchFactor, PlatingThickness};
use crate::CalcError;
pub const COPPER_MELTING_TEMP_C: f64 = 1084.62;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FusingResult {
pub copper_thickness_mils: f64,
pub area_sq_mils: f64,
pub area_circular_mils: f64,
pub fusing_current_a: f64,
pub melting_temp_c: f64,
}
pub fn fusing_current(
area_circular_mils: f64,
time_s: f64,
ambient_c: f64,
melting_temp_c: f64,
) -> Result<f64, CalcError> {
if area_circular_mils <= 0.0 {
return Err(CalcError::OutOfRange {
name: "area_circular_mils",
value: area_circular_mils,
expected: "> 0",
});
}
if time_s <= 0.0 {
return Err(CalcError::OutOfRange {
name: "time_s",
value: time_s,
expected: "> 0",
});
}
if melting_temp_c <= ambient_c {
return Err(CalcError::OutOfRange {
name: "melting_temp_c",
value: melting_temp_c,
expected: "> ambient_c",
});
}
let delta_t = melting_temp_c - ambient_c;
let log_term = (1.0 + delta_t / (234.0 + ambient_c)).log10();
let current = area_circular_mils * (log_term / (33.0 * time_s)).sqrt();
Ok(current)
}
pub fn fusing_current_trace(
width_mils: f64,
base_copper: CopperWeight,
plating: PlatingThickness,
etch_factor: EtchFactor,
time_s: f64,
ambient_c: f64,
) -> Result<FusingResult, CalcError> {
if width_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "width_mils",
value: width_mils,
});
}
let copper_thickness_mils =
base_copper.thickness_mils() + plating.thickness_mils();
let area_sq_mils = etch_factor.cross_section_sq_mils(width_mils, copper_thickness_mils);
let area_circular_mils = area_sq_mils * (4.0 / std::f64::consts::PI);
let fusing_current_a = fusing_current(
area_circular_mils,
time_s,
ambient_c,
COPPER_MELTING_TEMP_C,
)?;
Ok(FusingResult {
copper_thickness_mils,
area_sq_mils,
area_circular_mils,
fusing_current_a,
melting_temp_c: COPPER_MELTING_TEMP_C,
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn saturn_fusing_current_from_area() {
let current = fusing_current(23.93, 1.0, 22.0, COPPER_MELTING_TEMP_C).unwrap();
assert_relative_eq!(current, 3.5147, max_relative = 0.001);
}
#[test]
fn circular_mils_conversion() {
let a_sq = 18.79_f64;
let a_circ = a_sq * (4.0 / std::f64::consts::PI);
assert_relative_eq!(a_circ, 23.93, epsilon = 0.01);
}
#[test]
fn etch_two_to_one_area() {
let area = EtchFactor::TwoToOne.cross_section_sq_mils(10.0, 2.10);
assert_relative_eq!(area, 18.795, epsilon = 0.01);
}
#[test]
fn saturn_full_trace_oz15_bare() {
let result = fusing_current_trace(
10.0,
CopperWeight::Oz15,
PlatingThickness::Bare,
EtchFactor::TwoToOne,
1.0,
22.0,
)
.unwrap();
assert_relative_eq!(result.copper_thickness_mils, 2.10, epsilon = 1e-10);
assert_relative_eq!(result.area_sq_mils, 18.795, epsilon = 0.01);
assert_relative_eq!(result.area_circular_mils, 23.93, epsilon = 0.02);
assert_relative_eq!(result.fusing_current_a, 3.5147, max_relative = 0.001);
}
#[test]
fn error_on_zero_time() {
assert!(fusing_current(23.93, 0.0, 22.0, COPPER_MELTING_TEMP_C).is_err());
}
#[test]
fn error_on_ambient_above_melting() {
assert!(fusing_current(23.93, 1.0, 1100.0, COPPER_MELTING_TEMP_C).is_err());
}
}