1 module hoekjed.kern.wiskunde;
2 import hoekjed.overig;
3 import std.math : abs, cos, sin, sqrt;
4 import std.stdio;
5 
6 version (HoekjeD_Double) {
7 	alias nauwkeurigheid = double;
8 } else {
9 	alias nauwkeurigheid = float;
10 }
11 
12 alias Vec(uint grootte = 3, Soort = nauwkeurigheid) = Mat!(grootte, 1, Soort);
13 alias Mat(uint aantal = 3, Soort = nauwkeurigheid) = Mat!(aantal, aantal, Soort);
14 
15 struct Mat(uint rij_aantal, uint kolom_aantal, Soort = nauwkeurigheid)
16 		if (rij_aantal > 0 && kolom_aantal > 0) {
17 	enum uint grootte = rij_aantal * kolom_aantal;
18 	enum bool isVec = kolom_aantal == 1;
19 	enum bool isMat = !isVec;
20 	enum bool isVierkant = kolom_aantal == rij_aantal;
21 
22 	alias MatSoort = Mat!(rij_aantal, kolom_aantal, Soort);
23 
24 	union {
25 		Soort[grootte] vec;
26 		Soort[kolom_aantal][rij_aantal] mat;
27 		static if (isVec) {
28 			struct {
29 				static if (grootte >= 1)
30 					Soort x;
31 				static if (grootte >= 2)
32 					Soort y;
33 				static if (grootte >= 3)
34 					Soort z;
35 				static if (grootte >= 4)
36 					Soort w;
37 			}
38 		}
39 	}
40 
41 	// VOEG TOE: constructer met ... arg lijst
42 
43 	// this(Soort[][] inhoud) {
44 	// 	foreach (i; 0 .. rij_aantal)
45 	// 		foreach (j; 0 .. kolom_aantal)
46 	// 			this.mat[i][j] = inhoud[i][j];
47 	// }
48 
49 	static if (isVec)
50 		alias vec this;
51 	else
52 		alias mat this;
53 
54 	static immutable auto nul = _bereken_nul();
55 	private static auto _bereken_nul() {
56 		MatSoort resultaat;
57 		static foreach (i; 0 .. grootte)
58 			resultaat.vec[i] = 0;
59 		return resultaat;
60 	}
61 
62 	static if (isVierkant) {
63 		static immutable auto identiteit = _bereken_identiteit();
64 		private static auto _bereken_identiteit() {
65 			MatSoort resultaat;
66 			static foreach (i; 0 .. rij_aantal)
67 				static foreach (j; 0 .. kolom_aantal)
68 					resultaat.vec[i + j * kolom_aantal] = (i == j ? 1 : 0);
69 			return resultaat;
70 		}
71 	}
72 
73 	auto gekantelde() const {
74 		Mat!(kolom_aantal, rij_aantal, Soort) resultaat;
75 		static foreach (i; 0 .. rij_aantal)
76 			static foreach (j; 0 .. kolom_aantal)
77 				resultaat.mat[j][i] = this.mat[i][j];
78 		return resultaat;
79 	}
80 
81 	static {
82 		Mat!(4, 4, nauwkeurigheid) draaiMx(nauwkeurigheid hoek) {
83 			Mat!(4, 4, nauwkeurigheid) draaiM = Mat!(4, 4, nauwkeurigheid).nul;
84 			nauwkeurigheid cos = cos(hoek);
85 			nauwkeurigheid sin = sin(hoek);
86 			draaiM[0][0] = 1;
87 			draaiM[1][1] = cos;
88 			draaiM[1][2] = -sin;
89 			draaiM[2][1] = sin;
90 			draaiM[2][2] = cos;
91 			draaiM[3][3] = 1;
92 			return draaiM;
93 		}
94 
95 		unittest {
96 			import std.math : PI, PI_2;
97 
98 			Mat!4 draai = Mat!(4).draaiMx(0);
99 			Mat!4 draai2 = Mat!(4).identiteit;
100 			assert(draai == draai2);
101 
102 			draai = Mat!(4).draaiMx(PI_2);
103 			draai2 = Mat!(4).nul;
104 			draai2[0][0] = 1;
105 			draai2[1][2] = -1;
106 			draai2[2][1] = 1;
107 			draai2[3][3] = 1;
108 			float delta = 1e-5;
109 			float verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
110 			assert(verschil < delta);
111 
112 			draai = Mat!(4).draaiMx(PI);
113 			draai2 = Mat!(4).identiteit;
114 			draai2[1][1] = -1;
115 			draai2[2][2] = -1;
116 			verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
117 			assert(verschil < delta);
118 		}
119 
120 		Mat!(4, 4, nauwkeurigheid) draaiMy(nauwkeurigheid hoek) {
121 			Mat!(4, 4, nauwkeurigheid) draaiM = Mat!(4, 4, nauwkeurigheid).nul;
122 			nauwkeurigheid cos = cos(hoek);
123 			nauwkeurigheid sin = sin(hoek);
124 			draaiM[0][0] = cos;
125 			draaiM[0][2] = sin;
126 			draaiM[1][1] = 1;
127 			draaiM[2][0] = -sin;
128 			draaiM[2][2] = cos;
129 			draaiM[3][3] = 1;
130 			return draaiM;
131 		}
132 
133 		unittest {
134 			import std.math : PI, PI_2;
135 
136 			Mat!4 draai = Mat!(4).draaiMy(0);
137 			Mat!4 draai2 = Mat!(4).identiteit;
138 			assert(draai == draai2);
139 
140 			draai = Mat!(4).draaiMy(PI_2);
141 			draai2 = Mat!(4).nul;
142 			draai2[0][2] = 1;
143 			draai2[1][1] = 1;
144 			draai2[2][0] = -1;
145 			draai2[3][3] = 1;
146 			float delta = 1e-5;
147 			float verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
148 			assert(verschil < delta);
149 
150 			draai = Mat!(4).draaiMy(PI);
151 			draai2 = Mat!(4).identiteit;
152 			draai2[0][0] = -1;
153 			draai2[2][2] = -1;
154 			verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
155 			assert(verschil < delta);
156 		}
157 
158 		Mat!(4, 4, nauwkeurigheid) draaiMz(nauwkeurigheid hoek) {
159 			Mat!(4, 4, nauwkeurigheid) draaiM = Mat!(4, 4, nauwkeurigheid).nul;
160 			nauwkeurigheid cos = cos(hoek);
161 			nauwkeurigheid sin = sin(hoek);
162 			draaiM[0][0] = cos;
163 			draaiM[0][1] = -sin;
164 			draaiM[1][0] = sin;
165 			draaiM[1][1] = cos;
166 			draaiM[2][2] = 1;
167 			draaiM[3][3] = 1;
168 			return draaiM;
169 		}
170 
171 		unittest {
172 			import std.math : PI, PI_2;
173 
174 			Mat!4 draai = Mat!(4).draaiMz(0);
175 			Mat!4 draai2 = Mat!(4).identiteit;
176 			assert(draai == draai2);
177 
178 			draai = Mat!(4).draaiMz(PI_2);
179 			draai2[0][0] = 0;
180 			draai2[0][1] = -1;
181 			draai2[1][0] = 1;
182 			draai2[1][1] = 0;
183 			float delta = 1e-5;
184 			float verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
185 			assert(verschil < delta);
186 
187 			draai = Mat!(4).draaiMz(PI);
188 			draai2 = Mat!(4).identiteit;
189 			draai2[0][0] = -1;
190 			draai2[1][1] = -1;
191 			verschil = (draai.elk(&abs!(float)) - draai2.elk(&abs!(float))).som();
192 			assert(verschil < delta);
193 		}
194 	}
195 
196 	static if (isVec) {
197 		auto inp(Resultaat = Soort, R:
198 				Mat!(rij_aantal, 1, T), T)(const R rechts) const {
199 			Resultaat resultaat = 0;
200 			static foreach (i; 0 .. grootte)
201 				resultaat.vec[i] += this.vec[i] * rechts.vec[i];
202 			return resultaat;
203 		}
204 
205 		static if (rij_aantal == 3) {
206 			auto uitp(R : Mat!(rij_aantal, 1, T), T)(const R rechts) const {
207 				Mat!(rij_aantal, 1, typeof(Soort.init * T.init)) resultaat;
208 				resultaat.vec[0] = this.vec[1] * rechts.vec[2] - rechts.vec[1] * this.vec[2];
209 				resultaat.vec[1] = this.vec[2] * rechts.vec[0] - rechts.vec[2] * this.vec[0];
210 				resultaat.vec[2] = this.vec[0] * rechts.vec[1] - rechts.vec[0] * this.vec[1];
211 				return resultaat;
212 			}
213 		}
214 
215 		auto lengte(T = nauwkeurigheid)() const {
216 			T l = 0;
217 			static foreach (i; 0 .. rij_aantal) {
218 				l += this.vec[i];
219 			}
220 			return l;
221 		}
222 
223 		auto normaliseer(T = nauwkeurigheid)() const {
224 			Mat!(rij_aantal, kolom_aantal, T) n;
225 			n.vec[] = this.vec[];
226 			n = n * cast(T)(1 / this.lengte());
227 			return n;
228 		}
229 	}
230 
231 	static if (isMat) {
232 		// auto maal(T, R:
233 		// 		Mat!(kolom_aantal, 1, T))(R rechts) {
234 		// 	alias Onderdeel2 = typeof(Soort * T);
235 		// 	Vec!(rij_aantal, Onderdeel2) resultaat = Vec!(R, Onderdeel2).nul;
236 		// 	static foreach (i; 0 .. rij_aantal)
237 		// 		static foreach (j; 0 .. rij_aantal)
238 		// 			resultaat.vec[i] += this.mat[i][j] * rechts.vec[j];
239 		// 	return resultaat;
240 		// } Is het zelfde als:
241 
242 		auto maal(T, uint K)(const T[K][kolom_aantal] rechts) const 
243 				if (is(Resultaat!(Soort, "*", T))) {
244 			alias Onderdeel2 = Resultaat!(Soort, "*", T);
245 			Mat!(rij_aantal, K, Onderdeel2) resultaat = Mat!(rij_aantal, K, Onderdeel2).nul;
246 			static foreach (i; 0 .. rij_aantal)
247 				static foreach (j; 0 .. K)
248 					static foreach (k; 0 .. kolom_aantal)
249 						resultaat.mat[i][j] += this.mat[i][k] * rechts[k][j];
250 			return resultaat;
251 		}
252 
253 		auto maal(T)(const T[kolom_aantal] rechts) const 
254 				if (is(Resultaat!(Soort, "*", T))) {
255 			return maal!(T, 1)(cast(T[1][kolom_aantal]) rechts);
256 		}
257 
258 		unittest {
259 			import std.math : PI_2;
260 
261 			Mat!4 a = Mat!4.draaiMz(PI_2);
262 			Vec!4 resultaat = a.maal([1, 0, 0, 1]);
263 			int[4] b = [0,1,0,1];
264 			float verschil = (resultaat.elk(&abs!(float)) - b).som();
265 			float delta = 1e-5;
266 			assert(verschil < delta);
267 
268 			Mat!(2, 3) c;
269 			Mat!(3, 2) d;
270 			foreach (i; 0 .. c.grootte) {
271 				c.vec[i] = i + 1;
272 				d.vec[i] = i + 1;
273 			}
274 			auto c_gevonden = c.maal(c.gekantelde);
275 			assert(is(typeof(c_gevonden) == Mat!2));
276 			Mat!2 c_verwacht = Mat!2([14, 32, 32, 77]);
277 			assert(c_gevonden == c_verwacht, c_gevonden.toString() ~ "\n!=\n" ~ c_verwacht.toString());
278 
279 			auto d_gevonden = c.maal(d);
280 			assert(is(typeof(d_gevonden) == Mat!2));
281 			Mat!2 d_verwacht = Mat!2([22, 28, 49, 64]);
282 			assert(d_gevonden == d_verwacht);
283 		}
284 
285 		unittest {
286 			Mat!(2, 3, int) a;
287 			Mat!(3, 5, float) b;
288 			foreach (i; 0 .. 6)
289 				a.vec[i] = i;
290 			foreach (i; 0 .. 15)
291 				b.vec[i] = -i;
292 			auto c = a.maal(b);
293 			scope (failure)
294 				writefln("c:\n%l", c);
295 
296 			assert(is(typeof(c) == Mat!(2, 5, float)));
297 			Mat!(2, 5, float) d;
298 			foreach (i; 0 .. 5)
299 				d.vec[i] = -(25 + 3 * i);
300 			foreach (i; 0 .. 5)
301 				d.vec[i + 5] = -(70 + 12 * i);
302 			scope (failure)
303 				writefln("d:\n%l", d);
304 
305 			assert(c == d);
306 		}
307 	}
308 
309 	auto som(T = nauwkeurigheid)() const {
310 		T resultaat = 0;
311 		static foreach (i; 0 .. grootte) {
312 			resultaat += this.vec[i];
313 		}
314 		return resultaat;
315 	}
316 
317 	unittest {
318 		Mat!2 a;
319 		foreach (i; 0 .. a.grootte)
320 			a.vec[i] = 2 * i;
321 		assert(a.som() == 12);
322 	}
323 
324 	auto elk(S1, S2)(S1 function(S2 onderdeel) functie) const if (!is(S1 == void)) {
325 		Mat!(rij_aantal, kolom_aantal, S1) resultaat;
326 		static foreach (i; 0 .. grootte)
327 			resultaat.vec[i] = functie(this.vec[i]);
328 		return resultaat;
329 	}
330 
331 	void elk(S)(void function(S onderdeel) functie) const {
332 		static foreach (i; 0 .. grootte)
333 			functie(this.vec[i]);
334 	}
335 
336 	unittest {
337 		bool even(int getal) {
338 			return getal % 2 == 0;
339 		}
340 
341 		Mat!(2, 3, int) a;
342 		foreach (i; 0 .. a.grootte)
343 			a.vec[i] = i;
344 		auto b = a.elk(&even);
345 		assert(is(typeof(b) == Mat!(2, 3, bool)));
346 		foreach (i; 0 .. b.grootte)
347 			assert(b.vec[i] == (i % 2 == 0));
348 	}
349 
350 	auto elk(S1, S2)(S1 delegate(S2 onderdeel) functie) const if (!is(S1 == void)) {
351 		Mat!(rij_aantal, kolom_aantal, S1) resultaat;
352 		static foreach (i; 0 .. grootte)
353 			resultaat.vec[i] = functie(this.vec[i]);
354 		return resultaat;
355 	}
356 
357 	void elk(S)(void delegate(S onderdeel) functie) const {
358 		static foreach (i; 0 .. grootte)
359 			functie(this.vec[i]);
360 	}
361 
362 	unittest {
363 		Vec!5 a;
364 		uint i = 0;
365 		void overschrijven(float getal) {
366 			a.vec[i++] = getal;
367 		}
368 
369 		Vec!5 b;
370 		foreach (j; 0 .. b.grootte)
371 			b.vec[j] = 5 - j;
372 		Vec!5 c = b;
373 		b.elk(&overschrijven);
374 		assert(a == b, "a: " ~ a.toString(true) ~ "\nb: " ~ b.toString(true));
375 		assert(b == c);
376 	}
377 
378 	auto opBinary(string op, T)(const T rechts) const 
379 			if (!isLijst!T) {
380 		Mat!(rij_aantal, kolom_aantal, Resultaat!(Soort, op, T)) resultaat;
381 		static foreach (i; 0 .. grootte)
382 			mixin("resultaat.vec[i] = this.vec[i] " ~ op ~ " rechts;");
383 		return resultaat;
384 	}
385 
386 	unittest {
387 		Mat!5 a = Mat!5.nul;
388 		a = (a + 2) * 3;
389 		foreach (i; 0 .. a.grootte)
390 			assert(a.vec[i] == 6);
391 	}
392 
393 	static if (isVec) {
394 		// PAS OP: Wegens onduidelijke reden is gebruik van letterlijke lijsten momenteel niet goed
395 		// ondersdoor templates. Zie https://forum.dlang.org/post/sfgv2a$u08$1@digitalmars.com voor meer.
396 		auto opBinary(string op, T:U[grootte], U)(const T rechts) const 
397 				if (isLijst!T && !isLijst!(T, 2)) {
398 			Vec!(grootte, Resultaat!(Soort, op, U)) resultaat;
399 			static foreach (i; 0 .. grootte)
400 				mixin("resultaat.vec[i] = this.vec[i] " ~ op ~ " rechts[i];");
401 			return resultaat;
402 		}
403 	}
404 
405 	unittest {
406 		Vec!5 a = Vec!5([1, 2, 3, 4, 5]);
407 		int[5] b = [5,4,3,2,1];
408 		a = a - b;
409 		foreach (i; 0 .. 4)
410 			assert(a[i] == -4 + 2 * i, to!string(a));
411 	}
412 
413 	auto opBinary(string op, T:U[kolom_aantal][rij_aantal], U)(const T rechts) const 
414 			if (isLijst!(T, 2)) {
415 		Mat!(rij_aantal, kolom_aantal, Resultaat!(Soort, op, U)) resultaat;
416 		static foreach (i; 0 .. rij_aantal)
417 			static foreach (j; 0 .. kolom_aantal)
418 				mixin("resultaat.mat[i][j] = this.mat[i][j] " ~ op ~ " rechts[i][j];");
419 		return resultaat;
420 	}
421 
422 	unittest {
423 		int x = 0; // PAS OP: Wegens onduidelijke redenen vereist voor de leesbaarheid van a.
424 		Mat!(3, 3, float) a = Mat!(3, 3, float)([1, 2, 3, 4, 5, 6, 7, 8, 9]);
425 		Mat!(3, 3, double) b = Mat!(3, 3, double).identiteit;
426 
427 		auto c = a + b;
428 		assert(c.isSoort!(Mat!(3, 3, double)));
429 		foreach (i; 0 .. 9) {
430 			auto verwacht = i + 1 + (i % 4 == 0);
431 			assert(c.vec[i] == verwacht);
432 		}
433 
434 		auto d = c - a;
435 		assert(d.isSoort(c));
436 		assert(d == b);
437 
438 		auto e = a * b;
439 		assert(c.isSoort(b));
440 		foreach (i; 0 .. 9) {
441 			auto verwacht = (i + 1) * (i % 4 == 0);
442 			assert(e.vec[i] == verwacht);
443 		}
444 	}
445 
446 	M opCast(M : Mat!(rij_aantal, kolom_aantal, T), T)() const {
447 		M resultaat;
448 		static foreach (i; 0 .. grootte)
449 			resultaat.vec[i] = cast(T) this.vec[i];
450 		return resultaat;
451 	}
452 
453 	unittest {
454 		Mat!(2, 3, float) a = Mat!(2, 3, float)([1.0f, 2, 3, 4, 5, 6]);
455 		Mat!(2, 3, int) b = cast(Mat!(2, 3, int)) a;
456 		static foreach (i; 0 .. 6)
457 			assert(b.vec[i] == cast(int) a.vec[i]);
458 	}
459 
460 	import std.format : FormatSpec;
461 	import std.range : put;
462 	import std.conv : to;
463 
464 	string toString(bool mooi = false) const {
465 		char[] cs;
466 		cs.reserve(6 * grootte);
467 		cs ~= '{';
468 		static foreach (i; 0 .. rij_aantal) {
469 			cs ~= '[';
470 			static foreach (j; 0 .. kolom_aantal) {
471 				cs ~= this.mat[i][j].to!string;
472 				static if (j != kolom_aantal - 1)
473 					cs ~= ", ";
474 			}
475 			static if (i != rij_aantal - 1)
476 				cs ~= mooi ? "],\n " : "], ";
477 			else
478 				cs ~= ']';
479 		}
480 		cs ~= '}';
481 		return cast(string) cs[0 .. $];
482 	}
483 
484 	void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const {
485 		sink.put("[");
486 		const string tussenvoegsel = fmt.spec == 'l' ? ",\n " : ", ";
487 		static foreach (i; 0 .. rij_aantal - 1) {
488 			sink.put(mat[i].to!string);
489 			sink.put(tussenvoegsel);
490 		}
491 		static if (rij_aantal > 0)
492 			sink.put(mat[rij_aantal - 1].to!string);
493 		sink.put("]");
494 	}
495 
496 	bool opEquals(R)(const R ander) const {
497 		static if (is(const R == typeof(this)))
498 			return this.mat == (cast(typeof(this)) ander).mat;
499 		else {
500 			static if (!isVec && is(const R == typeof(this.mat)))
501 				return this.mat == ander;
502 			else {
503 				static if (isVec && is(const R == typeof(this.vec)))
504 					return this.vec == ander;
505 				else
506 					return false;
507 			}
508 		}
509 	}
510 
511 	unittest {
512 		Mat!(1, 2, float) mat = Mat!(1, 2, float)([1, 2]);
513 		Mat!(1, 2, float) mat2 = Mat!(1, 2, float)([1, 2]);
514 		Mat!(2, 1, float) mat3 = Mat!(2, 1, float)([1, 2]);
515 		Vec!(2, float) vec = Vec!(2, float)([1, 2]);
516 		float[2] lijst = [1, 2];
517 		float[2][1] lijst2 = [[1, 2]];
518 		assert(mat == mat);
519 		assert(mat == mat2);
520 		assert(mat != mat3);
521 		assert(mat != vec);
522 		assert(mat != lijst);
523 		assert(mat == lijst2);
524 
525 		assert(mat2 == mat2);
526 		assert(mat2 != mat3);
527 		assert(mat2 != vec);
528 		assert(mat2 != lijst);
529 		assert(mat2 == lijst2);
530 
531 		assert(mat3 == mat3);
532 		assert(mat3 == vec);
533 		assert(mat3 == lijst); //! mat 3 is een vec
534 		assert(mat3 != lijst2);
535 
536 		assert(vec == vec);
537 		assert(vec == lijst);
538 		assert(vec != lijst2);
539 	}
540 
541 	static if(is(typeof(abs!Soort))) {
542 		bool isOngeveer(const MatSoort ander, nauwkeurigheid delta = 1e-5) const {
543 			import std.math : abs;
544 			return (this - ander).elk(&abs!Soort).som() < delta;
545 		}
546 	}
547 
548 	unittest{
549 		float delta = 1e-6;
550 		Mat!3 a = Mat!(3).identiteit;
551 		float[3][3] verschil = [[delta, -delta, delta], [delta,delta,-delta], [-delta,-delta,delta]];
552 		Mat!3 b = a+verschil;
553 		assert(a.isOngeveer(b));
554 		assert(!a.isOngeveer(b, 8 * delta));
555 	}
556 
557 	// Krijg de draai nodig om een vector vanaf de y as in een richting te draai.
558 	// Hierbij wordt eerst de x draai toegepast gevolgd door de z draai.
559 	// Er is dus geen sprake van draai om de y as.
560 	// (Denk hierbij dus aan stamping gevolgd door giering, ook wel 'pitch' gevolgd door 'yaw')
561 	static Vec!3 krijgDraai(Vec!3 richting) {
562 		import std.math : acos, atan, PI_2, signbit;
563 
564 		if (richting.x == 0 && richting.y == 0) {
565 			if (richting.z == 0)
566 				return richting; // [0,0,0] -> [0,0,0]
567 			return Vec!3([PI_2, 0, 0]); // [0,0,z] -> [PI/2,0,0]
568 		}
569 		nauwkeurigheid R = Vec!2([richting.x, richting.y]).lengte();
570 		// [x,y,z] -> [atan(z/sqrt(x²+y²)),0,-teken(x)*acos(y/sqrt(x²+y²))]
571 		return Vec!3([
572 				atan(richting.z / R), 0,
573 				-signbit(richting.x) * acos(richting.y / R)
574 				]);
575 	}
576 
577 	// VOEG TOE: test
578 
579 	// Het omgekeerde van krijgDraai.
580 	// Draai om de y as wordt aangenomen als 0 aangzien dit geen invloed heeft.
581 	static Vec!3 krijgRichting(Vec!3 draai) {
582 		import std.math : sin, cos;
583 
584 		return Vec!3([-sin(draai.z), cos(draai.z), sin(draai.x)]);
585 	}
586 
587 	// VOEG TOE: test
588 }