1+ ########################################################################################
2+ ##
3+ ## TESTS FOR
4+ ## 'tritium.bubbler.py'
5+ ##
6+ ########################################################################################
7+
8+ # IMPORTS ==============================================================================
9+
10+ import unittest
11+ import numpy as np
12+
13+ from pathsim_chem .tritium import Bubbler4
14+
15+ from pathsim .solvers import EUF
16+ from pathsim .events .schedule import ScheduleList
17+
18+
19+ # TESTS ================================================================================
20+
21+ class TestFusionBubbler4 (unittest .TestCase ):
22+ """
23+ Test the implementation of the 'Bubbler4' block class from the fusion toolbox.
24+ The block inherits from `ODE` and models a 4-vial tritium collection system.
25+ """
26+
27+ def test_init (self ):
28+ """Test initialization with various parameter combinations"""
29+
30+ # Default initialization
31+ B = Bubbler4 ()
32+ self .assertEqual (B .conversion_efficiency , 0.9 )
33+ self .assertEqual (B .vial_efficiency , 0.9 )
34+ self .assertEqual (B .replacement_times , None )
35+ self .assertEqual (len (B .events ), 0 ) # No events when replacement_times is None
36+
37+ # Specific initialization
38+ B = Bubbler4 (conversion_efficiency = 0.8 , vial_efficiency = 0.7 , replacement_times = [100 , 200 ])
39+ self .assertEqual (B .conversion_efficiency , 0.8 )
40+ self .assertEqual (B .vial_efficiency , 0.7 )
41+ self .assertEqual (B .replacement_times , [100 , 200 ])
42+
43+ # Set solver to check internal solver instance
44+ B .set_solver (EUF , parent = None )
45+ self .assertTrue (B .engine )
46+ np .testing .assert_array_equal (B .engine .initial_value , np .zeros (4 ))
47+
48+
49+ def test_init_replacement_times (self ):
50+ """Test different replacement_times configurations"""
51+
52+ # Single list - should replicate for all vials
53+ B = Bubbler4 (replacement_times = [100 , 200 , 300 ])
54+ B .set_solver (EUF , parent = None )
55+ self .assertEqual (len (B .events ), 4 )
56+
57+ # List of lists - one per vial
58+ times = [[100 , 200 ], [150 , 250 ], [120 , 220 ], [180 , 280 ]]
59+ B = Bubbler4 (replacement_times = times )
60+ B .set_solver (EUF , parent = None )
61+ self .assertEqual (len (B .events ), 4 )
62+ for event in B .events :
63+ self .assertIsInstance (event , ScheduleList )
64+
65+ # NumPy array
66+ B = Bubbler4 (replacement_times = np .array ([100 , 200 , 300 ]))
67+ B .set_solver (EUF , parent = None )
68+ self .assertEqual (len (B .events ), 4 )
69+
70+ # Invalid case - wrong length
71+ with self .assertRaises (ValueError ):
72+ B = Bubbler4 (replacement_times = [[100 ], [200 ], [300 ]]) # Only 3 vials
73+
74+
75+ def test_update_outputs (self ):
76+ """Test the update method and output calculations"""
77+
78+ # Test with default parameters
79+ B = Bubbler4 ()
80+ B .set_solver (EUF , parent = None )
81+
82+ # Set some test inputs
83+ B .inputs [0 ] = 10.0 # soluble input
84+ B .inputs [1 ] = 5.0 # insoluble input
85+
86+ B .update (0.0 )
87+
88+ # Check that outputs are set (initial state is zero)
89+ self .assertEqual (B .outputs [0 ], 0.0 ) # vial 1
90+ self .assertEqual (B .outputs [1 ], 0.0 ) # vial 2
91+ self .assertEqual (B .outputs [2 ], 0.0 ) # vial 3
92+ self .assertEqual (B .outputs [3 ], 0.0 ) # vial 4
93+
94+ # Calculate expected sample_out with default parameters (ve=0.9, ce=0.9)
95+ ve , ce = 0.9 , 0.9
96+ sol , ins = 10.0 , 5.0
97+ expected_sample_out = (1 - ce )* ins + (1 - ve )** 2 * (ce * ins + (1 - ve )** 2 * sol )
98+ self .assertAlmostEqual (B .outputs [4 ], expected_sample_out , places = 10 )
99+
100+
101+ def test_mass_conservation (self ):
102+ """Test that mass is conserved in the system"""
103+
104+ B = Bubbler4 (conversion_efficiency = 0.8 , vial_efficiency = 0.7 )
105+ B .set_solver (EUF , parent = None )
106+
107+ # Set test inputs
108+ sol_in , ins_in = 10.0 , 5.0
109+ B .inputs [0 ] = sol_in
110+ B .inputs [1 ] = ins_in
111+
112+ # Get rates and sample_out
113+ u = np .array ([sol_in , ins_in ])
114+ x = np .zeros (4 )
115+ rates = B .func_dyn (x , u , 0.0 )
116+
117+ B .update (0.0 )
118+ sample_out = B .outputs [4 ]
119+
120+ # Total input rate
121+ total_in = sol_in + ins_in
122+
123+ # Total accumulation rate in vials
124+ total_vial_rates = np .sum (rates )
125+
126+ # Mass conservation: input = vial accumulation + sample output
127+ total_out = total_vial_rates + sample_out
128+
129+ self .assertAlmostEqual (total_in , total_out , places = 10 )
130+
131+
132+ def test_vial_reset_functionality (self ):
133+ """Test that vial reset events work correctly"""
134+
135+ B = Bubbler4 (replacement_times = [10.0 ])
136+ B .set_solver (EUF , parent = None )
137+
138+ # Manually set some vial inventories
139+ x = np .array ([1.0 , 2.0 , 3.0 , 4.0 ])
140+ B .engine .set (x )
141+
142+ # Test reset function for vial 0
143+ reset_event = B .events [0 ]
144+ reset_func = reset_event .func_act
145+
146+ # Execute reset
147+ reset_func (None )
148+
149+ # Check that vial 0 was reset but others remain
150+ x_after = B .engine .get ()
151+ self .assertEqual (x_after [0 ], 0.0 )
152+ self .assertEqual (x_after [1 ], 2.0 )
153+ self .assertEqual (x_after [2 ], 3.0 )
154+ self .assertEqual (x_after [3 ], 4.0 )
155+
156+ # Test reset function for vial 3
157+ reset_event = B .events [3 ]
158+ reset_func = reset_event .func_act
159+
160+ # Execute reset
161+ reset_func (None )
162+
163+ # Check that vial 0 was reset but others remain
164+ x_after = B .engine .get ()
165+ self .assertEqual (x_after [0 ], 0.0 )
166+ self .assertEqual (x_after [1 ], 2.0 )
167+ self .assertEqual (x_after [2 ], 3.0 )
168+ self .assertEqual (x_after [3 ], 0.0 )
169+
170+
171+ # RUN TESTS LOCALLY ====================================================================
172+
173+ if __name__ == '__main__' :
174+ unittest .main (verbosity = 2 )
0 commit comments