11//! [Day 10: Factory](https://adventofcode.com/2025/day/10)
22
3+ use good_lp:: { Expression , Solution , SolverModel , default_solver, variable, variables} ;
4+ use rayon:: prelude:: * ;
35use z3:: ast:: Int ;
46
7+ // this is only to make clippy happy... cf. https://stackoverflow.com/a/74629224
8+ const F64_BITS : u64 = 64 ;
9+ const F64_EXPONENT_BITS : u64 = 11 ;
10+ const F64_EXPONENT_MAX : u64 = ( 1 << F64_EXPONENT_BITS ) - 1 ;
11+ const F64_EXPONENT_BIAS : u64 = 1023 ;
12+ const F64_FRACTION_BITS : u64 = 52 ;
13+
14+ pub fn f64_to_u64 ( f : f64 ) -> Option < u64 > {
15+ let bits = f. to_bits ( ) ;
16+ let sign = bits & ( 1 << ( F64_EXPONENT_BITS + F64_FRACTION_BITS ) ) != 0 ;
17+ let exponent = ( bits >> F64_FRACTION_BITS ) & ( ( 1 << F64_EXPONENT_BITS ) - 1 ) ;
18+ let fraction = bits & ( ( 1 << F64_FRACTION_BITS ) - 1 ) ;
19+
20+ match ( sign, exponent, fraction) {
21+ ( _, 0 , 0 ) => {
22+ debug_assert ! ( f == 0.0 ) ;
23+ Some ( 0 )
24+ }
25+ ( true , _, _) => {
26+ debug_assert ! ( f < 0.0 ) ;
27+ None
28+ }
29+ ( _, F64_EXPONENT_MAX , 0 ) => {
30+ debug_assert ! ( f. is_infinite( ) ) ;
31+ None
32+ }
33+ ( _, F64_EXPONENT_MAX , _) => {
34+ debug_assert ! ( f. is_nan( ) ) ;
35+ None
36+ }
37+ ( _, 0 , _) => {
38+ debug_assert ! ( f. is_subnormal( ) ) ;
39+ None
40+ }
41+ _ => {
42+ if exponent < F64_EXPONENT_BIAS {
43+ debug_assert ! ( f < 1.0 ) ;
44+ None
45+ } else {
46+ let mantissa = fraction | ( 1 << F64_FRACTION_BITS ) ;
47+ let left_shift =
48+ exponent. cast_signed ( ) - ( F64_EXPONENT_BIAS + F64_FRACTION_BITS ) . cast_signed ( ) ;
49+ if left_shift < 0 {
50+ let right_shift = ( -left_shift) . cast_unsigned ( ) ;
51+ if mantissa & ( 1 << ( right_shift - 1 ) ) != 0 {
52+ debug_assert ! ( f. fract( ) != 0.0 ) ;
53+ None
54+ } else {
55+ Some ( mantissa >> right_shift)
56+ }
57+ } else if left_shift > ( F64_BITS - F64_FRACTION_BITS - 1 ) . cast_signed ( ) {
58+ debug_assert ! ( f > 2.0f64 . powi( 63 ) ) ;
59+ None
60+ } else {
61+ Some ( mantissa << left_shift)
62+ }
63+ }
64+ }
65+ }
66+ }
67+
568struct Machine {
669 lights : u32 ,
770 wirings : Vec < u32 > ,
@@ -33,8 +96,51 @@ impl Machine {
3396 . min ( )
3497 }
3598
36- /// Return the fewest button presses required to correctly configure the joltage level counters .
37- fn optimize ( & self ) -> u64 {
99+ /// Return the fewest button presses required to correctly configure the joltage level counters.
100+ /// Use Integer Linear Programming solver.
101+ fn optimize_lp ( & self ) -> u64 {
102+ let n = self . wirings . len ( ) ;
103+
104+ // 1) Create the container for problem variables
105+ let mut vars = variables ! ( ) ;
106+
107+ // 2) Create `n` integer variables and collect them
108+ let presses: Vec < _ > = ( 0 ..n)
109+ . map ( |_| vars. add ( variable ( ) . integer ( ) . min ( 0 ) ) )
110+ . collect ( ) ;
111+
112+ // 3) Build the objective: minimize sum(w_i)
113+ let objective: Expression = presses. iter ( ) . sum ( ) ;
114+
115+ // 4) Create the problem with that objective
116+ let mut problem = vars. minimise ( objective) . using ( default_solver) ;
117+
118+ // 5) Add the equations.
119+ for ( i, & target) in self . joltages . iter ( ) . enumerate ( ) {
120+ let mut lhs = Expression :: from ( 0 ) ;
121+
122+ for ( j, w) in self . wirings . iter ( ) . enumerate ( ) {
123+ if w & ( 1 << i) != 0 {
124+ lhs += presses[ j] ;
125+ }
126+ }
127+
128+ problem = problem. with ( lhs. eq ( target) ) ;
129+ }
130+
131+ // 6) Solve with the default solver
132+ // 7) Extract integer values (solution.value returns f64)
133+ problem. solve ( ) . map_or ( 0 , |solution| {
134+ presses
135+ . iter ( )
136+ . map ( |& v| f64_to_u64 ( solution. value ( v) . round ( ) ) . unwrap ( ) )
137+ . sum ( )
138+ } )
139+ }
140+
141+ /// Return the fewest button presses required to correctly configure the joltage level counters.
142+ /// Use Z3 solver (complicates parallelism).
143+ fn optimize_z3 ( & self ) -> u64 {
38144 let solver = z3:: Optimize :: new ( ) ;
39145
40146 let vars: Vec < _ > = ( 0 ..self . wirings . len ( ) )
@@ -63,10 +169,12 @@ impl Machine {
63169
64170 let sum_vars = z3:: ast:: Int :: add ( & vars. iter ( ) . collect :: < Vec < _ > > ( ) ) ;
65171 solver. minimize ( & sum_vars) ;
172+ solver. minimize ( & sum_vars) ; // Sometimes, the solver fails to find the optimal solution on the first attempt.
66173
67174 match solver. check ( & [ ] ) {
68175 z3:: SatResult :: Sat => {
69176 let model = solver. get_model ( ) . unwrap ( ) ;
177+
70178 vars. iter ( )
71179 . filter_map ( |v| {
72180 let val = model. get_const_interp ( v) . unwrap ( ) ;
@@ -142,25 +250,41 @@ impl Puzzle {
142250
143251 /// Solve part one.
144252 fn part1 ( & self ) -> usize {
145- self . machines . iter ( ) . filter_map ( Machine :: score) . sum ( )
253+ self . machines . par_iter ( ) . filter_map ( Machine :: score) . sum ( )
146254 }
147255
148256 /// Solve part two.
149- fn part2 ( & self ) -> u64 {
150- self . machines . iter ( ) . map ( Machine :: optimize) . sum ( )
257+ fn part2_lp ( & self ) -> u64 {
258+ self . machines . par_iter ( ) . map ( Machine :: optimize_lp) . sum ( )
259+ }
260+
261+ /// Solve part two too.
262+ fn part2_z3 ( & self ) -> u64 {
263+ self . machines . par_iter ( ) . map ( Machine :: optimize_z3) . sum ( )
151264 }
152265}
153266
154267/// # Panics
155268#[ must_use]
156269pub fn solve ( data : & str ) -> ( usize , u64 ) {
157270 let puzzle = Puzzle :: new ( data) ;
158- ( puzzle. part1 ( ) , puzzle. part2 ( ) )
271+ ( puzzle. part1 ( ) , puzzle. part2_lp ( ) )
272+ }
273+ /// # Panics
274+ #[ must_use]
275+ pub fn solve_z3 ( data : & str ) -> ( usize , u64 ) {
276+ let puzzle = Puzzle :: new ( data) ;
277+ ( puzzle. part1 ( ) , puzzle. part2_z3 ( ) )
159278}
160279
161280pub fn main ( ) {
162281 let args = aoc:: parse_args ( ) ;
163- args. run ( solve) ;
282+
283+ if args. has_option ( "--z3" ) {
284+ args. run ( solve_z3) ;
285+ } else {
286+ args. run ( solve) ;
287+ }
164288}
165289
166290#[ cfg( test) ]
@@ -178,6 +302,6 @@ mod test {
178302 #[ test]
179303 fn part2 ( ) {
180304 let puzzle = Puzzle :: new ( TEST_INPUT ) ;
181- assert_eq ! ( puzzle. part2 ( ) , 33 ) ;
305+ assert_eq ! ( puzzle. part2_lp ( ) , 33 ) ;
182306 }
183307}
0 commit comments