qnty 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qnty/__init__.py CHANGED
@@ -1,3 +1,172 @@
1
- from .variables import Dimensionless, Length, Pressure
1
+ """
2
+ Qnty - High-Performance Unit System for Engineering
3
+ ====================================================
4
+
5
+ A fast, type-safe unit system library for Python with dimensional safety
6
+ and optimized unit conversions for engineering calculations.
7
+ """
8
+
9
+ from .dimension import BaseDimension, DimensionSignature
10
+ from .equation import Equation
11
+ from .expression import Expression
12
+ from .unit import registry
13
+ from .units import register_all_units
14
+ from .variable import FastQuantity, TypeSafeSetter, TypeSafeVariable
15
+ from .variables import (
16
+ AbsorbedDose,
17
+ Acceleration,
18
+ ActivationEnergy,
19
+ AmountOfSubstance,
20
+ AnglePlane,
21
+ AngleSolid,
22
+ AngularAcceleration,
23
+ AngularMomentum,
24
+ Area,
25
+ AreaPerUnitVolume,
26
+ AtomicWeight,
27
+ Concentration,
28
+ DynamicFluidity,
29
+ ElectricCapacitance,
30
+ ElectricCharge,
31
+ ElectricCurrentIntensity,
32
+ ElectricDipoleMoment,
33
+ ElectricFieldStrength,
34
+ ElectricInductance,
35
+ ElectricPotential,
36
+ ElectricResistance,
37
+ ElectricalConductance,
38
+ ElectricalPermittivity,
39
+ ElectricalResistivity,
40
+ EnergyFlux,
41
+ EnergyHeatWork,
42
+ EnergyPerUnitArea,
43
+ Force,
44
+ ForceBody,
45
+ ForcePerUnitMass,
46
+ FrequencyVoltageRatio,
47
+ FuelConsumption,
48
+ HeatOfCombustion,
49
+ HeatOfFusion,
50
+ HeatOfVaporization,
51
+ HeatTransferCoefficient,
52
+ Illuminance,
53
+ KineticEnergyOfTurbulence,
54
+ Length,
55
+ LinearMassDensity,
56
+ LinearMomentum,
57
+ LuminanceSelf,
58
+ LuminousFlux,
59
+ LuminousIntensity,
60
+ MagneticField,
61
+ MagneticFlux,
62
+ MagneticInductionFieldStrength,
63
+ MagneticMoment,
64
+ MagneticPermeability,
65
+ MagnetomotiveForce,
66
+ Mass,
67
+ MassDensity,
68
+ MassFlowRate,
69
+ MassFlux,
70
+ MassFractionOfI,
71
+ MassTransferCoefficient,
72
+ MolalityOfSoluteI,
73
+ MolarConcentrationByMass,
74
+ MolarFlowRate,
75
+ MolarFlux,
76
+ MolarHeatCapacity,
77
+ MolarityOfI,
78
+ MoleFractionOfI,
79
+ MomentOfInertia,
80
+ MomentumFlowRate,
81
+ MomentumFlux,
82
+ NormalityOfSolution,
83
+ ParticleDensity,
84
+ Permeability,
85
+ PhotonEmissionRate,
86
+ PowerPerUnitMass,
87
+ PowerPerUnitVolume,
88
+ PowerThermalDuty,
89
+ Pressure,
90
+ RadiationDoseEquivalent,
91
+ RadiationExposure,
92
+ Radioactivity,
93
+ SecondMomentOfArea,
94
+ SecondRadiationConstantPlanck,
95
+ SpecificEnthalpy,
96
+ SpecificGravity,
97
+ SpecificHeatCapacityConstantPressure,
98
+ SpecificLength,
99
+ SpecificSurface,
100
+ SpecificVolume,
101
+ Stress,
102
+ SurfaceMassDensity,
103
+ SurfaceTension,
104
+ Temperature,
105
+ ThermalConductivity,
106
+ Time,
107
+ Torque,
108
+ TurbulenceEnergyDissipationRate,
109
+ VelocityAngular,
110
+ VelocityLinear,
111
+ ViscosityDynamic,
112
+ ViscosityKinematic,
113
+ Volume,
114
+ VolumeFractionOfI,
115
+ VolumetricCalorificHeatingValue,
116
+ VolumetricCoefficientOfExpansion,
117
+ VolumetricFlowRate,
118
+ VolumetricFlux,
119
+ VolumetricMassFlowRate,
120
+ Wavenumber
121
+ )
122
+
123
+ # Register all units to the global registry
124
+ register_all_units(registry)
125
+
126
+ # Finalize registry after all registrations
127
+ registry.finalize_registration()
128
+
129
+ # Define public API
130
+ __all__ = [
131
+ # Core variable types (most commonly used)
132
+ "Length", "Pressure", "Temperature", "Time", "Mass", "Volume", "Area",
133
+ "Force", "EnergyHeatWork", "PowerThermalDuty",
134
+
135
+ # Core classes for advanced usage
136
+ "FastQuantity", "TypeSafeVariable", "TypeSafeSetter",
137
+ "DimensionSignature", "BaseDimension",
138
+ "Expression", "Equation",
139
+
140
+ # All other variable types (95 additional types)
141
+ "AbsorbedDose", "Acceleration", "ActivationEnergy", "AmountOfSubstance",
142
+ "AnglePlane", "AngleSolid", "AngularAcceleration", "AngularMomentum",
143
+ "AreaPerUnitVolume", "AtomicWeight", "Concentration", "DynamicFluidity",
144
+ "ElectricCapacitance", "ElectricCharge", "ElectricCurrentIntensity",
145
+ "ElectricDipoleMoment", "ElectricFieldStrength", "ElectricInductance",
146
+ "ElectricPotential", "ElectricResistance", "ElectricalConductance",
147
+ "ElectricalPermittivity", "ElectricalResistivity", "EnergyFlux",
148
+ "EnergyPerUnitArea", "ForceBody", "ForcePerUnitMass",
149
+ "FrequencyVoltageRatio", "FuelConsumption", "HeatOfCombustion",
150
+ "HeatOfFusion", "HeatOfVaporization", "HeatTransferCoefficient",
151
+ "Illuminance", "KineticEnergyOfTurbulence", "LinearMassDensity",
152
+ "LinearMomentum", "LuminanceSelf", "LuminousFlux", "LuminousIntensity",
153
+ "MagneticField", "MagneticFlux", "MagneticInductionFieldStrength",
154
+ "MagneticMoment", "MagneticPermeability", "MagnetomotiveForce",
155
+ "MassDensity", "MassFlowRate", "MassFlux", "MassFractionOfI",
156
+ "MassTransferCoefficient", "MolalityOfSoluteI", "MolarConcentrationByMass",
157
+ "MolarFlowRate", "MolarFlux", "MolarHeatCapacity", "MolarityOfI",
158
+ "MoleFractionOfI", "MomentOfInertia", "MomentumFlowRate", "MomentumFlux",
159
+ "NormalityOfSolution", "ParticleDensity", "Permeability",
160
+ "PhotonEmissionRate", "PowerPerUnitMass", "PowerPerUnitVolume",
161
+ "RadiationDoseEquivalent", "RadiationExposure", "Radioactivity",
162
+ "SecondMomentOfArea", "SecondRadiationConstantPlanck", "SpecificEnthalpy",
163
+ "SpecificGravity", "SpecificHeatCapacityConstantPressure",
164
+ "SpecificLength", "SpecificSurface", "SpecificVolume", "Stress",
165
+ "SurfaceMassDensity", "SurfaceTension", "ThermalConductivity", "Torque",
166
+ "TurbulenceEnergyDissipationRate", "VelocityAngular", "VelocityLinear",
167
+ "ViscosityDynamic", "ViscosityKinematic", "VolumeFractionOfI",
168
+ "VolumetricCalorificHeatingValue", "VolumetricCoefficientOfExpansion",
169
+ "VolumetricFlowRate", "VolumetricFlux", "VolumetricMassFlowRate",
170
+ "Wavenumber",
171
+ ]
2
172
 
3
- __all__ = ["Length", "Pressure", "Dimensionless"]
qnty/dimension.py CHANGED
@@ -72,15 +72,113 @@ class DimensionSignature:
72
72
  return hash(self._signature)
73
73
 
74
74
 
75
- # Pre-defined dimension constants
76
- DIMENSIONLESS = DimensionSignature.create()
77
- LENGTH = DimensionSignature.create(length=1)
78
- MASS = DimensionSignature.create(mass=1)
79
- TIME = DimensionSignature.create(time=1)
80
- AREA = DimensionSignature.create(length=2)
81
- VOLUME = DimensionSignature.create(length=3)
82
- VELOCITY = DimensionSignature.create(length=1, time=-1)
83
- ACCELERATION = DimensionSignature.create(length=1, time=-2)
84
- FORCE = DimensionSignature.create(mass=1, length=1, time=-2)
85
- PRESSURE = DimensionSignature.create(mass=1, length=-1, time=-2)
86
- ENERGY = DimensionSignature.create(mass=1, length=2, time=-2)
75
+ # Pre-defined dimension constants (alphabetically ordered)
76
+ ABSORBED_DOSE = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
77
+ ACCELERATION = DimensionSignature.create(length=1, time=-2) # L T^-2
78
+ ACTIVATION_ENERGY = DimensionSignature.create(amount=-1, length=2, time=-2) # N^-1 L^2 T^-2
79
+ AMOUNT = DimensionSignature.create(amount=1) # N
80
+ AMOUNT_OF_SUBSTANCE = DimensionSignature.create(amount=1) # N
81
+ ANGLE_PLANE = DimensionSignature.create() # Dimensionless
82
+ ANGLE_SOLID = DimensionSignature.create() # Dimensionless
83
+ ANGULAR_ACCELERATION = DimensionSignature.create(time=-2) # T^-2
84
+ ANGULAR_MOMENTUM = DimensionSignature.create(length=2, mass=1, time=-1) # L^2 M T^-1
85
+ AREA = DimensionSignature.create(length=2) # L^2
86
+ AREA_PER_UNIT_VOLUME = DimensionSignature.create(length=-1) # L^-1
87
+ ATOMIC_WEIGHT = DimensionSignature.create(amount=-1, mass=1) # N^-1 M
88
+ CONCENTRATION = DimensionSignature.create(length=-3, mass=1) # L^-3 M
89
+ CURRENT = DimensionSignature.create(current=1) # A
90
+ DIMENSIONLESS = DimensionSignature.create() # Dimensionless
91
+ DYNAMIC_FLUIDITY = DimensionSignature.create(length=1, mass=-1, time=1) # L M^-1 T
92
+ ELECTRICAL_CONDUCTANCE = DimensionSignature.create(current=2, length=-2, mass=-1, time=3) # A^2 L^-2 M^-1 T^3
93
+ ELECTRICAL_PERMITTIVITY = DimensionSignature.create(current=2, length=-3, mass=-1, time=4) # A^2 L^-3 M^-1 T^4
94
+ ELECTRICAL_RESISTIVITY = DimensionSignature.create(current=-2, length=3, mass=1, time=-3) # A^-2 L^3 M T^-3
95
+ ELECTRIC_CAPACITANCE = DimensionSignature.create(current=2, length=-2, mass=-1, time=4) # A^2 L^-2 M^-1 T^4
96
+ ELECTRIC_CHARGE = DimensionSignature.create(amount=-1, current=1, time=1) # N^-1 A T
97
+ ELECTRIC_CURRENT_INTENSITY = DimensionSignature.create(current=1) # A
98
+ ELECTRIC_DIPOLE_MOMENT = DimensionSignature.create(current=1, length=1, time=1) # A L T
99
+ ELECTRIC_FIELD_STRENGTH = DimensionSignature.create(current=-1, length=1, mass=1, time=-3) # A^-1 L M T^-3
100
+ ELECTRIC_INDUCTANCE = DimensionSignature.create(current=-2, length=2, mass=1, time=-2) # A^-2 L^2 M T^-2
101
+ ELECTRIC_POTENTIAL = DimensionSignature.create(current=-1, length=2, mass=1, time=-3) # A^-1 L^2 M T^-3
102
+ ELECTRIC_RESISTANCE = DimensionSignature.create(current=-2, length=2, mass=1, time=-3) # A^-2 L^2 M T^-3
103
+ ENERGY_FLUX = DimensionSignature.create(mass=1, time=-3) # M T^-3
104
+ ENERGY_HEAT_WORK = DimensionSignature.create(length=2, mass=1, time=-2) # L^2 M T^-2
105
+ ENERGY_PER_UNIT_AREA = DimensionSignature.create(mass=1, time=-2) # M T^-2
106
+ FORCE = DimensionSignature.create(length=1, mass=1, time=-2) # L M T^-2
107
+ FORCE_BODY = DimensionSignature.create(length=-2, mass=1, time=-2) # L^-2 M T^-2
108
+ FORCE_PER_UNIT_MASS = DimensionSignature.create(length=1, time=-2) # L T^-2
109
+ FREQUENCY_VOLTAGE_RATIO = DimensionSignature.create(current=1, length=-2, mass=-1, time=3) # A L^-2 M^-1 T^3
110
+ FUEL_CONSUMPTION = DimensionSignature.create(length=-2) # L^-2
111
+ HEAT_OF_COMBUSTION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
112
+ HEAT_OF_FUSION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
113
+ HEAT_OF_VAPORIZATION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
114
+ HEAT_TRANSFER_COEFFICIENT = DimensionSignature.create(mass=1, temp=-1, time=-3) # M Θ^-1 T^-3
115
+ ILLUMINANCE = DimensionSignature.create(length=-2, luminosity=1) # L^-2 J
116
+ KINETIC_ENERGY_OF_TURBULENCE = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
117
+ LENGTH = DimensionSignature.create(length=1) # L
118
+ LINEAR_MASS_DENSITY = DimensionSignature.create(length=-1, mass=1) # L^-1 M
119
+ LINEAR_MOMENTUM = DimensionSignature.create(length=1, mass=1, time=-1) # L M T^-1
120
+ LUMINANCE_SELF = DimensionSignature.create(length=-2, luminosity=1) # L^-2 J
121
+ LUMINOSITY = DimensionSignature.create(luminosity=1) # J
122
+ LUMINOUS_FLUX = DimensionSignature.create(luminosity=1) # J
123
+ LUMINOUS_INTENSITY = DimensionSignature.create(luminosity=1) # J
124
+ MAGNETIC_FIELD = DimensionSignature.create(current=1, length=-1) # A L^-1
125
+ MAGNETIC_FLUX = DimensionSignature.create(current=-1, length=2, mass=1, time=-2) # A^-1 L^2 M T^-2
126
+ MAGNETIC_INDUCTION_FIELD_STRENGTH = DimensionSignature.create(current=-1, mass=1, time=-2) # A^-1 M T^-2
127
+ MAGNETIC_MOMENT = DimensionSignature.create(current=1, length=2) # A L^2
128
+ MAGNETIC_PERMEABILITY = DimensionSignature.create(current=-2, length=2, mass=1, time=-2) # A^-2 L^2 M T^-2
129
+ MAGNETOMOTIVE_FORCE = DimensionSignature.create(current=1) # A
130
+ MASS = DimensionSignature.create(mass=1) # M
131
+ MASS_DENSITY = DimensionSignature.create(length=-3, mass=1) # L^-3 M
132
+ MASS_FLOW_RATE = DimensionSignature.create(mass=1, time=-1) # M T^-1
133
+ MASS_FLUX = DimensionSignature.create(length=-2, mass=1, time=-1) # L^-2 M T^-1
134
+ MASS_FRACTION_OF_I = DimensionSignature.create() # Dimensionless
135
+ MASS_TRANSFER_COEFFICIENT = DimensionSignature.create(length=-2, mass=1, time=-1) # L^-2 M T^-1
136
+ MOLALITY_OF_SOLUTE_I = DimensionSignature.create(amount=1, mass=-1) # N M^-1
137
+ MOLARITY_OF_I = DimensionSignature.create(amount=1, length=-3) # N L^-3
138
+ MOLAR_CONCENTRATION_BY_MASS = DimensionSignature.create(amount=1) # N
139
+ MOLAR_FLOW_RATE = DimensionSignature.create(amount=1, time=-1) # N T^-1
140
+ MOLAR_FLUX = DimensionSignature.create(amount=1, length=-2, time=-1) # N L^-2 T^-1
141
+ MOLAR_HEAT_CAPACITY = DimensionSignature.create(amount=-1, length=2, temp=-1, time=-2) # N^-1 L^2 Θ^-1 T^-2
142
+ MOLE_FRACTION_OF_I = DimensionSignature.create() # Dimensionless
143
+ MOMENTUM_FLOW_RATE = DimensionSignature.create(length=1, mass=1, time=-2) # L M T^-2
144
+ MOMENTUM_FLUX = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
145
+ MOMENT_OF_INERTIA = DimensionSignature.create(length=2, mass=1) # L^2 M
146
+ NORMALITY_OF_SOLUTION = DimensionSignature.create(amount=1, length=-3) # N L^-3
147
+ PARTICLE_DENSITY = DimensionSignature.create(length=-3) # L^-3
148
+ PERMEABILITY = DimensionSignature.create(length=2) # L^2
149
+ PHOTON_EMISSION_RATE = DimensionSignature.create(length=-2, time=-1) # L^-2 T^-1
150
+ POWER_PER_UNIT_MASS = DimensionSignature.create(length=2, time=-3) # L^2 T^-3
151
+ POWER_PER_UNIT_VOLUME = DimensionSignature.create(length=-1, mass=1, time=-3) # L^-1 M T^-3
152
+ POWER_THERMAL_DUTY = DimensionSignature.create(length=2, mass=1, time=-3) # L^2 M T^-3
153
+ PRESSURE = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
154
+ RADIATION_DOSE_EQUIVALENT = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
155
+ RADIATION_EXPOSURE = DimensionSignature.create(current=1, mass=-1, time=1) # A M^-1 T
156
+ RADIOACTIVITY = DimensionSignature.create(time=-1) # T^-1
157
+ SECOND_MOMENT_OF_AREA = DimensionSignature.create(length=4) # L^4
158
+ SECOND_RADIATION_CONSTANT_PLANCK = DimensionSignature.create(length=1, temp=1) # L Θ
159
+ SPECIFIC_ENTHALPY = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
160
+ SPECIFIC_GRAVITY = DimensionSignature.create() # Dimensionless
161
+ SPECIFIC_HEAT_CAPACITY_CONSTANT_PRESSURE = DimensionSignature.create(length=2, mass=1, temp=-1, time=-2) # L^2 M Θ^-1 T^-2
162
+ SPECIFIC_LENGTH = DimensionSignature.create(length=1, mass=-1) # L M^-1
163
+ SPECIFIC_SURFACE = DimensionSignature.create(length=2, mass=-1) # L^2 M^-1
164
+ SPECIFIC_VOLUME = DimensionSignature.create(length=3, mass=-1) # L^3 M^-1
165
+ STRESS = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
166
+ SURFACE_MASS_DENSITY = DimensionSignature.create(length=-2, mass=1) # L^-2 M
167
+ SURFACE_TENSION = DimensionSignature.create(mass=1, time=-2) # M T^-2
168
+ TEMPERATURE = DimensionSignature.create(temp=1) # Θ
169
+ THERMAL_CONDUCTIVITY = DimensionSignature.create(length=1, mass=1, temp=1, time=-3) # L M Θ T^-3
170
+ TIME = DimensionSignature.create(time=1) # T
171
+ TORQUE = DimensionSignature.create(length=2, mass=1, time=-2) # L^2 M T^-2
172
+ TURBULENCE_ENERGY_DISSIPATION_RATE = DimensionSignature.create(length=2, time=-3) # L^2 T^-3
173
+ VELOCITY_ANGULAR = DimensionSignature.create(time=-1) # T^-1
174
+ VELOCITY_LINEAR = DimensionSignature.create(length=1, time=-1) # L T^-1
175
+ VISCOSITY_DYNAMIC = DimensionSignature.create(length=-1, mass=1, time=-1) # L^-1 M T^-1
176
+ VISCOSITY_KINEMATIC = DimensionSignature.create(length=2, time=-1) # L^2 T^-1
177
+ VOLUME = DimensionSignature.create(length=3) # L^3
178
+ VOLUMETRIC_CALORIFIC_HEATING_VALUE = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
179
+ VOLUMETRIC_COEFFICIENT_OF_EXPANSION = DimensionSignature.create(length=-3, mass=1, temp=-1) # L^-3 M Θ^-1
180
+ VOLUMETRIC_FLOW_RATE = DimensionSignature.create(length=3, time=-1) # L^3 T^-1
181
+ VOLUMETRIC_FLUX = DimensionSignature.create(length=1, time=-1) # L T^-1
182
+ VOLUMETRIC_MASS_FLOW_RATE = DimensionSignature.create(length=-3, mass=1, time=-1) # L^-3 M T^-1
183
+ VOLUME_FRACTION_OF_I = DimensionSignature.create() # Dimensionless
184
+ WAVENUMBER = DimensionSignature.create(length=-1) # L^-1
qnty/prefixes.py ADDED
@@ -0,0 +1,229 @@
1
+ """
2
+ SI Prefix System
3
+ ================
4
+
5
+ Standard SI prefixes for unit multiplication/division.
6
+ Provides systematic handling of metric prefixes like kilo-, milli-, micro-, etc.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class SIPrefix:
15
+ """
16
+ Standard SI prefix definition.
17
+
18
+ Attributes:
19
+ name: Full prefix name (e.g., "kilo", "milli")
20
+ symbol: Standard symbol (e.g., "k", "m")
21
+ factor: Multiplication factor relative to base unit (e.g., 1000, 0.001)
22
+ """
23
+ name: str
24
+ symbol: str
25
+ factor: float
26
+
27
+ def apply_to_name(self, base_name: str) -> str:
28
+ """Apply prefix to a base unit name."""
29
+ if not self.name:
30
+ return base_name
31
+ return f"{self.name}{base_name}"
32
+
33
+ def apply_to_symbol(self, base_symbol: str) -> str:
34
+ """Apply prefix to a base unit symbol."""
35
+ if not self.symbol:
36
+ return base_symbol
37
+ return f"{self.symbol}{base_symbol}"
38
+
39
+
40
+ class StandardPrefixes(Enum):
41
+ """
42
+ Standard SI prefixes with their multiplication factors.
43
+
44
+ From yotta (10^24) to yocto (10^-24).
45
+ """
46
+ # Larger prefixes (10^3 to 10^24)
47
+ YOTTA = SIPrefix("yotta", "Y", 1e24)
48
+ ZETTA = SIPrefix("zetta", "Z", 1e21)
49
+ EXA = SIPrefix("exa", "E", 1e18)
50
+ PETA = SIPrefix("peta", "P", 1e15)
51
+ TERA = SIPrefix("tera", "T", 1e12)
52
+ GIGA = SIPrefix("giga", "G", 1e9)
53
+ MEGA = SIPrefix("mega", "M", 1e6)
54
+ KILO = SIPrefix("kilo", "k", 1e3)
55
+ HECTO = SIPrefix("hecto", "h", 1e2)
56
+ DECA = SIPrefix("deca", "da", 1e1)
57
+
58
+ # Base (no prefix)
59
+ NONE = SIPrefix("", "", 1.0)
60
+
61
+ # Smaller prefixes (10^-1 to 10^-24)
62
+ DECI = SIPrefix("deci", "d", 1e-1)
63
+ CENTI = SIPrefix("centi", "c", 1e-2)
64
+ MILLI = SIPrefix("milli", "m", 1e-3)
65
+ MICRO = SIPrefix("micro", "μ", 1e-6)
66
+ NANO = SIPrefix("nano", "n", 1e-9)
67
+ PICO = SIPrefix("pico", "p", 1e-12)
68
+ FEMTO = SIPrefix("femto", "f", 1e-15)
69
+ ATTO = SIPrefix("atto", "a", 1e-18)
70
+ ZEPTO = SIPrefix("zepto", "z", 1e-21)
71
+ YOCTO = SIPrefix("yocto", "y", 1e-24)
72
+
73
+
74
+ # Common prefix groups for different unit types
75
+ COMMON_LENGTH_PREFIXES = [
76
+ StandardPrefixes.KILO,
77
+ StandardPrefixes.CENTI,
78
+ StandardPrefixes.MILLI,
79
+ StandardPrefixes.MICRO,
80
+ StandardPrefixes.NANO,
81
+ ]
82
+
83
+ COMMON_MASS_PREFIXES = [
84
+ StandardPrefixes.KILO, # Note: kilogram is the SI base unit
85
+ StandardPrefixes.MILLI,
86
+ StandardPrefixes.MICRO,
87
+ ]
88
+
89
+ COMMON_TIME_PREFIXES = [
90
+ StandardPrefixes.MILLI,
91
+ StandardPrefixes.MICRO,
92
+ StandardPrefixes.NANO,
93
+ StandardPrefixes.PICO,
94
+ ]
95
+
96
+ COMMON_ELECTRIC_PREFIXES = [
97
+ StandardPrefixes.KILO,
98
+ StandardPrefixes.MILLI,
99
+ StandardPrefixes.MICRO,
100
+ StandardPrefixes.NANO,
101
+ StandardPrefixes.PICO,
102
+ ]
103
+
104
+ COMMON_ENERGY_PREFIXES = [
105
+ StandardPrefixes.KILO,
106
+ StandardPrefixes.MEGA,
107
+ StandardPrefixes.GIGA,
108
+ ]
109
+
110
+ COMMON_POWER_PREFIXES = [
111
+ StandardPrefixes.KILO,
112
+ StandardPrefixes.MEGA,
113
+ StandardPrefixes.GIGA,
114
+ StandardPrefixes.MILLI,
115
+ StandardPrefixes.MICRO,
116
+ ]
117
+
118
+ COMMON_PRESSURE_PREFIXES = [
119
+ StandardPrefixes.KILO,
120
+ StandardPrefixes.MEGA,
121
+ StandardPrefixes.GIGA,
122
+ ]
123
+
124
+
125
+ def get_prefix_by_name(name: str) -> SIPrefix | None:
126
+ """Get a prefix by its name (e.g., 'kilo', 'milli')."""
127
+ for prefix_enum in StandardPrefixes:
128
+ if prefix_enum.value.name == name:
129
+ return prefix_enum.value
130
+ return None
131
+
132
+
133
+ def get_prefix_by_symbol(symbol: str) -> SIPrefix | None:
134
+ """Get a prefix by its symbol (e.g., 'k', 'm')."""
135
+ for prefix_enum in StandardPrefixes:
136
+ if prefix_enum.value.symbol == symbol:
137
+ return prefix_enum.value
138
+ return None
139
+
140
+
141
+ def get_prefix_by_factor(factor: float, tolerance: float = 1e-10) -> SIPrefix | None:
142
+ """Get a prefix by its multiplication factor."""
143
+ for prefix_enum in StandardPrefixes:
144
+ if abs(prefix_enum.value.factor - factor) < tolerance:
145
+ return prefix_enum.value
146
+ return None
147
+
148
+
149
+ # Define which units should get automatic prefixes
150
+ PREFIXABLE_UNITS = {
151
+ # Base SI units
152
+ 'meter': COMMON_LENGTH_PREFIXES,
153
+ 'gram': COMMON_MASS_PREFIXES,
154
+ 'second': COMMON_TIME_PREFIXES,
155
+ 'ampere': COMMON_ELECTRIC_PREFIXES,
156
+ 'kelvin': [], # Temperature usually doesn't use prefixes
157
+ 'mole': [
158
+ StandardPrefixes.MILLI,
159
+ StandardPrefixes.MICRO
160
+ ],
161
+ 'candela': [], # Luminous intensity rarely uses prefixes
162
+
163
+ # Derived SI units
164
+ 'pascal': COMMON_PRESSURE_PREFIXES,
165
+ 'joule': COMMON_ENERGY_PREFIXES,
166
+ 'watt': COMMON_POWER_PREFIXES,
167
+ 'coulomb': COMMON_ELECTRIC_PREFIXES,
168
+ 'volt': COMMON_ELECTRIC_PREFIXES,
169
+ 'farad': [
170
+ StandardPrefixes.MILLI,
171
+ StandardPrefixes.MICRO,
172
+ StandardPrefixes.NANO,
173
+ StandardPrefixes.PICO
174
+ ],
175
+ 'ohm': [
176
+ StandardPrefixes.KILO,
177
+ StandardPrefixes.MEGA,
178
+ StandardPrefixes.MILLI
179
+ ],
180
+ 'siemens': [
181
+ StandardPrefixes.MILLI,
182
+ StandardPrefixes.MICRO
183
+ ],
184
+ 'weber': [
185
+ StandardPrefixes.MILLI,
186
+ StandardPrefixes.MICRO
187
+ ],
188
+ 'tesla': [
189
+ StandardPrefixes.MILLI,
190
+ StandardPrefixes.MICRO,
191
+ StandardPrefixes.NANO
192
+ ],
193
+ 'henry': [
194
+ StandardPrefixes.MILLI,
195
+ StandardPrefixes.MICRO,
196
+ StandardPrefixes.NANO
197
+ ],
198
+ 'lumen': [],
199
+ 'lux': [],
200
+ 'becquerel': [
201
+ StandardPrefixes.KILO,
202
+ StandardPrefixes.MEGA,
203
+ StandardPrefixes.GIGA
204
+ ],
205
+ 'gray': [
206
+ StandardPrefixes.MILLI,
207
+ StandardPrefixes.MICRO
208
+ ],
209
+ 'sievert': [
210
+ StandardPrefixes.MILLI,
211
+ StandardPrefixes.MICRO
212
+ ],
213
+ 'hertz': [
214
+ StandardPrefixes.KILO,
215
+ StandardPrefixes.MEGA,
216
+ StandardPrefixes.GIGA
217
+ ],
218
+ 'newton': [
219
+ StandardPrefixes.KILO,
220
+ StandardPrefixes.MILLI
221
+ ],
222
+ 'bar': [
223
+ StandardPrefixes.MILLI
224
+ ], # Common non-SI unit
225
+ 'liter': [
226
+ StandardPrefixes.MILLI,
227
+ StandardPrefixes.MICRO
228
+ ], # Common non-SI unit
229
+ }
qnty/unit.py CHANGED
@@ -8,6 +8,7 @@ Unit definitions, constants and registry for the high-performance unit system.
8
8
  from dataclasses import dataclass
9
9
 
10
10
  from .dimension import DIMENSIONLESS, LENGTH, PRESSURE, DimensionSignature
11
+ from .prefixes import SIPrefix, StandardPrefixes
11
12
 
12
13
 
13
14
  @dataclass(frozen=True)
@@ -18,6 +19,21 @@ class UnitDefinition:
18
19
  dimension: DimensionSignature
19
20
  si_factor: float
20
21
  si_offset: float = 0.0
22
+ base_unit_name: str | None = None # Base unit without prefix
23
+ prefix: SIPrefix | None = None # SI prefix if applicable
24
+
25
+ @classmethod
26
+ def with_prefix(cls, base_def: 'UnitDefinition', prefix: SIPrefix) -> 'UnitDefinition':
27
+ """Create a new unit definition with an SI prefix."""
28
+ return cls(
29
+ name=prefix.apply_to_name(base_def.name),
30
+ symbol=prefix.apply_to_symbol(base_def.symbol),
31
+ dimension=base_def.dimension,
32
+ si_factor=base_def.si_factor * prefix.factor,
33
+ si_offset=base_def.si_offset,
34
+ base_unit_name=base_def.name,
35
+ prefix=prefix
36
+ )
21
37
 
22
38
 
23
39
  class UnitConstant:
@@ -50,7 +66,11 @@ class HighPerformanceRegistry:
50
66
  self.conversion_table: dict[tuple[str, str], float] = {} # (from_unit, to_unit) -> factor
51
67
  self.dimensional_groups: dict[int, list[UnitDefinition]] = {}
52
68
  self._dimension_cache: dict[int, UnitConstant] = {} # Cache for common dimension mappings
69
+ self._finalized = False
70
+ self.base_units: dict[str, UnitDefinition] = {} # Track base units for prefix generation
71
+ self.prefixable_units: set[str] = set() # Track which units can have prefixes
53
72
 
73
+ # Keep legacy initialization for backward compatibility
54
74
  self._initialize_units()
55
75
  self._precompute_conversions()
56
76
 
@@ -85,8 +105,56 @@ class HighPerformanceRegistry:
85
105
  self.dimensional_groups[dim_sig] = []
86
106
  self.dimensional_groups[dim_sig].append(unit_def)
87
107
 
108
+ def register_unit(self, unit_def: UnitDefinition):
109
+ """Register a single unit definition."""
110
+ if self._finalized:
111
+ raise RuntimeError("Cannot register units after registry is finalized")
112
+
113
+ self.units[unit_def.name] = unit_def
114
+
115
+ # Group by dimension
116
+ dim_sig = unit_def.dimension._signature
117
+ if dim_sig not in self.dimensional_groups:
118
+ self.dimensional_groups[dim_sig] = []
119
+ self.dimensional_groups[dim_sig].append(unit_def)
120
+
121
+ def register_with_prefixes(
122
+ self,
123
+ unit_def: UnitDefinition,
124
+ prefixes: list[StandardPrefixes] | None = None
125
+ ):
126
+ """
127
+ Register a unit and automatically generate prefixed variants.
128
+
129
+ Args:
130
+ unit_def: The base unit definition
131
+ prefixes: List of StandardPrefixes enum values to apply. If None, uses common prefixes.
132
+ """
133
+ if self._finalized:
134
+ raise RuntimeError("Cannot register units after registry is finalized")
135
+
136
+ # Register base unit
137
+ self.register_unit(unit_def)
138
+ self.base_units[unit_def.name] = unit_def
139
+ self.prefixable_units.add(unit_def.name)
140
+
141
+ # Generate and register prefixed variants
142
+ if prefixes:
143
+ for prefix_enum in prefixes:
144
+ prefix = prefix_enum.value
145
+ if prefix.name: # Skip NONE prefix (empty name)
146
+ prefixed_unit = UnitDefinition.with_prefix(unit_def, prefix)
147
+ self.register_unit(prefixed_unit)
148
+
149
+ def finalize_registration(self):
150
+ """Called after all units registered to precompute conversions."""
151
+ if not self._finalized:
152
+ self._precompute_conversions()
153
+ self._finalized = True
154
+
88
155
  def _precompute_conversions(self):
89
156
  """Pre-compute all unit conversions for maximum speed."""
157
+ self.conversion_table.clear() # Clear existing conversions
90
158
  for group in self.dimensional_groups.values():
91
159
  for from_unit in group:
92
160
  for to_unit in group:
File without changes
@@ -0,0 +1,47 @@
1
+ """
2
+ Base Unit Module Definition
3
+ ===========================
4
+
5
+ Provides abstract base class for unit modules and registration functionality.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+ from ..unit import UnitDefinition
12
+
13
+
14
+ class UnitModule(ABC):
15
+ """Abstract base class for unit modules."""
16
+
17
+ @abstractmethod
18
+ def get_unit_definitions(self) -> list[UnitDefinition]:
19
+ """Return list of unit definitions for this module."""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def get_units_class(self) -> type[Any]:
24
+ """Return the units class for this module."""
25
+ pass
26
+
27
+ def register_to_registry(self, unit_registry):
28
+ """Register all unit definitions to the given registry."""
29
+ from ..unit import UnitConstant
30
+
31
+ for unit_def in self.get_unit_definitions():
32
+ if unit_def.name not in unit_registry.units:
33
+ unit_registry.register_unit(unit_def)
34
+
35
+ # Populate the units class with constants from registry
36
+ units_class = self.get_units_class()
37
+ for unit_def in self.get_unit_definitions():
38
+ unit_constant = UnitConstant(unit_registry.units[unit_def.name])
39
+ setattr(units_class, unit_def.name, unit_constant)
40
+
41
+ # Add any aliases
42
+ if unit_def.symbol and unit_def.symbol != unit_def.name:
43
+ setattr(units_class, unit_def.symbol, unit_constant)
44
+
45
+ # Special case for inch - add in_ alias since 'in' is a Python keyword
46
+ if unit_def.name == "inch":
47
+ units_class.in_ = unit_constant