b1g3ar5
b1g3ar5

Reputation: 335

Using FSCheck generators

I have a function to generate doubles in a range:

let gen_doublein = 
    fun mx mn -> Arb.generate<float> |> Gen.suchThat ( (>) mx ) |> Gen.suchThat ( (<) mn )

and then a function to generate an array of 2 of these:

let gen_params:Gen<double array> = 
    gen { let! x = gen_doublein 0.0 20000.0
          let! y = gen_doublein 0.0 2000.0
          return [| x;y|] }

I put:

static member arb_params = Arb.fromGen  gen_params

in the Generator class and register it. All seems OK. To test that this is all OK I have:

let f2 (xs:double array) :double= exp (-2.0*xs.[0]) + xs.[1]*exp (-2.0*xs.[0])
let fcheck fn xs = fn xs > 0.0

then using an array generator 'arrayOfLength':

Check.Quick (Prop.forAll (arrayOfLength 2) (fcheck f2))

works as expected, however:

Check.Quick (Prop.forAll (Generators.arb_params) (fcheck f2))

just starts doing some calculation and never comes back. f# gurus please help.

Upvotes: 5

Views: 2477

Answers (3)

XperiAndri
XperiAndri

Reputation: 1208

Rewritten a sample from @b1g3ar5 this way

let mapRangeNormal (min : float<_>, max : float<_>) x =
    match x with
    | _ when Double.IsNaN x -> (min + max) / 2.0
    | _ when Double.IsPositiveInfinity x -> max
    | _ when Double.IsNegativeInfinity x -> min
    | _ -> min + (max - min) * (sin x + 1.0) / 2.0

let mapRangeUniform (min : float<_>, max : float<_>) x =
    match x with
    | _ when Double.IsNaN x -> (min + max) / 2.0
    | _ when Double.IsPositiveInfinity x -> max
    | _ when Double.IsNegativeInfinity x -> min
    | _ when x < 0.0 ->
           let newRange = max - min
           min - x * (newRange / Double.MaxValue) - newRange / 2.0
    | _ -> let newRange = max - min
           min + x * (newRange / Double.MaxValue) + newRange / 2.0

Upvotes: 2

b1g3ar5
b1g3ar5

Reputation: 21

The problem was the parameters were the wrong way round. Tomas's suggestion is a good one, and there are some helper functions to implement it.

// Another id function
let fd (d:double) = d
// Check that it is in bounds
let mn=1.0
let mx=5.0
let fdcheck d = (fd d <= mx) && (fd d >= mn)
// Run the check with the numbers generated within the bounds
Check.Quick (Prop.forAll (Arb.fromGen (Gen.map (fun x->
                                                match x with 
                                                | _ when Double.IsNaN x -> (mn+mx)/2.0
                                                | _ when x>  1e+17 ->mx
                                                | _ when x< -1e17 ->mn
                                                | _ -> mn + (mx-mn)*(sin x+1.0)/2.0
                                            ) Arb.generate<double>
                                    )
                       ) fdcheck
         )

Here I have a function which passes the test if the parameter is generated correctly. I'm not sure Tomas's idea with integers works because I think that a lot of small integers are generated and so the doubles don't explore the domain much - but maybe somebody who knows FSCheck might enlighten us.

Upvotes: 2

Tomas Petricek
Tomas Petricek

Reputation: 243051

I did not try this, but I think the problem is that the generator creates float values randomly and then checks whether they match the predicate you specified (the range). This means that it has to generate a large number of floats before it (randomly) generates one that matches.

It would be easier to generate values in a specified range by generating float values in a range [0 .. 1]
and then re-scaling them to match the range you need.

I'm not familiar with FsCheck enough, so I don't know if there is a generator for [0 .. 1] floating-point range, but you could start by generating integers and transforming them to floats:

let gen_doublein mx mn = gen {
   let! n = Arb.generate<int>
   let f = float n / float Int32.MaxValue 
   return mx + (f * (mn - mx)) }

EDIT I see that you solved the problem already. I think the solution I posted might still be relevant for smaller ranges (where the random generator does not produce enough matching values soon enough).

Upvotes: 4

Related Questions