diff options
| author | Marius Peter <dev@marius-peter.com> | 2025-11-22 18:38:50 +0100 |
|---|---|---|
| committer | Marius Peter <dev@marius-peter.com> | 2025-11-22 18:38:50 +0100 |
| commit | 708f0e0ba2fd7e7592ea20b1be35b7932b94f7aa (patch) | |
| tree | cf0d7abcdf501dab161f36ed76763ce44de5d80e /services | |
| parent | 7af921010521b1108144c85259dae4e435a78262 (diff) | |
Update NNLS unit tests.
Diffstat (limited to 'services')
| -rw-r--r-- | services/nnls.rkt | 182 |
1 files changed, 158 insertions, 24 deletions
diff --git a/services/nnls.rkt b/services/nnls.rkt index 90acb41..8fdccc8 100644 --- a/services/nnls.rkt +++ b/services/nnls.rkt @@ -180,27 +180,161 @@ (define test-date "2025-01-01") - (run-tests (test-suite "Nutrient measurement model" - #:before (λ () - (connect! #:path 'memory) - ;; (connect! #:path "test.sqlite3") - (migrate-all!) - - (define nitrogen (create-nutrient! "Nitrogen" "N")) - (define phosphorus (create-nutrient! "Phosphorus" "P")) - - (create-nutrient-measurement! test-date (hash nitrogen 0 phosphorus 0)) - (create-nutrient-target! test-date (hash nitrogen 100 phosphorus 50)) - - (create-fertilizer-product! "Nitrogen" "King Nitrogen" (hash nitrogen 100)) - (create-fertilizer-product! "Phosphorus" - "Phosphorescent Baboon" - (hash nitrogen 10 phosphorus 100)) - (create-fertilizer-product! "Diluted phosphorus" - "John's Phosphorus" - (hash nitrogen 3 phosphorus 30))) - #:after (λ () (disconnect!)) - - (test-case "Solve for NNLS" - (displayln (format "Final solution for fertilizers is combination ~a" - (find-ferti-recipe))))))) + (run-tests (test-suite "NNLS" + (test-case "Build fertilizer product matrix" + (connect! #:path 'memory) + (migrate-all!) + + (define n1 (create-nutrient! "N1" "N1")) + (define n2 (create-nutrient! "N2" "N2")) + (define nutrients (list n1 n2)) + + (define f1 (create-fertilizer-product! "F1" "F1" (hash n1 10 n2 20))) + (define f2 (create-fertilizer-product! "F2" "F2" (hash n1 30 n2 5))) + (define fertilizers (list f1 f2)) + + (define matrix (get-fertilizer-product-matrix nutrients fertilizers)) + + (check-= (matrix-ref matrix 0 0) 10 0 "N1 in F1") + (check-= (matrix-ref matrix 0 1) 30 0 "N1 in F2") + (check-= (matrix-ref matrix 1 0) 20 0 "N2 in F1") + (check-= (matrix-ref matrix 1 1) 5 0 "N2 in F2") + + (disconnect!)) + + (test-case "Single nutrient, single fertilizer" + (define A (matrix [[2]])) + (define y (col-matrix [10])) + (define ε 1e-6) + + (define result (lawson-hanson-1974 A y ε)) + + (check-= (matrix-ref result 0 0) 5.0 ε "Should give x = 5 since 2*5 = 10")) + + (test-case "Two variables, known solution" + (define A (matrix [[1 0] [0 1]])) + (define y (col-matrix [3 4])) + (define epsilon 1e-6) + + (define result (lawson-hanson-1974 A y epsilon)) + + (check-= (matrix-ref result 0 0) 3.0 epsilon "x1 should be 3") + (check-= (matrix-ref result 1 0) 4.0 epsilon "x2 should be 4")) + + (test-case "Overdetermined system" + (define A (matrix [[1 1] [2 1] [1 2]])) + (define y (col-matrix [3 5 5])) + (define ε 1e-4) + + (define result (lawson-hanson-1974 A y ε)) + + ;; Solution should be approximately [1.636, 1.636] (least squares fit) + (check-= (matrix-ref result 0 0) 1.636 0.01 "x1 approximately 1.636") + (check-= (matrix-ref result 1 0) 1.636 0.01 "x2 approximately 1.636")) + + (test-case "Non-negativity enforcement" + (define A (matrix [[1 -1] [1 1]])) + (define y (col-matrix [1 3])) + (define epsilon 1e-6) + + (define result (lawson-hanson-1974 A y epsilon)) + + ;; All results should be non-negative + (check-true (>= (matrix-ref result 0 0) 0) "x1 should be non-negative") + (check-true (>= (matrix-ref result 1 0) 0) "x2 should be non-negative")) + + (test-case "Zero target" + (define A (matrix [[1 2] [3 4]])) + (define y (col-matrix [0 0])) + (define ε 1e-6) + + (define result (lawson-hanson-1974 A y ε)) + + (check-= (matrix-ref result 0 0) 0.0 ε "x1 should be 0") + (check-= (matrix-ref result 1 0) 0.0 ε "x2 should be 0")) + + (test-case "Ferti recipe" + (connect! #:path 'memory) + (migrate-all!) + + (define nitrogen (create-nutrient! "Nitrogen" "N")) + (define phosphorus (create-nutrient! "Phosphorus" "P")) + + (create-nutrient-measurement! test-date (hash nitrogen 0 phosphorus 0)) + (create-nutrient-target! test-date (hash nitrogen 100 phosphorus 50)) + + (create-fertilizer-product! "Nitrogen" "King Nitrogen" (hash nitrogen 100)) + (create-fertilizer-product! "Phosphorus" + "Phosphorescent Baboon" + (hash nitrogen 10 phosphorus 100)) + (create-fertilizer-product! "Diluted phosphorus" + "John's Phosphorus" + (hash nitrogen 3 phosphorus 30)) + + (define recipe (find-ferti-recipe)) + + (check-equal? (length recipe) 3 "Should have 3 fertilizer products") + + (for ([pair recipe]) + (check-true (>= (cdr pair) 0) "Fertilizer quantity should be non-negative")) + + (disconnect!)) + + ;; Test deficit calculation edge cases + (test-case "Deficit calculation with missing data" + (connect! #:path 'memory) + (migrate-all!) + + (define n (create-nutrient! "TestNutrient" "TN")) + + ;; No measurement, no target + (check-false (get-latest-nutrient-measurement-value n)) + (check-false (get-latest-nutrient-target-value n)) + + ;; Add only target + (create-nutrient-target! test-date (hash n 100)) + (check-= (get-latest-nutrient-target-value n) 100 0) + + ;; Add measurement + (create-nutrient-measurement! test-date (hash n 50)) + (define measured (get-latest-nutrient-measurement-value n)) + (define targeted (get-latest-nutrient-target-value n)) + + ;; Deficit should be 100% (from 50 to 100) + (define deficit (* 100 (/ (- targeted measured) measured))) + (check-= deficit 100.0 0.01 "Deficit should be 100%") + + (disconnect!)) + + ;; Test recipe with realistic constraints + (test-case "Recipe calculation with real-world scenario" + (connect! #:path 'memory) + (migrate-all!) + + (define n (create-nutrient! "N" "N")) + (define p (create-nutrient! "P" "P")) + (define k (create-nutrient! "K" "K")) + + ;; Current levels + (create-nutrient-measurement! test-date (hash n 50 p 10 k 100)) + + ;; Target levels + (create-nutrient-target! test-date (hash n 150 p 30 k 200)) + + ;; Fertilizers with different NPK ratios + (create-fertilizer-product! "" "Balanced" (hash n 100 p 100 k 100)) + (create-fertilizer-product! "Nitrogen blend" "High-N" (hash n 200 p 50 k 50)) + (create-fertilizer-product! "Phosphorus blend" "High-P" (hash n 50 p 200 k 50)) + (create-fertilizer-product! "Potassium blend" "High-K" (hash n 50 p 50 k 200)) + + (define recipe (find-ferti-recipe)) + + (check-equal? (length recipe) 4 "Should have 4 fertilizer options") + + ;; Verify solution is non-negative + (for ([pair recipe]) + (check-true (>= (cdr pair) 0) + (format "~a quantity must be non-negative" + (fertilizer-name (car pair))))) + + (disconnect!))))) |