(*--------------------------------------------------------------------------- Copyright (c) 2013 Daniel C. Bünzli. All rights reserved. Distributed under a BSD license, see license at the end of the file. %%PROJECTNAME%% release 0.8.0 --------------------------------------------------------------------------*) (* Gg tests. Given a Gg module M, its tests have names that start with "M." and are defined in a module M_tests. Most of the time a test name correspond to the name(s) of tested function, but often other functions are also tested implicitely. Some tests use exact binary comparison between floats but this is only done when the result should be exact w.r.t to IEEE 754's properties. *) open Checkm open Checkm.C.Special open Gg let eps = 1e-9 module Test = Checkm.Test (* help ocamlbuild. *) let ( & ) f x = f x (* Float tests *) module Float_tests = struct let test n f = Test.add_test ("Float." ^ n) f (* Float generators. *) let pp_bfloat pp f = Format.fprintf pp "%F@ (%a)" f Float.pp f let any_float = let g _ rs = let r2 = Int64.shift_left (Int64.of_int (Random.State.bits rs)) 34 in let r1 = Int64.shift_left (Int64.of_int (Random.State.bits rs)) 4 in let r0 = Int64.of_int (Random.State.bits rs land 0xF) in Int64.float_of_bits (Int64.logor r2 (Int64.logor r1 r0)) in g, pp_bfloat let uint_float = let g _ rs = Int64.to_float (Random.State.int64 rs Int64.max_int) in g, Format.pp_print_float module Testable_float = struct include (Float : C.Testable with type t = float) let print = pp_bfloat end module Cf = C.Make (Testable_float) open Cf.Order let () = test "max_frac_float" & fun r -> r >> Cf.holds (C.neg Float.is_int) Float.max_frac_float >> Cf.holds (C.neg Float.is_int) (-. Float.max_frac_float) >> Cf.holds Float.is_int (Float.max_frac_float +. 1.) >> Cf.holds Float.is_int (-. Float.max_frac_float -. 1.) >> C.success let () = test "max_int_arith" & fun r -> r >> (ldexp 1. 53 = Float.max_int_arith) >> (Float.max_int_arith <> Float.max_int_arith -. 1.) >> Cf.holds Float.is_int (Float.max_int_arith -. 1.) >> (Float.max_int_arith +. 1. = Float.max_int_arith) (* rnd mode dep ? *) >> (Float.max_int_arith +. 2. = Float.succ Float.max_int_arith) >> C.success let () = test "deg_of_rad" & fun r -> r >> (Float.deg_of_rad 0. = 0.) >> (abs_float (Float.deg_of_rad Float.pi -. 180.) < 1e-10) >> Cf.holds Float.is_nan (Float.deg_of_rad nan) >> C.success let () = test "rad_of_deg" & fun r -> r >> (Float.rad_of_deg 0. = 0.) >> (abs_float (Float.rad_of_deg 180. -. Float.pi) < 1e-10) >> Cf.holds Float.is_nan (Float.rad_of_deg nan) >> C.success let () = test "wrap_angle" & fun r -> let is_num f = not (Float.is_nan f || Float.is_inf f) in r >> (abs_float (Float.wrap_angle 0.) < 1e-10) >> (abs_float (Float.wrap_angle (2. *. Float.pi)) < 1e-10) >> (abs_float (Float.wrap_angle (4. *. Float.pi)) < 1e-10) >> (abs_float (Float.wrap_angle (6. *. Float.pi)) < 1e-10) >> (abs_float (Float.wrap_angle (-2. *. Float.pi)) < 1e-10) >> (abs_float (Float.wrap_angle (-4. *. Float.pi)) < 1e-10) >> (abs_float (Float.wrap_angle (-6. *. Float.pi)) < 1e-10) >> (abs_float ((Float.wrap_angle Float.pi) +. Float.pi) < 1e-10) >> (abs_float ((Float.wrap_angle (3. *. Float.pi)) +. Float.pi) < 1e-10) >> (abs_float ((Float.wrap_angle (5. *. Float.pi)) +. Float.pi) < 1e-10) >> (abs_float ((Float.wrap_angle (-.Float.pi)) +. Float.pi) < 1e-10) >> (abs_float ((Float.wrap_angle (-.3. *. Float.pi)) +. Float.pi) < 1e-10) >> (abs_float ((Float.wrap_angle (-.5. *. Float.pi)) +. Float.pi) < 1e-10) >> Cf.for_all ~cond:is_num any_float begin fun v r -> let w = Float.wrap_angle v in r >> (w < Float.pi) >> (w >= -.Float.pi) >> C.success end >> Cf.holds Float.is_nan (Float.wrap_angle nan) >> C.success let () = test "random functions" & fun r -> r >> C.for_all Gen.unit begin fun () r -> let s = Random.get_state () in let v = Float.random ~min:(-2.) ~len:4. () in let v' = Float.srandom ~min:(-2.) ~len:4. s () in r >> (v = v') >> (-2. <= v) >> (v <= 2.) >> C.success end >> C.success let () = test "mix" & fun r -> r >> (Float.mix 1. 3. 0.5 = 2.) >> (Float.mix 1. 3. 0. = 1.) >> (Float.mix 1. 3. 1. = 3.) >> Cf.holds Float.is_nan (Float.mix nan 3. 0.) >> Cf.holds Float.is_nan (Float.mix 1. nan 0.) >> Cf.holds Float.is_nan (Float.mix 0. 0. nan) >> C.success let () = test "step" & fun r -> r >> (Float.step 4. (-3.) = 0.) >> (Float.step 4. 3. = 0.) >> (Float.step 4. 4. = 1.) >> (Float.step 4. 5. = 1.) >> C.success let () = test "smooth_step" & fun r -> r >> (Float.smooth_step 2. 4. (-3.) = 0.) >> (Float.smooth_step 2. 4. 2. = 0.) >> (Float.smooth_step 2. 4. 3. = 0.5) >> (Float.smooth_step 2. 4. 4. = 1.) >> (Float.smooth_step 2. 4. 5. = 1.) >> C.success let () = test "fmax" & fun r -> r >> (Float.fmax 2. 3. = 3.) >> (Float.fmax 3. 2. = 3.) >> (Float.fmax nan 3. = 3.) >> (Float.fmax 3. nan = 3.) >> Cf.holds Float.is_nan (Float.fmax nan nan) >> C.success let () = test "fmin" & fun r -> r >> (Float.fmin 2. 3. = 2.) >> (Float.fmin 3. 2. = 2.) >> (Float.fmin nan 2. = 2.) >> (Float.fmin 2. nan = 2.) >> Cf.holds Float.is_nan (Float.fmin nan nan) >> C.success let () = test "clamp" & fun r -> r >> (Float.clamp 1. 3. (-1.) = 1.) >> (Float.clamp 1. 3. 1. = 1.) >> (Float.clamp 1. 3. 2. = 2.) >> (Float.clamp 1. 3. 3. = 3.) >> (Float.clamp 1. 3. 4. = 3.) >> C.success let () = test "remap" & fun r -> r >> (Float.remap 4. 8. 2. 4. 0. = 0.) >> (Float.remap 4. 8. 2. 4. 2. = 1.) >> (Float.remap 4. 8. 2. 4. 4. = 2.) >> (Float.remap 4. 8. 2. 4. 6. = 3.) >> (Float.remap 4. 8. 2. 4. 8. = 4.) >> (Float.remap 4. 8. 2. 4. 10. = 5.) >> (Float.remap 4. 8. 2. 4. 12. = 6.) >> (Float.remap 1. 1. 4. 5. 6. = 4.) >> (Float.remap 1. 1. 5. 4. 6. = 5.) >> (Float.remap 2. 4. 4. 2. 3. = 3.) >> (Float.remap 2. 4. 4. 2. 2. = 4.) >> (Float.remap 2. 4. 4. 2. 4. = 2.) >> Cf.holds Float.is_nan (Float.remap nan 8. 2. 4. 4.) >> Cf.holds Float.is_nan (Float.remap 4. nan 2. 4. 4.) >> Cf.holds Float.is_nan (Float.remap 4. 8. nan 4. 4.) >> Cf.holds Float.is_nan (Float.remap 4. 8. 2. nan 4.) >> Cf.holds Float.is_nan (Float.remap 4. 8. 2. 4. nan) >> C.success let () = test "round" & fun r -> r >> (Float.round (-2.8) = -3.) >> (Float.round (-2.5) = -2.) >> (Float.round (-2.2) = -2.) >> (Float.round 0. = 0.) >> (Float.round 2.2 = 2.) >> (Float.round 2.5 = 3.) >> (Float.round 2.8 = 3.) >> (Float.round neg_infinity = neg_infinity) >> (Float.round infinity = infinity) >> Cf.holds Float.is_nan (Float.round nan) >> C.success open Ci.Order let () = test "int_of_round" & fun r -> r >> (Float.int_of_round (-2.8) = -3) >> (Float.int_of_round (-2.5) = -2) >> (Float.int_of_round (-2.2) = -2) >> (Float.int_of_round 0. = 0) >> (Float.int_of_round 2.2 = 2) >> (Float.int_of_round 2.5 = 3) >> (Float.int_of_round 2.8 = 3) >> C.success open Cf.Order let () = test "round_dfrac" & fun r -> r >> (Float.round_dfrac 1 (-1.28) = -1.3) >> (Float.round_dfrac 1 (-1.25) = -1.2) >> (Float.round_dfrac 1 (-1.22) = -1.2) >> (Float.round_dfrac 3 (-0.0035) = -0.003) >> (Float.round_dfrac 2 (-0.0035) = 0.) >> (Float.round_dfrac 1 (-0.) = 0.) >> (Float.round_dfrac 1 0. = 0.) >> (Float.round_dfrac 3 0.0035 = 0.004) >> (Float.round_dfrac 1 1.22 = 1.2) >> (Float.round_dfrac 1 1.25 = 1.3) >> (Float.round_dfrac 1 1.28 = 1.3) >> (Float.round_dfrac 4 1.00044 = 1.0004) >> (Float.round_dfrac 4 1.000445 = 1.0004) >> (Float.round_dfrac 4 1.000449 = 1.0004) >> (Float.round_dfrac 4 1.000450 = 1.0005) >> (Float.round_dfrac 16 neg_infinity = neg_infinity) >> (Float.round_dfrac 16 infinity = infinity) >> Cf.holds Float.is_nan (Float.round_dfrac 16 nan) >> C.success let () = test "round_dsig" & fun r -> r >> (Float.round_dsig 1 (-1.28e7) = -1.3e7) >> (Float.round_dsig 1 (-1.25e5) = -1.2e5) >> (Float.round_dsig 1 (-1.22e2) = -1.2e2) >> (Float.round_dsig 2 (-0.002e3) = -0.002e3) >> (Float.round_dsig 0 (-0.00243e4) = -0.002e4) >> (Float.round_dsig 1 (-0.00243e6) = -0.0024e6) >> (Float.round_dsig 1 0.00456e7 = 0.0046e7) >> (Float.round_dsig 1 1.22e8 = 1.2e8) >> (Float.round_dsig 1 1.25e5 = 1.3e5) >> (Float.round_dsig 1 1.28e2 = 1.3e2) >> (Float.round_dsig 4 1.00044e25 = 1.0004e25) >> (Float.round_dsig 4 1.000445e20 = 1.0004e20) >> (Float.round_dsig 4 1.000449e10 = 1.0004e10) >> (Float.round_dsig 4 1.000450e6 = 1.0005e6) >> Cf.holds Float.is_nan (Float.round_dsig 16 neg_infinity) >> Cf.holds Float.is_nan (Float.round_dsig 16 infinity) >> Cf.holds Float.is_nan (Float.round_dsig 16 nan) >> C.success let () = test "round_zero" & fun r -> let eps = 0.005 in r >> (Float.round_zero eps (-0.0051) = -0.0051) >> (Float.round_zero eps (-0.0050) = -0.0050) >> (Float.round_zero eps (-0.0049) = -0.) >> (Float.round_zero eps 0. = 0.) >> (Float.round_zero eps 0.0049 = 0.) >> (Float.round_zero eps 0.0050 = 0.0050) >> (Float.round_zero eps 0.0051 = 0.0051) >> (Float.round_zero eps infinity = infinity) >> (Float.round_zero eps neg_infinity = neg_infinity) >> Cf.holds Float.is_nan (Float.round_zero eps nan) >> C.success let () = test "chop" & fun r -> let eps = 0.0005 in r >> (Float.chop 0.6 2.5 = 3.) >> (Float.chop 0.6 (-2.5) = -2.) >> (Float.chop eps (-2.00051) = -2.00051) >> (Float.chop eps (-2.00050) = -2.00050) >> (Float.chop eps (-2.00049) = -2.) >> (Float.chop eps (-2.) = -2.) >> (Float.chop eps 2. = 2.) >> (Float.chop eps 2.00049 = 2.) >> (Float.chop eps 2.00050 = 2.00050) >> (Float.chop eps 2.00051 = 2.00051) >> (Float.chop eps infinity = infinity) >> (Float.chop eps neg_infinity = neg_infinity) >> Cf.holds Float.is_nan (Float.chop eps nan) >> C.success let () = test "sign" & fun r -> r >> (Float.sign 0. = 0.) >> (Float.sign (-0.) = 0.) >> (Float.sign (-3.) = -1.) >> (Float.sign (3.) = 1.) >> (Float.sign neg_infinity = -1.) >> (Float.sign infinity = 1.) >> Cf.holds Float.is_nan (Float.sign nan) >> C.success let () = test "sign_bit" & fun r -> r >> Cf.holds Float.sign_bit (-. nan) >> Cf.holds Float.sign_bit neg_infinity >> Cf.holds Float.sign_bit (-3.) >> Cf.holds Float.sign_bit (-0.) >> Cf.holds (C.neg Float.sign_bit) 0. >> Cf.holds (C.neg Float.sign_bit) 3. >> Cf.holds (C.neg Float.sign_bit) infinity >> Cf.holds (C.neg Float.sign_bit) nan >> C.success let () = test "succ" & fun r -> r >> Cf.holds Float.is_nan (Float.succ (-. nan)) >> (Float.succ neg_infinity = -. max_float) >> (Float.succ (-. Float.min_sub_float) = -0.) >> (Float.succ (-0.) = Float.min_sub_float) >> (Float.succ (0.) = Float.min_sub_float) >> (Float.succ (1.) = 1. +. epsilon_float) >> (Float.succ max_float = infinity) >> (Float.succ infinity = infinity) >> Cf.holds Float.is_nan (Float.succ nan) >> C.success let () = test "pred" & fun r -> r >> Cf.holds Float.is_nan (Float.pred nan) >> (Float.pred infinity = max_float) >> (Float.pred (1. +. epsilon_float) = 1.) >> (Float.pred (Float.min_sub_float) = 0.) >> (Float.pred (0.) = -. Float.min_sub_float) >> (Float.pred (-. 0.) = -. Float.min_sub_float) >> (Float.pred (-. max_float) = neg_infinity) >> (Float.pred neg_infinity = neg_infinity) >> Cf.holds Float.is_nan (Float.pred (-. nan)) >> C.success let magic_payload = 0x43616D6C open Ci.Order let () = test "nan_nan_payload" & fun r -> let n = Float.nan magic_payload in r >> Cf.holds Float.is_nan n >> (Float.nan_payload n = magic_payload) >> (Float.nan_payload (Pervasives.nan) = 0x1) >> Cf.raises_invalid_arg Float.nan_payload 567. >> Cf.raises_invalid_arg Float.nan_payload max_float >> Cf.raises_invalid_arg Float.nan_payload neg_infinity >> C.success open Cf.Order let () = test "is_zero" & fun r -> r >> Cf.holds (C.neg (Float.is_zero ~eps)) infinity >> Cf.holds (C.neg (Float.is_zero ~eps)) Pervasives.nan >> Cf.holds (C.neg (Float.is_zero ~eps)) (Float.nan magic_payload) >> Cf.holds (C.neg (Float.is_zero ~eps)) 1e-8 >> Cf.holds (C.neg (Float.is_zero ~eps)) 1e-9 >> Cf.holds (Float.is_zero ~eps) 1e-10 >> Cf.holds (Float.is_zero ~eps) 1e-11 >> C.success let () = test "is_inf" & fun r -> r >> Cf.holds Float.is_inf infinity >> Cf.holds Float.is_inf neg_infinity >> Cf.holds (C.neg Float.is_inf) Pervasives.nan >> Cf.holds (C.neg Float.is_inf) (Float.nan magic_payload) >> Cf.holds (C.neg Float.is_inf) 0. >> Cf.holds (C.neg Float.is_inf) 3. >> C.success let () = test "is_int" & fun r -> r >> Cf.holds (C.neg Float.is_int) nan >> Cf.holds (C.neg Float.is_int) (Float.nan magic_payload) >> Cf.holds (C.neg Float.is_int) infinity >> Cf.holds (C.neg Float.is_int) neg_infinity >> Cf.holds (C.neg Float.is_int) Float.max_sub_float >> Cf.holds (C.neg Float.is_int) Float.min_sub_float >> Cf.holds (C.neg Float.is_int) Float.max_frac_float >> Cf.holds Float.is_int max_float >> Cf.holds Float.is_int Float.max_int_arith >> Cf.holds Float.is_int 0. >> Cf.holds Float.is_int (-0.) >> Cf.holds Float.is_int (-. max_float) >> Cf.holds Float.is_int (-. Float.max_int_arith) >> Cf.for_all uint_float begin fun x r -> let frac_neighbours x r = if Pervasives.(>) x Float.max_frac_float then r else r >> Cf.holds (C.neg Float.is_int) (Float.pred x) >> Cf.holds (C.neg Float.is_int) (Float.succ x) >> C.success in r >> C.holds Float.is_int x >> Cf.holds Float.is_int (-. x) >> frac_neighbours x >> C.success end >> C.success let () = test "equal_tol" & fun r -> let eps = 0.001 in r >> Cf.holds (C.neg (Float.equal_tol ~eps 499.4)) 500. >> Cf.holds (C.neg (Float.equal_tol ~eps 500.)) 499.4 >> Cf.holds (Float.equal_tol ~eps 500.) 499.5 >> Cf.holds (Float.equal_tol ~eps 500.) 499.6 >> Cf.holds (Float.equal_tol ~eps 500.) 500.0 >> Cf.holds (Float.equal_tol ~eps 0.) 0. >> Cf.holds (C.neg (Float.equal_tol ~eps 0.)) 3. >> Cf.holds (C.neg (Float.equal_tol ~eps 0.)) infinity >> Cf.holds (C.neg (Float.equal_tol ~eps 0.)) neg_infinity >> Cf.holds (C.neg (Float.equal_tol ~eps 0.)) nan >> Cf.holds (C.neg (Float.equal_tol ~eps 3.)) 0. >> Cf.holds (Float.equal_tol ~eps 3.) 3. >> Cf.holds (C.neg (Float.equal_tol ~eps 3.)) infinity >> Cf.holds (C.neg (Float.equal_tol ~eps 3.)) neg_infinity >> Cf.holds (C.neg (Float.equal_tol ~eps 3.)) nan >> Cf.holds (C.neg (Float.equal_tol ~eps infinity)) 0. >> Cf.holds (C.neg (Float.equal_tol ~eps infinity)) 3. >> Cf.holds (Float.equal_tol ~eps infinity) infinity >> Cf.holds (C.neg (Float.equal_tol ~eps infinity)) neg_infinity >> Cf.holds (C.neg (Float.equal_tol ~eps infinity)) nan >> Cf.holds (C.neg (Float.equal_tol ~eps neg_infinity)) 0. >> Cf.holds (C.neg (Float.equal_tol ~eps neg_infinity)) 3. >> Cf.holds (C.neg (Float.equal_tol ~eps neg_infinity)) infinity >> Cf.holds (Float.equal_tol ~eps neg_infinity) neg_infinity >> Cf.holds (C.neg (Float.equal_tol ~eps neg_infinity)) nan >> Cf.holds (C.neg (Float.equal_tol ~eps nan)) 0. >> Cf.holds (C.neg (Float.equal_tol ~eps nan)) 3. >> Cf.holds (C.neg (Float.equal_tol ~eps nan)) infinity >> Cf.holds (C.neg (Float.equal_tol ~eps nan)) neg_infinity >> Cf.holds (Float.equal_tol ~eps nan) nan >> Cf.for_all any_float begin fun x r -> r >> Cf.holds (Float.equal_tol ~eps x) x >> C.success end >> C.success let float_trip x r = let pr ppf x = Float.pp ppf (Int64.float_of_bits x) in let x' = float_of_string (Float.to_string x) in r >> C.Order.(=) ~pr (Int64.bits_of_float x) (Int64.bits_of_float x') >> C.success let () = test "to_string, non nan" & fun r -> r >> float_trip (0. /. -1.) >> float_trip 0. >> float_trip infinity >> float_trip neg_infinity >> float_trip Float.min_sub_float >> float_trip (-. Float.min_sub_float) >> float_trip Float.max_sub_float >> float_trip (-. Float.max_sub_float) >> float_trip max_float >> float_trip (-. max_float) >> Cf.for_all ~cond:(C.neg Float.is_nan) any_float float_trip >> C.success (* let () = test "to_string, any" & fun r -> let skip_msg = "Negative NaNs are not well handled by strtod on this platform. \ float_of_string (Float.to_string n) with a negative NaN will \ not round trip." in r >> C.catch (float_trip (-. nan)) Test.skip skip_msg >> Cf.for_all any_float float_trip >> C.success *) end (* Vector tests *) module type V = sig include Gg.V val gen : min:float -> len:float -> t Checkm.gen (* random vector. *) end module V_tests (V : V) = struct (* generic tests. *) let v_prefix = "V" ^ (string_of_int V.dim) ^ "." let test n f = Test.add_test (v_prefix ^ n) f let g_v = V.gen ~min:(-100.) ~len:200. let indices = (* list of component indexes. *) let o = ref [] in for i = V.dim - 1 downto 0 do o := i :: !o done; !o (* Test vector, relies on correct V.map and V.mapi. The value at index_i is i. Useful for tests as each component is different in a predictable way. *) let index = V.mapi (fun i _ -> float i) V.zero let db_index = V.map (fun x -> 2. *. x) index let sq_index = V.map (fun x -> x *. x) index module Cv = C.Make (V) (* Constructors, accessors and constants *) open Cf.Order let () = test "comp" & fun r -> let check r i = r >> (V.comp i index = float i) in List.fold_left check r indices >> C.success (* Functions *) open Cv.Order let () = test "neg" & fun r -> r >> Cv.for_all g_v begin fun v r -> r >> (V.neg (V.neg v) = v) >> (V.neg v = V.smul (-1.) v) >> C.success end >> C.success let () = test "add" & fun r -> r >> Cv.for_all g_v begin fun v r -> r >> (V.add (V.neg v) v = V.zero) >> (V.add v (V.neg v) = V.zero) >> C.success end >> C.success let () = test "sub" & fun r -> r >> Cv.for_all g_v begin fun v r -> r >> (V.sub v v = V.zero) >> (V.sub (V.neg v) (V.neg v) = V.zero) >> C.success end >> C.success let () = test "mul" & fun r -> r >> (V.mul index index = sq_index) >> C.success let () = test "div" & fun r -> let inc_index = V.map (fun x -> x +. 1.) index in (* to avoid /. zero *) r >> (V.div (V.mul inc_index inc_index) inc_index = inc_index) >> C.success let () = test "smul" & fun r -> r >> (V.smul 2. index = db_index) >> (V.smul (-2.) index = V.neg db_index) >> C.success let () = test "half" & fun r -> r >> (V.half db_index = index) >> C.success open Cf.Order let () = test "dot, norm, norm2" & fun r -> let sq_sum n = (n * (n + 1) * (2 * n + 1)) / 6 in r >> (V.dot index index = V.norm2 index) >> (V.norm2 index = float (sq_sum (V.dim - 1))) >> (V.norm index = sqrt (V.norm2 index)) >> C.success let () = test "unit" & fun r -> let unitable v = Pervasives.(<>) (V.norm v) 0. in r >> Cv.for_all ~cond:unitable g_v begin fun v r -> r >> (Float.chop ~eps (V.norm (V.unit v)) = 1.) >> C.success end >> C.success let () = test "homogene" & fun r -> let h = V.homogene index in let imax = V.dim - 1 in let check r i = if Pervasives.(=) i imax then r >> (V.comp i h = 1.) >> C.success else r >> (V.comp i h = (V.comp i index) /. (V.comp imax index)) >> C.success in List.fold_left check r indices >> C.success open Cv.Order let () = test "mix" & fun r -> let tr_index = V.map (fun x -> 3. *. x) index in r >> (V.mix index tr_index 0. = index) >> (V.mix index tr_index 0.5 = db_index) >> (V.mix index tr_index 1.0 = tr_index) >> C.success (* Traversal *) open Cf.Order let () = test "map" & fun r -> let check r i = r >> (V.comp i db_index = (float i) *. 2.) >> C.success in List.fold_left check r indices >> C.success let () = test "mapi" & fun r -> let check r i = r >> (V.comp i index = (float i)) >> C.success in List.fold_left check r indices >> C.success open Cv.Order let () = test "fold" & fun r -> let ro = V.fold (fun acc c -> int_of_float c :: acc) [] index in r >> C.Order.(=) (List.rev ro) (indices) >> C.success let () = test "foldi" & fun r -> let ro = V.foldi (fun acc i c -> (i * int_of_float c) :: acc) [] index in r >> C.Order.(=) ro (List.rev_map (fun x -> x * x) indices) >> C.success let () = test "iter" & fun r -> let acc = ref [] in V.iter (fun c -> acc := int_of_float c :: !acc) index; r >> C.Order.(=) (List.rev !acc) indices >> C.success let () = test "iteri" & fun r -> let acc = ref [] in V.iteri (fun i c -> acc := i * int_of_float c :: !acc) index; r >> C.Order.(=) !acc (List.rev_map (fun x -> x * x) indices) >> C.success (* Predicates and comparisons *) let () = test "for_all" & fun r -> let eq = Pervasives.(=) in r >> Cv.holds (V.for_all (fun x -> eq x 0.)) V.zero >> Cv.holds (V.for_all (fun x -> eq x infinity)) V.infinity >> Cv.holds (V.for_all (fun x -> eq x neg_infinity)) V.neg_infinity >> C.success let () = test "exists" & fun r -> let check r i = let p v = (V.exists (fun c -> Pervasives.(=) c (float i))) v in r >> Cv.holds p index >> C.success in List.fold_left check r indices >> C.success open Cb.Order let () = test "equal_f" & fun r -> r >> C.for_all (Gen.t2 g_v g_v) begin fun (v, v') r -> r >> (V.equal v v = V.equal_f Pervasives.(=) v v) >> (V.equal v v' = V.equal_f Pervasives.(=) v v') >> C.success end >> C.success open Ci.Order let () = test "compare_f" & fun r -> r >> C.for_all (Gen.t2 g_v g_v) begin fun (v, v') r -> r >> (V.compare v v = V.compare_f Pervasives.compare v v) >> (V.compare v v' = V.compare_f Pervasives.compare v v') >> C.success end >> C.success end module V2_tests = struct module V2 = struct include V2 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in V2.v (r ()) (r ()) in g, pp end include V_tests (V2) open Cf.Order let () = test "x, y" & fun r -> r >> (V2.x index = 0.) >> (V2.y index = 1.) >> C.success let () = test "angle" & fun r -> let chop v = Float.chop ~eps v in r >> (chop (V2.angle V2.ox) = 0.) >> (chop (V2.angle V2.oy) = Float.pi_div_2) >> (chop (V2.angle (V2.v (- 1.) 0.)) = Float.pi) >> (chop (V2.angle (V2.neg V2.ox)) = (-. Float.pi)) >> (chop (V2.angle (V2.smul 2. (V2.neg V2.oy))) = -. Float.pi_div_2) >> C.success open Cv.Order let () = test "ox, oy, basis" & fun r -> r >> (V2.basis 0 = V2.ox) >> (V2.basis 1 = V2.oy) >> C.success let () = test "of_tuple, to_tuple" & fun r -> r >> (V2.of_tuple (0., 1.) = index) >> (V2.of_tuple (V2.to_tuple index) = index) >> C.success let () = test "of_polar" & fun r -> let chop v = V2.map (Float.chop ~eps) v in r >> (chop (V2.of_polar (V2.v 1. 0.)) = V2.ox) >> (chop (V2.of_polar (V2.v 1. Float.pi_div_2)) = V2.oy) >> (chop (V2.of_polar (V2.v 1. Float.pi)) = (V2.neg V2.ox)) >> (chop (V2.of_polar (V2.v 2. (-. Float.pi_div_2))) = (V2.smul 2. (V2.neg V2.oy))) >> C.success let () = test "to_polar" & fun r -> let chop v = V2.map (Float.chop ~eps) v in r >> (chop (V2.to_polar V2.ox) = (V2.v 1. 0.)) >> (chop (V2.to_polar V2.oy) = (V2.v 1. Float.pi_div_2)) >> (chop (V2.to_polar (V2.v (- 1.) 0.)) = (V2.v 1. Float.pi)) >> (chop (V2.to_polar (V2.neg V2.ox)) = (V2.v 1. (-. Float.pi))) >> (chop (V2.to_polar (V2.smul 2. (V2.neg V2.oy))) = (V2.v 2. (-. Float.pi_div_2))) >> C.success let () = test "of_v3, of_v4" & fun r -> r >> (V2.of_v3 (V3.v 0. 1. 2.) = index) >> (V2.of_v4 (V4.v 0. 1. 2. 3.) = index) >> C.success let () = test "polar" & fun r -> let chop v = V2.map (Float.chop ~eps) v in r >> (chop (V2.polar 1. 0.) = V2.ox) >> (chop (V2.polar 1. Float.pi_div_2) = V2.oy) >> (chop (V2.polar 1. Float.pi) = (V2.neg V2.ox)) >> (chop (V2.polar 2. (-. Float.pi_div_2)) = (V2.smul 2. (V2.neg V2.oy))) >> C.success let () = test "ortho" & fun r -> r >> (V2.ortho V2.ox = V2.oy) >> (V2.ortho V2.oy = (V2.neg V2.ox)) >> (V2.ortho (V2.neg V2.ox) = (V2.neg V2.oy)) >> (V2.ortho (V2.neg V2.oy) = V2.ox) >> C.success let () = test "ltr" & fun r -> let m = M2.v 1. 2. 3. 4. in r >> (V2.ltr m (V2.v 1. 2.) = V2.v 5. 11.) >> C.success let () = test "tr" & fun r -> let m = M3.v 1. 2. 3. 4. 5. 6. 7. 8. 9. in r >> (V2.tr m (V2.v 1. 2.) = V2.v 5. 14.) >> C.success end module V3_tests = struct module V3 = struct include V3 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in V3.v (r ()) (r ()) (r ()) in g, pp end include V_tests (V3) open Cf.Order let () = test "x, y, z" & fun r -> r >> (V3.x index = 0.) >> (V3.y index = 1.) >> (V3.z index = 2.) >> C.success let () = test "azimuth" & fun r -> let chop v = Float.chop ~eps v in r >> (chop 0. = V3.azimuth V3.oz) >> (chop (-. Float.pi) = V3.azimuth (V3.neg V3.oz)) >> (chop 0. = V3.azimuth V3.ox) >> (chop (-. Float.pi) = V3.azimuth (V3.neg V3.ox)) >> (chop Float.pi_div_2 = V3.azimuth V3.oy) >> (chop (-. Float.pi_div_2) = V3.azimuth (V3.neg V3.oy)) >> C.success let () = test "zenith" & fun r -> let chop v = Float.chop ~eps v in r >> (chop 0. = V3.zenith V3.oz) >> (chop Float.pi_div_2 = V3.zenith V3.ox) >> (chop Float.pi = V3.zenith (V3.neg V3.oz)) >> (chop Float.pi_div_2 = V3.zenith V3.oy) >> (chop 0. = V3.zenith (V3.smul 2. V3.oz)) >> (chop Float.pi_div_2 = V3.zenith (V3.neg V3.ox)) >> (chop Float.pi_div_2 = V3.zenith (V3.neg V3.oy)) >> C.success open Cv.Order let () = test "ox, oy, oz, basis" & fun r -> r >> (V3.basis 0 = V3.ox) >> (V3.basis 1 = V3.oy) >> (V3.basis 2 = V3.oz) >> C.success let () = test "of_tuple, to_tuple" & fun r -> r >> (V3.of_tuple (0., 1., 2.) = index) >> (V3.of_tuple (V3.to_tuple index) = index) >> C.success let () = test "of_spherical" & fun r -> let chop v = V3.map (Float.chop ~eps) v in r >> (chop (V3.of_spherical (V3.v 1. 0. 0.)) = V3.oz) >> (chop (V3.of_spherical (V3.v 1. 0. Float.pi_div_2)) = V3.ox) >> (chop (V3.of_spherical (V3.v 1. 0. Float.pi)) = V3.neg V3.oz) >> (chop (V3.of_spherical (V3.v 1. Float.pi_div_2 0.)) = V3.oz) >> (chop (V3.of_spherical (V3.v 1. Float.pi_div_2 Float.pi_div_2)) = V3.oy) >> (chop (V3.of_spherical (V3.v 1. Float.pi_div_2 Float.pi)) = V3.neg V3.oz) >> (chop (V3.of_spherical (V3.v 2. Float.pi 0.)) = (V3.smul 2. V3.oz)) >> (chop (V3.of_spherical (V3.v 1. Float.pi Float.pi_div_2)) = V3.neg V3.ox) >> (chop (V3.of_spherical (V3.v 1. Float.pi Float.pi)) = V3.neg V3.oz) >> (chop (V3.of_spherical (V3.v 1. (-. Float.pi_div_2) 0.)) = V3.oz) >> (chop (V3.of_spherical (V3.v 1. (-. Float.pi_div_2) Float.pi_div_2)) = (V3.neg V3.oy)) >> (chop (V3.of_spherical (V3.v 1. (-. Float.pi_div_2) Float.pi)) = V3.neg V3.oz) >> C.success let () = test "to_spherical" & fun r -> let chop v = V3.map (Float.chop ~eps) v in r >> (chop (V3.v 1. 0. 0.) = V3.to_spherical V3.oz) >> (chop (V3.v 1. 0. Float.pi_div_2) = V3.to_spherical V3.ox) >> (chop (V3.v 1. (-. Float.pi) Float.pi) = V3.to_spherical (V3.neg V3.oz)) >> (chop (V3.v 1. Float.pi_div_2 Float.pi_div_2) = V3.to_spherical V3.oy) >> (chop (V3.v 1. (-. Float.pi) Float.pi) = V3.to_spherical (V3.neg V3.oz)) >> (chop (V3.v 2. 0. 0.) = V3.to_spherical (V3.smul 2. V3.oz)) >> (chop (V3.v 1. (-. Float.pi) Float.pi_div_2) = V3.to_spherical (V3.neg V3.ox)) >> (chop (V3.v 1. (-. Float.pi_div_2) Float.pi_div_2) = V3.to_spherical (V3.neg V3.oy)) >> C.success let () = test "of_v2, of_v4" & fun r -> r >> (V3.of_v2 (V2.v 0. 1.) ~z:2. = index) >> (V3.of_v4 (V4.v 0. 1. 2. 3.) = index) >> C.success let () = test "cross" & fun r -> r >> (V3.cross V3.ox V3.oy = V3.oz) >> (V3.cross V3.oy V3.ox = V3.neg V3.oz) >> (V3.cross (V3.neg V3.ox) V3.oy = V3.neg V3.oz) >> C.success let () = test "spherical" & fun r -> let chop v = V3.map (Float.chop ~eps) v in r >> (chop (V3.spherical 1. 0. 0.) = V3.oz) >> (chop (V3.spherical 1. 0. Float.pi_div_2) = V3.ox) >> (chop (V3.spherical 1. 0. Float.pi) = V3.neg V3.oz) >> (chop (V3.spherical 1. Float.pi_div_2 0.) = V3.oz) >> (chop (V3.spherical 1. Float.pi_div_2 Float.pi_div_2) = V3.oy) >> (chop (V3.spherical 1. Float.pi_div_2 Float.pi) = V3.neg V3.oz) >> (chop (V3.spherical 2. Float.pi 0.) = (V3.smul 2. V3.oz)) >> (chop (V3.spherical 1. Float.pi Float.pi_div_2) = V3.neg V3.ox) >> (chop (V3.spherical 1. Float.pi Float.pi) = V3.neg V3.oz) >> (chop (V3.spherical 1. (-. Float.pi_div_2) 0.) = V3.oz) >> (chop (V3.spherical 1. (-. Float.pi_div_2) Float.pi_div_2) = (V3.neg V3.oy)) >> (chop (V3.spherical 1. (-. Float.pi_div_2) Float.pi) = V3.neg V3.oz) >> C.success let () = test "ltr" & fun r -> let m = M3.v 1. 2. 3. 4. 5. 6. 7. 8. 9. in r >> (V3.ltr m (V3.v 1. 2. 3.) = V3.v 14. 32. 50.) >> C.success let () = test "tr" & fun r -> let m = M4.v 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. in r >> (V3.tr m (V3.v 1. 2. 3.) = V3.v 14. 38. 62.) >> C.success end module V4_tests = struct module V4 = struct include V4 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in V4.v (r ()) (r ()) (r ()) (r ()) in g, pp end include V_tests (V4) open Cf.Order let () = test "x, y, z, w" & fun r -> r >> (V4.x index = 0.) >> (V4.y index = 1.) >> (V4.z index = 2.) >> (V4.w index = 3.) >> C.success open Cv.Order let () = test "ox, oy, oz, basis" & fun r -> r >> (V4.basis 0 = V4.ox) >> (V4.basis 1 = V4.oy) >> (V4.basis 2 = V4.oz) >> (V4.basis 3 = V4.ow) >> C.success let () = test "of_tuple, to_tuple" & fun r -> r >> (V4.of_tuple (0., 1., 2., 3.) = index) >> (V4.of_tuple (V4.to_tuple index) = index) >> C.success let () = test "of_v2, of_v3" & fun r -> r >> (V4.of_v2 (V2.v 0. 1.) ~z:2. ~w:3. = index) >> (V4.of_v3 (V3.v 0. 1. 2.) ~w:3. = index) >> C.success let () = test "ltr" & fun r -> let m = M4.v 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. in r >> (V4.ltr m (V4.v 1. 2. 3. 4.) = V4.v 30. 70. 110. 150.) >> C.success end (* Point tests *) module type P = sig include Gg.P val compare : t -> t -> int val pp : Format.formatter -> t -> unit val gen : min:float -> len:float -> t Checkm.gen (* random point. *) end module P_tests (P : P) (V : Gg.V with type t = P.t) = struct (* generic tests.*) let p_prefix = "P" ^ (string_of_int P.dim) ^ "." let test n f = Test.add_test (p_prefix ^ n) f let g_p = P.gen ~min:(-100.) ~len:200. let index = V.mapi (fun i _ -> (float i)) P.o let inc_index = V.map (fun x -> x +. 1.) index module Cp = C.Make (P) open Cp.Order let () = test "o" & fun r -> r >> Cp.holds (V.for_all (fun c -> Pervasives.(=) c 0.)) P.o >> C.success let () = test "mid" & fun r -> r >> (P.mid inc_index (V.smul 3. inc_index) = V.smul 2. inc_index) >> C.success end module P2_tests = struct module P2 = struct include P2 let compare = V2.compare let pp = V2.pp let gen = V2_tests.V2.gen end include P_tests (P2) (V2) open Cf.Order let () = test "x, y" & fun r -> r >> (P2.x index = 0.) >> (P2.y index = 1.) >> C.success open Cp.Order let () = test "tr" & fun r -> let m = M3.v 1. 2. 3. 4. 5. 6. 7. 8. 9. in r >> (P2.tr m (P2.v 1. 2.) = P2.v 8. 20.) >> C.success end module P3_tests = struct module P3 = struct include P3 let compare = V3.compare let pp = V3.pp let gen = V3_tests.V3.gen end include P_tests (P3) (V3) open Cf.Order let () = test "x, y, z" & fun r -> r >> (P3.x index = 0.) >> (P3.y index = 1.) >> (P3.z index = 2.) >> C.success open Cp.Order let () = test "tr" & fun r -> let m = M4.v 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. in r >> (P3.tr m (P3.v 1. 2. 3.) = P3.v 18. 46. 74.) >> C.success end (* Matrix tests *) module type M = sig include Gg.M val gen : min:float -> len:float -> t Checkm.gen (* random matrix. *) end module M_tests (M : M) = struct (* generic tests. *) let m_prefix = "M" ^ (string_of_int M.dim) ^ "." let test n f = Test.add_test (m_prefix ^ n) f let g_m = M.gen ~min:(-100.) ~len:200. let indices = (* list of (row, column, linear index) in column-major order. *) let o = ref [] in for j = M.dim - 1 downto 0 do for i = M.dim - 1 downto 0 do o := (i, j, M.dim * j + i) :: !o done; done; !o (* Test matrices, relies on correct M.map and M.mapi. The value at lindex_ij is the linear column-major index of position (i,j). Useful for tests as each element is different in a predictable way. *) let lindex = M.mapi (fun i j _ -> (float (M.dim * j + i))) M.zero let db_lindex = M.map (fun x -> 2. *. x) lindex module Cm = C.Make (M) (* Functions *) open Cm.Order let () = test "neg" & fun r -> r >> Cm.for_all g_m begin fun m r -> r >> (M.neg (M.neg m) = m) >> (M.neg m = M.smul (-1.) m) >> C.success end >> C.success let () = test "add" & fun r -> r >> Cm.for_all g_m begin fun m r -> r >> (M.add (M.neg m) m = M.zero) >> (M.add m (M.neg m) = M.zero) >> C.success end >> C.success let () = test "sub" & fun r -> r >> Cm.for_all g_m begin fun m r -> r >> (M.sub m m = M.zero) >> (M.sub (M.neg m) (M.neg m) = M.zero) >> C.success end >> C.success let () = test "mul" & fun r -> (* M.mul is better tested by M4.inv *) r >> Cm.for_all g_m begin fun m r -> r >> (M.mul M.id m = m) >> (M.mul m M.id = m) >> C.success end >> C.success let () = test "emul" & fun r -> r >> (M.emul lindex lindex = M.map (fun x -> x *. x) lindex) >> C.success let () = test "ediv" & fun r -> let inc_lindex = M.map (fun x -> x +. 1.) lindex in (* to avoid /. zero *) r >> (M.ediv (M.emul inc_lindex inc_lindex) inc_lindex = inc_lindex) >> C.success let () = test "smul" & fun r -> r >> (M.smul 2. lindex = db_lindex) >> (M.smul (-2.) lindex = M.neg db_lindex) >> C.success let () = test "transpose" & fun r -> r >> (M.transpose M.id = M.id) >> Cm.for_all g_m begin fun m r -> r >> (M.transpose (M.transpose m) = m) >> C.success end >> C.success open Cf.Order let () = test "trace" & fun r -> r >> (M.trace M.id = (float M.dim)) >> (M.trace lindex = float ((M.dim * M.dim * M.dim - M.dim) / 2)) >> C.success let () = test "det" & fun r -> r >> (M.det M.id = 1.0) >> Cm.for_all g_m begin fun m r -> let eps = 1e-6 in r >> (Float.round_zero eps ((M.det (M.transpose m)) -. M.det m) = 0.) >> C.success end >> C.success open Cm.Order let () = test "inv" & fun r -> let invertible m = Pervasives.(<>) (Float.round_zero ~eps (M.det m)) 0. in r >> (M.inv M.id = M.id) >> Cm.for_all ~cond:invertible g_m begin fun m r -> let id' = M.map (Float.chop ~eps) (M.mul (M.inv m) m) in r >> (id' = M.id) >> C.success end >> C.success (* Traversal *) open Cf.Order let () = test "map" & fun r -> let check r (i, j, li) = r >> (M.el i j db_lindex = float (2 * li)) in List.fold_left check r indices >> C.success let () = test "mapi" & fun r -> let check r (i, j, li) = r >> (M.el i j lindex = float li) in List.fold_left check r indices >> C.success open Cm.Order let () = test "fold" & fun r -> let ro = M.fold (fun acc li -> (int_of_float li) :: acc) [] lindex in r >> C.Order.(=) (List.rev ro) (List.map (fun (_, _, li) -> li) indices) >> C.success let () = test "foldi" & fun r -> let ro = M.foldi (fun acc i j li -> (i, j, int_of_float li) :: acc) [] lindex in r >> C.Order.(=) (List.rev ro) (indices) >> C.success let () = test "iter" & fun r -> let acc = ref [] in M.iter (fun e -> acc := int_of_float e :: !acc) lindex; r >> C.Order.(=) !acc (List.rev_map (fun (_, _, li) -> li) indices) >> C.success let () = test "iteri" & fun r -> let acc = ref [] in M.iteri (fun i j e -> acc := (i, j, int_of_float e) :: !acc) lindex; r >> C.Order.(=) (List.rev !acc) indices >> C.success (* Predicates and comparisons *) let () = test "for_all" & fun r -> r >> Cm.holds (M.for_all (fun x -> Pervasives.(=) x 0.)) M.zero >> C.success let () = test "exists" & fun r -> let check r (_, _, li) = let p m = (M.exists (fun e -> Pervasives.(=) e (float li))) m in r >> Cm.holds p lindex >> C.success in List.fold_left check r indices >> C.success open Cb.Order let () = test "equal_f" & fun r -> r >> C.for_all (Gen.t2 g_m g_m) begin fun (m, m') r -> r >> (M.equal m m = M.equal_f Pervasives.(=) m m) >> (M.equal m m' = M.equal_f Pervasives.(=) m m') >> C.success end >> C.success open Ci.Order let () = test "compare_f" & fun r -> r >> C.for_all (Gen.t2 g_m g_m) begin fun (m, m') r -> r >> (M.compare m m = M.compare_f Pervasives.compare m m) >> (M.compare m m' = M.compare_f Pervasives.compare m m') >> C.success end >> C.success end module M2_tests = struct module M2 = struct include M2 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in M2.v (r ()) (r ()) (r ()) (r ()) in g, pp end include M_tests (M2) open Cf.Order let () = test "eij" & fun r -> r >> (M2.e00 lindex = 0.) >> (M2.e10 lindex = 1.) >> (M2.e01 lindex = 2.) >> (M2.e11 lindex = 3.) >> C.success open Cm.Order let () = test "row, of_rows" & fun r -> r >> (M2.of_rows (M2.row 0 lindex) (M2.row 1 lindex) = lindex) >> C.success let () = test "col, of_cols" & fun r -> r >> (M2.of_cols (V2.basis 0) (V2.basis 1) = M2.id) >> (M2.of_cols (M2.col 0 lindex) (M2.col 1 lindex) = lindex) >> C.success (* 2D space transformations *) open V2_tests.Cv.Order let () = test "rot" & fun r -> let m = M2.rot Float.pi_div_2 in let chop = V2.map (Float.chop ~eps) in r >> (chop (V2.ltr m V2.ox) = V2.oy) >> (chop (V2.ltr m V2.oy) = (V2.neg V2.ox)) >> (chop (V2.ltr m (V2.neg V2.ox)) = (V2.neg V2.oy)) >> (chop (V2.ltr m (V2.neg V2.oy)) = V2.ox) >> C.success let () = test "scale" & fun r -> let s = V2.v 2. 3. in let m = M2.scale s in r >> (V2.ltr m s = V2.mul s s) >> C.success end module M3_tests = struct module M3 = struct include M3 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in M3.v (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) in g, pp end include M_tests (M3) open Cf.Order let () = test "eij" & fun r -> r >> (M3.e00 lindex = 0.) >> (M3.e10 lindex = 1.) >> (M3.e20 lindex = 2.) >> (M3.e01 lindex = 3.) >> (M3.e11 lindex = 4.) >> (M3.e21 lindex = 5.) >> (M3.e02 lindex = 6.) >> (M3.e12 lindex = 7.) >> (M3.e22 lindex = 8.) >> C.success open Cm.Order let () = test "row, of_rows" & fun r -> r >> (M3.of_rows (M3.row 0 lindex) (M3.row 1 lindex) (M3.row 2 lindex) = lindex) >> C.success let () = test "col, of_cols" & fun r -> r >> (M3.of_cols (V3.basis 0) (V3.basis 1) (V3.basis 2) = M3.id) >> (M3.of_cols (M3.col 0 lindex) (M3.col 1 lindex) (M3.col 2 lindex) = lindex) >> C.success let () = test "of_m2_v2" & fun r -> r >> (M3.of_m2_v2 (M2.v 1. 2. 4. 5.) (V2.v 3. 6.) = M3.v 1. 2. 3. 4. 5. 6. 0. 0. 1.) >> C.success (* 2D space transformations *) open V2_tests.Cv.Order let () = test "move" & fun r -> let d = (V2.v 1. 2.) in let m = M3.move d in r >> (V2.tr m d = d) >> (P2.tr m d = V2.smul 2. d) >> (P2.tr m P2.o = d) >> C.success let () = test "rot" & fun r -> let chop v = V2.map (Float.chop ~eps) v in let m = M3.rot Float.pi_div_2 in r >> (chop (V2.tr m V2.ox) = V2.oy) >> (chop (P2.tr m V2.ox) = V2.oy) >> (chop (P2.tr m P2.o) = P2.o) >> C.success let () = test "scale2" & fun r -> let s = V2.v 2. 3. in let m = M3.scale2 s in r >> (V2.tr m s = V2.mul s s) >> (P2.tr m s = V2.mul s s) >> (P2.tr m P2.o = P2.o) >> C.success let () = test "rigid" & fun r -> let chop v = V2.map (Float.chop ~eps) v in let m = (M3.rigid (V2.v 2. 3.) Float.pi_div_4) in let m' = (M3.mul (M3.move (V2.v 2. 3.)) (M3.rot Float.pi_div_4)) in let ri = (M3.rigid (V2.v 1. 2.) Float.pi_div_2) in r >> Cm.Order.(=) m m' >> (chop (V2.tr ri V2.ox) = V2.oy) >> (chop (P2.tr ri V2.ox) = P2.v 1. 3.) >> C.success let () = test "srigid" & fun r -> let m = M3.srigid (V2.v 2. 3.) Float.pi_div_4 (V2.v 2. 3.) in let ri = M3.mul (M3.move (V2.v 2. 3.)) (M3.rot Float.pi_div_4) in let m' = M3.mul ri (M3.scale2 (V2.v 2. 3.)) in let cmp = M3.compare_f (Float.compare_tol ~eps) in r >> C.Order.(=) ~cmp ~pr:M3.pp m m' >> C.success (* 3D space transformations *) open V3_tests.Cv.Order let () = test "rot_map" & fun r -> let m = M3.rot_map V3.ox V3.oz in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.ltr m V3.ox) = V3.oz) >> C.success let () = test "rot_axis" & fun r -> let m = M3.rot_axis V3.ox Float.pi_div_2 in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.ltr m V3.oy) = V3.oz) >> C.success let () = test "rot_zyx" & fun r -> let a = Float.pi_div_2 in let m = M3.rot_zyx (V3.v a a a) in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.ltr m V3.oy) = V3.oy) >> C.success let () = test "scale" & fun r -> let s = V3.v 2. 3. 4. in let m = M3.scale s in r >> (V3.ltr m s = V3.mul s s) >> C.success end module M4_tests = struct module M4 = struct include M4 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in M4.v (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) (r ()) in g, pp end include M_tests (M4) open Cf.Order let () = test "eij" & fun r -> r >> (M4.e00 lindex = 0.) >> (M4.e10 lindex = 1.) >> (M4.e20 lindex = 2.) >> (M4.e30 lindex = 3.) >> (M4.e01 lindex = 4.) >> (M4.e11 lindex = 5.) >> (M4.e21 lindex = 6.) >> (M4.e31 lindex = 7.) >> (M4.e02 lindex = 8.) >> (M4.e12 lindex = 9.) >> (M4.e22 lindex = 10.) >> (M4.e32 lindex = 11.) >> (M4.e03 lindex = 12.) >> (M4.e13 lindex = 13.) >> (M4.e23 lindex = 14.) >> (M4.e33 lindex = 15.) >> C.success open Cm.Order let () = test "row, of_rows" & fun r -> r >> (M4.of_rows (M4.row 0 lindex) (M4.row 1 lindex) (M4.row 2 lindex) (M4.row 3 lindex) = lindex) >> C.success let () = test "col, of_cols" & fun r -> r >> (M4.of_cols (V4.basis 0) (V4.basis 1) (V4.basis 2) (V4.basis 3)=M4.id) >> (M4.of_cols (M4.col 0 lindex) (M4.col 1 lindex) (M4.col 2 lindex) (M4.col 3 lindex) = lindex) >> C.success let () = test "of_m3_v3" & fun r -> r >> (M4.of_m3_v3 (M3.v 1. 2. 3. 5. 6. 7. 9. 10. 11.) (V3.v 4. 8. 12.) = M4.v 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 0. 0. 0. 1.) >> C.success (* 3D space transformations *) open V3_tests.Cv.Order let () = test "move" & fun r -> let d = (V3.v 1. 2. 3.) in let m = M4.move d in r >> (V3.tr m d = d) >> (P3.tr m d = V3.smul 2. d) >> (P3.tr m P3.o = d) >> C.success let () = test "rot_map" & fun r -> let m = M4.rot_map V3.oz V3.ox in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.tr m V3.oz) = V3.ox) >> (chop (P3.tr m V3.oz) = V3.ox) >> (chop (P3.tr m P3.o) = P3.o) >> C.success let () = test "rot_axis" & fun r -> let m = M4.rot_axis V3.oy Float.pi_div_2 in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.tr m V3.oz) = V3.ox) >> (chop (P3.tr m V3.oz) = V3.ox) >> (chop (P3.tr m P3.o) = P3.o) >> C.success let () = test "rot_zyx" & fun r -> let a = -. Float.pi_div_2 in let m = M4.rot_zyx (V3.v a a a) in let chop = V3.map (Float.chop ~eps) in r >> (chop (V3.tr m V3.oy) = V3.neg V3.oy) >> (chop (P3.tr m V3.oy) = V3.neg V3.oy) >> (chop (P3.tr m P3.o) = P3.o) >> C.success let () = test "scale3" & fun r -> let s = V3.v 2. 3. 4. in let m = M4.scale3 s in r >> (V3.tr m s = V3.mul s s) >> (P3.tr m s = V3.mul s s) >> (P3.tr m P3.o = P3.o) >> C.success let () = test "rigid, rigidq" & fun r -> let v = V3.v 2. 3. 4. in let a = Float.pi_div_4 in let m = M4.rigid v (V3.ox, a) in let mq = M4.rigidq v (Quat.rot_axis V3.ox a) in let m' = M4.mul (M4.move v) (M4.rot_axis V3.ox a) in let cmp = M4.compare_f (Float.compare_tol ~eps) in r >> C.Order.(=) ~cmp ~pr:M4.pp m m' >> C.Order.(=) ~cmp ~pr:M4.pp mq m' >> C.success let () = test "srigid, srigidq" & fun r -> let v = V3.v 2. 3. 4. in let a = Float.pi_div_4 in let m = M4.srigid v (V3.ox, a) v in let mq = M4.srigidq v (Quat.rot_axis V3.ox a) v in let ri = M4.mul (M4.move v) (M4.rot_axis V3.ox a) in let m' = M4.mul ri (M4.scale3 v) in let cmp = M4.compare_f (Float.compare_tol ~eps) in r >> C.Order.(=) ~cmp ~pr:M4.pp m m' >> C.Order.(=) ~cmp ~pr:M4.pp mq m' >> C.success (* 4D space transformations *) open V4_tests.Cv.Order let () = test "scale" & fun r -> let s = V4.v 2. 3. 4. 5. in let m = M4.scale s in r >> (V4.ltr m s = V4.mul s s) >> C.success end module Quat_tests = struct let test n f = Test.add_test ("Quat." ^ n) f let g_q = V4_tests.g_v let chop3 = V3.map (Float.chop ~eps) let chop4 = V4.map (Float.chop ~eps) module Cq = V4_tests.Cv open Cq.Order let () = test "mul, conj, inv" & fun r -> (* conj is used by inv. *) r >> Cq.for_all ~cond:(fun q -> not (V4.equal q Quat.zero)) g_q begin fun q r -> r >> (chop4 (Quat.mul q (Quat.inv q)) = Quat.id) >> C.success end >> C.success let () = test "rot_map, M3.of_quat, of_m3" & fun r -> let m = M3.rot_map V3.ox V3.oz in let q = Quat.rot_map V3.ox V3.oz in let fcmp = Float.compare_tol ~eps in let mcmp = M3.compare_f fcmp in let qcmp = V4.compare_f fcmp in r >> (C.Order.(=) ~cmp:mcmp ~pr:M3.pp m (M3.of_quat q)) >> (C.Order.(=) ~cmp:qcmp ~pr:V4.pp q (Quat.of_m3 m)) >> (V3_tests.Cv.Order.(=) (chop3 (Quat.apply3 q V3.ox)) V3.oz) >> (V4_tests.Cv.Order.(=) (chop4 (Quat.apply4 q V4.ox)) V4.oz) >> C.success let () = test "rot_axis, to_rot, M3.of_quat, of_m4" & fun r -> let theta = Float.pi_div_2 in let m = M4.rot_axis V3.ox theta in let q = Quat.rot_axis V3.ox theta in let axis, theta' = Quat.to_rot_axis q in let fcmp = Float.compare_tol ~eps in let mcmp = M4.compare_f fcmp in let vcmp = V3.compare_f fcmp in let qcmp = V4.compare_f fcmp in r >> (C.Order.(=) ~cmp:mcmp ~pr:M4.pp m (M4.of_quat q)) >> (C.Order.(=) ~cmp:qcmp ~pr:V4.pp q (Quat.of_m4 m)) >> (C.Order.(=) ~cmp:vcmp ~pr:V3.pp axis V3.ox) >> (C.Order.(=) ~cmp:fcmp ~pr:Format.pp_print_float theta' theta) >> (V3_tests.Cv.Order.(=) (chop3 (Quat.apply3 q V3.oy)) V3.oz) >> C.success let () = test "rot_zyx, to_zyx, M3.of_m3, of_m3" & fun ru -> let theta = Float.pi_div_2 in let r = V3.v (-. theta) theta theta in let m = M3.rot_zyx r in let q = Quat.rot_zyx r in let r' = Quat.to_rot_zyx q in let fcmp = Float.compare_tol ~eps:1e-6 in let mcmp = M3.compare_f fcmp in let vcmp = V3.compare_f fcmp in let qcmp = V4.compare_f fcmp in ru >> (C.Order.(=) ~cmp:mcmp ~pr:M3.pp m (M3.of_quat q)) >> (C.Order.(=) ~cmp:qcmp ~pr:V4.pp q (Quat.of_m3 m)) >> (C.Order.(=) ~cmp:vcmp ~pr:V3.pp r r') >> (V3_tests.Cv.Order.(=) (chop3 (Quat.apply3 q V3.oy)) (V3.neg V3.oy)) >> C.success end (* Size tests *) module type Size = sig include Gg.Size val compare : t -> t -> int val pp : Format.formatter -> t -> unit val gen : min:float -> len:float -> t Checkm.gen (* random size. *) end module Size_tests (Size : Size) (V : Gg.V with type t = Size.t) = struct let s_prefix = "Size" ^ (string_of_int Size.dim) ^ "." let test n f = Test.add_test (s_prefix ^ n) f let g_s = Size.gen ~min:(-100.) ~len:200. let index = V.mapi (fun i _ -> (float i)) Size.zero module Cs = C.Make (Size) open Cs.Order let () = test "zero" & fun r -> r >> Cs.holds (V.for_all (fun c -> Pervasives.(=) c 0.)) Size.zero >> C.success let () = test "unit" & fun r -> r >> Cs.holds (V.for_all (fun c -> Pervasives.(=) c 1.)) Size.unit >> C.success end module Size2_tests = struct module Size2 = struct include Size2 let compare = V2.compare let pp = V2.pp let gen = V2_tests.V2.gen end include Size_tests (Size2) (V2) open Cf.Order let () = test "w, h" & fun r -> r >> (Size2.w index = 0.) >> (Size2.h index = 1.) >> C.success end module Size3_tests = struct module Size3 = struct include Size3 let compare = V3.compare let pp = V3.pp let gen = V3_tests.V3.gen end include Size_tests (Size3) (V3) open Cf.Order let () = test "w, h, d" & fun r -> r >> (Size3.w index = 0.) >> (Size3.h index = 1.) >> (Size3.d index = 2.) >> C.success end (* Box tests. *) module type Box = sig include Gg.Box val gen : min:float -> len:float -> t Checkm.gen (* random box. *) end module Box_tests (V : V) (P : P with type t = V.t) (Size : Size with type t = V.t) (M : M with type v = V.t) (Box : Box with type v = V.t and type p = P.t and type size = Size.t and type m = M.t) = struct let p_prefix = "Box" ^ (string_of_int Box.dim) ^ "." let test n f = Test.add_test (p_prefix ^ n) f let g_box = Box.gen ~min:(-100.) ~len:200. let fcmp = Float.compare_tol ~eps let vcmp = V.compare_f fcmp let bcmp = Box.compare_f fcmp let vt = V.mapi (fun i _ -> float (i + 1)) V.zero let usize = V.mapi (fun i _ -> 1.) V.zero let bt = Box.v vt vt let bt_mid = Box.v_mid V.zero V.(2. * vt) module Cbox = C.Make (Box) module Cv = C.Make (V) open Cv.Order let () = test "v_mid, o, size, zero, unit" & fun r -> r >> (Box.o Box.zero = P.o) >> (Box.o Box.unit = P.o) >> (Box.o bt = vt) >> (Box.o bt_mid = V.neg vt) >> Cbox.raises_invalid_arg Box.o Box.empty >> (Box.size Box.zero = V.zero) >> (Box.size Box.unit = usize) >> (Box.size bt = vt) >> (Box.size bt_mid = V.(2. * vt)) >> Cbox.raises_invalid_arg Box.size Box.empty >> C.success open Cv.Order let () = test "min, max, mid, of_pts" & fun r -> let max = V.add (Box.o bt) (Box.size bt) in let mid = V.smul 0.5 (V.add (Box.o bt) max) in let minbt = Box.min bt in let maxbt = Box.max bt in r >> (minbt = vt) >> (maxbt = max) >> C.Order.(=) ~pr:V.pp ~cmp:vcmp (Box.mid bt) mid >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.of_pts minbt maxbt) bt >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.of_pts maxbt minbt) bt >> Cbox.raises_invalid_arg Box.min Box.empty >> Cbox.raises_invalid_arg Box.max Box.empty >> Cbox.raises_invalid_arg Box.mid Box.empty >> C.success open Cf.Order let () = test "area" & fun r -> let rec fact n = if Pervasives.(<=) n 0 then 1 else n * fact (n - 1) in let choices n k = (fact n) / (fact k) * (fact (n - k )) in let usides = (choices Box.dim 2) * (1 lsl (Box.dim - 2)) in r >> (Box.area Box.unit = (float usides) *. 1.) >> C.success open Cbox.Order let () = test "inter, isects" & fun r -> let isects (b, b') = Box.isects b b' in let check a b c r = r >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.inter a b) c >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.inter b a) c >> C.holds isects (a, b) >> C.holds isects (b, a) >> C.success in let check_empty a b r = r >> (Cbox.holds Box.is_empty (Box.inter a b)) >> (Cbox.holds Box.is_empty (Box.inter b a)) >> C.holds (C.neg isects) (a, b) >> C.holds (C.neg isects) (b, a) >> C.success in let umid = V.smul 0.5 usize in let b = Box.v umid usize in let i = Box.v umid umid in r >> check_empty Box.unit bt >> check Box.unit b i >> check_empty Box.empty Box.empty >> Cbox.for_all g_box begin fun b r -> r >> check b b b >> check_empty b Box.empty >> C.success end >> C.success open Cv.Order let () = test "union, subset" & fun r -> let subset (b, b') = Box.subset b b' in let check a b c r = r >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.union a b) c >> C.Order.(=) ~pr:Box.pp ~cmp:bcmp (Box.union b a) c >> C.holds subset (a, c) >> C.holds subset (b, c) >> C.success in let umid = V.smul 0.5 usize in let b = Box.v umid usize in let u = Box.union b bt in r >> check b bt u >> (Box.min u = umid) >> (Box.max u = Box.max bt) >> Cbox.for_all g_box begin fun b r -> r >> check b b b >> check b Box.empty b >> check Box.empty b b >> C.success end open Cv.Order let () = test "inset, is_pt" & fun r -> let vmid = V.smul 0.5 usize in let i = Box.inset vmid Box.unit in let o = Box.inset (V.neg usize) Box.unit in let e = Box.inset usize Box.unit in r >> (Cbox.holds Box.is_pt i) >> (Box.min i = vmid) >> (Box.max i = vmid) >> (Box.min o = V.neg usize) >> (Box.max o = V.smul 2. usize) >> (Cbox.holds Box.is_empty e) >> (Cbox.holds Box.is_empty (Box.inset vmid Box.empty)) >> (Cbox.holds Box.is_empty (Box.inset (V.neg vmid) Box.empty)) >> C.success open Cbox.Order let () = test "round" & fun r -> let vmid = V.smul 0.5 usize in let b = Box.v vmid V.zero in let b' = Box.v (V.neg vmid) V.zero in r >> (Box.round b = Box.unit) >> (Box.round b' = Box.v (V.neg usize) usize) >> (Box.round Box.empty = Box.empty) >> C.success let () = test "move" & fun r -> r >> (Box.move (V.neg vt) bt = Box.v P.o vt) >> C.success open Cbox.Order let () = test "map_f" & fun r -> r >> (Box.map_f (fun _ -> 0.) bt = Box.zero) >> C.success open Cbox.Order let () = test "mem" & fun r -> let mem (p, b) = Box.mem p b in let rec basis i r = if Pervasives.(<) i 0 then C.success r else let v = V.basis i in r >> (C.holds mem (v, Box.unit)) >> (C.holds (C.neg mem) (V.neg v, Box.unit)) >> (C.holds (C.neg mem) (V.smul 2. v, Box.unit)) >> (C.holds (C.neg mem) (v, Box.empty)) >> basis (i - 1) in r >> C.holds mem (V.zero, Box.unit) >> C.holds mem (usize, Box.unit) >> C.holds mem (V.smul 0.5 usize, Box.unit) >> C.holds (C.neg mem) (V.smul 2. usize, Box.unit) >> basis (Box.dim - 1) >> C.success open Cb.Order let () = test "equal_f" & fun r -> r >> C.for_all (Gen.t2 g_box g_box) begin fun (v, v') r -> r >> (Box.equal v v = Box.equal_f Pervasives.(=) v v) >> (Box.equal v v' = Box.equal_f Pervasives.(=) v v') >> C.success end >> C.success open Ci.Order let () = test "compare_f" & fun r -> r >> C.for_all (Gen.t2 g_box g_box) begin fun (v, v') r -> let e = Box.empty in r >> (Box.compare v v = Box.compare_f Pervasives.compare v v) >> (Box.compare v v' = Box.compare_f Pervasives.compare v v') >> (Box.compare v e = Box.compare_f Pervasives.compare v e) >> (Box.compare e v = Box.compare_f Pervasives.compare e v) >> C.success end >> C.success end module Box2_tests = struct module Box2 = struct include Box2 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in let o = P2.v (r ()) (r ()) in let size = Size2.v (abs_float (r ())) (abs_float (r ())) in Box2.v o size in g, pp end include Box_tests (V2_tests.V2) (P2_tests.P2) (Size2_tests.Size2) (M2_tests.M2) (Box2) open Cf.Order let () = test "ox, oy, w, h" & fun r -> r >> (Box2.ox bt = 1.) >> (Box2.oy bt = 2.) >> (Box2.w bt = 1.) >> (Box2.h bt = 2.) >> C.success let () = test "minx, miny, maxx, maxy, midx, midy" & fun r -> r >> (Box2.minx bt = V2.x (Box2.min bt)) >> (Box2.miny bt = V2.y (Box2.min bt)) >> (Box2.maxx bt = V2.x (Box2.max bt)) >> (Box2.maxy bt = V2.y (Box2.max bt)) >> (Box2.midx bt = V2.x (Box2.mid bt)) >> (Box2.midy bt = V2.y (Box2.mid bt)) >> C.success open Cv.Order let () = test "bottom_{left,right}, top_{left,right}" & fun r -> r >> (Box2.bottom_left bt = V2.v 1. 2.) >> (Box2.bottom_right bt = V2.v 2. 2.) >> (Box2.top_left bt = V2.v 1. 4.) >> (Box2.top_right bt = V2.v 2. 4.) >> C.success open Cbox.Order let () = test "ltr, tr" & fun r -> let ml = M2.rot Float.pi_div_2 in let mh = M3.rigid (V2.v (4.) (-1.)) Float.pi_div_2 in let lb = Box2.v (P2.v (-4.) 1.) (Size2.v 2. 1.) in let hb = Box2.v P2.o (Size2.v 2. 1.) in r >> C.Order.(=) ~pr:Box2.pp ~cmp:bcmp (Box2.ltr ml bt) lb >> C.Order.(=) ~pr:Box2.pp ~cmp:bcmp (Box2.tr mh bt) hb >> (Box2.ltr ml Box2.empty = Box2.empty) >> (Box2.tr mh Box2.empty = Box2.empty) >> C.success end module Box3_tests = struct module Box3 = struct include Box3 let gen ~min ~len = let g _ rs = let r = Float.srandom rs ~min ~len in let o = P3.v (r ()) (r ()) (r ()) in let size = Size3.v (abs_float (r ())) (abs_float (r ())) (abs_float (r ())) in Box3.v o size in g, pp end include Box_tests (V3_tests.V3) (P3_tests.P3) (Size3_tests.Size3) (M3_tests.M3) (Box3) open Cf.Order let () = test "ox, oy, oz, w, h, d" & fun r -> r >> (Box3.ox bt = 1.) >> (Box3.oy bt = 2.) >> (Box3.oz bt = 3.) >> (Box3.w bt = 1.) >> (Box3.h bt = 2.) >> (Box3.d bt = 3.) >> C.success let () = test "minx, miny, minz, maxx, maxy, maxz, midx, midy, midz" & fun r-> r >> (Box3.minx bt = V3.x (Box3.min bt)) >> (Box3.miny bt = V3.y (Box3.min bt)) >> (Box3.minz bt = V3.z (Box3.min bt)) >> (Box3.maxx bt = V3.x (Box3.max bt)) >> (Box3.maxy bt = V3.y (Box3.max bt)) >> (Box3.maxz bt = V3.z (Box3.max bt)) >> (Box3.midx bt = V3.x (Box3.mid bt)) >> (Box3.midy bt = V3.y (Box3.mid bt)) >> (Box3.midz bt = V3.z (Box3.mid bt)) >> C.success open Cf.Order let () = test "area, volume" & fun r -> r >> (Box3.area Box3.unit = 6.) >> (Box3.volume Box3.unit = 1.) >> C.success open Cbox.Order let () = test "ltr, tr" & fun r -> let theta = Float.pi_div_2 in let ml = M3.rot_zyx (V3.v theta theta 0.) in let mh = M4.rigid (V3.v 0. (-1.) 0.) (V3.oy, -. Float.pi) in let lb = Box3.v (P3.v (0.) (-1.) (-1.)) (Size3.v 1. 1. 1.) in let hb = Box3.v (P3.v (-1.) (-1.) (-1.)) (Size3.v 1. 1. 1.) in r >> C.Order.(=) ~pr:Box3.pp ~cmp:bcmp (Box3.ltr ml Box3.unit) lb >> C.Order.(=) ~pr:Box3.pp ~cmp:bcmp (Box3.tr mh Box3.unit) hb >> (Box3.ltr ml Box3.empty = Box3.empty) >> (Box3.tr mh Box3.empty = Box3.empty) >> C.success end module Color_tests = struct let test n f = Test.add_test ("Color." ^ n) f module type Param = sig val bits : int end let c lab = V2.norm (V2.v (V3.y lab) (V3.z lab)) let deltaE_lab lab1 lab2 = let cie76 = V3.norm (V3.sub lab1 lab2) in let dL,da,db = V3.to_tuple (V3.sub lab1 lab2) in let c1 = c lab1 and c2 = c lab2 in let dC = c1 -. c2 in let dH2 = da *. da +. db *. db -. dC *. dC in let dH2 = if dH2 < 0.0 then 0.0 else dH2 in let sC = 1. +. 0.045 *. c1 and sH = 1. +. 0.015 *. c2 in let cie94 = sqrt (dL *. dL +. (dC *. dC /. sC) +. (dH2 /. sH)) in V2.v cie76 cie94 let to_lab v = V3.of_v4 (Color.to_lab v) let deltaE color1 color2 = deltaE_lab (to_lab color1) (to_lab color2) let print_deltaE name fmt v = let dE = V2.x !v and dE94 = V2.y !v in Format.fprintf fmt "%s Max DeltaE = %g, Max CIE94 DeltaE = %g@\n" name dE dE94 module Testable_color (P: Param) = struct module Cc = C.Make(struct type conv_to_lab = v4 -> v3 type t = conv_to_lab * Test.run * v4 let digits = truncate (ceil ((float P.bits) *. log(2.) /. log(10.))) let eps = 2. ** (float (-P.bits)) let print_float fmt v = Format.fprintf fmt "%.*f" digits v let pp fmt (_,_,v) = V4.pp_f print_float fmt v let compare (conv,r,a) (_,_,b) = let cmp = V4.compare_f (Float.compare_tol ~eps) a b in if cmp = 0 then 0 else begin let dE = deltaE_lab (conv a) (conv b) in ignore (C.log (print_deltaE "") (ref dE) r); cmp end end) let update_max maxDeltaE dE = let dE76,dE94 = V2.to_tuple dE in let max76,max94 = V2.to_tuple !maxDeltaE in maxDeltaE := V2.v (max dE76 max76) (max dE94 max94) let compare_rgb max id color1 color2 = fun r -> update_max max (deltaE color1 color2); r >> Cc.Order.(=) ~id (to_lab,r,color1) (to_lab,r,color2) let compare_lab max id lab1 lab2 = fun r -> update_max max (deltaE_lab (V3.of_v4 lab1) (V3.of_v4 lab2)); r >> Cc.Order.(=) ~id (V3.of_v4,r,lab1) (V3.of_v4,r,lab2) end (** Lab fractional bits ~ 8 if we want 16-bit precision *) module Color8 = Testable_color(struct let bits = 8 end) (* Require 15-bit precision, so that 8-bit sRGB to 16-bit linear is accurate, * but allow for last bit to change. * The precision of ICC profiles is 16-bit too, and this inevitably leads * to different rounding when using built-in profiles vs when using an ICC * profile. * *) module Color16 = Testable_color(struct let bits = 15 end) (* Require 23-bit precision from roundtrips, * so that float32 HDR image conversions are accurate enough *) module Color23 = Testable_color(struct let bits = 23 end) type testcase = { srgb: Color.srgb; color: color; lab: Color.lab; gray: v2; } let to_testcase r' g' b' lr lg lb l a b gray = let alpha = 0.5 in { srgb = V4.v r' g' b' alpha; color = V4.v lr lg lb alpha; lab = V4.v l a b alpha; gray = V2.v gray alpha } let rec generate_testcases f lst = try let line = input_line f in let testcase = Scanf.sscanf line "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f" to_testcase in generate_testcases f (testcase :: lst) with End_of_file -> List.rev lst let srgb_dEmax = ref V2.zero let srgb_check r t = r >> Color16.compare_rgb srgb_dEmax "RGB" (Color.of_srgb t.srgb) t.color let srgb_roundtrip color r = let srgb = Color.to_srgb color in let color' = Color.of_srgb srgb in r >> Color23.compare_rgb srgb_dEmax "sRGB roundtrip" color' color let lab_dEmax = ref V2.zero let lab_check r t = r >> Color8.compare_rgb lab_dEmax "RGB" (Color.of_lab t.lab) t.color >> Color8.compare_lab lab_dEmax "LAB" (Color.to_lab t.color) t.lab let lch_dEmax = ref V2.zero let lch_ab_roundtrip color r = let lch = Color.to_lch_ab color in let color' = Color.of_lch_ab lch in r >> Color23.compare_rgb lch_dEmax "LCh_ab roundtrip" color' color let lab_roundtrip color r = let lab = Color.to_lab color in let color' = Color.of_lab lab in r >> Color23.compare_rgb lab_dEmax "LAB roundtrip" color' color let luv_dEmax = ref V2.zero let luv_roundtrip color r = let luv = Color.to_luv color in let color' = Color.of_luv luv in r >> Color23.compare_rgb luv_dEmax "LUV roundtrip" color' color let lchuv_dEmax = ref V2.zero let lchuv_roundtrip color r = let lchuv = Color.to_lch_uv color in let color' = Color.of_lch_uv lchuv in r >> Color23.compare_rgb lchuv_dEmax "LCh_uv roundtrip" color' color let run_checks testcases f r = List.fold_left f r testcases let color_gen = V4_tests.V4.gen ~min:0. ~len:1. let () = let f = open_in "test/rgbtest.csv" in ignore (input_line f);(* header *) let testcases = generate_testcases f [] in begin test "of_srgba, to_srgba (testcases)" & fun r -> srgb_dEmax := V2.zero; r >> run_checks testcases srgb_check >> C.log (print_deltaE "sRGB testcases") srgb_dEmax >> C.success end; begin test "of_srgba, to_srgba (roundtrip)" & fun r -> srgb_dEmax := V2.zero; C.for_all color_gen srgb_roundtrip r >> C.log (print_deltaE "sRGB <-> RGB") srgb_dEmax >> C.success end; begin test "of_lab, to_lab (testcases)" & fun r -> lab_dEmax := V2.zero; r >> run_checks testcases lab_check >> C.log (print_deltaE "LAB testcases") lab_dEmax >> C.success end; begin test "of_lab, to_lab (roundtrip)" & fun r -> lab_dEmax := V2.zero; C.for_all color_gen lab_roundtrip r >> C.log (print_deltaE "LAB <-> RGB") lab_dEmax >> C.success end; begin test "of_lch, to_lch (roundtrip)" & fun r -> lch_dEmax := V2.zero; r >> C.for_all color_gen lch_ab_roundtrip >> C.log (print_deltaE "LCH <-> RGB") lch_dEmax >> C.success end; begin test "of_luv, to_luv (roundtrip)" & fun r -> luv_dEmax := V2.zero; C.for_all color_gen luv_roundtrip r >> C.log (print_deltaE "LUV <-> RGB") luv_dEmax >> C.success end; begin test "of_luv (lch), to_luv (lch) (roundtrip)" & fun r -> lchuv_dEmax := V2.zero; C.for_all color_gen lchuv_roundtrip r >> C.log (print_deltaE "LCH_uv <-> RGB") lchuv_dEmax >> C.success end; close_in f end (* main *) let main () = let usage = Printf.sprintf "Usage: %s <options>\n\ Gg's test suite. Without any options runs all tests.\nOptions:" (Filename.basename Sys.executable_name) in let run_conf = ref (Test.run_conf) in let logger = ref (Test.term_log) in let options = (Test.log_args logger) @ (Test.run_conf_args run_conf) in Arg.parse options (fun _ -> ()) usage; match Test.run (!logger Format.std_formatter) !run_conf !Test.list with | `Ok -> exit 0 | `Fail -> exit 1 let () = main () (*--------------------------------------------------------------------------- Copyright (c) 2013 Daniel C. Bünzli All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Daniel C. Bünzli nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------*)