Update NNLS unit tests.

Commit
708f0e0ba2fd7e7592ea20b1be35b7932b94f7aa
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
services/nnls.rkt
index 90acb41f..8fdccc88 100644..100644
@@ -180,27 +180,161 @@
180 180
181 181 (define test-date "2025-01-01")
182 182
183 Removed: (run-tests (test-suite "Nutrient measurement model"
184 Removed: #:before (λ ()
185 Removed: (connect! #:path 'memory)
186 Removed: ;; (connect! #:path "test.sqlite3")
187 Removed: (migrate-all!)
183 Added: (run-tests (test-suite "NNLS"
184 Added: (test-case "Build fertilizer product matrix"
185 Added: (connect! #:path 'memory)
186 Added: (migrate-all!)
188 187
189 Removed: (define nitrogen (create-nutrient! "Nitrogen" "N"))
190 Removed: (define phosphorus (create-nutrient! "Phosphorus" "P"))
188 Added: (define n1 (create-nutrient! "N1" "N1"))
189 Added: (define n2 (create-nutrient! "N2" "N2"))
190 Added: (define nutrients (list n1 n2))
191 191
192 Removed: (create-nutrient-measurement! test-date (hash nitrogen 0 phosphorus 0))
193 Removed: (create-nutrient-target! test-date (hash nitrogen 100 phosphorus 50))
192 Added: (define f1 (create-fertilizer-product! "F1" "F1" (hash n1 10 n2 20)))
193 Added: (define f2 (create-fertilizer-product! "F2" "F2" (hash n1 30 n2 5)))
194 Added: (define fertilizers (list f1 f2))
194 195
195 Removed: (create-fertilizer-product! "Nitrogen" "King Nitrogen" (hash nitrogen 100))
196 Removed: (create-fertilizer-product! "Phosphorus"
197 Removed: "Phosphorescent Baboon"
198 Removed: (hash nitrogen 10 phosphorus 100))
199 Removed: (create-fertilizer-product! "Diluted phosphorus"
200 Removed: "John's Phosphorus"
201 Removed: (hash nitrogen 3 phosphorus 30)))
202 Removed: #:after (λ () (disconnect!))
196 Added: (define matrix (get-fertilizer-product-matrix nutrients fertilizers))
203 197
204 Removed: (test-case "Solve for NNLS"
205 Removed: (displayln (format "Final solution for fertilizers is combination ~a"
206 Removed: (find-ferti-recipe)))))))
198 Added: (check-= (matrix-ref matrix 0 0) 10 0 "N1 in F1")
199 Added: (check-= (matrix-ref matrix 0 1) 30 0 "N1 in F2")
200 Added: (check-= (matrix-ref matrix 1 0) 20 0 "N2 in F1")
201 Added: (check-= (matrix-ref matrix 1 1) 5 0 "N2 in F2")
202 Added:
203 Added: (disconnect!))
204 Added:
205 Added: (test-case "Single nutrient, single fertilizer"
206 Added: (define A (matrix [[2]]))
207 Added: (define y (col-matrix [10]))
208 Added: (define ε 1e-6)
209 Added:
210 Added: (define result (lawson-hanson-1974 A y ε))
211 Added:
212 Added: (check-= (matrix-ref result 0 0) 5.0 ε "Should give x = 5 since 2*5 = 10"))
213 Added:
214 Added: (test-case "Two variables, known solution"
215 Added: (define A (matrix [[1 0] [0 1]]))
216 Added: (define y (col-matrix [3 4]))
217 Added: (define epsilon 1e-6)
218 Added:
219 Added: (define result (lawson-hanson-1974 A y epsilon))
220 Added:
221 Added: (check-= (matrix-ref result 0 0) 3.0 epsilon "x1 should be 3")
222 Added: (check-= (matrix-ref result 1 0) 4.0 epsilon "x2 should be 4"))
223 Added:
224 Added: (test-case "Overdetermined system"
225 Added: (define A (matrix [[1 1] [2 1] [1 2]]))
226 Added: (define y (col-matrix [3 5 5]))
227 Added: (define ε 1e-4)
228 Added:
229 Added: (define result (lawson-hanson-1974 A y ε))
230 Added:
231 Added: ;; Solution should be approximately [1.636, 1.636] (least squares fit)
232 Added: (check-= (matrix-ref result 0 0) 1.636 0.01 "x1 approximately 1.636")
233 Added: (check-= (matrix-ref result 1 0) 1.636 0.01 "x2 approximately 1.636"))
234 Added:
235 Added: (test-case "Non-negativity enforcement"
236 Added: (define A (matrix [[1 -1] [1 1]]))
237 Added: (define y (col-matrix [1 3]))
238 Added: (define epsilon 1e-6)
239 Added:
240 Added: (define result (lawson-hanson-1974 A y epsilon))
241 Added:
242 Added: ;; All results should be non-negative
243 Added: (check-true (>= (matrix-ref result 0 0) 0) "x1 should be non-negative")
244 Added: (check-true (>= (matrix-ref result 1 0) 0) "x2 should be non-negative"))
245 Added:
246 Added: (test-case "Zero target"
247 Added: (define A (matrix [[1 2] [3 4]]))
248 Added: (define y (col-matrix [0 0]))
249 Added: (define ε 1e-6)
250 Added:
251 Added: (define result (lawson-hanson-1974 A y ε))
252 Added:
253 Added: (check-= (matrix-ref result 0 0) 0.0 ε "x1 should be 0")
254 Added: (check-= (matrix-ref result 1 0) 0.0 ε "x2 should be 0"))
255 Added:
256 Added: (test-case "Ferti recipe"
257 Added: (connect! #:path 'memory)
258 Added: (migrate-all!)
259 Added:
260 Added: (define nitrogen (create-nutrient! "Nitrogen" "N"))
261 Added: (define phosphorus (create-nutrient! "Phosphorus" "P"))
262 Added:
263 Added: (create-nutrient-measurement! test-date (hash nitrogen 0 phosphorus 0))
264 Added: (create-nutrient-target! test-date (hash nitrogen 100 phosphorus 50))
265 Added:
266 Added: (create-fertilizer-product! "Nitrogen" "King Nitrogen" (hash nitrogen 100))
267 Added: (create-fertilizer-product! "Phosphorus"
268 Added: "Phosphorescent Baboon"
269 Added: (hash nitrogen 10 phosphorus 100))
270 Added: (create-fertilizer-product! "Diluted phosphorus"
271 Added: "John's Phosphorus"
272 Added: (hash nitrogen 3 phosphorus 30))
273 Added:
274 Added: (define recipe (find-ferti-recipe))
275 Added:
276 Added: (check-equal? (length recipe) 3 "Should have 3 fertilizer products")
277 Added:
278 Added: (for ([pair recipe])
279 Added: (check-true (>= (cdr pair) 0) "Fertilizer quantity should be non-negative"))
280 Added:
281 Added: (disconnect!))
282 Added:
283 Added: ;; Test deficit calculation edge cases
284 Added: (test-case "Deficit calculation with missing data"
285 Added: (connect! #:path 'memory)
286 Added: (migrate-all!)
287 Added:
288 Added: (define n (create-nutrient! "TestNutrient" "TN"))
289 Added:
290 Added: ;; No measurement, no target
291 Added: (check-false (get-latest-nutrient-measurement-value n))
292 Added: (check-false (get-latest-nutrient-target-value n))
293 Added:
294 Added: ;; Add only target
295 Added: (create-nutrient-target! test-date (hash n 100))
296 Added: (check-= (get-latest-nutrient-target-value n) 100 0)
297 Added:
298 Added: ;; Add measurement
299 Added: (create-nutrient-measurement! test-date (hash n 50))
300 Added: (define measured (get-latest-nutrient-measurement-value n))
301 Added: (define targeted (get-latest-nutrient-target-value n))
302 Added:
303 Added: ;; Deficit should be 100% (from 50 to 100)
304 Added: (define deficit (* 100 (/ (- targeted measured) measured)))
305 Added: (check-= deficit 100.0 0.01 "Deficit should be 100%")
306 Added:
307 Added: (disconnect!))
308 Added:
309 Added: ;; Test recipe with realistic constraints
310 Added: (test-case "Recipe calculation with real-world scenario"
311 Added: (connect! #:path 'memory)
312 Added: (migrate-all!)
313 Added:
314 Added: (define n (create-nutrient! "N" "N"))
315 Added: (define p (create-nutrient! "P" "P"))
316 Added: (define k (create-nutrient! "K" "K"))
317 Added:
318 Added: ;; Current levels
319 Added: (create-nutrient-measurement! test-date (hash n 50 p 10 k 100))
320 Added:
321 Added: ;; Target levels
322 Added: (create-nutrient-target! test-date (hash n 150 p 30 k 200))
323 Added:
324 Added: ;; Fertilizers with different NPK ratios
325 Added: (create-fertilizer-product! "" "Balanced" (hash n 100 p 100 k 100))
326 Added: (create-fertilizer-product! "Nitrogen blend" "High-N" (hash n 200 p 50 k 50))
327 Added: (create-fertilizer-product! "Phosphorus blend" "High-P" (hash n 50 p 200 k 50))
328 Added: (create-fertilizer-product! "Potassium blend" "High-K" (hash n 50 p 50 k 200))
329 Added:
330 Added: (define recipe (find-ferti-recipe))
331 Added:
332 Added: (check-equal? (length recipe) 4 "Should have 4 fertilizer options")
333 Added:
334 Added: ;; Verify solution is non-negative
335 Added: (for ([pair recipe])
336 Added: (check-true (>= (cdr pair) 0)
337 Added: (format "~a quantity must be non-negative"
338 Added: (fertilizer-name (car pair)))))
339 Added:
340 Added: (disconnect!)))))