Repository: qutip/qutip

Issue:
Bloch Sphere Colorbar not working in v5
### Bug Description

Adding points and colors to a `Bloch` object and rendering the `Bloch` object does not result in correctly colored points in QuTiP 5.
All points have the same colors. Running the same code in QuTiP 4 generates the correct output.

### Code to Reproduce the Bug

```shell
import matplotlib as mpl
import numpy as np
from matplotlib import cm
from qutip import Bloch, basis, sesolve, sigmax, sigmay, sigmaz

delta = 0.5 * 2 * np.pi
v = 2.0 * 2 * np.pi
H0 = delta / 2.0 * sigmax()
H1 = v / 2.0 * sigmaz()
H = [H0, [H1, "t"]]
psi0 = basis(2, 0)

tlist = np.linspace(-10.0, 10.0, 1500)
expt_list = sesolve(H, psi0, tlist, [sigmax(), sigmay(), sigmaz()]).expect

b = Bloch()
# normalize colors to times in tlist ##
nrm = mpl.colors.Normalize(-2, 10)
colors = cm.cool(nrm(tlist))

# add data points from expectation values ##
b.add_points([expt_list[0], expt_list[1], -expt_list[2]], "m")

# customize sphere properties ##
b.point_color = list(colors)
b.point_marker = ["o"]
b.point_size = [20]

b.zlpos = [1.1, -1.2]

b.show()
```


### Code Output

```shell
a plot (see below)
```


### Expected Behaviour

I expect the same coloring of points as with version 4. The expected plot is shown below.

### Your Environment

```shell
QuTiP Version:      5.0.0.dev0+c64f38e
Numpy Version:      1.23.0
Scipy Version:      1.8.1
Cython Version:     0.29.30
Matplotlib Version: 3.5.2
Python Version:     3.8.10
Number of CPUs:     4
BLAS Info:          OPENBLAS
INTEL MKL Ext:      False
Platform Info:      Linux (x86_64)
```


### Additional Context

![image](https://user-images.githubusercontent.com/11479319/184672085-4cc30919-2733-4147-b533-02004b69f990.png)

![image](https://user-images.githubusercontent.com/11479319/184672141-1a88d68a-954b-4427-a2ba-eb0707fa9ad6.png)



Discussion:
Thank you @christian512 for reporting this bug! I was aware of it and hoping to fix it as part of #1913. The issue here is that a while ago a change that breaks backwards compatibility was made to the bloch sphere. I think you should see the correct behaviour if you change colours using:
```
b.defaul_point_colors
```
but ideally there should not be a break in backwards compatibility for QuTiP 5.0. I was planing to revert this change as part of #1913 but I have not gone through it yet. 
Hi @Ericgig @AGaliciaMartinez! Is this still open? If yes, can I work on it?

If I understand correctly, the idea is that it should accept any list of colours, not just the default, right? Or as part of reverting the change is it expected to add some new features?
@DnMGalan This is still open yes.  I think just making it backward compatible with the way v4 allowed for the colors to be customized is all that's needed.   In Asier's old PR #1913  there he had some open plans for further improvements, but am not sure how much of a priority those are?
Hi @nwlambert, thank you for your reply. Perfect, then I will focus on solving the issue of accepting colors as in V4.

One question I have is that in V5, the 's' and 'm' point styles use the same list of colors. In the 'add_points' function, it is specified that for the 's' style, only one color should be passed, however, as the task is to maintain backward compatibility, I just wanted to confirm if we should keep that part of the code unchanged.
Yeah, seems like in v5 if you give a list of colors to add_points it ignores whether you choose 's' or 'm' and just plots multicolored. I would keep that as is (since i guess the logic of choosing 's' or 'm'  is more about what default values to choose if the user provides no list of colors), and maybe update the docstring to just say giving a list of colors to add_points will override the choice of 's' or 'm'? 

in v4.7 on the other hand, giving point_color() seems to still obey the choice of s or m used in add_points().  i would say its not so important to preserve that (presumably if you give a list you want multiple colors). @AGaliciaMartinez @Ericgig does this sound reasonable?  




Resolving patch:
diff --git a/qutip/bloch.py b/qutip/bloch.py
--- a/qutip/bloch.py
+++ b/qutip/bloch.py
@@ -169,8 +169,10 @@ def __init__(self, fig=None, axes=None, view=None, figsize=None,
         # ---point options---
         # List of colors for Bloch point markers, default = ['b','g','r','y']
         self.point_default_color = ['b', 'r', 'g', '#CC6600']
+        # Old variable used in V4 to customise the color of the points
+        self.point_color = None
         # List that stores the display colors for each set of points
-        self.point_color = []
+        self._inner_point_color = []
         # Size of point markers, default = 25
         self.point_size = [25, 32, 35, 45]
         # Shape of point markers, default = ['o','^','d','s']
@@ -360,7 +362,7 @@ def add_points(self, points, meth='s', colors=None, alpha=1.0):
         self.point_style.append(meth)
         self.points.append(points)
         self.point_alpha.append(alpha)
-        self.point_color.append(colors)
+        self._inner_point_color.append(colors)
 
     def add_states(self, state, kind='vector', colors=None, alpha=1.0):
         """Add a state vector Qobj to Bloch sphere.
@@ -792,19 +794,21 @@ def plot_points(self):
             dist = np.linalg.norm(points, axis=0)
             if not np.allclose(dist, dist[0], rtol=1e-12):
                 indperm = np.argsort(dist)
-                points = points[:, indperm]
             else:
                 indperm = np.arange(num_points)
 
             s = self.point_size[np.mod(k, len(self.point_size))]
             marker = self.point_marker[np.mod(k, len(self.point_marker))]
             style = self.point_style[k]
-            if self.point_color[k] is not None:
-                color = self.point_color[k]
+
+            if self._inner_point_color[k] is not None:
+                color = self._inner_point_color[k]
+            elif self.point_color is not None:
+                color = self.point_color
             elif self.point_style[k] in ['s', 'l']:
-                color = self.point_default_color[
+                color = [self.point_default_color[
                     k % len(self.point_default_color)
-                ]
+                ]]
             elif self.point_style[k] == 'm':
                 length = np.ceil(num_points/len(self.point_default_color))
                 color = np.tile(self.point_default_color, length.astype(int))
@@ -812,9 +816,9 @@ def plot_points(self):
                 color = list(color)
 
             if self.point_style[k] in ['s', 'm']:
-                self.axes.scatter(np.real(points[1]),
-                                  -np.real(points[0]),
-                                  np.real(points[2]),
+                self.axes.scatter(np.real(points[1][indperm]),
+                                  -np.real(points[0][indperm]),
+                                  np.real(points[2][indperm]),
                                   s=s,
                                   marker=marker,
                                   color=color,
@@ -824,6 +828,7 @@ def plot_points(self):
                                   )
 
             elif self.point_style[k] == 'l':
+                color = color[k % len(color)]
                 self.axes.plot(np.real(points[1]),
                                -np.real(points[0]),
                                np.real(points[2]),


Test patch: