Quaternion square root

11

Background

Quaternion is a number system that extends complex numbers. A quaternion has the following form

$$ a + bi + cj + dk $$

where \$ a,b,c,d \$ are real numbers and \$ i,j,k \$ are three fundamental quaternion units. The units have the following properties:

$$ i^2 = j^2 = k^2 = -1 $$ $$ ij = k, jk = i, ki = j $$ $$ ji = -k, kj = -i, ik = -j $$

Note that quaternion multiplication is not commutative.

Task

Given a non-real quaternion, compute at least one of its square roots.

How?

According to this Math.SE answer, we can express any non-real quaternion in the following form:

$$ q = a + b\vec{u} $$

where \$ a,b\$ are real numbers and \$ \vec{u} \$ is the imaginary unit vector in the form \$ xi + yj + zk \$ with \$ x^2 + y^2 + z^2 = 1 \$. Any such \$ \vec{u} \$ has the property \$ \vec{u}^2 = -1 \$, so it can be viewed as the imaginary unit.

Then the square of \$ q \$ looks like this:

$$ q^2 = (a^2 - b^2) + 2ab\vec{u} $$

Inversely, given a quaternion \$ q' = x + y\vec{u} \$, we can find the square root of \$ q' \$ by solving the following equations

$$ x = a^2 - b^2, y = 2ab $$

which is identical to the process of finding the square root of a complex number.

Note that a negative real number has infinitely many quaternion square roots, but a non-real quaternion has only two square roots.

Input and output

Input is a non-real quaternion. You can take it as four real (floating-point) numbers, in any order and structure of your choice. Non-real means that at least one of \$ b,c,d \$ is non-zero.

Output is one or two quaternions which, when squared, are equal to the input.

Test cases

   Input (a, b, c, d)  =>  Output (a, b, c, d) rounded to 6 digits

 0.0,  1.0,  0.0,  0.0 =>  0.707107,  0.707107,  0.000000,  0.000000
 1.0,  1.0,  0.0,  0.0 =>  1.098684,  0.455090,  0.000000,  0.000000
 1.0, -1.0,  1.0,  0.0 =>  1.168771, -0.427800,  0.427800,  0.000000
 2.0,  0.0, -2.0, -1.0 =>  1.581139,  0.000000, -0.632456, -0.316228
 1.0,  1.0,  1.0,  1.0 =>  1.224745,  0.408248,  0.408248,  0.408248
 0.1,  0.2,  0.3,  0.4 =>  0.569088,  0.175720,  0.263580,  0.351439
99.0,  0.0,  0.0,  0.1 =>  9.949876,  0.000000,  0.000000,  0.005025

Generated using this Python script. Only one of the two correct answers is specified for each test case; the other is all four values negated.

Scoring & winning criterion

Standard rules apply. The shortest program or function in bytes in each language wins.

Bubbler

Posted 2018-11-15T22:54:07.167

Reputation: 16 616

Can we take the quaternion as a, (b, c, d)? – nwellnhof – 2018-11-16T13:19:00.233

@nwellnhof Sure. Even something like a,[b,[c,[d]]] is fine, if you can somehow save bytes with it :) – Bubbler – 2018-11-16T13:50:27.293

Answers

29

APL (NARS), 2 bytes

NARS has built-in support for quaternions. ¯\_(⍨)_/¯

Adám

Posted 2018-11-15T22:54:07.167

Reputation: 37 779

4I can't help it: you should include " ¯_(ツ)_/¯ " In your answer – Barranka – 2018-11-16T05:22:08.630

7You dropped this \ – Andrew – 2018-11-16T08:33:58.910

@Barranka Done. – Adám – 2018-11-16T09:08:40.787

@Andrew blame it on the Android app... Thank you for picking it up :) – Barranka – 2018-11-16T14:31:55.417

2It'd be better if it's ¯\_(⍨)√¯ – Zacharý – 2018-11-16T21:03:32.057

8

Python 2, 72 bytes

def f(a,b,c,d):s=((a+(a*a+b*b+c*c+d*d)**.5)*2)**.5;print s/2,b/s,c/s,d/s

Try it online!

More or less a raw formula. I thought I could use list comprehensions to loop over b,c,d, but this seems to be longer. Python is really hurt here by a lack of vector operations, in particular scaling and norm.

Python 3, 77 bytes

def f(a,*l):r=a+sum(x*x for x in[a,*l])**.5;return[x/(r*2)**.5for x in[r,*l]]

Try it online!

Solving the quadratic directly was also shorter than using Python's complex-number square root to solve it like in the problem statement.

xnor

Posted 2018-11-15T22:54:07.167

Reputation: 115 687

"Input is a non-real quaternion. You can take it as four real (floating-point) numbers, in any order and structure of your choice." So you can consider it to be a pandas series or numpy array. Series have scaling with simple multiplication, and there are various ways to get norm, such as (s*s).sum()**.5. – Acccumulation – 2018-11-16T20:04:44.207

6

Wolfram Language (Mathematica), 19 bytes

Sqrt
<<Quaternions`

Try it online!

Mathematica has Quaternion built-in too, but is more verbose.


Although built-ins look cool, do upvote solutions that don't use built-ins too! I don't want votes on questions reaching HNQ be skewed.

user202729

Posted 2018-11-15T22:54:07.167

Reputation: 14 620

4

JavaScript (ES7), 55 53 bytes

Based on the direct formula used by xnor.

Takes input as an array.

q=>q.map(v=>1/q?v/2/q:q=((v+Math.hypot(...q))/2)**.5)

Try it online!

How?

Given an array \$q=[a,b,c,d]\$, this computes:

$$x=\sqrt{\frac{a+\sqrt{a^2+b^2+c^2+d^2}}{2}}$$

And returns:

$$\left[x,\frac{b}{2x},\frac{c}{2x},\frac{d}{2x}\right]$$

q =>                            // q[] = input array
  q.map(v =>                    // for each value v in q[]:
    1 / q ?                     //   if q is numeric (2nd to 4th iteration):
      v / 2 / q                 //     yield v / 2q
    :                           //   else (1st iteration, with v = a):
      q = (                     //     compute x (as defined above) and store it in q
        (v + Math.hypot(...q))  //     we use Math.hypot(...q) to compute:
        / 2                     //       (q[0]**2 + q[1]**2 + q[2]**2 + q[3]**2) ** 0.5
      ) ** .5                   //     yield x
  )                             // end of map()

Arnauld

Posted 2018-11-15T22:54:07.167

Reputation: 111 334

3

Haskell, 51 bytes

f(a:l)|r<-a+sqrt(sum$(^2)<$>a:l)=(/sqrt(r*2))<$>r:l

Try it online!

A direct formula. The main trick to express the real part of the output as r/sqrt(r*2) to parallel the imaginary part expression, which saves a few bytes over:

54 bytes

f(a:l)|s<-sqrt$2*(a+sqrt(sum$(^2)<$>a:l))=s/2:map(/s)l

Try it online!

xnor

Posted 2018-11-15T22:54:07.167

Reputation: 115 687

3

Charcoal, 32 bytes

≔X⊗⁺§θ⁰XΣEθ×ιι·⁵¦·⁵η≧∕ηθ§≔θ⁰⊘ηIθ

Try it online! Link is to verbose version of code. Port of @xnor's Python answer. Explanation:

≔X⊗⁺§θ⁰XΣEθ×ιι·⁵¦·⁵η

Square all of the elements of the input and take the sum, then take the square root. This calculates \$ | x + y\vec{u} | = \sqrt{ x^2 + y^2 } = \sqrt{ (a^2 - b^2)^2 + (2ab)^2 } = a^2 + b^2 \$. Adding \$ x \$ gives \$ 2a^2 \$ which is then doubled and square rooted to give \$ 2a \$.

≧∕ηθ

Because \$ y = 2ab \$, calculate \$ b \$ by dividing by \$ 2a \$.

§≔θ⁰⊘η

Set the first element of the array (i.e. the real part) to half of \$ 2a \$.

Iθ

Cast the values to string and implicitly print.

Neil

Posted 2018-11-15T22:54:07.167

Reputation: 95 035

3

Java 8, 84 bytes

(a,b,c,d)->(a=Math.sqrt(2*(a+Math.sqrt(a*a+b*b+c*c+d*d))))/2+" "+b/a+" "+c/a+" "+d/a

Port of @xnor's Python 2 answer.

Try it online.

Explanation:

(a,b,c,d)->           // Method with four double parameters and String return-type
  (a=                 //  Change `a` to:
     Math.sqrt(       //   The square root of:
       2*             //    Two times:
         (a+          //     `a` plus,
          Math.sqrt(  //     the square-root of:
            a*a       //      `a`  squared,
            +b*b      //      `b` squared,
            +c*c      //      `c` squared,
            +d*d))))  //      And `d` squared summed together
  /2                  //  Then return this modified `a` divided by 2
  +" "+b/a            //  `b` divided by the modified `a`
  +" "+c/a            //  `c` divided by the modified `a`
  +" "+d/a            //  And `d` divided by the modified `a`, with space delimiters

Kevin Cruijssen

Posted 2018-11-15T22:54:07.167

Reputation: 67 575

2

05AB1E, 14 bytes

nOtsн+·t©/¦®;š

Port of @xnor's Python 2 answer.

Try it online or verify all test cases.

Explanation:

n                 # Square each number in the (implicit) input-list
 O                # Sum them
  t               # Take the square-root of that
   sн+            # Add the first item of the input-list
      ·           # Double it
       t          # Take the square-root of it
        ©         # Store it in the register (without popping)
         /        # Divide each value in the (implicit) input with it
          ¦       # Remove the first item
           ®;     # Push the value from the register again, and halve it
             š    # Prepend it to the list (and output implicitly)

Kevin Cruijssen

Posted 2018-11-15T22:54:07.167

Reputation: 67 575

2

Wolfram Language (Mathematica), 28 bytes

{s=#+Norm@{##},##2}/(2s)^.5&

Port of @xnor's Python 2 answer.

Try it online!

alephalpha

Posted 2018-11-15T22:54:07.167

Reputation: 23 988

2

C# .NET, 88 bytes

(a,b,c,d)=>((a=System.Math.Sqrt(2*(a+System.Math.Sqrt(a*a+b*b+c*c+d*d))))/2,b/a,c/a,d/a)

Port of my Java 8 answer, but returns a Tuple instead of a String. I thought that would have been shorter, but unfortunately the Math.Sqrt require a System-import in C# .NET, ending up at 4 bytes longer instead of 10 bytes shorter.. >.>

The lambda declaration looks pretty funny, though:

System.Func<double, double, double, double, (double, double, double, double)> f =

Try it online.

Kevin Cruijssen

Posted 2018-11-15T22:54:07.167

Reputation: 67 575

1

Perl 6, 49 bytes

{;(*+@^b>>².sum**.5*i).sqrt.&{.re,(@b X/2*.re)}}

Try it online!

Curried function taking input as f(b,c,d)(a). Returns quaternion as a,(b,c,d).

Explanation

{;                                             }  # Block returning WhateverCode
     @^b>>².sum**.5     # Compute B of quaternion written as q = a + B*u
                        # (length of vector (b,c,d))
  (*+              *i)  # Complex number a + B*i
                      .sqrt  # Square root of complex number
                           .&{                }  # Return
                              .re,  # Real part of square root
                                  (@b X/2*.re)  # b,c,d divided by 2* real part

nwellnhof

Posted 2018-11-15T22:54:07.167

Reputation: 10 037