Repository: quantumlib/Cirq

Issue:
Fractional exponents in diagrams
What do people think about replacing decimal fractions with ordinary fractions in diagrams (in cases where it is possible), e.g. displaying `X^(7/8)` rather than `X^0.875`?

An important property of quantum gates is that most of them are elements in finite [cyclic groups](https://en.wikipedia.org/wiki/Cyclic_group) and this is better exhibited by `X^(1/8)` than `X^0.125`. For example, the order of the minimal group containing the gate is clear from notation as it is equal to the denominator.


Discussion:
I think this is a good idea. If we're within 10**-precision of a fraction with a denominator less than 20-ish, it would add a nice amount of clarity. We also have a perfect place in the code to insert this functionality, because all the gates already delegate exponent-stringifying to the main drawing code.

Note that we can use unicode fraction characters, as long as they play nice in fixed width fonts and aren't too small to make out.

```
⅟ ½ ⅓ ¼ ⅕ ⅙ ⅐ ⅛ ⅑ ⅒
    ⅔   ⅖
      ¾ ⅗     ⅜
        ⅘
          ⅚   ⅝

              ⅞
```
I'd be happy to do this , but I think it's worth noting we may not be able to use unicode fractions, since I don't think there are very many ?

http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[:Decomposition_Type=Fraction:]
And frankly they are not very readable.  

Resolving patch:
diff --git a/cirq/circuits/circuit.py b/cirq/circuits/circuit.py
--- a/cirq/circuits/circuit.py
+++ b/cirq/circuits/circuit.py
@@ -20,6 +20,7 @@
 """
 
 from collections import defaultdict
+from fractions import Fraction
 
 from typing import (
     List, Any, Dict, FrozenSet, Callable, Iterable, Iterator, Optional,
@@ -1484,6 +1485,13 @@ def _formatted_exponent(info: protocols.CircuitDiagramInfo,
     # If it's a float, show the desired precision.
     if isinstance(info.exponent, float):
         if args.precision is not None:
+            # funky behavior of fraction, cast to str in constructor helps.
+            approx_frac = Fraction(info.exponent).limit_denominator(16)
+            if approx_frac.denominator not in [2, 4, 5, 10]:
+                if abs(float(approx_frac)
+                    - info.exponent) < 10**-args.precision:
+                    return '({})'.format(approx_frac)
+
             return '{{:.{}}}'.format(args.precision).format(info.exponent)
         return repr(info.exponent)
 


Test patch:
diff --git a/cirq/circuits/circuit_test.py b/cirq/circuits/circuit_test.py
--- a/cirq/circuits/circuit_test.py
+++ b/cirq/circuits/circuit_test.py
@@ -1256,7 +1256,7 @@ def test_diagram_wgate():
         exponent=0.12341234, phase_exponent=0.43214321)
     c = Circuit([Moment([test_wgate.on(qa)])])
     cirq.testing.assert_has_diagram(c, """
-a: ---PhasedX(0.43)^0.12---
+a: ---PhasedX(0.43)^(1/8)---
 """, use_unicode_characters=False, precision=2)
 
 
@@ -1556,7 +1556,7 @@ def test_transposed_diagram_exponent_order():
 │ │      │
 @─┼──────@^0.5
 │ │      │
-│ @──────@^0.125
+│ @──────@^(1/8)
 │ │      │
 """, transpose=True)
 
diff --git a/cirq/contrib/paulistring/convert_gate_set_test.py b/cirq/contrib/paulistring/convert_gate_set_test.py
--- a/cirq/contrib/paulistring/convert_gate_set_test.py
+++ b/cirq/contrib/paulistring/convert_gate_set_test.py
@@ -157,7 +157,7 @@ def test_converts_large_circuit():
             atol=1e-7)
 
     cirq.testing.assert_has_diagram(after, '''
-0: ───Y^0.5───@───[Z]^-0.304───[X]^0.333───[Z]^0.446───@───
+0: ───Y^0.5───@───[Z]^-0.304───[X]^(1/3)───[Z]^0.446───@───
               │                                        │
 1: ───────────@───@────────────────────────────────────@───
                   │
diff --git a/cirq/contrib/paulistring/convert_to_pauli_string_phasors_test.py b/cirq/contrib/paulistring/convert_to_pauli_string_phasors_test.py
--- a/cirq/contrib/paulistring/convert_to_pauli_string_phasors_test.py
+++ b/cirq/contrib/paulistring/convert_to_pauli_string_phasors_test.py
@@ -36,7 +36,7 @@ def test_convert():
         c_orig.to_unitary_matrix(),
         atol=1e-7)
     cirq.testing.assert_has_diagram(circuit, """
-0: ───[X]────────[Z]^0.125─────────
+0: ───[X]────────[Z]^(1/8)─────────
 
 1: ───[Y]^0.25───[Y]^-0.5────[Z]───
 """)
@@ -58,7 +58,7 @@ def test_convert_keep_clifford():
         c_orig.to_unitary_matrix(),
         atol=1e-7)
     cirq.testing.assert_has_diagram(circuit, """
-0: ───X──────────[Z]^0.125───
+0: ───X──────────[Z]^(1/8)───
 
 1: ───[Y]^0.25───H───────────
 """)
diff --git a/cirq/contrib/paulistring/optimize_test.py b/cirq/contrib/paulistring/optimize_test.py
--- a/cirq/contrib/paulistring/optimize_test.py
+++ b/cirq/contrib/paulistring/optimize_test.py
@@ -42,7 +42,7 @@ def test_optimize():
                                     │
 1: ───X───────@───S─────────Y^0.5───@───S───@───S─────────@─────────────
               │                             │             │
-2: ───────────@───X^0.125───────────────────@───X^0.875───@───X^0.125───
+2: ───────────@───X^(1/8)───────────────────@───X^(7/8)───@───X^(1/8)───
 """)
 
     c_opt = optimized_circuit(c_orig)
@@ -58,7 +58,7 @@ def test_optimize():
                        │
 1: ───@───────X^-0.5───@───@────────────────@───Z^-0.5──────────
       │                    │                │
-2: ───@────────────────────@───[X]^-0.875───@───[X]^-0.25───Z───
+2: ───@────────────────────@───[X]^(-7/8)───@───[X]^-0.25───Z───
 """)
 
 
diff --git a/cirq/contrib/paulistring/pauli_string_optimize_test.py b/cirq/contrib/paulistring/pauli_string_optimize_test.py
--- a/cirq/contrib/paulistring/pauli_string_optimize_test.py
+++ b/cirq/contrib/paulistring/pauli_string_optimize_test.py
@@ -51,7 +51,7 @@ def test_optimize():
     assert c_opt == c_expected
 
     cirq.testing.assert_has_diagram(c_opt, """
-0: ───[Y]^-0.5───@───[Z]^-0.125───[X]^0.5───[Z]^0.5───
+0: ───[Y]^-0.5───@───[Z]^(-1/8)───[X]^0.5───[Z]^0.5───
                  │
 1: ──────────────@────────────────────────────────────
 """)
@@ -77,7 +77,7 @@ def test_handles_measurement_gate():
     )
 
     cirq.testing.assert_has_diagram(c_opt, """
-0: ───[Y]^-0.5───@───[Z]^-0.125───[X]^0.5───[Z]^0.5───M('m0')───
+0: ───[Y]^-0.5───@───[Z]^(-1/8)───[X]^0.5───[Z]^0.5───M('m0')───
                  │
 1: ──────────────@───M('m1')────────────────────────────────────
 """)
diff --git a/cirq/ops/three_qubit_gates_test.py b/cirq/ops/three_qubit_gates_test.py
--- a/cirq/ops/three_qubit_gates_test.py
+++ b/cirq/ops/three_qubit_gates_test.py
@@ -208,3 +208,4 @@ def test_diagram():
                       |   |       |
 3: -------------------@---@^0.5---swap----------
 """, use_unicode_characters=False)
+    
\ No newline at end of file