11#!/usr/bin/env python3
22# -*- coding: utf-8 -*-
3+ import unittest
4+ import numpy as np
35from numpy .testing import assert_allclose , assert_approx_equal
6+
47from UliEngineering .Physics .Density import normalize_density_kg_per_m3 , DensityKgPerM3
5- import unittest
8+ from UliEngineering . Units import InvalidUnitInContextException
69
710
811class TestDensityNormalization (unittest .TestCase ):
9- def test_normalize_density (self ):
10- # Test kg/m^3 normalization (SI unit)
12+ def test_si_units (self ):
13+ """ Test SI base units and unicode variants."""
1114 assert_approx_equal (normalize_density_kg_per_m3 ("1 kg/m^3" ), 1.0 )
12- assert_approx_equal (normalize_density_kg_per_m3 ("1000 kg/m^3" ), 1000.0 )
1315 assert_approx_equal (normalize_density_kg_per_m3 ("1 kg/m3" ), 1.0 )
14- # Test g/cm^3 to kg/m^3 conversion
16+ assert_approx_equal (normalize_density_kg_per_m3 ("1 kg/m³" ), 1.0 )
17+ assert_approx_equal (normalize_density_kg_per_m3 ("1000 kg/m^3" ), 1000.0 )
18+ assert_approx_equal (normalize_density_kg_per_m3 ("2.5 kg/m³" ), 2.5 )
19+
20+ def test_chemistry_units (self ):
21+ """Test common chemistry/lab density units."""
22+ # g/cm³ variants
1523 assert_approx_equal (normalize_density_kg_per_m3 ("1 g/cm^3" ), 1000.0 )
1624 assert_approx_equal (normalize_density_kg_per_m3 ("2 g/cm^3" ), 2000.0 )
1725 assert_approx_equal (normalize_density_kg_per_m3 ("2.7 g/cm^3" ), 2700.0 )
18- # Test g/cm3 to kg/m^3 conversion
1926 assert_approx_equal (normalize_density_kg_per_m3 ("1 g/cm3" ), 1000.0 )
20- # Test g/L to kg/m^3 conversion
27+ assert_approx_equal (normalize_density_kg_per_m3 ("1 g/cm³" ), 1000.0 )
28+ # kg/L variants
29+ assert_approx_equal (normalize_density_kg_per_m3 ("1 kg/L" ), 1000.0 )
30+ assert_approx_equal (normalize_density_kg_per_m3 ("1 kg/l" ), 1000.0 )
31+ assert_approx_equal (normalize_density_kg_per_m3 ("2.5 kg/L" ), 2500.0 )
32+ # g/mL variants
33+ assert_approx_equal (normalize_density_kg_per_m3 ("1 g/mL" ), 1000.0 )
34+ assert_approx_equal (normalize_density_kg_per_m3 ("1 g/ml" ), 1000.0 )
35+ # g/L variants
2136 assert_approx_equal (normalize_density_kg_per_m3 ("1 g/L" ), 1.0 )
2237 assert_approx_equal (normalize_density_kg_per_m3 ("1000 g/L" ), 1000.0 )
2338 assert_approx_equal (normalize_density_kg_per_m3 ("1 g/l" ), 1.0 )
2439
25- def test_normalize_density_iterables (self ):
26- assert_allclose (normalize_density_kg_per_m3 (["1 g/cm^3" , "2 g/cm^3" ]), [1000.0 , 2000.0 ])
27- assert_allclose (normalize_density_kg_per_m3 (["1 kg/m^3" , "2 g/L" ]), [1.0 , 2.0 ])
40+ def test_imperial_units (self ):
41+ """Test imperial density units."""
42+ # lb/ft³
43+ assert_approx_equal (normalize_density_kg_per_m3 ("1 lb/ft³" ), 16.0184634 )
44+ assert_approx_equal (normalize_density_kg_per_m3 ("1 lb/ft3" ), 16.0184634 )
45+ # lb/in³
46+ assert_approx_equal (normalize_density_kg_per_m3 ("1 lb/in³" ), 27679.9047 )
47+ assert_approx_equal (normalize_density_kg_per_m3 ("1 lb/in3" ), 27679.9047 )
48+ # lb/gal (US)
49+ assert_approx_equal (normalize_density_kg_per_m3 ("1 lb/gal" ), 119.826427 )
50+ # oz/in³
51+ assert_approx_equal (normalize_density_kg_per_m3 ("1 oz/in³" ), 1729.994 )
52+ assert_approx_equal (normalize_density_kg_per_m3 ("1 oz/in3" ), 1729.994 )
53+
54+ def test_metric_ton_units (self ):
55+ """Test tonne per cubic meter units."""
56+ assert_approx_equal (normalize_density_kg_per_m3 ("1 t/m³" ), 1000.0 )
57+ assert_approx_equal (normalize_density_kg_per_m3 ("1 t/m^3" ), 1000.0 )
58+ assert_approx_equal (normalize_density_kg_per_m3 ("1 t/m3" ), 1000.0 )
59+ assert_approx_equal (normalize_density_kg_per_m3 ("2.5 t/m³" ), 2500.0 )
60+
61+ def test_bare_numbers (self ):
62+ """Test that bare numbers pass through unchanged."""
63+ assert_approx_equal (normalize_density_kg_per_m3 (1.0 ), 1.0 )
64+ assert_approx_equal (normalize_density_kg_per_m3 (1000 ), 1000.0 )
65+ assert_approx_equal (normalize_density_kg_per_m3 (0.5 ), 0.5 )
66+
67+ def test_numpy_array_input (self ):
68+ """Test numpy array input."""
69+ arr = np .array (["1 g/cm^3" , "2 kg/m^3" ])
70+ result = normalize_density_kg_per_m3 (arr )
71+ assert_allclose (result , [1000.0 , 2.0 ])
72+
73+ def test_list_input (self ):
74+ """Test list input."""
75+ result = normalize_density_kg_per_m3 (["1 g/cm^3" , "2 g/cm^3" ])
76+ assert_allclose (result , [1000.0 , 2000.0 ])
77+ result = normalize_density_kg_per_m3 (["1 kg/m^3" , "2 g/L" ])
78+ assert_allclose (result , [1.0 , 2.0 ])
79+
80+ def test_tuple_input (self ):
81+ """Test tuple input."""
82+ result = normalize_density_kg_per_m3 (("1 g/cm^3" , "2 kg/m^3" ))
83+ assert_allclose (result , [1000.0 , 2.0 ])
84+
85+ def test_bytes_input (self ):
86+ """Test bytes input."""
87+ assert_approx_equal (normalize_density_kg_per_m3 (b"1 kg/m^3" ), 1.0 )
88+ assert_approx_equal (normalize_density_kg_per_m3 (b"1 g/cm^3" ), 1000.0 )
89+
90+ def test_invalid_unit_raises (self ):
91+ """Test that invalid units raise InvalidUnitInContextException."""
92+ with self .assertRaises (InvalidUnitInContextException ):
93+ normalize_density_kg_per_m3 ("1 lb/m^3" )
94+ with self .assertRaises (InvalidUnitInContextException ):
95+ normalize_density_kg_per_m3 ("1 g/mm^3" )
96+
97+ def test_missing_numeric_part_raises (self ):
98+ """Test that missing numeric part raises ValueError."""
99+ with self .assertRaises (ValueError ):
100+ normalize_density_kg_per_m3 ("kg/m^3" )
101+
102+ def test_none_raises (self ):
103+ """Test that None raises ValueError."""
104+ with self .assertRaises (ValueError ):
105+ normalize_density_kg_per_m3 (None )
28106
29107 def test_type_annotation_exists (self ):
30- """Test that the new type annotation is available."""
108+ """Test that the type annotation is available."""
31109 self .assertIsNotNone (DensityKgPerM3 )
32110
33- def test_density_type_various_units (self ):
34- """Test DensityKgPerM3 type with various unit inputs ."""
111+ def test_comprehensive_unit_cases (self ):
112+ """Test all supported units in one parameterized block ."""
35113 test_cases = [
114+ # SI
36115 ("1 kg/m^3" , 1.0 ),
37116 ("1 kg/m3" , 1.0 ),
38- ("1000 kg/m^3" , 1000.0 ),
117+ ("1 kg/m³" , 1.0 ),
118+ # Chemistry
39119 ("1 g/cm^3" , 1000.0 ),
40- ("2 g/cm^3" , 2000.0 ),
41120 ("1 g/cm3" , 1000.0 ),
121+ ("1 g/cm³" , 1000.0 ),
122+ ("1 kg/L" , 1000.0 ),
123+ ("1 kg/l" , 1000.0 ),
124+ ("1 g/mL" , 1000.0 ),
125+ ("1 g/ml" , 1000.0 ),
42126 ("1 g/L" , 1.0 ),
43- ("1000 g/L" , 1000.0 ),
127+ ("1 g/l" , 1.0 ),
128+ # Imperial
129+ ("1 lb/ft³" , 16.0184634 ),
130+ ("1 lb/ft3" , 16.0184634 ),
131+ ("1 lb/in³" , 27679.9047 ),
132+ ("1 lb/in3" , 27679.9047 ),
133+ ("1 lb/gal" , 119.826427 ),
134+ ("1 oz/in³" , 1729.994 ),
135+ ("1 oz/in3" , 1729.994 ),
136+ # Tonne
137+ ("1 t/m³" , 1000.0 ),
138+ ("1 t/m^3" , 1000.0 ),
139+ ("1 t/m3" , 1000.0 ),
44140 ]
45141 for input_val , expected in test_cases :
46142 with self .subTest (input = input_val ):
47143 result = normalize_density_kg_per_m3 (input_val )
48- assert_approx_equal (result , expected )
144+ assert_approx_equal (result , expected )
145+
146+
147+ class TestDensityIntegration (unittest .TestCase ):
148+ """Integration tests for density normalization in downstream functions."""
149+
150+ def test_cylinder_weight_with_density_units (self ):
151+ """Test that cylinder weight functions accept density with units."""
152+ from UliEngineering .Math .Geometry .Cylinder import (
153+ cylinder_weight_by_diameter ,
154+ cylinder_weight_by_radius ,
155+ cylinder_weight_by_cross_sectional_area ,
156+ )
157+
158+ # radius=1mm, length=1mm, density=2700 kg/m^3 (aluminium)
159+ # volume = pi * (1)^2 * 1 = pi mm^3
160+ # weight = volume * density = pi * 2700
161+ expected = np .pi * 2700.0
162+
163+ # With SI density unit
164+ w = cylinder_weight_by_radius (1.0 , 1.0 , "2700 kg/m^3" )
165+ self .assertAlmostEqual (w , expected , delta = 0.1 )
166+
167+ # With g/cm^3 (2.7 g/cm^3 = 2700 kg/m^3)
168+ w = cylinder_weight_by_radius (1.0 , 1.0 , "2.7 g/cm^3" )
169+ self .assertAlmostEqual (w , expected , delta = 0.1 )
170+
171+ # With kg/L (2.7 kg/L = 2700 kg/m^3)
172+ w = cylinder_weight_by_radius (1.0 , 1.0 , "2.7 kg/L" )
173+ self .assertAlmostEqual (w , expected , delta = 0.1 )
174+
175+ # diameter=2mm, length=1mm => same volume and weight
176+ w = cylinder_weight_by_diameter (2.0 , 1.0 , "2.7 g/cm^3" )
177+ self .assertAlmostEqual (w , expected , delta = 0.1 )
178+
179+ # cross-sectional area = pi * r^2 = pi mm^2
180+ w = cylinder_weight_by_cross_sectional_area (np .pi , 1.0 , "2.7 g/cm^3" )
181+ self .assertAlmostEqual (w , expected , delta = 0.1 )
182+
183+ def test_viscosity_functions_with_density_units (self ):
184+ """Test that viscosity functions accept density with units."""
185+ from UliEngineering .Physics .Viscosity import (
186+ kinematic_viscosity ,
187+ reynolds_number ,
188+ )
189+
190+ # kinematic_viscosity(0.001 Pa·s, 1000 kg/m³) => 1e-6 m²/s
191+ nu = kinematic_viscosity (0.001 , "1 g/cm^3" )
192+ self .assertAlmostEqual (nu , 1e-6 , delta = 1e-12 )
193+
194+ nu = kinematic_viscosity (0.001 , "1 kg/L" )
195+ self .assertAlmostEqual (nu , 1e-6 , delta = 1e-12 )
196+
197+ # Reynolds number: Re = rho * v * L / eta
198+ Re = reynolds_number ("1 g/cm^3" , 1.0 , 0.1 , 0.001 )
199+ self .assertAlmostEqual (Re , 100000.0 , delta = 0.1 )
200+
201+
202+ if __name__ == '__main__' :
203+ unittest .main ()
0 commit comments