2

If $a \le b \le c$ be the length of the sides of a triangle of area $A$ whose vertices are uniformly random on a circle of radius $R$, and let its inradius be $r$. The largest deficit between the sum of two sides and the third side is $c+b-a$. We can compare this to the quantity $R + r + \sqrt{A}$ since both are in the units of length. What is the probability that the former is greater than the latter? Numerical evidences with more than $2 \times 10^9$ gives a probability of $0.749938$ which is suspiciously close to $3/4$.

Question: Is it true that

$$ P(c+b-a > R + r + \sqrt{A}) = \frac{3}{4} ? $$

If true, this will be another example of a geometric probabilities having a rational value. In that case, if there an intuitive explanation why it is $3/4$?

  • 3
    After 10 billion tests, I found probability to be 75.00138749%. I have also changed random number generators on every 30 million tests to avoid bias that is often found in software randomizers. If someone wants to push things further I will gladly share my Java code (heavily parallelized, it uses every single core in your CPU). – Oldboy Dec 31 '24 at 20:19
  • @Oldboy That is a huge search range. Yes please, could you share your oprimized code. On what hardware did you run this code to reach ten billion ans in how much runtime? – Nilotpal Sinha Jan 01 '25 at 03:00
  • 1
    @Niloptal Ok, I'm sending my code now. It will cost you one upvote :) – Oldboy Jan 01 '25 at 08:57
  • 1
    @Oldboy Done. Paid :) – Nilotpal Sinha Jan 01 '25 at 09:05
  • 1
    @Niloptal Thanks! This time next year I'll be a millionaire :) Happy new your to you nad all your family! – Oldboy Jan 01 '25 at 09:27
  • Your simulation with $2\times 10^9$ trials yielded a proportion that differs from $0.75$ by $0.000062$. If the probability in question is really $0.75$, then the probability that the simulation's proportion differs from $0.75$ by at least that much, is approximately $1.52\times 10^{-10}$. So I doubt that the probability in question is really $0.75$. – Dan Jan 02 '25 at 04:48
  • @Dan You have a point. But while on the one one hand my simulation is less than $0.75$ by $0.000062$, Oldboy's simulation is greater than $0.75$ by $0.0013$ so there is still a chance that the true value might still converge to $0.75$ for much larger simulations. – Nilotpal Sinha Jan 02 '25 at 08:16
  • @NilotpalSinha If the true probability is $0.75$ then the probability of Oldboy's results (including anything even further from $0.75$) is also very small, approximately $0.00135$. But this is strange: your simulation suggests that the true probability is less than $0.75$, and Oldboys' simulation suggests that the true probability is greater than $0.75$. Maybe a mistake was made somewhere, concerning which side of $0.75$ to measure. – Dan Jan 02 '25 at 08:27
  • @Dan One explanation for this strange behavior could be that simulation usually oscillates towards its convergent value so after any number of simulations, its current value will be $>$ or $<$ the target for a long range i.e. it will cross the $0.75$ mark infinitely many times as seen the graph of this problem https://math.stackexchange.com/questions/4836901/if-a-b-c-are-the-sides-of-a-triangle-what-is-the-probability-that-acb2 . – Nilotpal Sinha Jan 02 '25 at 08:37
  • 1
    On an analytic note, assuming that the probability is indeed $\frac{3}{4}$, then the problem is equivalent to proving $$P\left( s-a > \left(1 + \sqrt{\pi}\right) \frac{abc}{4\sqrt{s(s-a)(s-b)(s-c)}} + \sqrt{\frac{(s-a)(s-b)(s-c)}{s}} \right)= \frac{3}{4}$$ Maybe treating the RHS as a rational function might work? – Kraken Jan 02 '25 at 09:21
  • 2
    A quick and dirty way of ruling out $3/4$ is to run $N$ sims where for each sim we estimate the probability by generating $M$ triangles (for a total of $NM$ total triangles). These estimates will roughly be normally distributed and you can compute the mean and the error on the mean ($\sigma/\sqrt{N}$). Using $N=1000$ with $M=5\cdot 10^6$ for a total of $5$ billion triangles. We can do this in a min on a modern laptop. I find $\mu = 0.749941$ with an estimated error of $0.000006$. The value $3/4$ is over $10$ standard deviations away from $3/4$ - so its extremely unlikely its $3/4$. – Winther Jan 04 '25 at 12:29

2 Answers2

2

Ok, upon OP's request I'm sending my optimized java code.

  import java.util.Random;
  import java.util.concurrent.ArrayBlockingQueue;
  import java.util.concurrent.ThreadPoolExecutor;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicLong;

public class TriangleChecker { // We are going to use 1 thread per core. MacMini with M4 silicon has 10 cores public static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors();

// Intermediate test results will be reported after REPORT_BATCH number of tests
public static final long REPORT_BATCH = 1_000_000L;

// Total number of tests that we are going to run;
public static final long TEST_COUNT = 10_000_000_000L;

// Increase RND_TRESHOLD if you want to change random number generator more often
// Keep it small - if it is too big, it will impact performance
public static final double RND_TRESHOLD = 0.00000001;

// TASK_QUEUE_SIZE defines the maximum number of queued tests, 
// you don't need to change it, making it bigger will *not* make the code faster
public static final int TASK_QUEUE_SIZE = 100;

public static void main(String[] args) {
  ThreadPoolExecutor threadPoolExecutor = 
    new ThreadPoolExecutor(THREAD_COUNT, THREAD_COUNT, 0, TimeUnit.SECONDS, 
    new ArrayBlockingQueue&lt;&gt;(TASK_QUEUE_SIZE));
  // This handler is triggered when the task queue is full
  threadPoolExecutor.setRejectedExecutionHandler((task, executor) -&gt; {
    try {
      // Let's submit the task again. The pool is using blocking queue.
      // So main thread will wait until place in the queue becomes available
      executor.getQueue().put(task);      
    }
    catch(InterruptedException ie) {
      System.err.println(&quot;Interrupted while submitting a new test: &quot; + ie.toString());
    }      
  });
  System.out.println(
      String.format(&quot;Created thread pool with %d threads&quot;, THREAD_COUNT)
  );
  AtomicLong okCount = new AtomicLong(0), totalCount = new AtomicLong(0);
  while(totalCount.get() &lt; TEST_COUNT) {
    threadPoolExecutor.submit(() -&gt; {
      if(new Triangle().isOk()) {
        okCount.incrementAndGet();
      }
      long total = totalCount.incrementAndGet();
      if(total % REPORT_BATCH == 0) {
        double pct = 100 * (double) okCount.get() / (double) total;
        System.out.println(
            String.format(&quot;Current percentage: %.8f (after %,d runs)&quot;, pct, total)
        );
      }
    });
  }
  threadPoolExecutor.close();
}

}

class Triangle { // WLOG, we can assume that R=1 private final double a, b, c, r, area;

public Triangle() {
  // Triangle has 3 random points on a unit circle
  Point pA = new Point(), pB = new Point(), pC = new Point();
  double d1 = pA.distance(pB), d2 = pB.distance(pC), d3 = pC.distance(pA);
  double ccf = d1 + d2 + d3; 
  a = Math.min(d1, Math.min(d2, d3));
  c = Math.max(d1, Math.max(d2, d3));
  b = ccf - a - c;
  double s = ccf / 2D;
  // Heron's formula
  area = Math.sqrt(s * (s - a) * (s - b) * (s - c));
  r = area / s;
}

// This is the test that we are making for every random triangle
public boolean isOk() {
  return c + b - a &gt; 1D + r + Math.sqrt(area);
}

}

// Random point on a unit circle class Point { private static Random random = new Random(); private static final double fullCircle = 2D * Math.PI; private final double angle = random.nextDouble(fullCircle); private final double x = Math.cos(angle), y = Math.sin(angle);

Point() {
  if(random.nextDouble() &lt; TriangleChecker.RND_TRESHOLD) {
    System.out.println(&quot;--- Changing random number generator ---&quot;);
    random = new Random();
  }
}

double distance(Point p) {
  double dx = x - p.x, dy = y - p.y;
  return Math.sqrt(dx * dx + dy * dy);
}

}

Just put the code in a file named TriangleChecker.java. Adjust the program parameters (THREAD_COUNT, REPORT_BATCH, TEST_COUNT, RND_TRESHOLD) Then compile and run it:

javac TriangleChecker.java
java TriangleChecker

Becuase, this is Java, you can run it on every platform, on every machine imaginable. On my brand new MacMini with M4 silicon (10 cores), I was able to execute 10 billion tests in just a few (2-3) hours. Performance on my Mac laptop with M1 silicon was slightly lower so I gave up early and turned to M4. My guess is that on a comparable hardware, the program could easily complete 100 billion tests in about a day. I haven't had the opportunity to test the performance on some modern day Intel or AMD chip.

Hope this helps. If you want some adjustments, please let me know.

Oldboy
  • 17,264
  • 1
    I hadn't timed my unoptimized Julia code before but out of curiosity, I ran it for $10$ billion simulations on my Macbook M3 to compare against your runtime. My code is not optimized or parallelized and basically runs end to end on a single thread. Even though I did not change the random number generator every $30$ simulations, it still rand $10$ simulations in just $26$ mins. Julia is crazy fast !!! t. I think when properly parallelized Julia could run even faster. Sharing my code below as an answer. – Nilotpal Sinha Jan 01 '25 at 13:24
  • 1
    Just one small note: the "making a new random" is not doing what you think it does. You do not make it "more random" and avoid any bias simply by reseeding the RNG (which is effectively what this does). If the RNG did have some bias then you would need a completely different algorithm (e.g. math.random), not a new instance of the same algorithm with a different seed. – Winther Jan 04 '25 at 11:54
1

Not an answer but sharing my unoptimized and unparallelized Julia code runs $10$ billion simulations in just $26$ mins on a Macbook M3 Pro. Julia is crazy fast !!! t. If properly parallelized Julia could run even faster.

@time begin
using Random
using LinearAlgebra
using StaticArrays

@inline function calculate_side_lengths(x, y) # Calculate distances between points l = hypot(x[2] - x[1], y[2] - y[1]) m = hypot(x[3] - x[2], y[3] - y[2]) n = hypot(x[1] - x[3], y[1] - y[3]) return l, m, n end

function main() count = 0 f = 0 twopi = 2 * π while count < 10_000_000_000 count += 1 # Generate random angles directly angles = SVector(rand(SVector{3, Float64}) .* twopi) x = SVector(cos(angles[1]), cos(angles[2]), cos(angles[3])) y = SVector(sin(angles[1]), sin(angles[2]), sin(angles[3]))

    # Calculate side lengths
    l, m, n = calculate_side_lengths(x, y)

    # Sort lengths directly for a, b, c
    lengths = sort(SVector(l, m, n))  # Use SVector and sort it
    a, b, c = lengths[1], lengths[2], lengths[3]

    # Semi-perimeter and area
    s = (a + b + c) * 0.5
    area = sqrt(s * (s - a) * (s - b) * (s - c))
    r = area / s

    # Check condition
    if c + b - a &gt; 1 + r + area^0.5
        f += 1
    end

    # Print status periodically
    if count % 100_000_000 == 0
        println((count, f, f / count))
    end
end

end

main() end

enter image description here

  • 1
    There is no need to draw 3 random points. You can take advantage of the symmetry of the problem and just fix the first point to, say, $(0,1)$ (i.e. we simply align one axis of the coordinate system we use to point along the first point drawn). This reduces the random numbers you have to generate by 33%. – Winther Jan 03 '25 at 18:04
  • @Winther Thats true, I have that in the optimized version. The overall gain however is barely $3%$ in Julia since very little time is spend on the random number generation compared to computing sine, cosines and distance. – Nilotpal Sinha Jan 03 '25 at 19:09
  • 1
    Ok I see. If you needed to make it faster then using a fully compiled language with a good and fast RNG (e.g. Mersenne twister or PCG or similar) you could probably speed it up with a factor 3-4 + another factor of 3-4 with threads and get something like ~10 billion evaluations per minute. But anyway, its more than fast enough already for the purposes of just ruling out 0.75 as the true answer. – Winther Jan 04 '25 at 12:02