scirs2_core/
units.rs

1//! # Unit Conversion System
2//!
3//! This module provides a comprehensive unit conversion system for scientific computing,
4//! supporting dimensional analysis, automatic conversions, and unit safety.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use crate::numeric::ScientificNumber;
8use std::collections::HashMap;
9use std::fmt;
10
11/// Represents a physical dimension (length, time, mass, etc.)
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum Dimension {
14    Length,
15    Time,
16    Mass,
17    Temperature,
18    Current,
19    Amount, // Amount of substance
20    LuminousIntensity,
21    Angle,
22    SolidAngle,
23    // Derived dimensions
24    Area,          // Length²
25    Volume,        // Length³
26    Velocity,      // Length/Time
27    Acceleration,  // Length/Time²
28    Force,         // Mass⋅Length/Time²
29    Energy,        // Mass⋅Length²/Time²
30    Power,         // Mass⋅Length²/Time³
31    Pressure,      // Mass/(Length⋅Time²)
32    Frequency,     // 1/Time
33    Voltage,       // Mass⋅Length²/(Time³⋅Current)
34    Resistance,    // Mass⋅Length²/(Time³⋅Current²)
35    Capacitance,   // Time⁴⋅Current²/(Mass⋅Length²)
36    Inductance,    // Mass⋅Length²/(Time²⋅Current²)
37    MagneticField, // Mass/(Time²⋅Current)
38    Dimensionless, // No dimension
39}
40
41impl fmt::Display for Dimension {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        let name = match self {
44            Self::Length => "Length",
45            Self::Time => "Time",
46            Self::Mass => "Mass",
47            Self::Temperature => "Temperature",
48            Self::Current => "Current",
49            Self::Amount => "Amount",
50            Self::LuminousIntensity => "Luminous Intensity",
51            Self::Angle => "Angle",
52            Self::SolidAngle => "Solid Angle",
53            Self::Area => "Area",
54            Self::Volume => "Volume",
55            Self::Velocity => "Velocity",
56            Self::Acceleration => "Acceleration",
57            Self::Force => "Force",
58            Self::Energy => "Energy",
59            Self::Power => "Power",
60            Self::Pressure => "Pressure",
61            Self::Frequency => "Frequency",
62            Self::Voltage => "Voltage",
63            Self::Resistance => "Resistance",
64            Self::Capacitance => "Capacitance",
65            Self::Inductance => "Inductance",
66            Self::MagneticField => "Magnetic Field",
67            Self::Dimensionless => "Dimensionless",
68        };
69        write!(f, "{name}")
70    }
71}
72
73/// Represents a unit system (SI, Imperial, CGS, etc.)
74#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75pub enum UnitSystem {
76    SI,       // International System of Units
77    Imperial, // Imperial/US customary units
78    CGS,      // Centimeter-gram-second system
79    Natural,  // Natural units (c = ℏ = 1)
80    Atomic,   // Atomic units
81    Planck,   // Planck units
82}
83
84impl fmt::Display for UnitSystem {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let name = match self {
87            Self::SI => "SI",
88            Self::Imperial => "Imperial",
89            Self::CGS => "CGS",
90            Self::Natural => "Natural",
91            Self::Atomic => "Atomic",
92            Self::Planck => "Planck",
93        };
94        write!(f, "{name}")
95    }
96}
97
98/// Represents a specific unit with its properties
99#[derive(Debug, Clone)]
100pub struct UnitDefinition {
101    /// The name of the unit
102    pub name: String,
103    /// The symbol of the unit
104    pub symbol: String,
105    /// The dimension this unit measures
106    pub dimension: Dimension,
107    /// The unit system this unit belongs to
108    pub system: UnitSystem,
109    /// Conversion factor to the base unit in SI (multiply to convert to SI)
110    pub si_factor: f64,
111    /// Offset for non-linear conversions (e.g., temperature)
112    pub si_offset: f64,
113    /// Whether this is a base unit in its system
114    pub isbase: bool,
115}
116
117impl UnitDefinition {
118    /// Create a new unit definition
119    pub fn new(
120        name: String,
121        symbol: String,
122        dimension: Dimension,
123        system: UnitSystem,
124        si_factor: f64,
125        si_offset: f64,
126        isbase: bool,
127    ) -> Self {
128        Self {
129            name,
130            symbol,
131            dimension,
132            system,
133            si_factor,
134            si_offset,
135            isbase,
136        }
137    }
138
139    /// Convert a value from this unit to SI units
140    pub fn to_si(&self, value: f64) -> f64 {
141        value * self.si_factor + self.si_offset
142    }
143
144    /// Convert a value from SI units to this unit
145    pub fn from_si(&self, value: f64) -> f64 {
146        (value - self.si_offset) / self.si_factor
147    }
148}
149
150impl fmt::Display for UnitDefinition {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(
153            f,
154            "{} ({}) - {} [{}]",
155            self.name, self.symbol, self.dimension, self.system
156        )
157    }
158}
159
160/// A value with associated units
161#[derive(Debug, Clone)]
162pub struct UnitValue<T: ScientificNumber> {
163    value: T,
164    unit: String, // Unit symbol
165}
166
167impl<T: ScientificNumber> UnitValue<T> {
168    /// Create a new unit value
169    pub fn new(value: T, unit: String) -> Self {
170        Self { value, unit }
171    }
172
173    /// Get the raw value
174    pub fn value(&self) -> T {
175        self.value
176    }
177
178    /// Get the unit symbol
179    pub fn unit(&self) -> &str {
180        &self.unit
181    }
182}
183
184impl<T: ScientificNumber + fmt::Display> fmt::Display for UnitValue<T> {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "{} {}", self.value, self.unit)
187    }
188}
189
190/// Registry for unit definitions and conversions
191pub struct UnitRegistry {
192    units: HashMap<String, UnitDefinition>,
193    aliases: HashMap<String, String>, // Map aliases to canonical unit symbols
194}
195
196impl UnitRegistry {
197    /// Create a new unit registry
198    pub fn new() -> Self {
199        let mut registry = Self {
200            units: HashMap::new(),
201            aliases: HashMap::new(),
202        };
203
204        registry.register_standard_units();
205        registry
206    }
207
208    /// Register a unit definition
209    pub fn register_unit(&mut self, unit: UnitDefinition) {
210        self.units.insert(unit.symbol.clone(), unit);
211    }
212
213    /// Register an alias for a unit
214    pub fn register_alias(&mut self, alias: String, canonical: String) {
215        self.aliases.insert(alias, canonical);
216    }
217
218    /// Get a unit definition by symbol or alias
219    pub fn get_unit(&self, symbol: &str) -> Option<&UnitDefinition> {
220        // First try direct lookup
221        if let Some(unit) = self.units.get(symbol) {
222            return Some(unit);
223        }
224
225        // Then try alias lookup
226        if let Some(canonical) = self.aliases.get(symbol) {
227            return self.units.get(canonical);
228        }
229
230        None
231    }
232
233    /// Check if two units are compatible (same dimension)
234    pub fn are_compatible(&self, unit1: &str, unit2: &str) -> bool {
235        if let (Some(u1), Some(u2)) = (self.get_unit(unit1), self.get_unit(unit2)) {
236            u1.dimension == u2.dimension
237        } else {
238            false
239        }
240    }
241
242    /// Convert a value from one unit to another
243    pub fn convert(&self, value: f64, from_unit: &str, tounit: &str) -> CoreResult<f64> {
244        let from = self.get_unit(from_unit).ok_or_else(|| {
245            CoreError::InvalidArgument(ErrorContext::new(format!("Unknown unit: {from_unit}")))
246        })?;
247
248        let to = self.get_unit(tounit).ok_or_else(|| {
249            CoreError::InvalidArgument(ErrorContext::new(format!("Unknown unit: {tounit}")))
250        })?;
251
252        if from.dimension != to.dimension {
253            return Err(CoreError::InvalidArgument(ErrorContext::new(format!(
254                "Cannot convert {} ({}) to {} ({}): incompatible dimensions",
255                from_unit, from.dimension, tounit, to.dimension
256            ))));
257        }
258
259        // Convert from source unit to SI, then from SI to target unit
260        let si_value = from.to_si(value);
261        let result = to.from_si(si_value);
262
263        Ok(result)
264    }
265
266    /// Convert a UnitValue to a different unit
267    pub fn convert_value<T>(&self, value: &UnitValue<T>, tounit: &str) -> CoreResult<UnitValue<T>>
268    where
269        T: ScientificNumber + TryFrom<f64>,
270        f64: From<T>,
271    {
272        let converted_f64 = self.convert(value.value.into(), &value.unit, tounit)?;
273
274        let converted_value = T::try_from(converted_f64).map_err(|_| {
275            CoreError::TypeError(ErrorContext::new(
276                "Failed to convert back to original numeric type",
277            ))
278        })?;
279
280        Ok(UnitValue::new(converted_value, tounit.to_string()))
281    }
282
283    /// Get all units for a given dimension
284    pub fn get_units_for_dimension(&self, dimension: &Dimension) -> Vec<&UnitDefinition> {
285        self.units
286            .values()
287            .filter(|unit| unit.dimension == *dimension)
288            .collect()
289    }
290
291    /// Get all units in a given system
292    pub fn get_units_for_system(&self, system: &UnitSystem) -> Vec<&UnitDefinition> {
293        self.units
294            .values()
295            .filter(|unit| unit.system == *system)
296            .collect()
297    }
298
299    /// List all registered units
300    pub fn list_units(&self) -> Vec<&UnitDefinition> {
301        self.units.values().collect()
302    }
303
304    /// Register standard units (SI, Imperial, etc.)
305    fn register_standard_units(&mut self) {
306        // SI Base Units
307        self.register_unit(UnitDefinition::new(
308            "meter".to_string(),
309            "m".to_string(),
310            Dimension::Length,
311            UnitSystem::SI,
312            1.0,
313            0.0,
314            true,
315        ));
316        self.register_unit(UnitDefinition::new(
317            "second".to_string(),
318            "s".to_string(),
319            Dimension::Time,
320            UnitSystem::SI,
321            1.0,
322            0.0,
323            true,
324        ));
325        self.register_unit(UnitDefinition::new(
326            "kilogram".to_string(),
327            "kg".to_string(),
328            Dimension::Mass,
329            UnitSystem::SI,
330            1.0,
331            0.0,
332            true,
333        ));
334        self.register_unit(UnitDefinition::new(
335            "kelvin".to_string(),
336            "K".to_string(),
337            Dimension::Temperature,
338            UnitSystem::SI,
339            1.0,
340            0.0,
341            true,
342        ));
343        self.register_unit(UnitDefinition::new(
344            "ampere".to_string(),
345            "A".to_string(),
346            Dimension::Current,
347            UnitSystem::SI,
348            1.0,
349            0.0,
350            true,
351        ));
352        self.register_unit(UnitDefinition::new(
353            "mole".to_string(),
354            "mol".to_string(),
355            Dimension::Amount,
356            UnitSystem::SI,
357            1.0,
358            0.0,
359            true,
360        ));
361        self.register_unit(UnitDefinition::new(
362            "candela".to_string(),
363            "cd".to_string(),
364            Dimension::LuminousIntensity,
365            UnitSystem::SI,
366            1.0,
367            0.0,
368            true,
369        ));
370
371        // SI Derived Units
372        self.register_unit(UnitDefinition::new(
373            "radian".to_string(),
374            "rad".to_string(),
375            Dimension::Angle,
376            UnitSystem::SI,
377            1.0,
378            0.0,
379            false,
380        ));
381        self.register_unit(UnitDefinition::new(
382            "hertz".to_string(),
383            "Hz".to_string(),
384            Dimension::Frequency,
385            UnitSystem::SI,
386            1.0,
387            0.0,
388            false,
389        ));
390        self.register_unit(UnitDefinition::new(
391            "newton".to_string(),
392            "N".to_string(),
393            Dimension::Force,
394            UnitSystem::SI,
395            1.0,
396            0.0,
397            false,
398        ));
399        self.register_unit(UnitDefinition::new(
400            "joule".to_string(),
401            "J".to_string(),
402            Dimension::Energy,
403            UnitSystem::SI,
404            1.0,
405            0.0,
406            false,
407        ));
408        self.register_unit(UnitDefinition::new(
409            "watt".to_string(),
410            "W".to_string(),
411            Dimension::Power,
412            UnitSystem::SI,
413            1.0,
414            0.0,
415            false,
416        ));
417        self.register_unit(UnitDefinition::new(
418            "pascal".to_string(),
419            "Pa".to_string(),
420            Dimension::Pressure,
421            UnitSystem::SI,
422            1.0,
423            0.0,
424            false,
425        ));
426        self.register_unit(UnitDefinition::new(
427            "volt".to_string(),
428            "V".to_string(),
429            Dimension::Voltage,
430            UnitSystem::SI,
431            1.0,
432            0.0,
433            false,
434        ));
435
436        // Length units
437        self.register_unit(UnitDefinition::new(
438            "centimeter".to_string(),
439            "cm".to_string(),
440            Dimension::Length,
441            UnitSystem::CGS,
442            0.01,
443            0.0,
444            false,
445        ));
446        self.register_unit(UnitDefinition::new(
447            "millimeter".to_string(),
448            "mm".to_string(),
449            Dimension::Length,
450            UnitSystem::SI,
451            0.001,
452            0.0,
453            false,
454        ));
455        self.register_unit(UnitDefinition::new(
456            "kilometer".to_string(),
457            "km".to_string(),
458            Dimension::Length,
459            UnitSystem::SI,
460            1000.0,
461            0.0,
462            false,
463        ));
464        self.register_unit(UnitDefinition::new(
465            "inch".to_string(),
466            "in".to_string(),
467            Dimension::Length,
468            UnitSystem::Imperial,
469            0.0254,
470            0.0,
471            false,
472        ));
473        self.register_unit(UnitDefinition::new(
474            "foot".to_string(),
475            "ft".to_string(),
476            Dimension::Length,
477            UnitSystem::Imperial,
478            0.3048,
479            0.0,
480            false,
481        ));
482        self.register_unit(UnitDefinition::new(
483            "yard".to_string(),
484            "yd".to_string(),
485            Dimension::Length,
486            UnitSystem::Imperial,
487            0.9144,
488            0.0,
489            false,
490        ));
491        self.register_unit(UnitDefinition::new(
492            "mile".to_string(),
493            "mi".to_string(),
494            Dimension::Length,
495            UnitSystem::Imperial,
496            1609.344,
497            0.0,
498            false,
499        ));
500
501        // Time units
502        self.register_unit(UnitDefinition::new(
503            "minute".to_string(),
504            "min".to_string(),
505            Dimension::Time,
506            UnitSystem::SI,
507            60.0,
508            0.0,
509            false,
510        ));
511        self.register_unit(UnitDefinition::new(
512            "hour".to_string(),
513            "h".to_string(),
514            Dimension::Time,
515            UnitSystem::SI,
516            3600.0,
517            0.0,
518            false,
519        ));
520        self.register_unit(UnitDefinition::new(
521            "day".to_string(),
522            "d".to_string(),
523            Dimension::Time,
524            UnitSystem::SI,
525            86400.0,
526            0.0,
527            false,
528        ));
529        self.register_unit(UnitDefinition::new(
530            "year".to_string(),
531            "yr".to_string(),
532            Dimension::Time,
533            UnitSystem::SI,
534            31_557_600.0,
535            0.0,
536            false,
537        )); // Julian year
538
539        // Mass units
540        self.register_unit(UnitDefinition::new(
541            "gram".to_string(),
542            "g".to_string(),
543            Dimension::Mass,
544            UnitSystem::CGS,
545            0.001,
546            0.0,
547            false,
548        ));
549        self.register_unit(UnitDefinition::new(
550            "pound".to_string(),
551            "lb".to_string(),
552            Dimension::Mass,
553            UnitSystem::Imperial,
554            0.453_592_37,
555            0.0,
556            false,
557        ));
558        self.register_unit(UnitDefinition::new(
559            "ounce".to_string(),
560            "oz".to_string(),
561            Dimension::Mass,
562            UnitSystem::Imperial,
563            0.028_349_523_125,
564            0.0,
565            false,
566        ));
567        self.register_unit(UnitDefinition::new(
568            "ton".to_string(),
569            "t".to_string(),
570            Dimension::Mass,
571            UnitSystem::SI,
572            1000.0,
573            0.0,
574            false,
575        ));
576
577        // Temperature units
578        self.register_unit(UnitDefinition::new(
579            "celsius".to_string(),
580            "°C".to_string(),
581            Dimension::Temperature,
582            UnitSystem::SI,
583            1.0,
584            273.15,
585            false,
586        ));
587        self.register_unit(UnitDefinition::new(
588            "fahrenheit".to_string(),
589            "°F".to_string(),
590            Dimension::Temperature,
591            UnitSystem::Imperial,
592            5.0 / 9.0,
593            459.67 * 5.0 / 9.0,
594            false,
595        ));
596
597        // Angle units
598        self.register_unit(UnitDefinition::new(
599            "degree".to_string(),
600            "°".to_string(),
601            Dimension::Angle,
602            UnitSystem::SI,
603            std::f64::consts::PI / 180.0,
604            0.0,
605            false,
606        ));
607
608        // Energy units
609        self.register_unit(UnitDefinition::new(
610            "calorie".to_string(),
611            "cal".to_string(),
612            Dimension::Energy,
613            UnitSystem::CGS,
614            4.184,
615            0.0,
616            false,
617        ));
618        self.register_unit(UnitDefinition::new(
619            "kilocalorie".to_string(),
620            "kcal".to_string(),
621            Dimension::Energy,
622            UnitSystem::SI,
623            4184.0,
624            0.0,
625            false,
626        ));
627        self.register_unit(UnitDefinition::new(
628            "electron_volt".to_string(),
629            "eV".to_string(),
630            Dimension::Energy,
631            UnitSystem::Atomic,
632            1.602_176_634e-19,
633            0.0,
634            false,
635        ));
636        self.register_unit(UnitDefinition::new(
637            "kilowatt_hour".to_string(),
638            "kWh".to_string(),
639            Dimension::Energy,
640            UnitSystem::SI,
641            3_600_000.0,
642            0.0,
643            false,
644        ));
645
646        // Power units
647        self.register_unit(UnitDefinition::new(
648            "horsepower".to_string(),
649            "hp".to_string(),
650            Dimension::Power,
651            UnitSystem::Imperial,
652            745.7,
653            0.0,
654            false,
655        ));
656
657        // Pressure units
658        self.register_unit(UnitDefinition::new(
659            "atmosphere".to_string(),
660            "atm".to_string(),
661            Dimension::Pressure,
662            UnitSystem::SI,
663            101_325.0,
664            0.0,
665            false,
666        ));
667        self.register_unit(UnitDefinition::new(
668            "bar".to_string(),
669            "bar".to_string(),
670            Dimension::Pressure,
671            UnitSystem::SI,
672            100_000.0,
673            0.0,
674            false,
675        ));
676        self.register_unit(UnitDefinition::new(
677            "torr".to_string(),
678            "Torr".to_string(),
679            Dimension::Pressure,
680            UnitSystem::SI,
681            133.322,
682            0.0,
683            false,
684        ));
685        self.register_unit(UnitDefinition::new(
686            "pounds_per_square_inch".to_string(),
687            "psi".to_string(),
688            Dimension::Pressure,
689            UnitSystem::Imperial,
690            6894.76,
691            0.0,
692            false,
693        ));
694
695        // Register common aliases
696        self.register_alias("metre".to_string(), "m".to_string());
697        self.register_alias("meters".to_string(), "m".to_string());
698        self.register_alias("metres".to_string(), "m".to_string());
699        self.register_alias("seconds".to_string(), "s".to_string());
700        self.register_alias("minutes".to_string(), "min".to_string());
701        self.register_alias("hours".to_string(), "h".to_string());
702        self.register_alias("days".to_string(), "d".to_string());
703        self.register_alias("years".to_string(), "yr".to_string());
704        self.register_alias("degrees".to_string(), "°".to_string());
705        self.register_alias("deg".to_string(), "°".to_string());
706        self.register_alias("radians".to_string(), "rad".to_string());
707        self.register_alias("inches".to_string(), "in".to_string());
708        self.register_alias("feet".to_string(), "ft".to_string());
709        self.register_alias("pounds".to_string(), "lb".to_string());
710        self.register_alias("lbs".to_string(), "lb".to_string());
711    }
712}
713
714impl Default for UnitRegistry {
715    fn default() -> Self {
716        Self::new()
717    }
718}
719
720/// Global unit registry instance
721static GLOBAL_UNIT_REGISTRY: std::sync::LazyLock<std::sync::RwLock<UnitRegistry>> =
722    std::sync::LazyLock::new(|| std::sync::RwLock::new(UnitRegistry::new()));
723
724/// Get the global unit registry
725#[allow(dead_code)]
726pub fn global_unit_registry() -> &'static std::sync::RwLock<UnitRegistry> {
727    &GLOBAL_UNIT_REGISTRY
728}
729
730/// Convert a value between units using the global registry
731#[allow(dead_code)]
732pub fn convert(value: f64, from_unit: &str, tounit: &str) -> CoreResult<f64> {
733    let registry = global_unit_registry().read().map_err(|_| {
734        CoreError::ComputationError(ErrorContext::new(
735            "Failed to acquire read lock on unit registry",
736        ))
737    })?;
738    registry.convert(value, from_unit, tounit)
739}
740
741/// Create a UnitValue
742#[allow(dead_code)]
743pub fn unit_value<T: ScientificNumber>(value: T, unit: &str) -> UnitValue<T> {
744    UnitValue::new(value, unit.to_string())
745}
746
747/// Utility functions for common conversions
748pub mod conversions {
749
750    /// Temperature conversions
751    pub fn celsius_to_fahrenheit(celsius: f64) -> f64 {
752        celsius * 9.0 / 5.0 + 32.0
753    }
754
755    pub fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
756        (fahrenheit - 32.0) * 5.0 / 9.0
757    }
758
759    pub fn celsius_to_kelvin(celsius: f64) -> f64 {
760        celsius + 273.15
761    }
762
763    pub fn kelvin_to_celsius(kelvin: f64) -> f64 {
764        kelvin - 273.15
765    }
766
767    /// Length conversions
768    pub fn meters_to_feet(meters: f64) -> f64 {
769        meters / 0.3048
770    }
771
772    pub fn feet_to_meters(feet: f64) -> f64 {
773        feet * 0.3048
774    }
775
776    pub fn inches_to_cm(inches: f64) -> f64 {
777        inches * 2.54
778    }
779
780    pub fn cm_to_inches(cm: f64) -> f64 {
781        cm / 2.54
782    }
783
784    /// Mass conversions
785    pub fn kg_to_lbs(kg: f64) -> f64 {
786        kg / 0.453_592_37
787    }
788
789    pub fn lbs_to_kg(lbs: f64) -> f64 {
790        lbs * 0.453_592_37
791    }
792
793    /// Angle conversions
794    pub fn degrees_to_radians(degrees: f64) -> f64 {
795        degrees * std::f64::consts::PI / 180.0
796    }
797
798    pub fn radians_to_degrees(radians: f64) -> f64 {
799        radians * 180.0 / std::f64::consts::PI
800    }
801
802    /// Energy conversions
803    pub fn joules_to_calories(joules: f64) -> f64 {
804        joules / 4.184
805    }
806
807    pub fn calories_to_joules(calories: f64) -> f64 {
808        calories * 4.184
809    }
810
811    pub fn ev_to_joules(ev: f64) -> f64 {
812        ev * 1.602_176_634e-19
813    }
814
815    pub fn joules_to_ev(joules: f64) -> f64 {
816        joules / 1.602_176_634e-19
817    }
818}
819
820/// Macro for convenient unit conversions
821#[macro_export]
822macro_rules! convert_units {
823    ($value:expr, $from:expr => $to:expr) => {
824        $crate::units::convert($value, $from, $to)
825    };
826}
827
828#[cfg(test)]
829mod tests {
830    use super::*;
831
832    #[test]
833    fn test_unit_registry_creation() {
834        let registry = UnitRegistry::new();
835
836        assert!(registry.get_unit("m").is_some());
837        assert!(registry.get_unit("kg").is_some());
838        assert!(registry.get_unit("s").is_some());
839        assert!(registry.get_unit("K").is_some());
840    }
841
842    #[test]
843    fn test_unit_compatibility() {
844        let registry = UnitRegistry::new();
845
846        assert!(registry.are_compatible("m", "km"));
847        assert!(registry.are_compatible("m", "ft"));
848        assert!(!registry.are_compatible("m", "kg"));
849        assert!(!registry.are_compatible("s", "m"));
850    }
851
852    #[test]
853    fn test_length_conversions() {
854        let registry = UnitRegistry::new();
855
856        // Meter to kilometer
857        let result = registry.convert(1000.0, "m", "km").unwrap();
858        assert!((result - 1.0).abs() < 1e-10);
859
860        // Meter to foot
861        let result = registry.convert(1.0, "m", "ft").unwrap();
862        assert!((result - 3.280_839_895).abs() < 1e-6);
863
864        // Inch to centimeter
865        let result = registry.convert(1.0, "in", "cm").unwrap();
866        assert!((result - 2.54).abs() < 1e-10);
867    }
868
869    #[test]
870    fn test_temperature_conversions() {
871        let registry = UnitRegistry::new();
872
873        // Celsius to Kelvin
874        let result = registry.convert(0.0, "°C", "K").unwrap();
875        assert!((result - 273.15).abs() < 1e-10);
876
877        // Fahrenheit to Celsius
878        let result = registry.convert(32.0, "°F", "°C").unwrap();
879        assert!((result - 0.0).abs() < 1e-10);
880
881        // Fahrenheit to Kelvin
882        let result = registry.convert(32.0, "°F", "K").unwrap();
883        assert!((result - 273.15).abs() < 1e-10);
884    }
885
886    #[test]
887    fn test_angle_conversions() {
888        let registry = UnitRegistry::new();
889
890        // Degrees to radians
891        let result = registry.convert(180.0, "°", "rad").unwrap();
892        assert!((result - std::f64::consts::PI).abs() < 1e-10);
893
894        // Radians to degrees
895        let result = registry
896            .convert(std::f64::consts::PI / 2.0, "rad", "°")
897            .unwrap();
898        assert!((result - 90.0).abs() < 1e-10);
899    }
900
901    #[test]
902    fn test_energy_conversions() {
903        let registry = UnitRegistry::new();
904
905        // Joules to calories
906        let result = registry.convert(4.184, "J", "cal").unwrap();
907        assert!((result - 1.0).abs() < 1e-10);
908
909        // eV to Joules
910        let result = registry.convert(1.0, "eV", "J").unwrap();
911        assert!((result - 1.602_176_634e-19).abs() < 1e-25);
912    }
913
914    #[test]
915    fn test_unit_value() {
916        let value = UnitValue::new(5.0, "m".to_string());
917        assert_eq!(value.value(), 5.0);
918        assert_eq!(value.unit(), "m");
919
920        let formatted = format!("{value}");
921        assert!(formatted.contains("5"));
922        assert!(formatted.contains("m"));
923    }
924
925    #[test]
926    fn test_unit_value_conversion() {
927        let registry = UnitRegistry::new();
928        let value = UnitValue::new(1000.0, "m".to_string());
929
930        let converted: UnitValue<f64> = registry.convert_value(&value, "km").unwrap();
931        assert!((converted.value() - 1.0).abs() < 1e-10);
932        assert_eq!(converted.unit(), "km");
933    }
934
935    #[test]
936    fn test_incompatible_conversion() {
937        let registry = UnitRegistry::new();
938
939        let result = registry.convert(1.0, "m", "kg");
940        assert!(result.is_err());
941    }
942
943    #[test]
944    fn test_unknown_unit() {
945        let registry = UnitRegistry::new();
946
947        let result = registry.convert(1.0, "unknown", "m");
948        assert!(result.is_err());
949    }
950
951    #[test]
952    fn test_aliases() {
953        let registry = UnitRegistry::new();
954
955        // Test that aliases work
956        assert!(registry.get_unit("meters").is_some());
957        assert!(registry.get_unit("degrees").is_some());
958        assert!(registry.get_unit("feet").is_some());
959    }
960
961    #[test]
962    fn test_dimension_filtering() {
963        let registry = UnitRegistry::new();
964
965        let length_units = registry.get_units_for_dimension(&Dimension::Length);
966        assert!(length_units.len() > 5); // m, cm, mm, km, in, ft, yd, mi
967
968        let si_units = registry.get_units_for_system(&UnitSystem::SI);
969        assert!(si_units.len() > 10);
970    }
971
972    #[test]
973    fn test_global_convert_function() {
974        let result = convert(1000.0, "m", "km").unwrap();
975        assert!((result - 1.0).abs() < 1e-10);
976    }
977
978    #[test]
979    fn test_conversion_utilities() {
980        use conversions::*;
981
982        assert!((celsius_to_fahrenheit(0.0) - 32.0).abs() < 1e-10);
983        assert!((fahrenheit_to_celsius(32.0) - 0.0).abs() < 1e-10);
984        assert!((celsius_to_kelvin(0.0) - 273.15).abs() < 1e-10);
985
986        assert!((degrees_to_radians(180.0) - std::f64::consts::PI).abs() < 1e-10);
987        assert!((radians_to_degrees(std::f64::consts::PI) - 180.0).abs() < 1e-10);
988
989        assert!((meters_to_feet(1.0) - 3.280_839_895).abs() < 1e-6);
990        assert!((feet_to_meters(1.0) - 0.3048).abs() < 1e-10);
991    }
992}