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 }