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.contains(&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(
436            UnitCategory::Area,
437            &["acres", "acre", "ac"],
438            4_046.856_422_4,
439        ),
440        Unit::new_linear(UnitCategory::Area, &["ares", "are", "a"], 100.0),
441        Unit::new_linear(UnitCategory::Area, &["hectares", "hectare", "ha"], 10000.0),
442        Unit::new_linear(
443            UnitCategory::Area,
444            &["square megameters", "square megameter", "Mm²", "Mm2"],
445            1e12,
446        ),
447        // Volume units (base: liters)
448        Unit::new_linear(
449            UnitCategory::Volume,
450            &["liters", "l", "liter", "litre", "litres"],
451            1.0,
452        ),
453        Unit::new_linear(
454            UnitCategory::Volume,
455            &[
456                "milliliters",
457                "ml",
458                "milliliter",
459                "millilitre",
460                "millilitres",
461            ],
462            0.001,
463        ),
464        Unit::new_linear(
465            UnitCategory::Volume,
466            &[
467                "centiliters",
468                "cl",
469                "centiliter",
470                "centilitre",
471                "centilitres",
472            ],
473            0.01,
474        ),
475        Unit::new_linear(
476            UnitCategory::Volume,
477            &["deciliters", "dl", "deciliter", "decilitre", "decilitres"],
478            0.1,
479        ),
480        Unit::new_linear(
481            UnitCategory::Volume,
482            &["kiloliters", "kl", "kiloliter", "kilolitre", "kilolitres"],
483            1000.0,
484        ),
485        Unit::new_linear(
486            UnitCategory::Volume,
487            &["megaliter", "megaliters", "ML"],
488            1e6,
489        ),
490        Unit::new_linear(
491            UnitCategory::Volume,
492            &[
493                "cubic meters",
494                "cubic meter",
495                "m³",
496                "m3",
497                "cubic metres",
498                "cubic metre",
499            ],
500            1000.0,
501        ),
502        Unit::new_linear(
503            UnitCategory::Volume,
504            &[
505                "cubic kilometers",
506                "cubic kilometer",
507                "km³",
508                "km3",
509                "cubic kilometres",
510                "cubic kilometre",
511            ],
512            1e12,
513        ),
514        Unit::new_linear(
515            UnitCategory::Volume,
516            &[
517                "cubic centimeters",
518                "cubic centimeter",
519                "cm³",
520                "cm3",
521                "cc",
522                "cubic centimetres",
523                "cubic centimetre",
524            ],
525            0.001,
526        ),
527        Unit::new_linear(
528            UnitCategory::Volume,
529            &[
530                "cubic millimeters",
531                "cubic millimeter",
532                "mm³",
533                "mm3",
534                "cubic millimetres",
535                "cubic millimetre",
536            ],
537            1e-6,
538        ),
539        Unit::new_linear(
540            UnitCategory::Volume,
541            &["cubic inches", "in³", "in3", "cubic inch"],
542            0.016387064,
543        ),
544        Unit::new_linear(
545            UnitCategory::Volume,
546            &["cubic feet", "ft³", "ft3", "cubic foot"],
547            28.316846592,
548        ),
549        Unit::new_linear(
550            UnitCategory::Volume,
551            &["cubic yards", "yd³", "yd3", "cubic yard"],
552            764.554857984,
553        ),
554        Unit::new_linear(
555            UnitCategory::Volume,
556            &["cubic miles", "mi³", "mi3", "cubic mile"],
557            4.168_181_825_440_58e12,
558        ),
559        Unit::new_linear(
560            UnitCategory::Volume,
561            &["acre feet", "acre foot", "acre ft"],
562            1_233_481.837_547_52,
563        ),
564        Unit::new_linear(
565            UnitCategory::Volume,
566            &["bushels", "bushel", "bsh"],
567            35.23907016688,
568        ),
569        Unit::new_linear(
570            UnitCategory::Volume,
571            &["teaspoons", "tsp", "teaspoon"],
572            0.00492892159375,
573        ),
574        Unit::new_linear(
575            UnitCategory::Volume,
576            &["tablespoons", "tbsp", "tablespoon"],
577            0.01478676478125,
578        ),
579        Unit::new_linear(
580            UnitCategory::Volume,
581            &["fluid ounces", "fl oz", "fluid ounce"],
582            0.0295735295625,
583        ),
584        Unit::new_linear(UnitCategory::Volume, &["cups", "cup"], 0.236_588_236_5),
585        Unit::new_linear(UnitCategory::Volume, &["pints", "pt", "pint"], 0.473176473),
586        Unit::new_linear(
587            UnitCategory::Volume,
588            &["quarts", "qt", "quart"],
589            0.946352946,
590        ),
591        Unit::new_linear(
592            UnitCategory::Volume,
593            &["gallons", "gal", "gallon"],
594            3.785411784,
595        ),
596        Unit::new_linear(
597            UnitCategory::Volume,
598            &["imperial teaspoons", "imperial teaspoon", "imp tsp"],
599            0.00591938802083,
600        ),
601        Unit::new_linear(
602            UnitCategory::Volume,
603            &["imperial tablespoons", "imperial tablespoon", "imp tbsp"],
604            0.0177581640625,
605        ),
606        Unit::new_linear(
607            UnitCategory::Volume,
608            &["imperial fluid ounces", "imperial fluid ounce", "imp fl oz"],
609            0.0284130625,
610        ),
611        Unit::new_linear(
612            UnitCategory::Volume,
613            &["imperial pints", "imperial pint", "imp pt"],
614            0.56826125,
615        ),
616        Unit::new_linear(
617            UnitCategory::Volume,
618            &["imperial quarts", "imperial quart", "imp qt"],
619            1.1365225,
620        ),
621        Unit::new_linear(
622            UnitCategory::Volume,
623            &["imperial gallons", "imperial gallon", "imp gal"],
624            4.54609,
625        ),
626        Unit::new_linear(UnitCategory::Volume, &["metric cups", "metric cup"], 0.25),
627        // Angle units (base: degrees)
628        Unit::new_linear(UnitCategory::Angle, &["degrees", "deg", "°", "degree"], 1.0),
629        Unit::new_linear(
630            UnitCategory::Angle,
631            &["arc minutes", "arcminutes", "arcmin", "′", "arc minute"],
632            1.0 / 60.0,
633        ),
634        Unit::new_linear(
635            UnitCategory::Angle,
636            &["arc seconds", "arcseconds", "arcsec", "″", "arc second"],
637            1.0 / 3600.0,
638        ),
639        Unit::new_linear(
640            UnitCategory::Angle,
641            &["radians", "rad", "radian"],
642            180.0 / std::f64::consts::PI,
643        ),
644        Unit::new_linear(
645            UnitCategory::Angle,
646            &["gradians", "grad", "gradian", "gon"],
647            0.9,
648        ),
649        Unit::new_linear(
650            UnitCategory::Angle,
651            &["revolutions", "rev", "revolution", "rotation"],
652            360.0,
653        ),
654        // Duration units (base: seconds)
655        Unit::new_linear(
656            UnitCategory::Duration,
657            &["seconds", "s", "sec", "second"],
658            1.0,
659        ),
660        Unit::new_linear(
661            UnitCategory::Duration,
662            &["milliseconds", "ms", "millisecond"],
663            0.001,
664        ),
665        Unit::new_linear(
666            UnitCategory::Duration,
667            &["microseconds", "μs", "microsecond", "us"],
668            1e-6,
669        ),
670        Unit::new_linear(
671            UnitCategory::Duration,
672            &["nanoseconds", "ns", "nanosecond"],
673            1e-9,
674        ),
675        Unit::new_linear(
676            UnitCategory::Duration,
677            &["picoseconds", "ps", "picosecond"],
678            1e-12,
679        ),
680        Unit::new_linear(UnitCategory::Duration, &["minutes", "min", "minute"], 60.0),
681        Unit::new_linear(
682            UnitCategory::Duration,
683            &["hours", "h", "hr", "hour"],
684            3600.0,
685        ),
686        Unit::new_linear(UnitCategory::Duration, &["days", "d", "day"], 86400.0),
687        Unit::new_linear(UnitCategory::Duration, &["weeks", "wk", "week"], 604800.0),
688        // Speed units (base: meters per second)
689        Unit::new_linear(
690            UnitCategory::Speed,
691            &["meters per second", "m/s", "mps"],
692            1.0,
693        ),
694        Unit::new_linear(
695            UnitCategory::Speed,
696            &["kilometers per hour", "km/h", "kph", "kmph"],
697            1.0 / 3.6,
698        ),
699        Unit::new_linear(
700            UnitCategory::Speed,
701            &["miles per hour", "mph", "mi/h"],
702            0.44704,
703        ),
704        Unit::new_linear(
705            UnitCategory::Speed,
706            &["knots", "knot", "kn"],
707            0.514_444_444_444_444_5,
708        ),
709        // Energy units (base: joules)
710        Unit::new_linear(UnitCategory::Energy, &["joules", "j", "joule"], 1.0),
711        Unit::new_linear(
712            UnitCategory::Energy,
713            &["kilojoules", "kj", "kilojoule"],
714            1000.0,
715        ),
716        Unit::new_linear(UnitCategory::Energy, &["calories", "cal", "calorie"], 4.184),
717        Unit::new_linear(
718            UnitCategory::Energy,
719            &["kilocalories", "kcal", "kilocalorie", "food calorie", "Cal"],
720            4184.0,
721        ),
722        Unit::new_linear(
723            UnitCategory::Energy,
724            &["watt hours", "wh", "watt hour"],
725            3600.0,
726        ),
727        Unit::new_linear(
728            UnitCategory::Energy,
729            &["kilowatt hours", "kwh", "kilowatt hour"],
730            3.6e6,
731        ),
732        Unit::new_linear(
733            UnitCategory::Energy,
734            &["electronvolts", "ev", "electronvolt"],
735            1.602176634e-19,
736        ),
737        // Power units (base: watts)
738        Unit::new_linear(UnitCategory::Power, &["watts", "w", "watt"], 1.0),
739        Unit::new_linear(
740            UnitCategory::Power,
741            &["milliwatts", "mw", "milliwatt"],
742            0.001,
743        ),
744        Unit::new_linear(
745            UnitCategory::Power,
746            &["microwatts", "μw", "microwatt", "uw"],
747            1e-6,
748        ),
749        Unit::new_linear(UnitCategory::Power, &["nanowatts", "nw", "nanowatt"], 1e-9),
750        Unit::new_linear(UnitCategory::Power, &["picowatts", "pw", "picowatt"], 1e-12),
751        Unit::new_linear(
752            UnitCategory::Power,
753            &["femtowatts", "fw", "femtowatt"],
754            1e-15,
755        ),
756        Unit::new_linear(
757            UnitCategory::Power,
758            &["kilowatts", "kw", "kilowatt"],
759            1000.0,
760        ),
761        Unit::new_linear(UnitCategory::Power, &["megawatts", "MW", "megawatt"], 1e6),
762        Unit::new_linear(UnitCategory::Power, &["gigawatts", "GW", "gigawatt"], 1e9),
763        Unit::new_linear(UnitCategory::Power, &["terawatts", "TW", "terawatt"], 1e12),
764        Unit::new_linear(
765            UnitCategory::Power,
766            &["horsepower", "hp"],
767            745.699_871_582_270_1,
768        ),
769        // Pressure units (base: pascals)
770        Unit::new_linear(UnitCategory::Pressure, &["pascals", "pa", "pascal"], 1.0),
771        Unit::new_linear(
772            UnitCategory::Pressure,
773            &["hectopascals", "hpa", "hectopascal"],
774            100.0,
775        ),
776        Unit::new_linear(
777            UnitCategory::Pressure,
778            &["kilopascals", "kpa", "kilopascal"],
779            1000.0,
780        ),
781        Unit::new_linear(
782            UnitCategory::Pressure,
783            &["megapascals", "mpa", "megapascal"],
784            1e6,
785        ),
786        Unit::new_linear(
787            UnitCategory::Pressure,
788            &["gigapascals", "gpa", "gigapascal"],
789            1e9,
790        ),
791        Unit::new_linear(UnitCategory::Pressure, &["bars", "bar"], 100000.0),
792        Unit::new_linear(
793            UnitCategory::Pressure,
794            &["millibars", "mbar", "millibar"],
795            100.0,
796        ),
797        Unit::new_linear(
798            UnitCategory::Pressure,
799            &["millimeters of mercury", "mmhg", "mm hg"],
800            133.322387415,
801        ),
802        Unit::new_linear(
803            UnitCategory::Pressure,
804            &["inches of mercury", "inhg", "in hg"],
805            3386.389,
806        ),
807        Unit::new_linear(
808            UnitCategory::Pressure,
809            &["pounds per square inch", "psi", "lbf/in²"],
810            6894.757293168,
811        ),
812        Unit::new_linear(
813            UnitCategory::Pressure,
814            &["atmospheres", "atm", "atmosphere"],
815            101325.0,
816        ),
817        // Frequency units (base: hertz)
818        Unit::new_linear(UnitCategory::Frequency, &["hertz", "hz", "Hz"], 1.0),
819        Unit::new_linear(UnitCategory::Frequency, &["millihertz", "mHz"], 0.001),
820        Unit::new_linear(UnitCategory::Frequency, &["microhertz", "µHz", "uHz"], 1e-6),
821        Unit::new_linear(UnitCategory::Frequency, &["nanohertz", "nHz"], 1e-9),
822        Unit::new_linear(UnitCategory::Frequency, &["kilohertz", "kHz"], 1000.0),
823        Unit::new_linear(UnitCategory::Frequency, &["megahertz", "MHz"], 1e6),
824        Unit::new_linear(UnitCategory::Frequency, &["gigahertz", "GHz"], 1e9),
825        Unit::new_linear(UnitCategory::Frequency, &["terahertz", "THz"], 1e12),
826        // Electric Charge units (base: coulombs)
827        Unit::new_linear(
828            UnitCategory::ElectricCharge,
829            &["coulombs", "c", "coulomb"],
830            1.0,
831        ),
832        Unit::new_linear(
833            UnitCategory::ElectricCharge,
834            &["megaampere hours", "MAh"],
835            3.6e9,
836        ),
837        Unit::new_linear(
838            UnitCategory::ElectricCharge,
839            &["kiloampere hours", "kAh"],
840            3.6e6,
841        ),
842        Unit::new_linear(
843            UnitCategory::ElectricCharge,
844            &["ampere hours", "Ah"],
845            3600.0,
846        ),
847        Unit::new_linear(
848            UnitCategory::ElectricCharge,
849            &["milliampere hours", "mAh"],
850            3.6,
851        ),
852        Unit::new_linear(
853            UnitCategory::ElectricCharge,
854            &["microampere hours", "µAh", "uAh"],
855            0.0036,
856        ),
857        // Electric Current units (base: amperes)
858        Unit::new_linear(
859            UnitCategory::ElectricCurrent,
860            &["amperes", "A", "amp", "amps", "ampere"],
861            1.0,
862        ),
863        Unit::new_linear(UnitCategory::ElectricCurrent, &["megaamperes", "MA"], 1e6),
864        Unit::new_linear(
865            UnitCategory::ElectricCurrent,
866            &["kiloamperes", "kA"],
867            1000.0,
868        ),
869        Unit::new_linear(
870            UnitCategory::ElectricCurrent,
871            &["milliamperes", "mA"],
872            0.001,
873        ),
874        Unit::new_linear(
875            UnitCategory::ElectricCurrent,
876            &["microamperes", "µA", "uA"],
877            1e-6,
878        ),
879        // Electric Potential Difference units (base: volts)
880        Unit::new_linear(
881            UnitCategory::ElectricPotentialDifference,
882            &["volts", "V", "volt", "v"],
883            1.0,
884        ),
885        Unit::new_linear(
886            UnitCategory::ElectricPotentialDifference,
887            &["megavolts", "MV"],
888            1e6,
889        ),
890        Unit::new_linear(
891            UnitCategory::ElectricPotentialDifference,
892            &["kilovolts", "kV"],
893            1000.0,
894        ),
895        Unit::new_linear(
896            UnitCategory::ElectricPotentialDifference,
897            &["millivolts", "mV"],
898            0.001,
899        ),
900        Unit::new_linear(
901            UnitCategory::ElectricPotentialDifference,
902            &["microvolts", "µV", "uV"],
903            1e-6,
904        ),
905        // Electric Resistance units (base: ohms)
906        Unit::new_linear(
907            UnitCategory::ElectricResistance,
908            &["ohms", "Ω", "ohm", "ω"],
909            1.0,
910        ),
911        Unit::new_linear(
912            UnitCategory::ElectricResistance,
913            &["megaohms", "MΩ", "megohm"],
914            1e6,
915        ),
916        Unit::new_linear(
917            UnitCategory::ElectricResistance,
918            &["kiloohms", "kΩ", "kilohm"],
919            1000.0,
920        ),
921        Unit::new_linear(
922            UnitCategory::ElectricResistance,
923            &["milliohms", "mΩ", "milliohm"],
924            0.001,
925        ),
926        Unit::new_linear(
927            UnitCategory::ElectricResistance,
928            &["microohms", "µΩ", "microhm", "uΩ"],
929            1e-6,
930        ),
931        // Concentration of Mass units (base: grams per liter)
932        Unit::new_linear(
933            UnitCategory::ConcentrationMass,
934            &["grams per liter", "g/l"],
935            1.0,
936        ),
937        Unit::new_linear(
938            UnitCategory::ConcentrationMass,
939            &["milligrams per deciliter", "mg/dl"],
940            0.01,
941        ),
942        // Fuel Efficiency units (base: liters per 100 kilometers)
943        Unit::new_linear(
944            UnitCategory::FuelEfficiency,
945            &["liters per 100 kilometers", "l/100km"],
946            1.0,
947        ),
948        Unit::new_reciprocal(
949            UnitCategory::FuelEfficiency,
950            &["miles per gallon", "mpg"],
951            235.214_583_333_333_34,
952        ),
953        Unit::new_reciprocal(
954            UnitCategory::FuelEfficiency,
955            &["miles per imperial gallon", "imp mpg"],
956            282.480_936_331_822_2,
957        ),
958        // Information Storage units (base: bits)
959        Unit::new_linear(UnitCategory::InformationStorage, &["bits", "bit", "b"], 1.0),
960        Unit::new_linear(
961            UnitCategory::InformationStorage,
962            &["nibbles", "nibble"],
963            4.0,
964        ),
965        Unit::new_linear(
966            UnitCategory::InformationStorage,
967            &["bytes", "byte", "B"],
968            8.0,
969        ),
970        Unit::new_linear(
971            UnitCategory::InformationStorage,
972            &["kilobits", "kbit", "kb"],
973            1000.0,
974        ),
975        Unit::new_linear(
976            UnitCategory::InformationStorage,
977            &["megabits", "mbit", "mb"],
978            1e6,
979        ),
980        Unit::new_linear(
981            UnitCategory::InformationStorage,
982            &["gigabits", "gbit", "gb"],
983            1e9,
984        ),
985        Unit::new_linear(
986            UnitCategory::InformationStorage,
987            &["terabits", "tbit", "tb"],
988            1e12,
989        ),
990        Unit::new_linear(
991            UnitCategory::InformationStorage,
992            &["petabits", "pbit", "pb"],
993            1e15,
994        ),
995        Unit::new_linear(
996            UnitCategory::InformationStorage,
997            &["exabits", "ebit", "eb"],
998            1e18,
999        ),
1000        Unit::new_linear(
1001            UnitCategory::InformationStorage,
1002            &["zettabits", "zbit", "zb"],
1003            1e21,
1004        ),
1005        Unit::new_linear(
1006            UnitCategory::InformationStorage,
1007            &["yottabits", "ybit", "yb"],
1008            1e24,
1009        ),
1010        Unit::new_linear(
1011            UnitCategory::InformationStorage,
1012            &["kilobytes", "kbyte", "kB"],
1013            8000.0,
1014        ),
1015        Unit::new_linear(
1016            UnitCategory::InformationStorage,
1017            &["megabytes", "mbyte", "MB"],
1018            8e6,
1019        ),
1020        Unit::new_linear(
1021            UnitCategory::InformationStorage,
1022            &["gigabytes", "gbyte", "GB"],
1023            8e9,
1024        ),
1025        Unit::new_linear(
1026            UnitCategory::InformationStorage,
1027            &["terabytes", "tbyte", "TB"],
1028            8e12,
1029        ),
1030        Unit::new_linear(
1031            UnitCategory::InformationStorage,
1032            &["petabytes", "pbyte", "PB"],
1033            8e15,
1034        ),
1035        Unit::new_linear(
1036            UnitCategory::InformationStorage,
1037            &["exabytes", "ebyte", "EB"],
1038            8e18,
1039        ),
1040        Unit::new_linear(
1041            UnitCategory::InformationStorage,
1042            &["zettabytes", "zbyte", "ZB"],
1043            8e21,
1044        ),
1045        Unit::new_linear(
1046            UnitCategory::InformationStorage,
1047            &["yottabytes", "ybyte", "YB"],
1048            8e24,
1049        ),
1050        Unit::new_linear(
1051            UnitCategory::InformationStorage,
1052            &["kibibits", "kibit", "kib"],
1053            1024.0,
1054        ),
1055        Unit::new_linear(
1056            UnitCategory::InformationStorage,
1057            &["mebibits", "mibit", "mib"],
1058            1048576.0,
1059        ),
1060        Unit::new_linear(
1061            UnitCategory::InformationStorage,
1062            &["gibibits", "gibit", "gib"],
1063            1073741824.0,
1064        ),
1065        Unit::new_linear(
1066            UnitCategory::InformationStorage,
1067            &["tebibits", "tibit", "tib"],
1068            1099511627776.0,
1069        ),
1070        Unit::new_linear(
1071            UnitCategory::InformationStorage,
1072            &["pebibits", "pibit", "pib"],
1073            1125899906842624.0,
1074        ),
1075        Unit::new_linear(
1076            UnitCategory::InformationStorage,
1077            &["exbibits", "eibit", "eib"],
1078            1152921504606846976.0,
1079        ),
1080        Unit::new_linear(
1081            UnitCategory::InformationStorage,
1082            &["zebibits", "zibit", "zib"],
1083            1180591620717411303424.0,
1084        ),
1085        Unit::new_linear(
1086            UnitCategory::InformationStorage,
1087            &["yobibits", "yibit", "yib"],
1088            1208925819614629174706176.0,
1089        ),
1090        Unit::new_linear(
1091            UnitCategory::InformationStorage,
1092            &["kibibytes", "kibyte", "KiB"],
1093            8192.0,
1094        ),
1095        Unit::new_linear(
1096            UnitCategory::InformationStorage,
1097            &["mebibytes", "mibyte", "MiB"],
1098            8388608.0,
1099        ),
1100        Unit::new_linear(
1101            UnitCategory::InformationStorage,
1102            &["gibibytes", "gibyte", "GiB"],
1103            8589934592.0,
1104        ),
1105        Unit::new_linear(
1106            UnitCategory::InformationStorage,
1107            &["tebibytes", "tibyte", "TiB"],
1108            8796093022208.0,
1109        ),
1110        Unit::new_linear(
1111            UnitCategory::InformationStorage,
1112            &["pebibytes", "pibyte", "PiB"],
1113            9007199254740992.0,
1114        ),
1115        Unit::new_linear(
1116            UnitCategory::InformationStorage,
1117            &["exbibytes", "eibyte", "EiB"],
1118            9223372036854775808.0,
1119        ),
1120        Unit::new_linear(
1121            UnitCategory::InformationStorage,
1122            &["zebibytes", "zibyte", "ZiB"],
1123            9444732965739290427392.0,
1124        ),
1125        Unit::new_linear(
1126            UnitCategory::InformationStorage,
1127            &["yobibytes", "yibyte", "YiB"],
1128            9671406556917033397649408.0,
1129        ),
1130    ]
1131}
1132
1133pub fn resolve_unit(identifier: &str) -> Result<Unit> {
1134    let units = get_all_units();
1135    let identifier_lower = identifier.to_lowercase();
1136
1137    let mut exact_matches: Vec<Unit> = Vec::new();
1138    let mut case_matches: Vec<Unit> = Vec::new();
1139
1140    for unit in units {
1141        let is_exact = unit.matches_exact(identifier);
1142        let is_case = unit
1143            .identifiers
1144            .iter()
1145            .any(|alias| alias.to_lowercase() == identifier_lower);
1146
1147        if is_exact {
1148            exact_matches.push(unit.clone());
1149        }
1150
1151        if is_case {
1152            case_matches.push(unit);
1153        }
1154    }
1155
1156    if exact_matches.len() == 1 {
1157        return Ok(exact_matches.remove(0));
1158    }
1159
1160    if exact_matches.len() > 1 {
1161        let mut suggestions = exact_matches
1162            .iter()
1163            .map(|unit| format!("{} ({})", identifier, unit.category.name()))
1164            .collect::<Vec<_>>();
1165        suggestions.sort();
1166        suggestions.dedup();
1167        return Err(anyhow!(
1168            "Ambiguous unit '{}'; try a more specific name such as {}",
1169            identifier,
1170            suggestions.join(", ")
1171        ));
1172    }
1173
1174    if case_matches.is_empty() {
1175        return Err(anyhow!("Unknown unit: {}", identifier));
1176    }
1177
1178    if case_matches.len() == 1 {
1179        return Ok(case_matches.remove(0));
1180    }
1181
1182    let mut suggestions = case_matches
1183        .iter()
1184        .map(|unit| {
1185            let display = unit
1186                .identifiers
1187                .iter()
1188                .find(|alias| alias.to_lowercase() == identifier_lower)
1189                .copied()
1190                .unwrap_or(unit.identifiers[0]);
1191            format!("{} ({})", display, unit.category.name())
1192        })
1193        .collect::<Vec<_>>();
1194    suggestions.sort();
1195    suggestions.dedup();
1196
1197    Err(anyhow!(
1198        "Ambiguous unit '{}'; try using specific casing such as {}",
1199        identifier,
1200        suggestions.join(", ")
1201    ))
1202}
1203
1204pub fn find_unit(identifier: &str) -> Option<Unit> {
1205    resolve_unit(identifier).ok()
1206}
1207
1208pub fn convert(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1209    let from = resolve_unit(from_unit)?;
1210    let to = resolve_unit(to_unit)?;
1211
1212    if from.category != to.category {
1213        return Err(anyhow!(
1214            "Cannot convert {} to {}",
1215            from.category.name(),
1216            to.category.name()
1217        ));
1218    }
1219
1220    // Convert to base unit, then to target unit
1221    let base_value = from.convert_to_base(value);
1222    let result = to.convert_from_base(base_value);
1223
1224    Ok(result)
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229    use super::*;
1230
1231    #[test]
1232    fn test_basic_length_conversion() {
1233        let result = convert(1.0, "km", "m").unwrap();
1234        assert!((result - 1000.0).abs() < 1e-10);
1235    }
1236
1237    #[test]
1238    fn test_temperature_conversion() {
1239        let result = convert(0.0, "celsius", "fahrenheit").unwrap();
1240        assert!((result - 32.0).abs() < 1e-10);
1241
1242        let result = convert(100.0, "celsius", "fahrenheit").unwrap();
1243        assert!((result - 212.0).abs() < 1e-10);
1244    }
1245
1246    #[test]
1247    fn test_same_unit_conversion() {
1248        let result = convert(42.0, "meters", "m").unwrap();
1249        assert!((result - 42.0).abs() < 1e-10);
1250    }
1251
1252    #[test]
1253    fn test_incompatible_units() {
1254        let result = convert(1.0, "kg", "meters");
1255        assert!(result.is_err());
1256        assert!(result.unwrap_err().to_string().contains("Cannot convert"));
1257    }
1258
1259    #[test]
1260    fn test_unknown_unit() {
1261        let result = convert(1.0, "foobar", "meters");
1262        assert!(result.is_err());
1263        assert!(result.unwrap_err().to_string().contains("Unknown unit"));
1264    }
1265
1266    #[test]
1267    fn test_ambiguous_unit_identifier() {
1268        let result = convert(1.0, "ma", "amperes");
1269        assert!(result.is_err());
1270        assert!(result.unwrap_err().to_string().contains("Ambiguous unit"));
1271    }
1272
1273    #[test]
1274    fn test_information_storage() {
1275        let result = convert(1.0, "kibibytes", "bytes").unwrap();
1276        assert!((result - 1024.0).abs() < 1e-10);
1277    }
1278
1279    #[test]
1280    fn test_case_insensitive() {
1281        let result = convert(1.0, "KM", "M").unwrap();
1282        assert!((result - 1000.0).abs() < 1e-10);
1283    }
1284
1285    #[test]
1286    fn test_angle_conversion() {
1287        let result = convert(180.0, "degrees", "radians").unwrap();
1288        assert!((result - std::f64::consts::PI).abs() < 1e-10);
1289    }
1290
1291    #[test]
1292    fn test_area_conversion() {
1293        let result = convert(1.0, "square kilometers", "square meters").unwrap();
1294        assert!((result - 1e6).abs() < 1e-6);
1295    }
1296
1297    #[test]
1298    fn test_concentration_mass_conversion() {
1299        let result = convert(1.0, "grams per liter", "milligrams per deciliter").unwrap();
1300        assert!((result - 100.0).abs() < 1e-10);
1301    }
1302
1303    #[test]
1304    fn test_duration_conversion() {
1305        let result = convert(1.0, "hours", "seconds").unwrap();
1306        assert!((result - 3600.0).abs() < 1e-10);
1307    }
1308
1309    #[test]
1310    fn test_electric_charge_conversion() {
1311        let result = convert(1.0, "ampere hours", "coulombs").unwrap();
1312        assert!((result - 3600.0).abs() < 1e-10);
1313    }
1314
1315    #[test]
1316    fn test_electric_current_conversion() {
1317        let result = convert(1.0, "kiloamperes", "amperes").unwrap();
1318        assert!((result - 1000.0).abs() < 1e-10);
1319    }
1320
1321    #[test]
1322    fn test_electric_potential_difference_conversion() {
1323        let result = convert(1.0, "kilovolts", "volts").unwrap();
1324        assert!((result - 1000.0).abs() < 1e-10);
1325    }
1326
1327    #[test]
1328    fn test_electric_resistance_conversion() {
1329        let result = convert(1.0, "kiloohms", "ohms").unwrap();
1330        assert!((result - 1000.0).abs() < 1e-10);
1331    }
1332
1333    #[test]
1334    fn test_energy_conversion() {
1335        let result = convert(1.0, "kilocalories", "calories").unwrap();
1336        assert!((result - 1000.0).abs() < 1e-10);
1337    }
1338
1339    #[test]
1340    fn test_frequency_conversion() {
1341        let result = convert(1.0, "megahertz", "hertz").unwrap();
1342        assert!((result - 1e6).abs() < 1e-6);
1343    }
1344
1345    #[test]
1346    fn test_fuel_efficiency_conversion() {
1347        let result = convert(10.0, "liters per 100 kilometers", "miles per gallon").unwrap();
1348        assert!((result - 23.521_458_333_333_332).abs() < 1e-9);
1349
1350        let result = convert(7.5, "miles per gallon", "liters per 100 kilometers").unwrap();
1351        assert!((result - (235.214_583_333_333_34 / 7.5)).abs() < 1e-9);
1352
1353        let result = convert(40.0, "miles per gallon", "miles per imperial gallon").unwrap();
1354        assert!((result - 48.037_997_020_194_2).abs() < 1e-9);
1355
1356        let result = convert(
1357            50.0,
1358            "miles per imperial gallon",
1359            "liters per 100 kilometers",
1360        )
1361        .unwrap();
1362        assert!((result - 5.649_618_726_636_444).abs() < 1e-9);
1363    }
1364
1365    #[test]
1366    fn test_power_conversion() {
1367        let result = convert(1.0, "kilowatts", "watts").unwrap();
1368        assert!((result - 1000.0).abs() < 1e-10);
1369    }
1370
1371    #[test]
1372    fn test_pressure_conversion() {
1373        let result = convert(1.0, "bars", "pascals").unwrap();
1374        assert!((result - 100000.0).abs() < 1e-6);
1375    }
1376
1377    #[test]
1378    fn test_speed_conversion() {
1379        let result = convert(1.0, "kilometers per hour", "meters per second").unwrap();
1380        assert!((result - (1.0 / 3.6)).abs() < 1e-10);
1381    }
1382
1383    #[test]
1384    fn test_volume_conversion() {
1385        let result = convert(1.0, "liters", "milliliters").unwrap();
1386        assert!((result - 1000.0).abs() < 1e-10);
1387    }
1388}