random icon indicating copy to clipboard operation
random copied to clipboard

`Random` instance for `Complex` numbers

Open lehins opened this issue 5 years ago • 10 comments

Few of us have mentioned that Random is no longer useful, because Uniform and UniformRange was introduced as a more correct interface. Complex is an example of a type where Random can be very useful. We can't compare them and we can't produce a uniform distribution due to infinite many values, so there never can be neither Uniform nor UniformRange instances. But should it also mean that we can't have random complex numbers at all? For example I can see this as a sensible instance for complex numbers:

-- | /Note/ - `randomR (z1, z2)` will produce values in a rectangle with a diagonal
-- defined by a difference of `z1 - z2` and `random` will rely on `a` to produce value for
-- both real and imaginary parts.
instance Random a => Random (Complex a) where
  randomR ((al :+ bl), (ah :+ bh)) = runState $
    (:+) <$> state (randomR (al, ah)) <*> state (randomR (bl, bh))
  random = runState $ (:+) <$> state random <*> state random

or even better and more useful alternative could be this definition:

-- | /Note/ - `randomR` produces values in the annulus between two complex numbers and
-- `random` generates values within the unit circle.
instance (RealFloat a, Random a) => Random (Complex a) where
  randomR (z1, z2) = runState $
    mkPolar <$> state (randomR (magnitude z1, magnitude z2)) <*> state (randomR (0, 2*pi))
  random = random = runState $ mkPolar <$> state (randomR (0, 1)) <*> state (randomR (0, 2*pi))

Thoughts?

Note - randomR (0, 2*pi) would need to be adjusted not to include 2*pi, but that is an implementation detail

lehins avatar Jun 30 '20 13:06 lehins

I think it's better to avoid adding such instance. Instance assumes that there's singe reasonable definition of function for a given type. But for Complex we have several possible implementations from the get-go. (Should randomR do linear interpolation between two points?) Since it's easy to add some nonsensical instance and quite difficult to remove/change I think it's better to don't add it and leave generation of Complex numbers using dedicated function.

Shimuuar avatar Jun 30 '20 16:06 Shimuuar

Could you elaborate on that:

leave generation of Complex numbers using dedicated function.

Also. I don't agree that this is a good reasoning: "There are multiple ways to do it, let's not do it at all."

lehins avatar Jun 30 '20 16:06 lehins

Just few functions:

-- | Generate numbers |z| < 1
unitDisk :: StatefulGen g m => g -> m (Complex a)
-- | Generate numbers r1 < |z| < r2
complexRing :: StatefulGen g m => g -> (a,a) -> m (Complex a)
-- | Generate random value uniformly ditributed on a line connecting two points
... {- to lazy to write type signature -}

I think it's perfectly valid reasoning. Defining instance requires picking single implementation. it's easy when there's only one candidate. But when there're multiple choices and not god argument why one and not other should be picked it's better to pick neither. Any choice will lead to confusion and questions why A was chosen and not B, or C

Shimuuar avatar Jun 30 '20 17:06 Shimuuar

I think it's perfectly valid reasoning.

If that was the case there would be no Random Float and Random Double instance. Do we select [0, 1), (1, 0] or [0,1] as a default range? All three have valid reasons, but currently we can't have all of them, so we choose one. That might be a historic incident, but that is the one we have and might as well accept it.

In any case this reasoning cannot be applied to Random class, because we always have to make some sort of choice when we define an instance that doesn't follow the "uniform" laws that apply to UniformRange and Uniform type classes.

Instead of helper functions, I'd normally solve this with newtypes, this way instances for Uniform could be created ;)

newtype UnitCircle a = UnitCircle (Complex a)

instance (RealFloat a, Uniform a) => Uniform (UnitCircle a)

But that approach calls for a separate math library and would be a bit too much to be included in random.

What I want is a sensible and useful default generation of random values for types that are available in base. Random instance that generates complex ring and unit disk sounds like a very useful default. Just as with floating points on [0,1], complex numbers can be scaled and adjusted if need be.

Any choice will lead to confusion and questions why A was chosen and not B, or C

Questions can be answered, choices can be documented.

lehins avatar Jun 30 '20 17:06 lehins

Generating floats in [0,1] (without saying whether 0 is included or not) is long standing convention. So it fallsright into

Random instance that generates complex ring and unit disk sounds like a very useful default.

Problem is it sounds like useful default. We're not being guided by practice we're just guessing. And with high probability we're guessing wrong. Such is nature of guessing. Actually I never needed to generate random complex numbers and can't say anything about good defaults. And if one made wrong choice it's very difficult to change it later.

Shimuuar avatar Jun 30 '20 17:06 Shimuuar

Fine let's talk practical. Here is the way that wolfram does it: https://reference.wolfram.com/language/ref/RandomComplex.html

So, let's implement it the same way: random in a [0, 1] range for both real and imaginary and the range as I suggested it initially taking rectangle as bounds

lehins avatar Jun 30 '20 18:06 lehins

Just checked, Matlab does the same thing:

Generate a single random complex number with real and imaginary parts in the interval (0,1).

lehins avatar Jun 30 '20 18:06 lehins

Rectangle at least has advantage of working same way as tuples.

Shimuuar avatar Jun 30 '20 18:06 Shimuuar

I think the choice is either a rectangular range or no instance at all. I'm neutral here, let's decide about tuples and follow suite for Complex.

Bodigrim avatar Jun 30 '20 20:06 Bodigrim

In Julia, rand(Complex{Float64}) generates something in [0,1)+i[0,1).

dataopt avatar Sep 01 '22 12:09 dataopt