1 /**
2 Authors: [Ilya Yaroshenko](http://9il.github.io)
3 
4 Copyright: © 2014-2015 [Ilya Yaroshenko](http://9il.github.io)
5 
6 License: MIT
7 */
8 module atmosphere.estimate.generalized_inverse_gaussian;
9 
10 import std.traits;
11 import std.typecons;
12 import std.math;
13 
14 import atmosphere.statistic: GeneralizedInverseGaussinStatistic, GeneralizedInverseGaussinFixedLambdaStatistic;
15 
16 /++
17 Estimates parameters of the proper generalized inverse Gaussian distribution.
18 
19 Params:
20 	stat = GIG statistica.
21 	sample = obeservation.
22 	weights = sample weights.
23 	relTolerance = Relative tolerance.
24 	absTolerance = Absolute tolerance.
25 
26 Preconditions: $(D ax) and $(D bx) shall be finite reals. $(BR)
27 	$(D relTolerance) shall be normal positive real. $(BR)
28 	$(D absTolerance) shall be normal positive real no less then $(D T.epsilon*2).
29 
30 References: "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973)
31 
32 See_Also: `atmosphere.params`
33 +/
34 Tuple!(T, "lambda", T, "eta", T, "omega")
35 properGeneralizedInverseGaussianEstimate(T)
36 	(
37 		in T[] sample, 
38 		in T relTolerance = sqrt(T.epsilon),
39 		in T absTolerance = sqrt(T.epsilon),
40 	)
41 	if(isFloatingPoint!T)
42 {
43 	return properGeneralizedInverseGaussianEstimate(GeneralizedInverseGaussinStatistic!T(sample), relTolerance, absTolerance);
44 }
45 
46 ///
47 unittest
48 {
49 	import std.range;
50 	import std.random;
51 	import atmosphere.random;
52 	import atmosphere.likelihood;
53 	auto length = 1000;
54 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
55 	auto rng = Random(1234);
56 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
57 	auto params1 = properGeneralizedInverseGaussianEstimate(sample);
58 	auto params2 = properGeneralizedInverseGaussianFixedLambdaEstimate!double(lambda, sample);
59 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega, sample);
60 	auto lh1 = properGeneralizedInverseGaussianLikelihood(params1.lambda, params1.eta, params1.omega, sample);
61 	auto lh2 = properGeneralizedInverseGaussianLikelihood(lambda, params2.eta, params2.omega, sample);
62 	assert(lh0 <= lh1);
63 	assert(lh0 <= lh2);
64 	assert(lh2 <= lh1);
65 }
66 
67 ///ditto
68 Tuple!(T, "lambda", T, "eta", T, "omega")
69 properGeneralizedInverseGaussianEstimate(T)
70 	(
71 		in T[] sample, 
72 		in T[] weights,
73 		in T relTolerance = sqrt(T.epsilon),
74 		in T absTolerance = sqrt(T.epsilon),
75 	)
76 	if(isFloatingPoint!T)
77 {
78 	return properGeneralizedInverseGaussianEstimate(GeneralizedInverseGaussinStatistic!T(sample, weights), relTolerance, absTolerance);
79 }
80 
81 ///
82 unittest
83 {
84 	import std.range;
85 	import std.random;
86 	import atmosphere.random;
87 	import atmosphere.likelihood;
88 	auto length = 1000;
89 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
90 	auto rng = Random(1234);
91 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
92 	auto weights = iota(1.0, length + 1.0).array;
93 	auto params1 = properGeneralizedInverseGaussianEstimate!double(sample, weights);
94 	auto params2 = properGeneralizedInverseGaussianFixedLambdaEstimate!double(lambda, sample, weights);
95 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega, sample, weights);
96 	auto lh1 = properGeneralizedInverseGaussianLikelihood(params1.lambda, params1.eta, params1.omega, sample, weights);
97 	auto lh2 = properGeneralizedInverseGaussianLikelihood(lambda, params2.eta, params2.omega, sample, weights);
98 	assert(lh0 <= lh1);
99 	assert(lh0 <= lh2);
100 	assert(lh2 <= lh1);
101 }
102 
103 
104 ///ditto
105 Tuple!(T, "lambda", T, "eta", T, "omega")
106 properGeneralizedInverseGaussianEstimate(T)
107 	(
108 		in GeneralizedInverseGaussinStatistic!T stat,
109 		in T relTolerance = sqrt(T.epsilon),
110 		in T absTolerance = sqrt(T.epsilon),
111 	)
112 	if(isFloatingPoint!T)
113 body {
114 	import atmosphere.likelihood.generalized_inverse_gaussian;
115 	immutable prod = stat.mean * stat.meani;
116 	if(!isFinite(prod) || prod <= 1)
117 		return typeof(return)(T.nan, T.nan, T.nan);
118 	immutable u = prod / (prod - 1);
119 	assert(isFinite(u));
120 	immutable statFL = cast(GeneralizedInverseGaussinFixedLambdaStatistic!T) stat;
121 	T f(T lambda) {
122 		immutable params = properGeneralizedInverseGaussianFixedLambdaEstimate!T(lambda, statFL);
123 		with(params)
124 		{
125 			import std.conv;
126 			//assert(eta >= T.min_normal || eta, eta.to!string);
127 			assert(omega >= T.min_normal, omega.to!string);
128 			return eta.isNormal ? 
129 				-properGeneralizedInverseGaussianLikelihood!T(lambda, eta, omega, stat)
130 				: -T.infinity;
131 		}
132 	}
133 	import atmosphere.math: findLocalMin;
134 	immutable flm = findLocalMin!T(&f, -u, u, relTolerance, absTolerance);
135 	immutable lambda = flm.x;
136 	immutable params = properGeneralizedInverseGaussianFixedLambdaEstimate!T(lambda, statFL);
137 	with(params) return typeof(return)(lambda, eta, omega);
138 }
139 
140 /++
141 Estimates parameters of the generalized inverse Gaussian distribution.
142 
143 Params:
144 	stat = GIG statistica.
145 	sample = obeservation.
146 	weights = sample weights.
147 	relTolerance = Relative tolerance.
148 	absTolerance = Absolute tolerance.
149 
150 Preconditions: $(D ax) and $(D bx) shall be finite reals. $(BR)
151 	$(D relTolerance) shall be normal positive real. $(BR)
152 	$(D absTolerance) shall be normal positive real no less then $(D T.epsilon*2).
153 
154 References: "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973)
155 
156 See_Also: `atmosphere.params`
157 +/
158 Tuple!(T, "lambda", T, "chi", T, "psi")
159 generalizedInverseGaussianEstimate(T)
160 	(
161 		in T[] sample,
162 		in T relTolerance = sqrt(T.epsilon),
163 		in T absTolerance = sqrt(T.epsilon),
164 	)
165 	if(isFloatingPoint!T)
166 {
167 	return generalizedInverseGaussianEstimate(GeneralizedInverseGaussinStatistic!T(sample), relTolerance, absTolerance);
168 }
169 
170 ///
171 unittest
172 {
173 	import std.range;
174 	import std.random;
175 	import atmosphere.random;
176 	import atmosphere.likelihood;
177 	import atmosphere.params;
178 	auto length = 1000;
179 	auto lambda = -2.0, chi = 1.4, psi = 2.3;
180 	auto rng = Random(1234);
181 	auto sample = new GeneralizedInverseGaussianRNG!double(rng, lambda, chi, psi).take(length).array;
182 	auto params1 = generalizedInverseGaussianEstimate(sample);
183 	auto params2 = generalizedInverseGaussianFixedLambdaEstimate!double(lambda, sample);
184 	auto p0 = GIGChiPsi!double(chi, psi);
185 	auto p1 = GIGChiPsi!double(params1.chi, params1.psi);
186 	auto p2 = GIGChiPsi!double(params2.chi, params2.psi);
187 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, p0.eta, p0.omega, sample);
188 	auto lh1 = properGeneralizedInverseGaussianLikelihood(params1.lambda, p1.eta, p1.omega, sample);
189 	auto lh2 = properGeneralizedInverseGaussianLikelihood(lambda, p2.eta, p2.omega, sample);
190 	assert(lh0 <= lh1);
191 	assert(lh0 <= lh2);
192 	assert(lh2 <= lh1);
193 }
194 
195 
196 ///ditto
197 Tuple!(T, "lambda", T, "chi", T, "psi")
198 generalizedInverseGaussianEstimate(T)
199 	(
200 		in T[] sample,
201 		in T[] weights,
202 		in T relTolerance = sqrt(T.epsilon),
203 		in T absTolerance = sqrt(T.epsilon),
204 	)
205 	if(isFloatingPoint!T)
206 {
207 	return generalizedInverseGaussianEstimate(GeneralizedInverseGaussinStatistic!T(sample, weights), relTolerance, absTolerance);
208 }
209 
210 ///
211 unittest
212 {
213 	import std.range;
214 	import std.random;
215 	import atmosphere.random;
216 	import atmosphere.likelihood;
217 	import atmosphere.params;
218 	auto length = 1000;
219 	auto lambda = -2.0, chi = 1.4, psi = 2.3;
220 	auto rng = Random(1234);
221 	auto sample = new GeneralizedInverseGaussianRNG!double(rng, lambda, chi, psi).take(length).array;
222 	auto weights = iota(1.0, length + 1.0).array;
223 	auto params1 = generalizedInverseGaussianEstimate!double(sample, weights);
224 	auto params2 = generalizedInverseGaussianFixedLambdaEstimate!double(lambda, sample, weights);
225 	auto p0 = GIGChiPsi!double(chi, psi);
226 	auto p1 = GIGChiPsi!double(params1.chi, params1.psi);
227 	auto p2 = GIGChiPsi!double(params2.chi, params2.psi);
228 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, p0.eta, p0.omega, sample, weights);
229 	auto lh1 = properGeneralizedInverseGaussianLikelihood(params1.lambda, p1.eta, p1.omega, sample, weights);
230 	auto lh2 = properGeneralizedInverseGaussianLikelihood(lambda, p2.eta, p2.omega, sample, weights);
231 	assert(lh0 <= lh1);
232 	assert(lh0 <= lh2);
233 	assert(lh2 <= lh1);
234 }
235 
236 ///ditto
237 Tuple!(T, "lambda", T, "chi", T, "psi")
238 generalizedInverseGaussianEstimate(T)
239 	(
240 		in GeneralizedInverseGaussinStatistic!T stat, 		
241 		in T relTolerance = sqrt(T.epsilon),
242 		in T absTolerance = sqrt(T.epsilon),
243 	)
244 	if(isFloatingPoint!T)
245 body {
246 	import atmosphere.params : GIGEtaOmega;
247 	import atmosphere.estimate.gamma;
248 	import atmosphere.estimate.inverse_gamma;
249 	import atmosphere.likelihood.gamma;
250 	import atmosphere.likelihood.inverse_gamma;
251 	import atmosphere.likelihood.generalized_inverse_gaussian;
252 	import atmosphere.statistic: GammaStatistic, InverseGammaStatistic;
253 	
254 	immutable prod = stat.mean * stat.meani;
255 	if(!isFinite(prod) || prod <= 1)
256 		return typeof(return)(T.nan, T.nan, T.nan);
257 	immutable u = prod / (prod - 1);
258 	assert(isFinite(u));
259 	
260 	immutable gammaStat = cast(GammaStatistic!T)stat;
261 	immutable inverseGammaStat = cast(InverseGammaStatistic!T)stat;
262 	
263 	immutable gammaParams = gammaEstimate!T(gammaStat);
264 	immutable inverseGammaParams = inverseGammaEstimate!T(inverseGammaStat);
265 	immutable properParams = properGeneralizedInverseGaussianEstimate!T(stat, relTolerance, absTolerance);
266 	import std.stdio;
267 	
268 	T gammaLikelihood = gammaLikelihood!T(gammaParams.shape, gammaParams.scale, gammaStat);
269 	T inverseGammaLikelihood = inverseGammaLikelihood!T(inverseGammaParams.shape, inverseGammaParams.scale, inverseGammaStat);
270 
271 	T properLikelihood = properParams.eta >= T.min_normal && properParams.omega >= T.min_normal ? 
272 		properGeneralizedInverseGaussianLikelihood!T(properParams.lambda, properParams.eta, properParams.omega,  stat)
273 		: 0;
274 	
275 	if(!(gammaLikelihood > -T.infinity))
276 		gammaLikelihood = -T.infinity;
277 	if(!(inverseGammaLikelihood > -T.infinity))
278 		inverseGammaLikelihood = -T.infinity;
279 	if(!(properLikelihood > -T.infinity))
280 		properLikelihood = -T.infinity;
281 	if(properLikelihood > gammaLikelihood && properLikelihood > inverseGammaLikelihood)
282 		with(GIGEtaOmega!T(properParams.eta, properParams.omega)) return typeof(return)(properParams.lambda, chi, psi);
283 	if(gammaLikelihood > inverseGammaLikelihood)
284 		with(       gammaParams) return typeof(return)(shape,         0, 2 / scale);
285 	if(gammaLikelihood < inverseGammaLikelihood)
286 		with(inverseGammaParams) return typeof(return)(-shape, scale / 2,         0);
287 	return typeof(return).init;
288 }
289 
290 
291 /++
292 Estimates parameters of the proper generalized inverse Gaussian distribution for fixed `lambda`.
293 
294 See_Also: `atmosphere.params`
295 +/
296 Tuple!(T, "eta", T, "omega")
297 properGeneralizedInverseGaussianFixedLambdaEstimate(T)(in T lambda, in T[] sample)
298 	if(isFloatingPoint!T)
299 {
300 	return properGeneralizedInverseGaussianFixedLambdaEstimate(lambda, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample));
301 }
302 
303 ///
304 unittest
305 {
306 	import std.range;
307 	import std.random;
308 	import atmosphere.random;
309 	import atmosphere.likelihood;
310 	auto length = 1000;
311 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
312 	auto rng = Random(1234);
313 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
314 	auto params = properGeneralizedInverseGaussianFixedLambdaEstimate!double(lambda, sample);
315 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega, sample);
316 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, params.eta, params.omega, sample);
317 	assert(lh0 <= lh1);
318 }
319 
320 
321 ///ditto
322 Tuple!(T, "eta", T, "omega")
323 properGeneralizedInverseGaussianFixedLambdaEstimate(T)(in T lambda, in T[] sample, in T[] weights)
324 	if(isFloatingPoint!T)
325 {
326 	return properGeneralizedInverseGaussianFixedLambdaEstimate(lambda, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample, weights));
327 }
328 
329 ///
330 unittest
331 {
332 	import std.range;
333 	import std.random;
334 	import atmosphere.random;
335 	import atmosphere.likelihood;
336 	auto length = 1000;
337 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
338 	auto rng = Random(1234);
339 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
340 	auto weights = iota(1.0, length + 1.0).array;
341 	auto params = properGeneralizedInverseGaussianFixedLambdaEstimate!double(lambda, sample, weights);
342 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega, sample, weights);
343 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, params.eta, params.omega, sample, weights);
344 	assert(lh0 <= lh1);
345 }
346 
347 ///ditto
348 Tuple!(T, "eta", T, "omega")
349 properGeneralizedInverseGaussianFixedLambdaEstimate(T)
350 	(in T lambda, in GeneralizedInverseGaussinFixedLambdaStatistic!T stat)
351 	if(isFloatingPoint!T)
352 in {
353 	assert(isFinite(lambda));
354 }
355 body {
356 	import std.numeric: findRoot;
357 	import atmosphere.math;
358 	immutable prod = stat.mean * stat.meani;
359 	if(!isFinite(prod) || prod <= 1)
360 		return typeof(return)(T.nan, T.nan);
361 	immutable u = prod / (prod - 1);
362 	assert(isFinite(u));
363 	immutable alambda = fabs(lambda);
364 	if(alambda >= u)
365 	{
366 		return typeof(return)(alambda > 0 ? 0 : T.infinity, 0);
367 	}
368 	
369 	T f(T omega) //@safe pure nothrow @nogc 
370 	out(r) {
371 		assert(isFinite(r));
372 	}
373 	body {
374 		assert(!isNaN(omega));
375 		assert(omega >= 0);
376 		assert(omega < T.infinity);
377 		auto r = besselKD(alambda, omega) - prod;
378 		return r == T.infinity ? T.max : r;
379 	}
380 	immutable a = T.min_normal;
381 	immutable b = T.max;
382     T fa = f(a);
383     if(fa == T.infinity)
384     	fa = T.max;
385     T omega = void;
386     if(fa <= 0)
387         omega = a;
388     else
389     {
390 	    immutable fb = f(b);
391 	    if(fb >= 0)
392 	        omega = b;
393 	    else
394 	    {
395 		    immutable r = findRoot(&f, a, b, fa, fb, (T a, T b) => false);
396 		    // Return the first value if it is smaller or NaN
397 		    omega = !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1];	    	
398 	    }
399 
400     }
401     
402 	assert(omega >= T.min_normal);
403 	immutable eta = sqrt(stat.mean / stat.meani) / besselKRM(lambda, omega);
404 	auto ret = typeof(return)(eta, omega);
405 	return ret;
406 }
407 
408 
409 /++
410 Estimates parameters of the generalized inverse Gaussian distribution for fixed `lambda`.
411 
412 See_Also: `atmosphere.params`
413 +/
414 Tuple!(T, "chi", T, "psi")
415 generalizedInverseGaussianFixedLambdaEstimate(T)(in T lambda, in T[] sample)
416 	if(isFloatingPoint!T)
417 {
418 	return generalizedInverseGaussianFixedLambdaEstimate(lambda, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample));
419 }
420 
421 ///
422 unittest
423 {
424 	import std.range;
425 	import std.random;
426 	import atmosphere.random;
427 	import atmosphere.likelihood;
428 	import atmosphere.params;
429 	auto length = 1000;
430 	auto lambda = -2.0, chi = 1.4, psi = 2.3;
431 	auto rng = Random(1234);
432 	auto sample = new GeneralizedInverseGaussianRNG!double(rng, lambda, chi, psi).take(length).array;
433 	auto params = generalizedInverseGaussianFixedLambdaEstimate!double(lambda, sample);
434 	auto p0 = GIGChiPsi!double(chi, psi);
435 	auto p1 = GIGChiPsi!double(params.chi, params.psi);
436 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, p0.chi, p0.psi, sample);
437 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, p1.chi, p1.psi, sample);
438 	assert(lh0 <= lh1);
439 }
440 
441 
442 ///ditto
443 Tuple!(T, "chi", T, "psi")
444 generalizedInverseGaussianFixedLambdaEstimate(T)(in T lambda, in T[] sample, in T[] weights)
445 	if(isFloatingPoint!T)
446 {
447 	return generalizedInverseGaussianFixedLambdaEstimate(lambda, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample, weights));
448 }
449 
450 ///
451 unittest
452 {
453 	import std.range;
454 	import std.random;
455 	import atmosphere.random;
456 	import atmosphere.likelihood;
457 	import atmosphere.params;
458 	auto length = 1000;
459 	auto lambda = -2.0, chi = 1.4, psi = 2.3;
460 	auto rng = Random(1234);
461 	auto sample = new GeneralizedInverseGaussianRNG!double(rng, lambda, chi, psi).take(length).array;
462 	auto weights = iota(1.0, length + 1.0).array;
463 	auto params = generalizedInverseGaussianFixedLambdaEstimate!double(lambda, sample, weights);
464 	auto p0 = GIGChiPsi!double(chi, psi);
465 	auto p1 = GIGChiPsi!double(params.chi, params.psi);
466 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, p0.chi, p0.psi, sample, weights);
467 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, p1.chi, p1.psi, sample, weights);
468 	assert(lh0 <= lh1);
469 }
470 
471 ///ditto
472 Tuple!(T, "chi", T, "psi")
473 generalizedInverseGaussianFixedLambdaEstimate(T)
474 	(in T lambda, in GeneralizedInverseGaussinFixedLambdaStatistic!T stat)
475 	if(isFloatingPoint!T)
476 in {
477 	assert(isNormal(lambda));
478 }
479 body {
480 	import atmosphere.params : GIGEtaOmega;
481 	immutable prod = stat.mean * stat.meani;
482 	if(!isFinite(prod) || prod <= 1)
483 		return typeof(return)(T.nan, T.nan);
484 	immutable u = prod / (prod - 1);
485 	assert(isFinite(u));
486 	immutable alambda = fabs(lambda);
487 	if(alambda >= u)
488 		return lambda > 0 ? typeof(return)(0, 2 * alambda / stat.mean) : typeof(return)(2 * alambda / stat.meani, 0);
489 	immutable properParams = properGeneralizedInverseGaussianFixedLambdaEstimate!T(lambda, stat);
490 	with(GIGEtaOmega!T(properParams.eta, properParams.omega)) return typeof(return)(chi, psi);
491 }
492 
493 
494 /++
495 Estimates parameter `omega` of the proper generalized inverse Gaussian distribution for fixed `lambda` and `eta`.
496 
497 See_Also: `atmosphere.params`
498 +/
499 T properGeneralizedInverseGaussianFixedLambdaEtaEstimate(T)(in T lambda, in T eta, in T[] sample)
500 	if(isFloatingPoint!T)
501 {
502 	return properGeneralizedInverseGaussianFixedLambdaEtaEstimate(lambda, eta, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample));
503 }
504 
505 ///
506 unittest
507 {
508 	import std.range;
509 	import std.random;
510 	import atmosphere.random;
511 	import atmosphere.likelihood;
512 	auto length = 1000;
513 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
514 	auto rng = Random(1234);
515 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
516 	auto omega1 = properGeneralizedInverseGaussianFixedLambdaEtaEstimate!double(lambda, eta, sample);
517 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega , sample);
518 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega1, sample);
519 	assert(lh0 <= lh1);
520 }
521 
522 ///ditto
523 T properGeneralizedInverseGaussianFixedLambdaEtaEstimate(T)(in T lambda, in T eta, in T[] sample, in T[] weights)
524 	if(isFloatingPoint!T)
525 {
526 	return properGeneralizedInverseGaussianFixedLambdaEtaEstimate(lambda, eta, GeneralizedInverseGaussinFixedLambdaStatistic!T(sample, weights));
527 }
528 
529 ///
530 unittest
531 {
532 	import std.range;
533 	import std.random;
534 	import atmosphere.random;
535 	import atmosphere.likelihood;
536 	auto length = 1000;
537 	auto lambda = -2.0, eta = 1.4, omega = 2.3;
538 	auto rng = Random(1234);
539 	auto sample = ProperGeneralizedInverseGaussianSRNG!double(rng, lambda, eta, omega).take(length).array;
540 	auto weights = iota(1.0, length + 1.0).array;
541 	auto omega1 = properGeneralizedInverseGaussianFixedLambdaEtaEstimate!double(lambda, eta, sample, weights);
542 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega , sample, weights);
543 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, eta, omega1, sample, weights);
544 	assert(lh0 <= lh1);
545 }
546 
547 
548 ///ditto
549 T properGeneralizedInverseGaussianFixedLambdaEtaEstimate(T)
550 	(in T lambda, in T eta, in GeneralizedInverseGaussinFixedLambdaStatistic!T stat)
551 	if(isFloatingPoint!T)
552 in {
553 	assert(isNormal(lambda));
554 }
555 body {
556 	import std.numeric: findRoot;
557 	import atmosphere.math;
558 	immutable d = stat.mean / eta + stat.meani * eta;
559 	return findRoot((T omega) => besselKRS(lambda, omega) - d, double.min_normal, double.max);
560 }
561 
562 
563 version(none)
564 unittest
565 {
566 	import std.range;
567 	import std.random;
568 	import atmosphere.random;
569 	import atmosphere.likelihood;
570 	import atmosphere.params;
571 	auto length = 1000;
572 	auto lambda = -2.0, chi = 1.4, psi = 2.3;
573 	auto rng = Random(1234);
574 	auto sample = new GeneralizedInverseGaussianRNG!double(rng, lambda, chi, psi).take(length).array;
575 	auto chi1 = generalizedInverseGaussianFixedLambdaPsiEstimate!double(lambda, psi, sample);
576 	auto p0 = GIGChiPsi!double(chi , psi);
577 	auto p1 = GIGChiPsi!double(chi1, psi);
578 	auto lh0 = properGeneralizedInverseGaussianLikelihood(lambda, p0.eta, p0.omega , sample);
579 	auto lh1 = properGeneralizedInverseGaussianLikelihood(lambda, p1.eta, p1.omega, sample);
580 	assert(lh0 <= lh1);
581 }