Skip to content

Random instance for Complex numbers #74

Description

@lehins

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

Activity

  1. Shimuuar commented on Jun 30, 2020

    @Shimuuar
    Contributor

    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.

  2. lehins commented on Jun 30, 2020

    @lehins
    ContributorAuthor

    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."

  3. Shimuuar commented on Jun 30, 2020

    @Shimuuar
    Contributor

    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

  4. lehins commented on Jun 30, 2020

    @lehins
    ContributorAuthor

    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.

  5. Shimuuar commented on Jun 30, 2020

    @Shimuuar
    Contributor

    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.

  6. lehins commented on Jun 30, 2020

    @lehins
    ContributorAuthor

    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

  7. lehins commented on Jun 30, 2020

    @lehins
    ContributorAuthor

    Just checked, Matlab does the same thing:

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

  8. Shimuuar commented on Jun 30, 2020

    @Shimuuar
    Contributor

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

  9. Bodigrim commented on Jun 30, 2020

    @Bodigrim
    Contributor

    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.

  10. dataopt commented on Sep 1, 2022

    @dataopt

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions