Units and complex numbers

brian2.units do not work with python’s complex numbers. As currently brian2 integrators do not support complex numbers this may not seem like a problem at all. However, if one continues to work with brian2 units also in analytical and symbolic parts of ones code, e.g., comparing spike spectra from Brian simmulations to some theory, one ends up with complex expressions. In these cases it would be great if one can just use the same units system.

One solution to this might be to undertand if brian2’s units were derived and are compatible to some other python unit system, and then see if some of them are complex number compatible.

Cheers and stay healthy!
ttxtea

Hi. Never thought about this much, I wonder whether in practice it would be that useful in practice, given that complex signal processing functions etc. would probably not correctly deal with the units internally. Do you have any concrete example we could discuss?
On a basic level, using the unit system with complex numbers should already work:

>>> x = (np.arange(3) + 1j)*mV + (4 + 2j)*mV
>>> x
array([4.+3.j, 5.+3.j, 6.+3.j]) * mvolt
>>> np.abs(x)
array([5.        , 5.83095189, 6.70820393]) * mvolt
>>> np.mean(x)
5.+3.j * mvolt

Wow, this means basic complex number functionality it is already implemented. I am sorry to not have noticed that. When I do

import brian2
brian2.Hz * 1j

in atom/hydrogen. It results in

`---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/.local/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj)
    343             method = get_real_method(obj, self.print_method)
    344             if method is not None:
--> 345                 return method()
    346             return None
    347         else:

~/.local/lib/python3.8/site-packages/brian2/units/fundamentalunits.py in _repr_latex_(self)
   1605 
   1606     def _repr_latex_(self):
-> 1607         return '`

```
0.+1.j * hertz
``` + latex(self) + '`

```
DISCOURSE_PLACEHOLDER_2
```
   1608 
   1609     def __str__(self):

~/.local/lib/python3.8/site-packages/sympy/printing/printer.py in __call__(self, *args, **kwargs)
    371 
    372     def __call__(self, *args, **kwargs):
--> 373         return self.__wrapped__(*args, **kwargs)
    374 
    375     @property

~/.local/lib/python3.8/site-packages/sympy/printing/latex.py in latex(expr, **settings)
   2913 
   2914     """
-> 2915     return LatexPrinter(settings).doprint(expr)
   2916 
   2917 

~/.local/lib/python3.8/site-packages/sympy/printing/latex.py in doprint(self, expr)
    252 
    253     def doprint(self, expr):
--> 254         tex = Printer.doprint(self, expr)
    255 
    256         if self._settings['mode'] == 'plain':

~/.local/lib/python3.8/site-packages/sympy/printing/printer.py in doprint(self, expr)
    289     def doprint(self, expr):
    290         """Returns printer's representation for expr (as a string)"""
--> 291         return self._str(self._print(expr))
    292 
    293     def _print(self, expr, **kwargs):

~/.local/lib/python3.8/site-packages/sympy/printing/printer.py in _print(self, expr, **kwargs)
    306             if (self.printmethod and hasattr(expr, self.printmethod)
    307                     and not isinstance(expr, BasicMeta)):
--> 308                 return getattr(expr, self.printmethod)(self, **kwargs)
    309 
    310             # See if the class of expr is known, or if one of its super

~/.local/lib/python3.8/site-packages/brian2/units/fundamentalunits.py in _latex(self, expr)
   1599         unitless = np.array(self / best_unit, copy=False)
   1600         if unitless.ndim == 0:
-> 1601             sympy_quantity = np.float(unitless)
   1602         else:
   1603             sympy_quantity = Matrix(unitless)

TypeError: can't convert complex to float`

```
DISCOURSE_PLACEHOLDER_2
```

However, the same expression in normal ipython console works as you have suggested. This must be some issues concerning my installation. So sorry again for presuming complex numbers would not work in general. I’ll try to figgure out what the issues is and report back.

Saying we implemented it is probably an exaggeration :slight_smile: The quantity system basically attaches a number to a unit and knows that e.g. if you add two quantities, the numbers add, but the units don’t change (and have to be the same) – so this means that we don’t really care about whether the numbers are floats or complex, Python/numpy takes care of these calculations.
There are certainly places where our assumption about the numbers being floats “leaks through”, but those shouldn’t be that hard to fix.

The error you are seeing is one of these places: we sometimes use the shorthand np.float(q) to remove the units from a quantity and get the “pure numpy” array. This obviously fails for a complex number, but instead of np.float we could also use np.asarray (which we do in a number of other places), which should fix your specific error.

I don’t think it’s an installation issue, your atom/hydrogen setup simply seems to support fancy LaTeX representations, and the generation of this representation fails – print(brian2.Hz * 1j) probably works just fine.