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.finitemixture;
9 
10 import std.range;
11 import std.traits;
12 
13 /++
14 Returns: Callable structure
15 Params:
16 	funcs = input range of functions
17 	weights = mixture weights
18 +/
19 auto finiteMixture(FRange, T)(FRange funcs, const(T)[] weights)
20 	if(
21 		isForwardRange!FRange && 
22 		isCallable!(ElementType!FRange) &&
23 		isFloatingPoint!T)
24 in {
25 	assert(weights.length);
26 	static if(hasLength!FRange)
27 		assert(funcs.length == weights.length);
28 }
29 body {
30 	struct Result(T, FRange)
31 		if(
32 			isForwardRange!FRange && 
33 			isCallable!(ElementType!FRange) &&
34 			isFloatingPoint!T)
35 	{
36 		private FRange funcs;
37 		private const(T)[] weights;
38 		this(T)(FRange funcs, const(T)[] weights)
39 		{
40 			this.funcs = funcs;
41 			this.weights = weights;
42 		}
43 		T opCall(T x)
44 		{
45 			T s = 0;
46 			auto funcs = this.funcs.save;
47 			foreach(w; weights)
48 			{
49 				assert(!funcs.empty);
50 				s += funcs.front()(x);
51 				funcs.popFront;
52 			}
53 			return s;
54 		}
55 	}
56 	return Result!(T, FRange)(funcs, weights);
57 }
58 
59 ///
60 unittest
61 {
62 	import std.algorithm : map;
63 	import std.range : sequence;
64 	import atmosphere.pdf;
65 	import atmosphere.utilities;
66 	auto pdfs = sequence!"n+1"().map!(shape => GammaSPDF!real(shape, 1));
67 	double[] weights = [0.25, 0.5, 0.125, 0.125];
68 	auto pdf0 = finiteMixture(pdfs, weights);           //struct
69 	auto pdf1 = finiteMixture(pdfs[3..4].chain(pdfs[0..3]), [0.125, 0.25, 0.5, 0.125]); //struct
70 	PDF!double pdf2 = toPDF(pdf0);                      //object
71 	foreach(x; iota(1, 4))
72 		assert(approxEqual(pdf1(x), pdf2(x)));
73 }