From 06b9500dde883283cc12fb794475a926a06cd867 Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime.jacquemin@cea.fr> Date: Tue, 23 Jan 2024 16:01:03 +0100 Subject: [PATCH] [Kernel] Comments about the invariant computation and other stuffs - The functions `Nat.of_int`, `Nat.of_strictly_positive_int` and `Finite.of_int` now returns an option instead of forcing valid bounds on their inputs. - The spectral exponent search is simpler and returns the maximal exponent in the given limit that returns a spectral radius lower than one. - A bug has been fixed in the invariant computation. The sum over the inputs missed an element. --- src/kernel_services/analysis/filter/finite.ml | 4 +- .../analysis/filter/finite.mli | 7 +- .../analysis/filter/linear_filter.ml | 81 ++++++++++--------- .../analysis/filter/linear_filter.mli | 19 ++++- src/kernel_services/analysis/filter/nat.ml | 8 +- src/kernel_services/analysis/filter/nat.mli | 4 +- .../filter => libraries/utils}/parray.ml | 0 .../filter => libraries/utils}/parray.mli | 4 + 8 files changed, 72 insertions(+), 55 deletions(-) rename src/{kernel_services/analysis/filter => libraries/utils}/parray.ml (100%) rename src/{kernel_services/analysis/filter => libraries/utils}/parray.mli (90%) diff --git a/src/kernel_services/analysis/filter/finite.ml b/src/kernel_services/analysis/filter/finite.ml index f992d181c71..a3851bc9961 100644 --- a/src/kernel_services/analysis/filter/finite.ml +++ b/src/kernel_services/analysis/filter/finite.ml @@ -33,8 +33,8 @@ let next : type n. n finite -> n succ finite = fun n -> n + 1 let ( == ) : type n. n finite -> n finite -> bool = fun l r -> l = r let to_int : type n. n finite -> int = fun n -> n -let of_int : type n. n succ nat -> int -> n succ finite = - fun limit n -> min (max 0 n) (Nat.to_int limit) +let of_int : type n. n succ nat -> int -> n succ finite option = + fun limit n -> if 0 <= n && n < Nat.to_int limit then Some n else None let for_each (type n) acc (limit : n nat) (f : n finite -> 'a -> 'a) = let acc = ref acc in diff --git a/src/kernel_services/analysis/filter/finite.mli b/src/kernel_services/analysis/filter/finite.mli index 1f2a6b986a7..36d4f1ebd55 100644 --- a/src/kernel_services/analysis/filter/finite.mli +++ b/src/kernel_services/analysis/filter/finite.mli @@ -30,10 +30,9 @@ val next : 'n finite -> 'n succ finite val ( == ) : 'n finite -> 'n finite -> bool (* The call [of_int limit n] returns a finite value representing the n-nd - element of a finite set of cardinal limit. If n is strictly negative then it - returns the first element. If n is greater or equal to the limit then it - returns the last element. This function complexity is O(1). *) -val of_int : 'n succ nat -> int -> 'n succ finite + element of a finite set of cardinal limit. If n is not in the bounds, none is + returned. This function complexity is O(1). *) +val of_int : 'n succ nat -> int -> 'n succ finite option (* The call [to_int n] returns an integer equal to n. This function complexity is O(1). *) diff --git a/src/kernel_services/analysis/filter/linear_filter.ml b/src/kernel_services/analysis/filter/linear_filter.ml index 20192fe0398..977c7043350 100644 --- a/src/kernel_services/analysis/filter/linear_filter.ml +++ b/src/kernel_services/analysis/filter/linear_filter.ml @@ -28,46 +28,47 @@ module Make (Field : Field.S) = struct module Linear = Linear.Space (Field) open Linear.Types + open Field.Types + open Linear - (* Invariant search fuel parameters. TODO: let the user change those. *) - let fuel = 50 - let coarse_refinement_fuel = 5 - let fine_refinement_fuel = 50 - let refinement_threshold = 200 - (* Find a first exponant such as the spectral radius is lower than one. *) - let rec first_exponant m exp fuel = - if Field.(Linear.Matrix.norm m <= one) then (exp, m) - else if fuel < 0 then raise (Invalid_argument "Divergeant filter") - else first_exponant Linear.Matrix.(m * m) (exp * 2) (fuel - 1) + let rec first_steps max steps matrix exponent = + let steps = (matrix, exponent) :: steps in + if Field.(Matrix.norm matrix < one) then + if exponent * 2 > max then Some steps + else first_steps max steps Matrix.(matrix * matrix) (exponent * 2) + else if exponent <= max then + first_steps max steps Matrix.(matrix * matrix) (exponent * 2) + else None - (* Refine the exponant to improve the precision. *) - let rec refine base m exp coarse fine = - if coarse < 0 || fine < 0 || exp > refinement_threshold - then exp, m - else if (exp * 2) > refinement_threshold - then refine base Linear.Matrix.(base * m) (exp + 1) coarse (fine - 1) - else refine base Linear.Matrix.(m * m) (exp * 2) (coarse - 1) fine + let rec refine max = function + | [] -> None + | [ (matrix, exponent) ] -> Some (Matrix.norm matrix, exponent) + | (matrix, exponent) :: (matrix', exponent') :: previous -> + let exponent'' = exponent + exponent' in + if exponent'' > max + then refine max ((matrix, exponent) :: previous) + else refine max ((Matrix.(matrix * matrix'), exponent'') :: previous) - (* Looking for an exponant n such as norm(A^n) ≤ 1, which implies that the - spectral radius of A is lower than 1. The exponant is refined to improve the - invariant's precision. *) - let find_exponant base = - let exponant, matrix = first_exponant base 1 fuel in - let coarse = coarse_refinement_fuel and fine = fine_refinement_fuel in - let exponant, matrix = refine base matrix exponant coarse fine in - (Linear.Matrix.norm matrix, exponant) + let find_exponent max_acceptable_exponent base = + let open Option.Operators in + let* steps = first_steps max_acceptable_exponent [] base 1 in + refine max_acceptable_exponent steps module Types = struct - type ('n, 'm) filter = Filter : ('n succ, 'm succ) data -> ('n, 'm) filter + + type ('n, 'm) filter = + | Filter : ('n succ, 'm succ) data -> ('n succ, 'm succ) filter + and ('n, 'm) data = { state : ('n, 'n) matrix ; input : ('n, 'm) matrix ; measure : 'm vector } + end open Types @@ -76,24 +77,26 @@ module Make (Field : Field.S) = struct let create state input measure = Filter { state ; input ; measure } - let pretty fmt (Filter f) = - let open Linear in + type ('n, 'm) formatter = Format.formatter -> ('n, 'm) filter -> unit + let pretty : type n m. (n, m) formatter = fun fmt (Filter f) -> Format.fprintf fmt "Filter:@.@." ; Format.fprintf fmt "- State :@.@. @[<v>%a@]@.@." Matrix.pretty f.state ; Format.fprintf fmt "- Input :@.@. @[<v>%a@]@.@." Matrix.pretty f.input - let sum order f g stop = - let rec compute (acc, res) i = - if i > 0 then compute (f acc, Field.(res + g acc)) (i - 1) else res - in compute (Linear.Matrix.id order, Field.zero) (stop - 1) - - let invariant (Filter f) = - let order, _ = Linear.Matrix.dimensions f.input in - let spectral, exponant = find_exponant f.state in - let power p = Linear.Matrix.(f.state * p) in - let norm p = Linear.Matrix.(p * f.input |> norm) in + let sum order p norm stop = + let ( + ) res acc = Field.(res + norm acc) in + let rec aux (m, r) i = if i >= 0 then aux (p m, r + m) (i - 1) else r in + aux (Matrix.id order, Field.zero) (stop - 1) + + type ('n, 'm) invariant = ('n, 'm) filter -> int -> scalar option + let invariant : type n m. (n, m) invariant = fun (Filter f) max -> + let open Option.Operators in + let order, _ = Matrix.dimensions f.input in + let+ spectral, exponant = find_exponent max f.state in + let power p = Matrix.(f.state * p) in + let norm p = Matrix.(p * f.input |> norm) in let sum = sum order power norm exponant in - let bound = Field.(Linear.Vector.norm f.measure * sum / (one - spectral)) in + let bound = Field.(Vector.norm f.measure * sum / (one - spectral)) in let order = Field.of_int (Nat.to_int order) in Field.(bound / order) diff --git a/src/kernel_services/analysis/filter/linear_filter.mli b/src/kernel_services/analysis/filter/linear_filter.mli index 9eb759f98ed..c7e2b8f25b9 100644 --- a/src/kernel_services/analysis/filter/linear_filter.mli +++ b/src/kernel_services/analysis/filter/linear_filter.mli @@ -28,15 +28,30 @@ module Make (Field : Field.S) : sig open Linear.Types open Field.Types + (* A value of type [(n, m) filter] describes a linear filter of order n (i.e + with n state variables) and with m inputs. *) module Types : sig type ('n, 'm) filter end open Types val create : ('n succ, 'n succ) matrix -> ('n succ, 'm succ) matrix -> - 'm succ vector -> ('n, 'm) filter + 'm succ vector -> ('n succ, 'm succ) filter val pretty : Format.formatter -> ('n, 'm) filter -> unit - val invariant : ('n, 'm) filter -> scalar + + (* Invariant computation. The computation of [invariant filter max] relies on + the search of an exponent such as the norm of the state matrix is strictly + lower than one. This search depth is bounded by [max]. If no exponent is + found before this limit is reached, the function returns None. If an + exponent [e] is found, the invariant computation complexity is bounded by + O(e * (n^3 + n^2 * m)) with [n] the filter's order and [m] its number of + inputs. Only the invariant's upper bound [λ] is returned, the filter's + invariant is thus bounded by ±λ. The only thing that limit the optimality + of this bound is [max], the initial search depth. However, for most simple + filters, a depth of 200 will gives an exact upper bound up to at least ten + digits, which is more than enough. Moreover, for those simple filters, the + computation is immediate, even when using rational numbers. *) + val invariant : ('n, 'm) filter -> int -> scalar option end diff --git a/src/kernel_services/analysis/filter/nat.ml b/src/kernel_services/analysis/filter/nat.ml index c85a25a5a47..3f9705c9aa3 100644 --- a/src/kernel_services/analysis/filter/nat.ml +++ b/src/kernel_services/analysis/filter/nat.ml @@ -37,11 +37,7 @@ let prev : type n. n succ nat -> n nat = fun n -> n - 1 let to_int : type n. n nat -> int = fun n -> n let of_int n = - let next (PositiveOrNull n) = PositiveOrNull (succ n) in - let rec aux acc n = if n <= 0 then acc else aux (next acc) (n - 1) in - aux (PositiveOrNull zero) n + if n < 0 then None else Some (PositiveOrNull n) let of_strictly_positive_int n = - let next (StrictlyPositive n) = StrictlyPositive (succ n) in - let rec aux acc n = if n <= 1 then acc else aux (next acc) (n - 1) in - aux (StrictlyPositive one) n + if n < 1 then None else Some (StrictlyPositive n) diff --git a/src/kernel_services/analysis/filter/nat.mli b/src/kernel_services/analysis/filter/nat.mli index 9942dfbee1a..d3d6cf83fea 100644 --- a/src/kernel_services/analysis/filter/nat.mli +++ b/src/kernel_services/analysis/filter/nat.mli @@ -41,8 +41,8 @@ val to_int : 'n nat -> int (* Returns a positive or null natural. If the given parameter is stricly negative then 0 is returned. This function complexity is O(n). *) -val of_int : int -> positive_or_null +val of_int : int -> positive_or_null option (* Returns a strictly positive natural. If the given parameter is less or equal than zero, then 1 is returned. This function complexity is O(n). *) -val of_strictly_positive_int : int -> strictly_positive +val of_strictly_positive_int : int -> strictly_positive option diff --git a/src/kernel_services/analysis/filter/parray.ml b/src/libraries/utils/parray.ml similarity index 100% rename from src/kernel_services/analysis/filter/parray.ml rename to src/libraries/utils/parray.ml diff --git a/src/kernel_services/analysis/filter/parray.mli b/src/libraries/utils/parray.mli similarity index 90% rename from src/kernel_services/analysis/filter/parray.mli rename to src/libraries/utils/parray.mli index 96df4db916f..4d28c548a5d 100644 --- a/src/kernel_services/analysis/filter/parray.mli +++ b/src/libraries/utils/parray.mli @@ -20,6 +20,10 @@ (* *) (**************************************************************************) +(* Persistent array, based on "A Persistent Union-Find Data Structure" by + Sylvain Conchon and Jean-Chistophe Filliâtre. For further details, see + https://www.lri.fr/~filliatr/ftp/publis/puf-wml07.pdf *) + module Types : sig type 'a array end -- GitLab