0 | module Hedgehog.Internal.Range
  1 |
  2 | import Data.Bounded
  3 | import Data.Fin
  4 | import Data.Maybe
  5 | import Decidable.Equality
  6 | import Derive.Prelude
  7 | import Derive.Pretty
  8 |
  9 | import Hedgehog.Internal.Util
 10 |
 11 | %hide Prelude.Range
 12 |
 13 | %language ElabReflection
 14 | %default total
 15 |
 16 | public export
 17 | MaxSizeNat : Nat
 18 | MaxSizeNat = 100
 19 |
 20 | ||| A wrapper of a natural number plus an erased proof that
 21 | ||| the value is within its bounds.
 22 | |||
 23 | ||| Unlike the original Hedgehog, we allow values in the range [0,100]
 24 | ||| treating size as a proper percentage.
 25 | public export
 26 | record Size where
 27 |   constructor MkSize
 28 |   size     : Nat
 29 |   0 sizeOk : size <= MaxSizeNat = True
 30 |
 31 | %runElab derive "Size" [Show,Eq,Ord]
 32 |
 33 | export
 34 | Pretty Size where
 35 |   prettyPrec _ (MkSize n _) = line $ show n
 36 |
 37 | public export
 38 | mkSize : (n : Nat) -> {auto 0 ok : n <= MaxSizeNat = True} -> Size
 39 | mkSize n = MkSize n ok
 40 |
 41 | public export
 42 | minSize : Size
 43 | minSize = mkSize 0
 44 |
 45 | public export
 46 | maxSize : Size
 47 | maxSize = mkSize MaxSizeNat
 48 |
 49 | export
 50 | mkSizeMay : Nat -> Maybe Size
 51 | mkSizeMay n with (decEq (n <= MaxSizeNat) True)
 52 |   mkSizeMay n | Yes _ = Just $ mkSize n
 53 |   mkSizeMay n | No  c = Nothing
 54 |
 55 | export
 56 | mkSizeOrZero : Nat -> Size
 57 | mkSizeOrZero = fromMaybe minSize . mkSizeMay
 58 |
 59 | export
 60 | mkSizeOrMax : Nat -> Size
 61 | mkSizeOrMax = fromMaybe maxSize . mkSizeMay
 62 |
 63 | public export
 64 | resize : (Nat -> Nat) -> Size -> Size
 65 | resize f s = mkSizeOrMax (f s.size)
 66 |
 67 | --------------------------------------------------------------------------------
 68 | --          Interface Implementations
 69 | --------------------------------------------------------------------------------
 70 |
 71 | export
 72 | MinBound Size where
 73 |   minBound = minSize
 74 |
 75 | export
 76 | MaxBound Size where
 77 |   maxBound = maxSize
 78 |
 79 | export
 80 | Num Size where
 81 |   a + b = mkSizeOrMax $ size a + size b
 82 |   a * b = mkSizeOrMax $ size a * size b
 83 |   fromInteger = mkSizeOrMax . fromInteger
 84 |
 85 | public export
 86 | ToInteger Size where toInteger = toInteger . size
 87 |
 88 | --------------------------------------------------------------------------------
 89 | --          Range
 90 | --------------------------------------------------------------------------------
 91 |
 92 | public export
 93 | record Range a where
 94 |   constructor MkRange
 95 |   origin  : a
 96 |   bounds' : Size -> (a,a)
 97 |
 98 | export
 99 | bounds : Size -> Range a -> (a,a)
100 | bounds s r = r.bounds' s
101 |
102 | ||| Get the lower bound of a range for the given size.
103 | export
104 | lowerBound : Ord a => Size -> Range a -> a
105 | lowerBound sz = uncurry min . bounds sz
106 |
107 | ||| Get the lower bound of a range for the given size.
108 | export
109 | upperBound : Ord a => Size -> Range a -> a
110 | upperBound sz = uncurry max . bounds sz
111 |
112 | export
113 | Functor Range where
114 |   map f (MkRange origin bounds') =
115 |     MkRange
116 |       (f origin)
117 |       (\si => let (x,y) := bounds' si in (f x, f y))
118 |
119 | --------------------------------------------------------------------------------
120 | --          Constant Ranges
121 | --------------------------------------------------------------------------------
122 |
123 | ||| Construct a range which represents a constant single value.
124 | |||
125 | ||| >>> bounds x $ singleton 5
126 | ||| (5,5)
127 | |||
128 | ||| >>> origin $ singleton 5
129 | ||| 5
130 | export
131 | singleton : a -> Range a
132 | singleton x = MkRange x $ \_ => (x, x)
133 |
134 | ||| Construct a range which is unaffected by the size parameter with a origin
135 | ||| point which may differ from the bounds.
136 | |||
137 | ||| A range from @-10@ to @10@, with the origin at @0@:
138 | |||
139 | ||| >>> bounds x $ constantFrom 0 (-10) 10
140 | ||| (-10,10)
141 | |||
142 | ||| >>> origin $ constantFrom 0 (-10) 10
143 | ||| 0
144 | |||
145 | ||| A range from @1970@ to @2100@, with the origin at @2000@:
146 | |||
147 | ||| >>> bounds x $ constantFrom 2000 1970 2100
148 | ||| (1970,2100)
149 | |||
150 | ||| >>> origin $ constantFrom 2000 1970 2100
151 | ||| 2000
152 | export
153 | constantFrom : (origin,lower,upper : a) -> Range a
154 | constantFrom o l u = MkRange o $ \_ => (l, u)
155 |
156 | ||| Construct a range which is unaffected by the size parameter.
157 | |||
158 | ||| A range from @0@ to @10@, with the origin at @0@:
159 | |||
160 | ||| >>> bounds x $ constant 0 10
161 | ||| (0,10)
162 | |||
163 | ||| >>> origin $ constant 0 10
164 | ||| 0
165 | |||
166 | export
167 | constant : a -> a -> Range a
168 | constant x y = constantFrom x x y
169 |
170 | ||| Construct a range which is unaffected by the size parameter using the full
171 | ||| range of a data type.
172 | |||
173 | ||| A range from @-128@ to @127@, with the origin at @0@:
174 | |||
175 | ||| >>> bounds x (constantBounded :: Range Int8)
176 | ||| (-128,127)
177 | |||
178 | ||| >>> origin (constantBounded :: Range Int8)
179 | ||| 0
180 | export
181 | constantBounded : (MinBound a, MaxBound a) => Num a => Range a
182 | constantBounded = constantFrom 0 minBound maxBound
183 |
184 | --------------------------------------------------------------------------------
185 | --          Linear Ranges
186 | --------------------------------------------------------------------------------
187 |
188 | ||| Truncate a value so it stays within some range.
189 | |||
190 | ||| >>> clamp 5 10 15
191 | ||| 10
192 | |||
193 | ||| >>> clamp 5 10 0
194 | ||| 5
195 | export
196 | clamp : Ord a => (lower,upper,val : a) -> a
197 | clamp l u v =
198 |   if l > u
199 |      then min l (max u v)
200 |      else min u (max l v)
201 |
202 | ||| Scales an integral value linearly with the size parameter.
203 | export
204 | scaleLinear : ToInteger a => Size -> (origin,bound : a) -> a
205 | scaleLinear sz o0 b0 =
206 |   let o    := toInteger o0
207 |       b    := toInteger b0
208 |       rng  := b - o
209 |       diff := (rng * toInteger sz) `safeDiv` 100
210 |    in fromInteger $ o + diff
211 |
212 | ||| Scales a fractional value linearly with the size parameter.
213 | export
214 | scaleLinearFrac : Fractional a => Neg a => Size -> (origin,bound : a) -> a
215 | scaleLinearFrac sz o b =
216 |   let diff = (b - o) * (fromIntegral sz / 100)
217 |    in o + diff
218 |
219 | export %inline
220 | scaled :
221 |      {auto _ : Ord a}
222 |   -> (scale : Size -> (origin,bound : a) -> a)
223 |   -> (origin,lower,upper : a)
224 |   -> Range a
225 | scaled f o l u =
226 |   MkRange o $ \sz =>
227 |     let x_sized := clamp l u $ f sz o l
228 |         y_sized := clamp l u $ f sz o u
229 |      in (x_sized, y_sized)
230 |
231 | ||| Construct a range which scales the bounds relative to the size parameter.
232 | |||
233 | ||| >>> bounds 0 $ linearFrom 0 (-10) 10
234 | ||| (0,0)
235 | |||
236 | ||| >>> bounds 50 $ linearFrom 0 (-10) 20
237 | ||| (-5,10)
238 | |||
239 | ||| >>> bounds 100 $ linearFrom 0 (-10) 20
240 | ||| (-10,20)
241 | export
242 | linearFrom : Ord a => ToInteger a => (origin,lower,upper : a) -> Range a
243 | linearFrom = scaled scaleLinear
244 |
245 | export
246 | linear : Ord a => ToInteger a => (origin,upper : a) -> Range a
247 | linear origin upper = linearFrom origin origin upper
248 |
249 | export
250 | linearFin : (n : Nat) -> Range (Fin $ S n)
251 | linearFin n = map toFin $ linearFrom 0 0 (natToInteger n)
252 |
253 |   where
254 |     toFin : Integer -> Fin (S n)
255 |     toFin k = fromMaybe FZ (integerToFin k (S n))
256 |
257 | ||| Construct a range which scales the bounds relative to the size parameter.
258 | |||
259 | ||| This works the same as 'linearFrom', but for fractional values.
260 | export
261 | linearFracFrom :
262 |      {auto _ : Ord a}
263 |   -> {auto _ : Fractional a}
264 |   -> {auto _ : Neg a}
265 |   -> (origin,lower,uppder : a) -> Range a
266 | linearFracFrom = scaled scaleLinearFrac
267 |
268 | ||| Construct a range which is scaled relative to the size parameter and uses
269 | ||| the full range of a data type.
270 | |||
271 | ||| >>> bounds 0 (linearBounded :: Range Int8)
272 | ||| (0,0)
273 | |||
274 | ||| >>> bounds 50 (linearBounded :: Range Int8)
275 | ||| (-64,64)
276 | |||
277 | ||| >>> bounds 99 (linearBounded :: Range Int8)
278 | |||   (-128,127)
279 | export
280 | linearBounded :  (MinBound a, MaxBound a) => Ord a => ToInteger a => Range a
281 | linearBounded = linearFrom minBound minBound maxBound
282 |
283 | --------------------------------------------------------------------------------
284 | --          Exponential Ranges
285 | --------------------------------------------------------------------------------
286 |
287 | EulerMinus1 : Double
288 | EulerMinus1 = euler - 1
289 |
290 | ||| Scale a floating-point number exponentially with the size parameter.
291 | |||
292 | ||| Note : This scales the difference between the two values exponentially.
293 | export
294 | scaleExponentialDouble : Size -> (origin,bound : Double) -> Double
295 | scaleExponentialDouble sz o b =
296 |   let e     := fromIntegral sz / 100.0
297 |       delta := b - o
298 |       diff  := pow (abs delta + 1) e - 1
299 |    in o + diff * signum delta
300 |
301 | ||| Scale an integral exponentially with the size parameter.
302 | export
303 | scaleExponential : ToInteger a => Size -> (origin,bound : a) -> a
304 | scaleExponential sz o b =
305 |   round (scaleExponentialDouble sz (fromIntegral o) (fromIntegral b))
306 |
307 | export
308 | exponentialDoubleFrom : (origin,lower,upper : Double) -> Range Double
309 | exponentialDoubleFrom = scaled scaleExponentialDouble
310 |
311 | export
312 | exponentialDouble : (lower,upper : Double) -> Range Double
313 | exponentialDouble l u = exponentialDoubleFrom l l u
314 |
315 | export
316 | exponentialFrom : Ord a => ToInteger a => (origin,lower,upper : a) -> Range a
317 | exponentialFrom = scaled scaleExponential
318 |
319 | export
320 | exponential : Ord a => ToInteger a => (lower,upper : a) -> Range a
321 | exponential l u = exponentialFrom l l u
322 |
323 | export
324 | exponentialFin : (n : Nat) -> Range (Fin $ S n)
325 | exponentialFin n = map toFin $ exponentialFrom 0 0 (natToInteger n)
326 |
327 |   where
328 |     toFin : Integer -> Fin (S n)
329 |     toFin k = fromMaybe FZ (integerToFin k (S n))
330 |
331 | --------------------------------------------------------------------------------
332 | --          Tests and Proofs
333 | --------------------------------------------------------------------------------
334 |
335 | singletonOriginId : origin (singleton x) = x
336 | singletonOriginId = Refl
337 |
338 | singletonBoundsId : bounds s (singleton x) = (x,x)
339 | singletonBoundsId = Refl
340 |
341 | constantFromOrigin : origin (constantFrom o l u) = o
342 | constantFromOrigin = Refl
343 |
344 | constantFromBounds : bounds s (constantFrom o l u) = (l,u)
345 | constantFromBounds = Refl
346 |
347 | constantOrigin : origin (constant o u) = o
348 | constantOrigin = Refl
349 |
350 | constantBounds : bounds s (constant o u) = (o,u)
351 | constantBounds = Refl
352 |
353 | One : Int
354 | One = 1
355 |
356 | Zero : Int
357 | Zero = 0
358 |
359 | clamp1_100_0 : clamp One 100 0 = One
360 | clamp1_100_0 = Refl
361 |
362 | clamp100_1_0 : clamp 100 One 0 = One
363 | clamp100_1_0 = Refl
364 |
365 | clamp1_100_150 : clamp One 100 150 = 100
366 | clamp1_100_150 = Refl
367 |
368 | clamp100_1_150 : clamp 100 One 150 = 100
369 | clamp100_1_150 = Refl
370 |
371 | clamp1_100_50 : clamp One 100 50 = 50
372 | clamp1_100_50 = Refl
373 |
374 | clamp100_1_50 : clamp 100 One 50 = 50
375 | clamp100_1_50 = Refl
376 |