Skip to content

Instantly share code, notes, and snippets.

@shannonwells
Last active April 18, 2016 16:56
Show Gist options
  • Save shannonwells/addd4eade8bb62680bdc to your computer and use it in GitHub Desktop.
Save shannonwells/addd4eade8bb62680bdc to your computer and use it in GitHub Desktop.
A Sphere class that can give you random, uniformly distributed coordinates on the surface of a sphere. Default creates a unit sphere. You can set the sphere's radius directly or set it by its known volume ("Assume a spherical cow...")
# https://www.jasondavies.com/maps/random-points/
require 'rspec'
require 'awesome_print'
class Sphere
attr_accessor :radius, :units
def initialize(radius=1.0)
srand
@radius = radius*1.0
end
# We want φ ∈ [-90°, 90°)
# φ = cos^(-1)(2x - 1), where x is uniformly distributed and x ∈ [0, 1)
def random_phi(units = :radians)
raise StandardError.new("only #{self.class.allowed_angular_units} allowed.") unless self.class.allowed_angular_units.include? units
x = rand
# acos returns [ 0 - π )
rads = Math.acos(2*x - 1) - Math::PI/2
return rads if units == :radians
return rads*180/Math::PI
end
# pick λ ∈ [-180°, 180°)
def random_theta(units = :radians)
degrees = rand*360 - 180
raise StandardError.new("only #{self.class.allowed_angular_units} allowed.") unless self.class.allowed_angular_units.include? units
return degrees if units == :degrees
return degrees*Math::PI/180
end
def random_latitude
random_phi(:degrees)
end
def random_longitude
random_theta(:degrees) + 360
end
def volume
Math::PI*radius*radius*radius
end
def area
4*Math::PI*radius*radius
end
# takes the new volume and sets the radius.
def volume=(new_volume)
float_vol = new_volume * 1.0
self.radius = Math.cbrt(float_vol/Math::PI)
end
private
def self.allowed_angular_units
[:radians, :degrees]
end
end
@shannonwells
Copy link
Author

# tests for above.
require 'rspec'
require 'awesome_print'
require './sphere'
sphere = Sphere.new
phis = []
thetas = []
100.times do
  phis << sphere.random_phi(:degrees)
  thetas << sphere.random_theta(:degrees)
  # phis << sphere.random_phi()
  # thetas << sphere.random_theta()
end
ap phi: phis
ap theta: thetas
ap min_phi: phis.min, max_phi: phis.max, avg_phi: phis.reduce(:+)/phis.length,
   min_theta: thetas.min, max_theta: thetas.max, avg_theta: thetas.reduce(:+)/thetas.length


RSpec.describe Sphere do
  let(:sphere) { Sphere.new }
  it "sphere is initialized" do
    expect(sphere.area).to be_within(0.009).of(12.56)
    expect(sphere.volume).to be_within(0.0006).of(3.141)
  end

  it "'s volume can be set'" do
    sphere.volume = 225.00
    expect(sphere.radius).to be_within(0.00004).of(4.1528)
  end

  it "can give a random theta" do
    expect(sphere.random_theta >= Math::PI*-1).to be_truthy
    expect(sphere.random_theta <= Math::PI).to be_truthy
  end
  it "can give a random phi" do
    expect(sphere.random_phi >= Math::PI/-2.0).to be_truthy
    expect(sphere.random_phi <= Math::PI/2.0).to be_truthy
  end

  it "can give a random theta in degrees" do
    expect(sphere.random_theta(:degrees) >= -180.0).to be_truthy
    expect(sphere.random_theta(:degrees) <= 180.0).to be_truthy
  end
  it "can give a random phi" do
    expect(sphere.random_phi(:degrees) >= -90.0).to be_truthy
    expect(sphere.random_phi(:degrees) <= 90.0).to be_truthy
  end

end

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