blots_core/
units.rs

1use anyhow::Result;
2use anyhow::anyhow;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum UnitCategory {
6    Angle,
7    Area,
8    ConcentrationMass,
9    Duration,
10    ElectricCharge,
11    ElectricCurrent,
12    ElectricPotentialDifference,
13    ElectricResistance,
14    Energy,
15    Frequency,
16    FuelEfficiency,
17    InformationStorage,
18    Length,
19    Mass,
20    Power,
21    Pressure,
22    Speed,
23    Temperature,
24    Volume,
25}
26
27impl UnitCategory {
28    pub fn name(&self) -> &'static str {
29        match self {
30            Self::Angle => "angle",
31            Self::Area => "area",
32            Self::ConcentrationMass => "concentration of mass",
33            Self::Duration => "duration",
34            Self::ElectricCharge => "electric charge",
35            Self::ElectricCurrent => "electric current",
36            Self::ElectricPotentialDifference => "electric potential difference",
37            Self::ElectricResistance => "electric resistance",
38            Self::Energy => "energy",
39            Self::Frequency => "frequency",
40            Self::FuelEfficiency => "fuel efficiency",
41            Self::InformationStorage => "information storage",
42            Self::Length => "length",
43            Self::Mass => "mass",
44            Self::Power => "power",
45            Self::Pressure => "pressure",
46            Self::Speed => "speed",
47            Self::Temperature => "temperature",
48            Self::Volume => "volume",
49        }
50    }
51}
52
53#[derive(Debug, Clone)]
54pub enum ConversionType {
55    /// Linear conversion: value * coefficient converts to base unit
56    Linear { coefficient: f64 },
57    /// Reciprocal conversion: coefficient / value converts to base unit
58    Reciprocal { coefficient: f64 },
59    /// Temperature conversion requires special formulas
60    Temperature {
61        to_kelvin: fn(f64) -> f64,
62        from_kelvin: fn(f64) -> f64,
63    },
64}
65
66#[derive(Debug, Clone)]
67pub struct Unit {
68    pub category: UnitCategory,
69    pub identifiers: &'static [&'static str],
70    pub conversion: ConversionType,
71}
72
73impl Unit {
74    fn new_linear(
75        category: UnitCategory,
76        identifiers: &'static [&'static str],
77        coefficient: f64,
78    ) -> Self {
79        Self {
80            category,
81            identifiers,
82            conversion: ConversionType::Linear { coefficient },
83        }
84    }
85
86    fn new_reciprocal(
87        category: UnitCategory,
88        identifiers: &'static [&'static str],
89        coefficient: f64,
90    ) -> Self {
91        Self {
92            category,
93            identifiers,
94            conversion: ConversionType::Reciprocal { coefficient },
95        }
96    }
97
98    fn new_temperature(
99        identifiers: &'static [&'static str],
100        to_kelvin: fn(f64) -> f64,
101        from_kelvin: fn(f64) -> f64,
102    ) -> Self {
103        Self {
104            category: UnitCategory::Temperature,
105            identifiers,
106            conversion: ConversionType::Temperature {
107                to_kelvin,
108                from_kelvin,
109            },
110        }
111    }
112
113    pub fn matches(&self, identifier: &str) -> bool {
114        if self.matches_exact(identifier) {
115            return true;
116        }
117        let id_lower = identifier.to_lowercase();
118        self.identifiers
119            .iter()
120            .any(|&i| i.to_lowercase() == id_lower)
121    }
122
123    pub fn matches_exact(&self, identifier: &str) -> bool {
124        self.identifiers.iter().any(|&i| i == identifier)
125    }
126
127    pub fn convert_to_base(&self, value: f64) -> f64 {
128        match &self.conversion {
129            ConversionType::Linear { coefficient } => value * coefficient,
130            ConversionType::Reciprocal { coefficient } => {
131                if value == 0.0 {
132                    f64::INFINITY
133                } else {
134                    coefficient / value
135                }
136            }
137            ConversionType::Temperature { to_kelvin, .. } => to_kelvin(value),
138        }
139    }
140
141    pub fn convert_from_base(&self, value: f64) -> f64 {
142        match &self.conversion {
143            ConversionType::Linear { coefficient } => value / coefficient,
144            ConversionType::Reciprocal { coefficient } => {
145                if value == 0.0 {
146                    f64::INFINITY
147                } else {
148                    coefficient / value
149                }
150            }
151            ConversionType::Temperature { from_kelvin, .. } => from_kelvin(value),
152        }
153    }
154}
155
156// Temperature conversion functions
157fn celsius_to_kelvin(c: f64) -> f64 {
158    c + 273.15
159}
160
161fn kelvin_to_celsius(k: f64) -> f64 {
162    k - 273.15
163}
164
165fn fahrenheit_to_kelvin(f: f64) -> f64 {
166    (f - 32.0) * 5.0 / 9.0 + 273.15
167}
168
169fn kelvin_to_fahrenheit(k: f64) -> f64 {
170    (k - 273.15) * 9.0 / 5.0 + 32.0
171}
172
173fn kelvin_to_kelvin(k: f64) -> f64 {
174    k
175}
176
177pub fn get_all_units() -> Vec<Unit> {
178    vec![
179        // Temperature units
180        Unit::new_temperature(&["kelvin", "k"], kelvin_to_kelvin, kelvin_to_kelvin),
181        Unit::new_temperature(
182            &["celsius", "°c", "c"],
183            celsius_to_kelvin,
184            kelvin_to_celsius,
185        ),
186        Unit::new_temperature(
187            &["fahrenheit", "°f", "f"],
188            fahrenheit_to_kelvin,
189            kelvin_to_fahrenheit,
190        ),
191        // Length units (base: meters)
192        Unit::new_linear(
193            UnitCategory::Length,
194            &["meters", "meter", "m", "metres", "metre"],
195            1.0,
196        ),
197        Unit::new_linear(
198            UnitCategory::Length,
199            &["kilometers", "kilometer", "km", "kilometres", "kilometre"],
200            1000.0,
201        ),
202        Unit::new_linear(
203            UnitCategory::Length,
204            &[
205                "centimeters",
206                "centimeter",
207                "cm",
208                "centimetres",
209                "centimetre",
210            ],
211            0.01,
212        ),
213        Unit::new_linear(
214            UnitCategory::Length,
215            &[
216                "millimeters",
217                "millimeter",
218                "mm",
219                "millimetres",
220                "millimetre",
221            ],
222            0.001,
223        ),
224        Unit::new_linear(
225            UnitCategory::Length,
226            &[
227                "micrometers",
228                "micrometer",
229                "μm",
230                "micrometres",
231                "micrometre",
232                "um",
233            ],
234            1e-6,
235        ),
236        Unit::new_linear(
237            UnitCategory::Length,
238            &["nanometers", "nanometer", "nm", "nanometres", "nanometre"],
239            1e-9,
240        ),
241        Unit::new_linear(
242            UnitCategory::Length,
243            &["picometers", "picometer", "pm", "picometres", "picometre"],
244            1e-12,
245        ),
246        Unit::new_linear(UnitCategory::Length, &["inches", "in", "inch"], 0.0254),
247        Unit::new_linear(UnitCategory::Length, &["feet", "ft", "foot"], 0.3048),
248        Unit::new_linear(UnitCategory::Length, &["yards", "yd", "yard"], 0.9144),
249        Unit::new_linear(UnitCategory::Length, &["miles", "mi", "mile"], 1609.344),
250        Unit::new_linear(
251            UnitCategory::Length,
252            &["scandinavian miles", "scandinavian mile"],
253            10000.0,
254        ),
255        Unit::new_linear(
256            UnitCategory::Length,
257            &["light years", "light year", "ly"],
258            9.460_730_472_580_8e15,
259        ),
260        Unit::new_linear(
261            UnitCategory::Length,
262            &["nautical miles", "nautical mile", "nmi"],
263            1852.0,
264        ),
265        Unit::new_linear(UnitCategory::Length, &["fathoms", "fathom"], 1.8288),
266        Unit::new_linear(UnitCategory::Length, &["furlongs", "furlong"], 201.168),
267        Unit::new_linear(
268            UnitCategory::Length,
269            &["astronomical units", "astronomical unit", "au"],
270            149_597_870_700.0,
271        ),
272        Unit::new_linear(
273            UnitCategory::Length,
274            &["parsecs", "parsec", "pc"],
275            3.085_677_581_467_191_6e16,
276        ),
277        Unit::new_linear(
278            UnitCategory::Length,
279            &["megameters", "megameter", "Mm"],
280            1e6,
281        ),
282        Unit::new_linear(
283            UnitCategory::Length,
284            &["hectometers", "hectometer", "hm"],
285            100.0,
286        ),
287        Unit::new_linear(
288            UnitCategory::Length,
289            &["decameters", "decameter", "dam"],
290            10.0,
291        ),
292        Unit::new_linear(
293            UnitCategory::Length,
294            &["decimeters", "decimeter", "dm"],
295            0.1,
296        ),
297        Unit::new_linear(
298            UnitCategory::Length,
299            &["thousandths of an inch", "thou", "mil"],
300            0.0000254,
301        ),
302        // Mass units (base: kilograms)
303        Unit::new_linear(UnitCategory::Mass, &["kilograms", "kg", "kilogram"], 1.0),
304        Unit::new_linear(UnitCategory::Mass, &["grams", "g", "gram"], 0.001),
305        Unit::new_linear(UnitCategory::Mass, &["milligrams", "mg", "milligram"], 1e-6),
306        Unit::new_linear(
307            UnitCategory::Mass,
308            &["micrograms", "μg", "microgram", "ug"],
309            1e-9,
310        ),
311        Unit::new_linear(UnitCategory::Mass, &["nanograms", "ng", "nanogram"], 1e-12),
312        Unit::new_linear(UnitCategory::Mass, &["picograms", "pg", "picogram"], 1e-15),
313        Unit::new_linear(
314            UnitCategory::Mass,
315            &["ounces", "oz", "ounce"],
316            0.028349523125,
317        ),
318        Unit::new_linear(
319            UnitCategory::Mass,
320            &["pounds", "lb", "lbs", "pound"],
321            0.45359237,
322        ),
323        Unit::new_linear(UnitCategory::Mass, &["stones", "st", "stone"], 6.35029318),
324        Unit::new_linear(
325            UnitCategory::Mass,
326            &["metric tons", "metric ton", "tonnes", "tonne", "t"],
327            1000.0,
328        ),
329        Unit::new_linear(
330            UnitCategory::Mass,
331            &["short tons", "short ton", "tons", "ton"],
332            907.18474,
333        ),
334        Unit::new_linear(UnitCategory::Mass, &["carats", "ct", "carat"], 0.0002),
335        Unit::new_linear(
336            UnitCategory::Mass,
337            &["troy ounces", "troy ounce", "oz t"],
338            0.0311034768,
339        ),
340        Unit::new_linear(UnitCategory::Mass, &["slugs", "slug"], 14.593903),
341        // Area units (base: square meters)
342        Unit::new_linear(
343            UnitCategory::Area,
344            &[
345                "square meters",
346                "square meter",
347                "m²",
348                "m2",
349                "square metres",
350                "square metre",
351            ],
352            1.0,
353        ),
354        Unit::new_linear(
355            UnitCategory::Area,
356            &[
357                "square kilometers",
358                "square kilometer",
359                "km²",
360                "km2",
361                "square kilometres",
362                "square kilometre",
363            ],
364            1e6,
365        ),
366        Unit::new_linear(
367            UnitCategory::Area,
368            &[
369                "square centimeters",
370                "square centimeter",
371                "cm²",
372                "cm2",
373                "square centimetres",
374                "square centimetre",
375            ],
376            1e-4,
377        ),
378        Unit::new_linear(
379            UnitCategory::Area,
380            &[
381                "square millimeters",
382                "square millimeter",
383                "mm²",
384                "mm2",
385                "square millimetres",
386                "square millimetre",
387            ],
388            1e-6,
389        ),
390        Unit::new_linear(
391            UnitCategory::Area,
392            &[
393                "square micrometers",
394                "square micrometer",
395                "μm²",
396                "μm2",
397                "square micrometres",
398                "square micrometre",
399                "um2",
400            ],
401            1e-12,
402        ),
403        Unit::new_linear(
404            UnitCategory::Area,
405            &[
406                "square nanometers",
407                "square nanometer",
408                "nm²",
409                "nm2",
410                "square nanometres",
411                "square nanometre",
412            ],
413            1e-18,
414        ),
415        Unit::new_linear(
416            UnitCategory::Area,
417            &["square inches", "in²", "in2", "square inch"],
418            0.00064516,
419        ),
420        Unit::new_linear(
421            UnitCategory::Area,
422            &["square feet", "ft²", "ft2", "square foot"],
423            0.09290304,
424        ),
425        Unit::new_linear(
426            UnitCategory::Area,
427            &["square yards", "yd²", "yd2", "square yard"],
428            0.83612736,
429        ),
430        Unit::new_linear(
431            UnitCategory::Area,
432            &["square miles", "mi²", "mi2", "square mile"],
433            2_589_988.110_336,
434        ),
435        Unit::new_linear(UnitCategory::Area, &["acres", "acre", "ac"], 4046.856_422_4),
436        Unit::new_linear(UnitCategory::Area, &["ares", "are", "a"], 100.0),
437        Unit::new_linear(UnitCategory::Area, &["hectares", "hectare", "ha"], 10000.0),
438        Unit::new_linear(
439            UnitCategory::Area,
440            &["square megameters", "square megameter", "Mm²", "Mm2"],
441            1e12,
442        ),
443        // Volume units (base: liters)
444        Unit::new_linear(
445            UnitCategory::Volume,
446            &["liters", "l", "liter", "litre", "litres"],
447            1.0,
448        ),
449        Unit::new_linear(
450            UnitCategory::Volume,
451            &[
452                "milliliters",
453                "ml",
454                "milliliter",
455                "millilitre",
456                "millilitres",
457            ],
458            0.001,
459        ),
460        Unit::new_linear(
461            UnitCategory::Volume,
462            &[
463                "centiliters",
464                "cl",
465                "centiliter",
466                "centilitre",
467                "centilitres",
468            ],
469            0.01,
470        ),
471        Unit::new_linear(
472            UnitCategory::Volume,
473            &["deciliters", "dl", "deciliter", "decilitre", "decilitres"],
474            0.1,
475        ),
476        Unit::new_linear(
477            UnitCategory::Volume,
478            &["kiloliters", "kl", "kiloliter", "kilolitre", "kilolitres"],
479            1000.0,
480        ),
481        Unit::new_linear(
482            UnitCategory::Volume,
483            &["megaliter", "megaliters", "ML"],
484            1e6,
485        ),
486        Unit::new_linear(
487            UnitCategory::Volume,
488            &[
489                "cubic meters",
490                "cubic meter",
491                "m³",
492                "m3",
493                "cubic metres",
494                "cubic metre",
495            ],
496            1000.0,
497        ),
498        Unit::new_linear(
499            UnitCategory::Volume,
500            &[
501                "cubic kilometers",
502                "cubic kilometer",
503                "km³",
504                "km3",
505                "cubic kilometres",
506                "cubic kilometre",
507            ],
508            1e12,
509        ),
510        Unit::new_linear(
511            UnitCategory::Volume,
512            &[
513                "cubic centimeters",
514                "cubic centimeter",
515                "cm³",
516                "cm3",
517                "cc",
518                "cubic centimetres",
519                "cubic centimetre",
520            ],
521            0.001,
522        ),
523        Unit::new_linear(
524            UnitCategory::Volume,
525            &[
526                "cubic millimeters",
527                "cubic millimeter",
528                "mm³",
529                "mm3",
530                "cubic millimetres",
531                "cubic millimetre",
532            ],
533            1e-6,
534        ),
535        Unit::new_linear(
536            UnitCategory::Volume,
537            &["cubic inches", "in³", "in3", "cubic inch"],
538            0.016387064,
539        ),
540        Unit::new_linear(
541            UnitCategory::Volume,
542            &["cubic feet", "ft³", "ft3", "cubic foot"],
543            28.316846592,
544        ),
545        Unit::new_linear(
546            UnitCategory::Volume,
547            &["cubic yards", "yd³", "yd3", "cubic yard"],
548            764.554857984,
549        ),
550        Unit::new_linear(
551            UnitCategory::Volume,
552            &["cubic miles", "mi³", "mi3", "cubic mile"],
553            4.168_181_825_440_58e12,
554        ),
555        Unit::new_linear(
556            UnitCategory::Volume,
557            &["acre feet", "acre foot", "acre ft"],
558            1_233_481.837_547_52,
559        ),
560        Unit::new_linear(
561            UnitCategory::Volume,
562            &["bushels", "bushel", "bsh"],
563            35.23907016688,
564        ),
565        Unit::new_linear(
566            UnitCategory::Volume,
567            &["teaspoons", "tsp", "teaspoon"],
568            0.00492892159375,
569        ),
570        Unit::new_linear(
571            UnitCategory::Volume,
572            &["tablespoons", "tbsp", "tablespoon"],
573            0.01478676478125,
574        ),
575        Unit::new_linear(
576            UnitCategory::Volume,
577            &["fluid ounces", "fl oz", "fluid ounce"],
578            0.0295735295625,
579        ),
580        Unit::new_linear(UnitCategory::Volume, &["cups", "cup"], 0.236_588_236_5),
581        Unit::new_linear(UnitCategory::Volume, &["pints", "pt", "pint"], 0.473176473),
582        Unit::new_linear(
583            UnitCategory::Volume,
584            &["quarts", "qt", "quart"],
585            0.946352946,
586        ),
587        Unit::new_linear(
588            UnitCategory::Volume,
589            &["gallons", "gal", "gallon"],
590            3.785411784,
591        ),
592        Unit::new_linear(
593            UnitCategory::Volume,
594            &["imperial teaspoons", "imperial teaspoon", "imp tsp"],
595            0.00591938802083,
596        ),
597        Unit::new_linear(
598            UnitCategory::Volume,
599            &["imperial tablespoons", "imperial tablespoon", "imp tbsp"],
600            0.0177581640625,
601        ),
602        Unit::new_linear(
603            UnitCategory::Volume,
604            &["imperial fluid ounces", "imperial fluid ounce", "imp fl oz"],
605            0.0284130625,
606        ),
607        Unit::new_linear(
608            UnitCategory::Volume,
609            &["imperial pints", "imperial pint", "imp pt"],
610            0.56826125,
611        ),
612        Unit::new_linear(
613            UnitCategory::Volume,
614            &["imperial quarts", "imperial quart", "imp qt"],
615            1.1365225,
616        ),
617        Unit::new_linear(
618            UnitCategory::Volume,
619            &["imperial gallons", "imperial gallon", "imp gal"],
620            4.54609,
621        ),
622        Unit::new_linear(UnitCategory::Volume, &["metric cups", "metric cup"], 0.25),
623        // Angle units (base: degrees)
624        Unit::new_linear(UnitCategory::Angle, &["degrees", "deg", "°", "degree"], 1.0),
625        Unit::new_linear(
626            UnitCategory::Angle,
627            &["arc minutes", "arcminutes", "arcmin", "′", "arc minute"],
628            1.0 / 60.0,
629        ),
630        Unit::new_linear(
631            UnitCategory::Angle,
632            &["arc seconds", "arcseconds", "arcsec", "″", "arc second"],
633            1.0 / 3600.0,
634        ),
635        Unit::new_linear(
636            UnitCategory::Angle,
637            &["radians", "rad", "radian"],
638            180.0 / std::f64::consts::PI,
639        ),
640        Unit::new_linear(
641            UnitCategory::Angle,
642            &["gradians", "grad", "gradian", "gon"],
643            0.9,
644        ),
645        Unit::new_linear(
646            UnitCategory::Angle,
647            &["revolutions", "rev", "revolution", "rotation"],
648            360.0,
649        ),
650        // Duration units (base: seconds)
651        Unit::new_linear(
652            UnitCategory::Duration,
653            &["seconds", "s", "sec", "second"],
654            1.0,
655        ),
656        Unit::new_linear(
657            UnitCategory::Duration,
658            &["milliseconds", "ms", "millisecond"],
659            0.001,
660        ),
661        Unit::new_linear(
662            UnitCategory::Duration,
663            &["microseconds", "μs", "microsecond", "us"],
664            1e-6,
665        ),
666        Unit::new_linear(
667            UnitCategory::Duration,
668            &["nanoseconds", "ns", "nanosecond"],
669            1e-9,
670        ),
671        Unit::new_linear(
672            UnitCategory::Duration,
673            &["picoseconds", "ps", "picosecond"],
674            1e-12,
675        ),
676        Unit::new_linear(UnitCategory::Duration, &["minutes", "min", "minute"], 60.0),
677        Unit::new_linear(
678            UnitCategory::Duration,
679            &["hours", "h", "hr", "hour"],
680            3600.0,
681        ),
682        Unit::new_linear(UnitCategory::Duration, &["days", "d", "day"], 86400.0),
683        Unit::new_linear(UnitCategory::Duration, &["weeks", "wk", "week"], 604800.0),
684        // Speed units (base: meters per second)
685        Unit::new_linear(
686            UnitCategory::Speed,
687            &["meters per second", "m/s", "mps"],
688            1.0,
689        ),
690        Unit::new_linear(
691            UnitCategory::Speed,
692            &["kilometers per hour", "km/h", "kph", "kmph"],
693            1.0 / 3.6,
694        ),
695        Unit::new_linear(
696            UnitCategory::Speed,
697            &["miles per hour", "mph", "mi/h"],
698            0.44704,
699        ),
700        Unit::new_linear(
701            UnitCategory::Speed,
702            &["knots", "knot", "kn"],
703            0.514_444_444_444_444_5,
704        ),
705        // Energy units (base: joules)
706        Unit::new_linear(UnitCategory::Energy, &["joules", "j", "joule"], 1.0),
707        Unit::new_linear(
708            UnitCategory::Energy,
709            &["kilojoules", "kj", "kilojoule"],
710            1000.0,
711        ),
712        Unit::new_linear(UnitCategory::Energy, &["calories", "cal", "calorie"], 4.184),
713        Unit::new_linear(
714            UnitCategory::Energy,
715            &["kilocalories", "kcal", "kilocalorie", "food calorie", "Cal"],
716            4184.0,
717        ),
718        Unit::new_linear(
719            UnitCategory::Energy,
720            &["watt hours", "wh", "watt hour"],
721            3600.0,
722        ),
723        Unit::new_linear(
724            UnitCategory::Energy,
725            &["kilowatt hours", "kwh", "kilowatt hour"],
726            3.6e6,
727        ),
728        Unit::new_linear(
729            UnitCategory::Energy,
730            &["electronvolts", "ev", "electronvolt"],
731            1.602176634e-19,
732        ),
733        // Power units (base: watts)
734        Unit::new_linear(UnitCategory::Power, &["watts", "w", "watt"], 1.0),
735        Unit::new_linear(
736            UnitCategory::Power,
737            &["milliwatts", "mw", "milliwatt"],
738            0.001,
739        ),
740        Unit::new_linear(
741            UnitCategory::Power,
742            &["microwatts", "μw", "microwatt", "uw"],
743            1e-6,
744        ),
745        Unit::new_linear(UnitCategory::Power, &["nanowatts", "nw", "nanowatt"], 1e-9),
746        Unit::new_linear(UnitCategory::Power, &["picowatts", "pw", "picowatt"], 1e-12),
747        Unit::new_linear(
748            UnitCategory::Power,
749            &["femtowatts", "fw", "femtowatt"],
750            1e-15,
751        ),
752        Unit::new_linear(
753            UnitCategory::Power,
754            &["kilowatts", "kw", "kilowatt"],
755            1000.0,
756        ),
757        Unit::new_linear(UnitCategory::Power, &["megawatts", "MW", "megawatt"], 1e6),
758        Unit::new_linear(UnitCategory::Power, &["gigawatts", "GW", "gigawatt"], 1e9),
759        Unit::new_linear(UnitCategory::Power, &["terawatts", "TW", "terawatt"], 1e12),
760        Unit::new_linear(
761            UnitCategory::Power,
762            &["horsepower", "hp"],
763            745.699_871_582_270_1,
764        ),
765        // Pressure units (base: pascals)
766        Unit::new_linear(UnitCategory::Pressure, &["pascals", "pa", "pascal"], 1.0),
767        Unit::new_linear(
768            UnitCategory::Pressure,
769            &["hectopascals", "hpa", "hectopascal"],
770            100.0,
771        ),
772        Unit::new_linear(
773            UnitCategory::Pressure,
774            &["kilopascals", "kpa", "kilopascal"],
775            1000.0,
776        ),
777        Unit::new_linear(
778            UnitCategory::Pressure,
779            &["megapascals", "mpa", "megapascal"],
780            1e6,
781        ),
782        Unit::new_linear(
783            UnitCategory::Pressure,
784            &["gigapascals", "gpa", "gigapascal"],
785            1e9,
786        ),
787        Unit::new_linear(UnitCategory::Pressure, &["bars", "bar"], 100000.0),
788        Unit::new_linear(
789            UnitCategory::Pressure,
790            &["millibars", "mbar", "millibar"],
791            100.0,
792        ),
793        Unit::new_linear(
794            UnitCategory::Pressure,
795            &["millimeters of mercury", "mmhg", "mm hg"],
796            133.322387415,
797        ),
798        Unit::new_linear(
799            UnitCategory::Pressure,
800            &["inches of mercury", "inhg", "in hg"],
801            3386.389,
802        ),
803        Unit::new_linear(
804            UnitCategory::Pressure,
805            &["pounds per square inch", "psi", "lbf/in²"],
806            6894.757293168,
807        ),
808        Unit::new_linear(
809            UnitCategory::Pressure,
810            &["atmospheres", "atm", "atmosphere"],
811            101325.0,
812        ),
813        // Frequency units (base: hertz)
814        Unit::new_linear(UnitCategory::Frequency, &["hertz", "hz", "Hz"], 1.0),
815        Unit::new_linear(UnitCategory::Frequency, &["millihertz", "mHz"], 0.001),
816        Unit::new_linear(UnitCategory::Frequency, &["microhertz", "µHz", "uHz"], 1e-6),
817        Unit::new_linear(UnitCategory::Frequency, &["nanohertz", "nHz"], 1e-9),
818        Unit::new_linear(UnitCategory::Frequency, &["kilohertz", "kHz"], 1000.0),
819        Unit::new_linear(UnitCategory::Frequency, &["megahertz", "MHz"], 1e6),
820        Unit::new_linear(UnitCategory::Frequency, &["gigahertz", "GHz"], 1e9),
821        Unit::new_linear(UnitCategory::Frequency, &["terahertz", "THz"], 1e12),
822        // Electric Charge units (base: coulombs)
823        Unit::new_linear(
824            UnitCategory::ElectricCharge,
825            &["coulombs", "c", "coulomb"],
826            1.0,
827        ),
828        Unit::new_linear(
829            UnitCategory::ElectricCharge,
830            &["megaampere hours", "MAh"],
831            3.6e9,
832        ),
833        Unit::new_linear(
834            UnitCategory::ElectricCharge,
835            &["kiloampere hours", "kAh"],
836            3.6e6,
837        ),
838        Unit::new_linear(
839            UnitCategory::ElectricCharge,
840            &["ampere hours", "Ah"],
841            3600.0,
842        ),
843        Unit::new_linear(
844            UnitCategory::ElectricCharge,
845            &["milliampere hours", "mAh"],
846            3.6,
847        ),
848        Unit::new_linear(
849            UnitCategory::ElectricCharge,
850            &["microampere hours", "µAh", "uAh"],
851            0.0036,
852        ),
853        // Electric Current units (base: amperes)
854        Unit::new_linear(
855            UnitCategory::ElectricCurrent,
856            &["amperes", "A", "amp", "amps", "ampere"],
857            1.0,
858        ),
859        Unit::new_linear(UnitCategory::ElectricCurrent, &["megaamperes", "MA"], 1e6),
860        Unit::new_linear(
861            UnitCategory::ElectricCurrent,
862            &["kiloamperes", "kA"],
863            1000.0,
864        ),
865        Unit::new_linear(
866            UnitCategory::ElectricCurrent,
867            &["milliamperes", "mA"],
868            0.001,
869        ),
870        Unit::new_linear(
871            UnitCategory::ElectricCurrent,
872            &["microamperes", "µA", "uA"],
873            1e-6,
874        ),
875        // Electric Potential Difference units (base: volts)
876        Unit::new_linear(
877            UnitCategory::ElectricPotentialDifference,
878            &["volts", "V", "volt", "v"],
879            1.0,
880        ),
881        Unit::new_linear(
882            UnitCategory::ElectricPotentialDifference,
883            &["megavolts", "MV"],
884            1e6,
885        ),
886        Unit::new_linear(
887            UnitCategory::ElectricPotentialDifference,
888            &["kilovolts", "kV"],
889            1000.0,
890        ),
891        Unit::new_linear(
892            UnitCategory::ElectricPotentialDifference,
893            &["millivolts", "mV"],
894            0.001,
895        ),
896        Unit::new_linear(
897            UnitCategory::ElectricPotentialDifference,
898            &["microvolts", "µV", "uV"],
899            1e-6,
900        ),
901        // Electric Resistance units (base: ohms)
902        Unit::new_linear(
903            UnitCategory::ElectricResistance,
904            &["ohms", "Ω", "ohm", "ω"],
905            1.0,
906        ),
907        Unit::new_linear(
908            UnitCategory::ElectricResistance,
909            &["megaohms", "MΩ", "megohm"],
910            1e6,
911        ),
912        Unit::new_linear(
913            UnitCategory::ElectricResistance,
914            &["kiloohms", "kΩ", "kilohm"],
915            1000.0,
916        ),
917        Unit::new_linear(
918            UnitCategory::ElectricResistance,
919            &["milliohms", "mΩ", "milliohm"],
920            0.001,
921        ),
922        Unit::new_linear(
923            UnitCategory::ElectricResistance,
924            &["microohms", "µΩ", "microhm", "uΩ"],
925            1e-6,
926        ),
927        // Concentration of Mass units (base: grams per liter)
928        Unit::new_linear(
929            UnitCategory::ConcentrationMass,
930            &["grams per liter", "g/l"],
931            1.0,
932        ),
933        Unit::new_linear(
934            UnitCategory::ConcentrationMass,
935            &["milligrams per deciliter", "mg/dl"],
936            0.01,
937        ),
938        // Fuel Efficiency units (base: liters per 100 kilometers)
939        Unit::new_linear(
940            UnitCategory::FuelEfficiency,
941            &["liters per 100 kilometers", "l/100km"],
942            1.0,
943        ),
944        Unit::new_reciprocal(
945            UnitCategory::FuelEfficiency,
946            &["miles per gallon", "mpg"],
947            235.214_583_333_333_34,
948        ),
949        Unit::new_reciprocal(
950            UnitCategory::FuelEfficiency,
951            &["miles per imperial gallon", "imp mpg"],
952            282.480_936_331_822_2,
953        ),
954        // Information Storage units (base: bits)
955        Unit::new_linear(UnitCategory::InformationStorage, &["bits", "bit", "b"], 1.0),
956        Unit::new_linear(
957            UnitCategory::InformationStorage,
958            &["nibbles", "nibble"],
959            4.0,
960        ),
961        Unit::new_linear(
962            UnitCategory::InformationStorage,
963            &["bytes", "byte", "B"],
964            8.0,
965        ),
966        Unit::new_linear(
967            UnitCategory::InformationStorage,
968            &["kilobits", "kbit", "kb"],
969            1000.0,
970        ),
971        Unit::new_linear(
972            UnitCategory::InformationStorage,
973            &["megabits", "mbit", "mb"],
974            1e6,
975        ),
976        Unit::new_linear(
977            UnitCategory::InformationStorage,
978            &["gigabits", "gbit", "gb"],
979            1e9,
980        ),
981        Unit::new_linear(
982            UnitCategory::InformationStorage,
983            &["terabits", "tbit", "tb"],
984            1e12,
985        ),
986        Unit::new_linear(
987            UnitCategory::InformationStorage,
988            &["petabits", "pbit", "pb"],
989            1e15,
990        ),
991        Unit::new_linear(
992            UnitCategory::InformationStorage,
993            &["exabits", "ebit", "eb"],
994            1e18,
995        ),
996        Unit::new_linear(
997            UnitCategory::InformationStorage,
998            &["zettabits", "zbit", "zb"],
999            1e21,
1000        ),
1001        Unit::new_linear(
1002            UnitCategory::InformationStorage,
1003            &["yottabits", "ybit", "yb"],
1004            1e24,
1005        ),
1006        Unit::new_linear(
1007            UnitCategory::InformationStorage,
1008            &["kilobytes", "kbyte", "kB"],
1009            8000.0,
1010        ),
1011        Unit::new_linear(
1012            UnitCategory::InformationStorage,
1013            &["megabytes", "mbyte", "MB"],
1014            8e6,
1015        ),
1016        Unit::new_linear(
1017            UnitCategory::InformationStorage,
1018            &["gigabytes", "gbyte", "GB"],
1019            8e9,
1020        ),
1021        Unit::new_linear(
1022            UnitCategory::InformationStorage,
1023            &["terabytes", "tbyte", "TB"],
1024            8e12,
1025        ),
1026        Unit::new_linear(
1027            UnitCategory::InformationStorage,
1028            &["petabytes", "pbyte", "PB"],
1029            8e15,
1030        ),
1031        Unit::new_linear(
1032            UnitCategory::InformationStorage,
1033            &["exabytes", "ebyte", "EB"],
1034            8e18,
1035        ),
1036        Unit::new_linear(
1037            UnitCategory::InformationStorage,
1038            &["zettabytes", "zbyte", "ZB"],
1039            8e21,
1040        ),
1041        Unit::new_linear(
1042            UnitCategory::InformationStorage,
1043            &["yottabytes", "ybyte", "YB"],
1044            8e24,
1045        ),
1046        Unit::new_linear(
1047            UnitCategory::InformationStorage,
1048            &["kibibits", "kibit", "kib"],
1049            1024.0,
1050        ),
1051        Unit::new_linear(
1052            UnitCategory::InformationStorage,
1053            &["mebibits", "mibit", "mib"],
1054            1048576.0,
1055        ),
1056        Unit::new_linear(
1057            UnitCategory::InformationStorage,
1058            &["gibibits", "gibit", "gib"],
1059            1073741824.0,
1060        ),
1061        Unit::new_linear(
1062            UnitCategory::InformationStorage,
1063            &["tebibits", "tibit", "tib"],
1064            1099511627776.0,
1065        ),
1066        Unit::new_linear(
1067            UnitCategory::InformationStorage,
1068            &["pebibits", "pibit", "pib"],
1069            1125899906842624.0,
1070        ),
1071        Unit::new_linear(
1072            UnitCategory::InformationStorage,
1073            &["exbibits", "eibit", "eib"],
1074            1152921504606846976.0,
1075        ),
1076        Unit::new_linear(
1077            UnitCategory::InformationStorage,
1078            &["zebibits", "zibit", "zib"],
1079            1180591620717411303424.0,
1080        ),
1081        Unit::new_linear(
1082            UnitCategory::InformationStorage,
1083            &["yobibits", "yibit", "yib"],
1084            1208925819614629174706176.0,
1085        ),
1086        Unit::new_linear(
1087            UnitCategory::InformationStorage,
1088            &["kibibytes", "kibyte", "KiB"],
1089            8192.0,
1090        ),
1091        Unit::new_linear(
1092            UnitCategory::InformationStorage,
1093            &["mebibytes", "mibyte", "MiB"],
1094            8388608.0,
1095        ),
1096        Unit::new_linear(
1097            UnitCategory::InformationStorage,
1098            &["gibibytes", "gibyte", "GiB"],
1099            8589934592.0,
1100        ),
1101        Unit::new_linear(
1102            UnitCategory::InformationStorage,
1103            &["tebibytes", "tibyte", "TiB"],
1104            8796093022208.0,
1105        ),
1106        Unit::new_linear(
1107            UnitCategory::InformationStorage,
1108            &["pebibytes", "pibyte", "PiB"],
1109            9007199254740992.0,
1110        ),
1111        Unit::new_linear(
1112            UnitCategory::InformationStorage,
1113            &["exbibytes", "eibyte", "EiB"],
1114            9223372036854775808.0,
1115        ),
1116        Unit::new_linear(
1117            UnitCategory::InformationStorage,
1118            &["zebibytes", "zibyte", "ZiB"],
1119            9444732965739290427392.0,
1120        ),
1121        Unit::new_linear(
1122            UnitCategory::InformationStorage,
1123            &["yobibytes", "yibyte", "YiB"],
1124            9671406556917033397649408.0,
1125        ),
1126    ]
1127}
1128
1129pub fn resolve_unit(identifier: &str) -> Result<Unit> {
1130    let units = get_all_units();
1131    let identifier_lower = identifier.to_lowercase();
1132
1133    let mut exact_matches: Vec<Unit> = Vec::new();
1134    let mut case_matches: Vec<Unit> = Vec::new();
1135
1136    for unit in units {
1137        let is_exact = unit.matches_exact(identifier);
1138        let is_case = unit
1139            .identifiers
1140            .iter()
1141            .any(|alias| alias.to_lowercase() == identifier_lower);
1142
1143        if is_exact {
1144            exact_matches.push(unit.clone());
1145        }
1146
1147        if is_case {
1148            case_matches.push(unit);
1149        }
1150    }
1151
1152    if exact_matches.len() == 1 {
1153        return Ok(exact_matches.remove(0));
1154    }
1155
1156    if exact_matches.len() > 1 {
1157        let mut suggestions = exact_matches
1158            .iter()
1159            .map(|unit| format!("{} ({})", identifier, unit.category.name()))
1160            .collect::<Vec<_>>();
1161        suggestions.sort();
1162        suggestions.dedup();
1163        return Err(anyhow!(
1164            "Ambiguous unit '{}'; try a more specific name such as {}",
1165            identifier,
1166            suggestions.join(", ")
1167        ));
1168    }
1169
1170    if case_matches.is_empty() {
1171        return Err(anyhow!("Unknown unit: {}", identifier));
1172    }
1173
1174    if case_matches.len() == 1 {
1175        return Ok(case_matches.remove(0));
1176    }
1177
1178    let mut suggestions = case_matches
1179        .iter()
1180        .map(|unit| {
1181            let display = unit
1182                .identifiers
1183                .iter()
1184                .find(|alias| alias.to_lowercase() == identifier_lower)
1185                .copied()
1186                .unwrap_or(unit.identifiers[0]);
1187            format!("{} ({})", display, unit.category.name())
1188        })
1189        .collect::<Vec<_>>();
1190    suggestions.sort();
1191    suggestions.dedup();
1192
1193    Err(anyhow!(
1194        "Ambiguous unit '{}'; try using specific casing such as {}",
1195        identifier,
1196        suggestions.join(", ")
1197    ))
1198}
1199
1200pub fn find_unit(identifier: &str) -> Option<Unit> {
1201    resolve_unit(identifier).ok()
1202}
1203
1204pub fn convert(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1205    let from = resolve_unit(from_unit)?;
1206    let to = resolve_unit(to_unit)?;
1207
1208    if from.category != to.category {
1209        return Err(anyhow!(
1210            "Cannot convert {} to {}",
1211            from.category.name(),
1212            to.category.name()
1213        ));
1214    }
1215
1216    // Convert to base unit, then to target unit
1217    let base_value = from.convert_to_base(value);
1218    let result = to.convert_from_base(base_value);
1219
1220    Ok(result)
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225    use super::*;
1226
1227    #[test]
1228    fn test_basic_length_conversion() {
1229        let result = convert(1.0, "km", "m").unwrap();
1230        assert!((result - 1000.0).abs() < 1e-10);
1231    }
1232
1233    #[test]
1234    fn test_temperature_conversion() {
1235        let result = convert(0.0, "celsius", "fahrenheit").unwrap();
1236        assert!((result - 32.0).abs() < 1e-10);
1237
1238        let result = convert(100.0, "celsius", "fahrenheit").unwrap();
1239        assert!((result - 212.0).abs() < 1e-10);
1240    }
1241
1242    #[test]
1243    fn test_same_unit_conversion() {
1244        let result = convert(42.0, "meters", "m").unwrap();
1245        assert!((result - 42.0).abs() < 1e-10);
1246    }
1247
1248    #[test]
1249    fn test_incompatible_units() {
1250        let result = convert(1.0, "kg", "meters");
1251        assert!(result.is_err());
1252        assert!(result.unwrap_err().to_string().contains("Cannot convert"));
1253    }
1254
1255    #[test]
1256    fn test_unknown_unit() {
1257        let result = convert(1.0, "foobar", "meters");
1258        assert!(result.is_err());
1259        assert!(result.unwrap_err().to_string().contains("Unknown unit"));
1260    }
1261
1262    #[test]
1263    fn test_ambiguous_unit_identifier() {
1264        let result = convert(1.0, "ma", "amperes");
1265        assert!(result.is_err());
1266        assert!(result.unwrap_err().to_string().contains("Ambiguous unit"));
1267    }
1268
1269    #[test]
1270    fn test_information_storage() {
1271        let result = convert(1.0, "kibibytes", "bytes").unwrap();
1272        assert!((result - 1024.0).abs() < 1e-10);
1273    }
1274
1275    #[test]
1276    fn test_case_insensitive() {
1277        let result = convert(1.0, "KM", "M").unwrap();
1278        assert!((result - 1000.0).abs() < 1e-10);
1279    }
1280
1281    #[test]
1282    fn test_angle_conversion() {
1283        let result = convert(180.0, "degrees", "radians").unwrap();
1284        assert!((result - std::f64::consts::PI).abs() < 1e-10);
1285    }
1286
1287    #[test]
1288    fn test_area_conversion() {
1289        let result = convert(1.0, "square kilometers", "square meters").unwrap();
1290        assert!((result - 1e6).abs() < 1e-6);
1291    }
1292
1293    #[test]
1294    fn test_concentration_mass_conversion() {
1295        let result = convert(1.0, "grams per liter", "milligrams per deciliter").unwrap();
1296        assert!((result - 100.0).abs() < 1e-10);
1297    }
1298
1299    #[test]
1300    fn test_duration_conversion() {
1301        let result = convert(1.0, "hours", "seconds").unwrap();
1302        assert!((result - 3600.0).abs() < 1e-10);
1303    }
1304
1305    #[test]
1306    fn test_electric_charge_conversion() {
1307        let result = convert(1.0, "ampere hours", "coulombs").unwrap();
1308        assert!((result - 3600.0).abs() < 1e-10);
1309    }
1310
1311    #[test]
1312    fn test_electric_current_conversion() {
1313        let result = convert(1.0, "kiloamperes", "amperes").unwrap();
1314        assert!((result - 1000.0).abs() < 1e-10);
1315    }
1316
1317    #[test]
1318    fn test_electric_potential_difference_conversion() {
1319        let result = convert(1.0, "kilovolts", "volts").unwrap();
1320        assert!((result - 1000.0).abs() < 1e-10);
1321    }
1322
1323    #[test]
1324    fn test_electric_resistance_conversion() {
1325        let result = convert(1.0, "kiloohms", "ohms").unwrap();
1326        assert!((result - 1000.0).abs() < 1e-10);
1327    }
1328
1329    #[test]
1330    fn test_energy_conversion() {
1331        let result = convert(1.0, "kilocalories", "calories").unwrap();
1332        assert!((result - 1000.0).abs() < 1e-10);
1333    }
1334
1335    #[test]
1336    fn test_frequency_conversion() {
1337        let result = convert(1.0, "megahertz", "hertz").unwrap();
1338        assert!((result - 1e6).abs() < 1e-6);
1339    }
1340
1341    #[test]
1342    fn test_fuel_efficiency_conversion() {
1343        let result = convert(10.0, "liters per 100 kilometers", "miles per gallon").unwrap();
1344        assert!((result - 23.521_458_333_333_332).abs() < 1e-9);
1345
1346        let result = convert(7.5, "miles per gallon", "liters per 100 kilometers").unwrap();
1347        assert!((result - (235.214_583_333_333_34 / 7.5)).abs() < 1e-9);
1348
1349        let result = convert(40.0, "miles per gallon", "miles per imperial gallon").unwrap();
1350        assert!((result - 48.037_997_020_194_2).abs() < 1e-9);
1351
1352        let result = convert(
1353            50.0,
1354            "miles per imperial gallon",
1355            "liters per 100 kilometers",
1356        )
1357        .unwrap();
1358        assert!((result - 5.649_618_726_636_444).abs() < 1e-9);
1359    }
1360
1361    #[test]
1362    fn test_power_conversion() {
1363        let result = convert(1.0, "kilowatts", "watts").unwrap();
1364        assert!((result - 1000.0).abs() < 1e-10);
1365    }
1366
1367    #[test]
1368    fn test_pressure_conversion() {
1369        let result = convert(1.0, "bars", "pascals").unwrap();
1370        assert!((result - 100000.0).abs() < 1e-6);
1371    }
1372
1373    #[test]
1374    fn test_speed_conversion() {
1375        let result = convert(1.0, "kilometers per hour", "meters per second").unwrap();
1376        assert!((result - (1.0 / 3.6)).abs() < 1e-10);
1377    }
1378
1379    #[test]
1380    fn test_volume_conversion() {
1381        let result = convert(1.0, "liters", "milliliters").unwrap();
1382        assert!((result - 1000.0).abs() < 1e-10);
1383    }
1384}