Quaternion.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. // Copyright (c) 2014 Jamison Hope
  2. // This is free software; for terms and warranty disclaimer see ./COPYING.
  3. package gnu.math;
  4. /**
  5. * A quaternion is a hypercomplex number of the form w + xi + yj + zk
  6. * where w, x, y, and k are real, and i, j, and k are imaginary units
  7. * which satisfy i^2 = j^2 = k^2 = ijk = -1.
  8. * @author Jamison Hope
  9. */
  10. public abstract class Quaternion extends Quantity {
  11. public Quaternion number() { return this; }
  12. public boolean isExact () {
  13. // Should we return false if unit() != unit.Empty ?
  14. return re().isExact() && im().isExact()
  15. && jm().isExact() && km().isExact();
  16. }
  17. /**
  18. * Check if value is finite, infinite, or NaN.
  19. * @return 1 if finite; 0 if infinite; -1 if NaN.
  20. */
  21. public int classifyFinite() {
  22. int r = re().classifyFinite();
  23. if (r < 0)
  24. return r;
  25. int i = im().classifyFinite();
  26. if (i < 0)
  27. return i;
  28. int j = jm().classifyFinite();
  29. if (j < 0)
  30. return j;
  31. int k = km().classifyFinite();
  32. if (k < 0)
  33. return k;
  34. // r,i,j,k are all either 0 or 1, answer is 1 iff all are 1, else 0
  35. return r * i * j * k;
  36. }
  37. public Quaternion toExact() {
  38. RealNum re = re();
  39. RealNum im = im();
  40. RealNum jm = jm();
  41. RealNum km = km();
  42. RatNum xre = re.toExact();
  43. RatNum xim = im.toExact();
  44. RatNum xjm = jm.toExact();
  45. RatNum xkm = km.toExact();
  46. if (xre == re && xim == im && xjm == jm && xkm == km)
  47. return this;
  48. else
  49. return new CQuaternion(xre, xim, xjm, xkm);
  50. }
  51. public Quaternion toInexact() {
  52. if (isExact())
  53. return this;
  54. return new DQuaternion(re().doubleValue(), im().doubleValue(),
  55. jm().doubleValue(), km().doubleValue());
  56. }
  57. private static CQuaternion jmOne;
  58. private static CQuaternion jmMinusOne;
  59. private static CQuaternion kmOne;
  60. private static CQuaternion kmMinusOne;
  61. public static CQuaternion jmOne() {
  62. if (jmOne == null)
  63. jmOne = new CQuaternion(IntNum.zero(), IntNum.zero(),
  64. IntNum.one(), IntNum.zero());
  65. return jmOne;
  66. }
  67. public static CQuaternion jmMinusOne() {
  68. if (jmMinusOne == null)
  69. jmMinusOne = new CQuaternion(IntNum.zero(), IntNum.zero(),
  70. IntNum.minusOne(), IntNum.zero());
  71. return jmMinusOne;
  72. }
  73. public static CQuaternion kmOne() {
  74. if (kmOne == null)
  75. kmOne = new CQuaternion(IntNum.zero(), IntNum.zero(),
  76. IntNum.zero(), IntNum.one());
  77. return kmOne;
  78. }
  79. public static CQuaternion kmMinusOne() {
  80. if (kmMinusOne == null)
  81. kmMinusOne = new CQuaternion(IntNum.zero(), IntNum.zero(),
  82. IntNum.zero(), IntNum.minusOne());
  83. return kmMinusOne;
  84. }
  85. public double doubleValue() { return re().doubleValue(); }
  86. public double doubleImagValue() { return im().doubleValue(); }
  87. public double doubleJmagValue() { return jm().doubleValue(); }
  88. public double doubleKmagValue() { return km().doubleValue(); }
  89. public final double doubleRealValue() { return doubleValue(); }
  90. public long longValue() { return re().longValue(); }
  91. public Complex complexPart() {
  92. return Complex.make(re(), im());
  93. }
  94. public Quaternion vectorPart() {
  95. return Quaternion.make(IntNum.zero(), im(), jm(), km());
  96. }
  97. public Quaternion unitVector() {
  98. int imSign = im().sign();
  99. int jmSign = jm().sign();
  100. int kmSign = km().sign();
  101. if (imSign == -2 || jmSign == -2 || kmSign == -2)
  102. return Quaternion.make(0, Double.NaN, Double.NaN, Double.NaN);
  103. if (imSign == 0 && jmSign == 0 && kmSign == 0)
  104. return IntNum.zero();
  105. if (imSign == 0 && jmSign == 0)
  106. return kmSign == 1 ? kmOne() : kmMinusOne();
  107. if (imSign == 0 && kmSign == 0)
  108. return jmSign == 1 ? jmOne() : jmMinusOne();
  109. if (jmSign == 0 && kmSign == 0)
  110. return imSign == 1 ? Complex.imOne() : Complex.imMinusOne();
  111. double im = doubleImagValue();
  112. double jm = doubleJmagValue();
  113. double km = doubleKmagValue();
  114. double vmag = DQuaternion.hypot3(im,jm,km);
  115. return Quaternion.make(0, im/vmag, jm/vmag, km/vmag);
  116. }
  117. public Quaternion unitQuaternion() {
  118. int reSign = re().sign();
  119. int imSign = im().sign();
  120. int jmSign = jm().sign();
  121. int kmSign = km().sign();
  122. if (reSign == -2 || imSign == -2 || jmSign == -2 || kmSign == -2)
  123. return Quaternion.make(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
  124. // try to be as exact as possible
  125. if (imSign == 0 && jmSign == 0 && kmSign == 0) {
  126. return Quaternion.make((RealNum) re().unitQuaternion(),
  127. IntNum.zero(),
  128. IntNum.zero(),
  129. IntNum.zero());
  130. }
  131. if (reSign == 0 && jmSign == 0 && kmSign == 0) {
  132. return Quaternion.make(IntNum.zero(),
  133. (RealNum) im().unitQuaternion(),
  134. IntNum.zero(),
  135. IntNum.zero());
  136. }
  137. if (reSign == 0 && imSign == 0 && kmSign == 0) {
  138. return Quaternion.make(IntNum.zero(),
  139. IntNum.zero(),
  140. (RealNum) jm().unitQuaternion(),
  141. IntNum.zero());
  142. }
  143. if (reSign == 0 && imSign == 0 && jmSign == 0) {
  144. return Quaternion.make(IntNum.zero(),
  145. IntNum.zero(),
  146. IntNum.zero(),
  147. (RealNum) km().unitQuaternion());
  148. }
  149. double re = doubleRealValue();
  150. double im = doubleImagValue();
  151. double jm = doubleJmagValue();
  152. double km = doubleKmagValue();
  153. double mag = DQuaternion.hypot4(re,im,jm,km);
  154. return Quaternion.make(re/mag,im/mag,jm/mag,km/mag);
  155. }
  156. public static Quaternion make(RealNum re, RealNum im, RealNum jm,
  157. RealNum km) {
  158. if (km.isZero() && km.isExact() && jm.isZero() && jm.isExact())
  159. return Complex.make(re, im);
  160. if (!re.isExact() && !im.isExact() && !jm.isExact() && !km.isExact())
  161. return new DQuaternion(re.doubleValue(), im.doubleValue(),
  162. jm.doubleValue(), km.doubleValue());
  163. return new CQuaternion(re, im, jm, km);
  164. }
  165. public static Quaternion make(double re, double im, double jm,
  166. double km) {
  167. if (jm == 0.0 && km == 0.0)
  168. return Complex.make(re, im);
  169. return new DQuaternion(re, im, jm, km);
  170. }
  171. public static Quaternion polar(double r, double t, double u, double v) {
  172. // tan(v) = z/y
  173. double z = r * Math.sin(t) * Math.sin(u) * Math.sin(v);
  174. double y = r * Math.sin(t) * Math.sin(u) * Math.cos(v);
  175. // tan(u) = sqrt(y^2 + z^2) / x
  176. double x = r * Math.sin(t) * Math.cos(u);
  177. // tan(t) = sqrt(x^2 + y^2 + z^2) / w
  178. double w = r * Math.cos(t);
  179. return Quaternion.make(w, x, y, z);
  180. }
  181. public static Quaternion polar(RealNum r, RealNum t, RealNum u, RealNum v) {
  182. return Quaternion.polar(r.doubleValue(), t.doubleValue(),
  183. u.doubleValue(), v.doubleValue());
  184. }
  185. public static Quaternion power(Quaternion x, Quaternion y) {
  186. if (y instanceof IntNum)
  187. return (Quaternion) x.power((IntNum) y);
  188. double y_re = y.doubleRealValue();
  189. double y_im = y.doubleImagValue();
  190. double y_jm = y.doubleJmagValue();
  191. double y_km = y.doubleKmagValue();
  192. if (x.isZero() && x.isExact() && y.isExact()) {
  193. if (y_re > 0)
  194. return IntNum.zero();
  195. else if (y_re == 0 && y_im == 0 && y_jm == 0 && y_km == 0)
  196. return IntNum.one();
  197. }
  198. double x_re = x.doubleRealValue();
  199. double x_im = x.doubleImagValue();
  200. double x_jm = x.doubleJmagValue();
  201. double x_km = x.doubleKmagValue();
  202. if (x_im == 0.0 && y_im == 0.0 && x_jm == 0.0 && y_jm == 0.0 &&
  203. x_km == 0.0 && y_km == 0.0 &&
  204. (x_re >= 0 || Double.isInfinite(x_re) || Double.isNaN(x_re)))
  205. return new DFloNum(Math.pow(x_re, y_re));
  206. return DQuaternion.power(x_re, x_im, x_jm, x_km,
  207. y_re, y_im, y_jm, y_km);
  208. }
  209. public Numeric abs() {
  210. return new DFloNum(DQuaternion.hypot4(doubleRealValue(),
  211. doubleImagValue(),
  212. doubleJmagValue(),
  213. doubleKmagValue()));
  214. }
  215. public RealNum angle() {
  216. return new DFloNum(Math.atan2(Math.hypot(Math.hypot(doubleImagValue(),
  217. doubleJmagValue()),
  218. doubleKmagValue()),
  219. doubleRealValue()));
  220. }
  221. public RealNum colatitude() {
  222. return new DFloNum(Math.atan2(Math.hypot(doubleJmagValue(),
  223. doubleKmagValue()),
  224. doubleImagValue()));
  225. }
  226. public RealNum longitude() {
  227. return new DFloNum(Math.atan2(doubleKmagValue(),doubleJmagValue()));
  228. }
  229. public Quaternion conjugate() {
  230. return Quaternion.make(re(), im().rneg(), jm().rneg(), km().rneg());
  231. }
  232. public static boolean equals(Quaternion x, Quaternion y) {
  233. return x.re().equals(y.re())
  234. && x.im().equals(y.im())
  235. && x.jm().equals(y.jm())
  236. && x.km().equals(y.km());
  237. }
  238. public boolean equals(Object obj) {
  239. if (obj == null || !(obj instanceof Quaternion))
  240. return false;
  241. return Quaternion.equals(this, (Quaternion) obj);
  242. }
  243. public static int compare(Quaternion x, Quaternion y) {
  244. int code = x.km().compare(y.km());
  245. if (code != 0)
  246. return code;
  247. code = x.jm().compare(y.jm());
  248. if (code != 0)
  249. return code;
  250. code = x.im().compare(y.im());
  251. if (code != 0)
  252. return code;
  253. return x.re().compare(y.re());
  254. }
  255. public int compare(Object obj) {
  256. if (!(obj instanceof Quaternion))
  257. return ((Numeric) obj).compareReversed(this);
  258. return Quaternion.compare(this, (Quaternion) obj);
  259. }
  260. public boolean isZero() {
  261. return re().isZero() && im().isZero() && jm().isZero() && km().isZero();
  262. }
  263. public String toString(int radix) {
  264. if (im().isZero() && jm().isZero() && km().isZero())
  265. return re().toString(radix);
  266. String imString = "", jmString = "", kmString = "";
  267. if (!im().isZero()) {
  268. imString = im().toString(radix) + "i";
  269. char ch0 = imString.charAt(0);
  270. if (ch0 != '-' && ch0 != '+')
  271. imString = "+" + imString;
  272. }
  273. if (!jm().isZero()) {
  274. jmString = jm().toString(radix) + "j";
  275. char ch0 = jmString.charAt(0);
  276. if (ch0 != '-' && ch0 != '+')
  277. jmString = "+" + jmString;
  278. }
  279. if (!km().isZero()) {
  280. kmString = km().toString(radix) + "k";
  281. char ch0 = kmString.charAt(0);
  282. if (ch0 != '-' && ch0 != '+')
  283. kmString = "+" + kmString;
  284. }
  285. if (re().isZero())
  286. return imString + jmString + kmString;
  287. return re().toString(radix) + imString + jmString + kmString;
  288. }
  289. public static Quaternion neg(Quaternion x) {
  290. return Quaternion.make(x.re().rneg(), x.im().rneg(),
  291. x.jm().rneg(), x.km().rneg());
  292. }
  293. public Numeric neg() { return Quaternion.neg(this); }
  294. public static Quaternion add(Quaternion x, Quaternion y, int k) {
  295. return Quaternion.make(RealNum.add(x.re(), y.re(), k),
  296. RealNum.add(x.im(), y.im(), k),
  297. RealNum.add(x.jm(), y.jm(), k),
  298. RealNum.add(x.km(), y.km(), k));
  299. }
  300. public Numeric add(Object y, int k) {
  301. if (y instanceof Quaternion)
  302. return Quaternion.add(this, (Quaternion)y, k);
  303. return ((Numeric)y).addReversed(this, k);
  304. }
  305. public Numeric addReversed(Numeric x, int k) {
  306. if (x instanceof Quaternion)
  307. return Quaternion.add((Quaternion)x, this, k);
  308. throw new IllegalArgumentException();
  309. }
  310. public static Quaternion times(Quaternion x, Quaternion y) {
  311. RealNum x_re = x.re();
  312. RealNum x_im = x.im();
  313. RealNum x_jm = x.jm();
  314. RealNum x_km = x.km();
  315. RealNum y_re = y.re();
  316. RealNum y_im = y.im();
  317. RealNum y_jm = y.jm();
  318. RealNum y_km = y.km();
  319. RealNum r = RealNum.add( RealNum.add( RealNum.times(x_re,y_re),
  320. RealNum.times(x_im,y_im), -1),
  321. RealNum.add( RealNum.times(x_jm,y_jm),
  322. RealNum.times(x_km,y_km), 1), -1);
  323. RealNum i = RealNum.add( RealNum.add( RealNum.times(x_re,y_im),
  324. RealNum.times(x_im,y_re), 1),
  325. RealNum.add( RealNum.times(x_jm,y_km),
  326. RealNum.times(x_km,y_jm), -1), 1);
  327. RealNum j = RealNum.add( RealNum.add( RealNum.times(x_re,y_jm),
  328. RealNum.times(x_im,y_km), -1),
  329. RealNum.add( RealNum.times(x_jm,y_re),
  330. RealNum.times(x_km,y_im), 1), 1);
  331. RealNum k = RealNum.add( RealNum.add( RealNum.times(x_re,y_km),
  332. RealNum.times(x_im,y_jm), 1),
  333. RealNum.add( RealNum.times(x_jm,y_im),
  334. RealNum.times(x_km,y_re), -1), -1);
  335. return Quaternion.make(r, i, j, k);
  336. }
  337. public Numeric mul(Object y) {
  338. if (y instanceof Quaternion)
  339. return Quaternion.times(this, (Quaternion) y);
  340. return ((Numeric)y).mulReversed(this);
  341. }
  342. public Numeric mulReversed(Numeric x) {
  343. if (x instanceof Quaternion)
  344. return Quaternion.times((Quaternion)x, this);
  345. throw new IllegalArgumentException();
  346. }
  347. public static Quaternion divide(Quaternion x, Quaternion y) {
  348. if (! x.isExact () || ! y.isExact ())
  349. return DQuaternion.div (x.doubleRealValue(), x.doubleImagValue(),
  350. x.doubleJmagValue(), x.doubleKmagValue(),
  351. y.doubleRealValue(), y.doubleImagValue(),
  352. y.doubleJmagValue(), y.doubleKmagValue());
  353. RealNum x_re = x.re();
  354. RealNum x_im = x.im();
  355. RealNum x_jm = x.jm();
  356. RealNum x_km = x.km();
  357. RealNum y_re = y.re();
  358. RealNum y_im = y.im();
  359. RealNum y_jm = y.jm();
  360. RealNum y_km = y.km();
  361. RealNum y_norm = RealNum.add(RealNum.add(RealNum.times(y_re,y_re),
  362. RealNum.times(y_im,y_im), 1),
  363. RealNum.add(RealNum.times(y_jm,y_jm),
  364. RealNum.times(y_km,y_km), 1),
  365. 1);
  366. // This computes (x * y^-1), which is different from (y^-1 * x).
  367. RealNum r = RealNum.add( RealNum.add( RealNum.times(x_re,y_re),
  368. RealNum.times(x_im,y_im), 1),
  369. RealNum.add( RealNum.times(x_jm,y_jm),
  370. RealNum.times(x_km,y_km), 1), 1);
  371. RealNum i = RealNum.add( RealNum.add( RealNum.times(x_im,y_re),
  372. RealNum.times(x_re,y_im), -1),
  373. RealNum.add( RealNum.times(x_km,y_jm),
  374. RealNum.times(x_jm,y_km), -1), 1);
  375. RealNum j = RealNum.add( RealNum.add( RealNum.times(x_jm,y_re),
  376. RealNum.times(x_re,y_jm), -1),
  377. RealNum.add( RealNum.times(x_im,y_km),
  378. RealNum.times(x_km,y_im), -1), 1);
  379. RealNum k = RealNum.add( RealNum.add( RealNum.times(x_km,y_re),
  380. RealNum.times(x_re,y_km), -1),
  381. RealNum.add( RealNum.times(x_jm,y_im),
  382. RealNum.times(x_im,y_jm), -1), 1);
  383. return Quaternion.make(RealNum.divide(r, y_norm),
  384. RealNum.divide(i, y_norm),
  385. RealNum.divide(j, y_norm),
  386. RealNum.divide(k, y_norm));
  387. }
  388. public Numeric div(Object y) {
  389. if (y instanceof Quaternion)
  390. return Quaternion.divide(this, (Quaternion) y);
  391. return ((Numeric)y).divReversed(this);
  392. }
  393. public Numeric divReversed(Numeric x) {
  394. if (x instanceof Quaternion)
  395. return Quaternion.divide((Quaternion)x, this);
  396. throw new IllegalArgumentException();
  397. }
  398. public Quaternion exp() {
  399. return DQuaternion.exp(doubleRealValue(), doubleImagValue(),
  400. doubleJmagValue(), doubleKmagValue());
  401. }
  402. public Quaternion log() {
  403. return DQuaternion.log(doubleRealValue(), doubleImagValue(),
  404. doubleJmagValue(), doubleKmagValue());
  405. }
  406. public Quaternion sqrt() {
  407. return DQuaternion.sqrt(doubleRealValue(), doubleImagValue(),
  408. doubleJmagValue(), doubleKmagValue());
  409. }
  410. public Quaternion sin() {
  411. return DQuaternion.sin(doubleRealValue(), doubleImagValue(),
  412. doubleJmagValue(), doubleKmagValue());
  413. }
  414. public Quaternion cos() {
  415. return DQuaternion.cos(doubleRealValue(), doubleImagValue(),
  416. doubleJmagValue(), doubleKmagValue());
  417. }
  418. public Quaternion tan() {
  419. return DQuaternion.tan(doubleRealValue(), doubleImagValue(),
  420. doubleJmagValue(), doubleKmagValue());
  421. }
  422. }