18
$\begingroup$

I want to find the first 5 prime numbers of the form $n^6 + 1091$. I have used this code:

 Timing[Select[Table[n^6 + 1091, {n, 10000}], PrimeQ, 5]]

Which gives the desired answer, and takes 0.0156 seconds. What if I didn't have a rough idea of what values of $n$ to check?

Timing[Select[Table[n^6 + 1091, {n, 100000}], PrimeQ, 5]]

Takes 10 times as long.

So, my question is: Is there a simple(ish) way to make Mathematica stop when the desired number of solutions are found?

$\endgroup$
5
  • 6
    $\begingroup$ Please don't be so hasty to Accept answers. I am working on one now and it is a discouragement to see an Accept given before I even have a chance to finish. $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 11:32
  • $\begingroup$ Sorry, I am fairly new to this. I have unaccepted. $\endgroup$ Commented Mar 18, 2013 at 11:34
  • $\begingroup$ Says the man who has the fastest gun in the west :) (I agree though not to accept answers too quickly) $\endgroup$ Commented Mar 18, 2013 at 11:34
  • $\begingroup$ @IstvánZachar Fast? I just spent 45 minutes writing what is really a very simple answer. $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 12:17
  • $\begingroup$ Thanks to everybody for the help, I have learnt something from each answer, and I would have chosen more than one as the official answer, if I could have. $\endgroup$ Commented Mar 18, 2013 at 17:12

5 Answers 5

18
$\begingroup$

A more efficient use of Select

If the likely bound of the problem is easily stored in memory it is practical to generate a Range, which is fast, and then Select from that. Since the Range will be unpacked by Select you must consider the unpacked size. For example:

ByteCount @ Developer`FromPackedArray @ Range@1*^7

240 000 032

This is a reasonable starting size in many cases.

Finding five values take a quarter of a second:

Select[Range@1*^7, PrimeQ[#^6 + 1091] &, 5] // Length // Timing
{0.249, 5}

Fining all values with a seed <= 10^7:

Select[Range@1*^7, PrimeQ[#^6 + 1091] &] // Length // Timing
{19.797, 3338}

You can see that this scales pretty well, without excessive overhead in the case that values are found quickly, yet for your search 3338 results can be found before the limit is reached.

(The seeds found with this method need to be converted to primes with #^6 + 1091 &.)

A method for larger problems

If this will not work, either because you have no idea what the likely upper bound is, or it is too high to hold a Range in memory, then it will be most efficient to operate in blocks, due to Mathematica's vector optimizations.

First, there is a more efficient way to build the list of candidates:

Range[100]^6 + 1091

This takes full advantage of the vectorized operations available.

Pick a large enough block size that the average element processing time is relatively low, but not so large as to process more elements than are likely needed. I will pick a block size of 100,000 and I will try to find 5,000 solutions:

block = 100000;

result = {};

find = 5000;

hits = 0;

Do[
  If[hits >= find, Return["Done!"]];
  Select[(n block + Range[block])^6 + 1091, PrimeQ] //
   (result = {result, #}; hits += Length@#) &,
  {n, 0, 1*^9}
] ~Monitor~ n

The results are stored in a linked list rather than using Sow. This has the advantage of letting you open a sub-session and examine the results up to that point. For example, in a separate cell enter:

Flatten @ result // Short

And use F7 to show the results, then resume calculation.

The Monitor lets you see how many blocks have been processed.


Timings

I made the claim that these methods are efficient. Let me give some comparative timings to support my position. WReach's lazy lists code which Rojo used in his answer is a wonderful approach. It is not however, as written, fast. My methods are by comparison clunky but they are also more practical.

I will use a variation of Timo's timeAvg function:

SetAttributes[timeAvg, HoldFirst]

timeAvg[func_] := Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 15}]

Compared to my first method:

Table[
  {n,
   Select[Range@1*^7, PrimeQ[#^6 + 1091] &, n] // timeAvg,
   sIntegers[] ~sMap~ (#^6 + 1091 &) ~sFilter~ PrimeQ ~sTake~ n // sList // timeAvg
  },
  {n, {5, 15, 50, 150, 500}}
] // TableForm[#, TableHeadings -> {None, {"n", "Select", "Lazy"}}] &

Mathematica graphics

My Select method shows the overhead of generating and unpacking the Range but it soon catches up and exceeds the lazy lists method in performance. Remember also the human overhead of writing this if it is not going to be used many times. The Select method is very simple and direct.

Now, for my second method there is a tuning parameter: the block size. It could be argued that changing this parameter mid-test is not fair play so I will use a fixed block size of 1000.

finder[find_, block_: 1000] := Module[{result = {}, hits = 0},
  Do[
    If[hits >= find, Return[Flatten@result ~Take~ find]];
    Select[(n block + Range[block])^6 + 1091, PrimeQ] //
      (result = {result, #}; hits += Length@#) &,
    {n, 0, 1*^9}
  ]
]

Table[
  {n,
   finder[n] // timeAvg,
   sIntegers[] ~sMap~ (#^6 + 1091 &) ~sFilter~ PrimeQ ~sTake~ n // sList // timeAvg
  },
  {n, {5, 15, 50, 150, 500, 1500, 5000}}
] // TableForm[#, TableHeadings -> {None, {"n", "Finder", "Lazy"}}] &

Mathematica graphics

Here the superiority of the block-based method is fully apparent.

$\endgroup$
5
  • $\begingroup$ +1 for the useful tutorial! May I ask why you are using nested lists in result= {result, #} instead of AppendTo? Is it to keep track of how far you've gone by looking at the depth of result? $\endgroup$
    – gpap
    Commented Mar 18, 2013 at 12:37
  • 1
    $\begingroup$ @gpap Linked lists are faster than AppendTo, yet provide the option of viewing intermediate results (unlike Sow). See the bottom of this page for a reference. Faster is Sow/Reap. The ultimate solution is Internal`Bag and friends, which is slightly faster than Sow/Reap and yet makes it possible to view results, but I didn't think that level of optimization was needed here. $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 12:46
  • $\begingroup$ thanks for that - favorited a couple for further reading. $\endgroup$
    – gpap
    Commented Mar 18, 2013 at 12:59
  • $\begingroup$ Splitting it into blocks like that is clever, and, I think, very readable, so I am selecting this as the answer. $\endgroup$ Commented Mar 18, 2013 at 17:11
  • $\begingroup$ @nick Thank you, I hope this method serves you well. If you are handling a very large problem you'll want to save results as you go, like this. $\endgroup$
    – Mr.Wizard
    Commented Mar 19, 2013 at 0:54
13
$\begingroup$

Using @WReach's lazy lists, you can neatly solve this

sIntegers[] ~sMap~ (#^6 + 1091 &) ~sFilter~ PrimeQ ~sTake~ 5 // sList
$\endgroup$
6
  • $\begingroup$ Shiny. I need to make better use of those constructs. +1 $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 12:50
  • 1
    $\begingroup$ By the way, what does the "s" stand for? Slothful? :^) $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 13:00
  • $\begingroup$ +1. I did somewhat similar thing, but conceptually yor way is better - if one goes with lazy lists, one should do it consistently. $\endgroup$ Commented Mar 18, 2013 at 13:13
  • $\begingroup$ @Mr.Wizard haha, he called them streams $\endgroup$
    – Rojo
    Commented Mar 18, 2013 at 13:20
  • 1
    $\begingroup$ Ah, I missed that. Also, just a reminder, please add extra space around ~infix~ operators, especially when they are not built-in functions. This greatly improves the legibility of the code. $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 13:21
9
$\begingroup$

Use While:

primes = {}; i = 1;
AbsoluteTiming[While[Length@primes < 10,
   i++; n = i^6 + 1091; If[PrimeQ@n, AppendTo[primes, n]]]; primes]
{0.046800, {3551349655007944406147, 9724154565432384001091, 
  19007561792329201176707, 24141016911038304486467, 
  26494507823224896001091, 301789003173921081001091, 
  4871535877925849664001091, 7484471576562177870314627, 
  9255722232902778801001091, 33270780762388607695743107}}

For comparison:

  AbsoluteTiming[Select[Table[n^6 + 1091, {n, 100000}], PrimeQ, 10]]
{0.234001, {3551349655007944406147, 9724154565432384001091, 
  19007561792329201176707, 24141016911038304486467, 
  26494507823224896001091, 301789003173921081001091, 
  4871535877925849664001091, 7484471576562177870314627, 
  9255722232902778801001091, 33270780762388607695743107}}

You can also break out of inbuilt functions, though in that case I suggest using Do instead of Table (no use of building a table if it will be aborted before completion), and sowing found solutions on the fly with Sow & Reap:

c = 0;
AbsoluteTiming[
 First@Last@
   Reap@Do[If[PrimeQ[n^6 + 1091], Sow[n^6 + 1091]; c++; 
      If[c == 10, Break[]]], {n, 100000}]]
{0.062400, {3551349655007944406147, 9724154565432384001091, 
  19007561792329201176707, 24141016911038304486467, 
  26494507823224896001091, 301789003173921081001091, 
  4871535877925849664001091, 7484471576562177870314627, 
  9255722232902778801001091, 33270780762388607695743107}}
$\endgroup$
6
  • $\begingroup$ Thanks István! I was looking for a more "Mathematica" way though. Using procedural approaches tends to slow things down a lot ie. This takes 3 times as long as the initial solution. Mathematica clearly has faster inbuilt code, it's just a case of getting it to stop. $\endgroup$ Commented Mar 18, 2013 at 11:19
  • 1
    $\begingroup$ Well, your problem specifically ask for a procedural break when condition is met. If you check timings, you will see that it depends on your range of scope (10000), and thus While is indeed faster. $\endgroup$ Commented Mar 18, 2013 at 11:21
  • $\begingroup$ Yep. You're right. Is there a way to get the inbuit algorithms to stop, though? $\endgroup$ Commented Mar 18, 2013 at 11:22
  • $\begingroup$ @nickjamesuk since you have a PrimeQ in the loop, it's very unlikely that an extra, slow addition will be more expensive than that. Using Do or While or whatever should not make a difference, speed-wise. $\endgroup$
    – acl
    Commented Mar 18, 2013 at 11:23
  • $\begingroup$ I like the Sow and Reap thing, I have always wanted to work out how they are used. Thanks so much for all your work. $\endgroup$ Commented Mar 18, 2013 at 11:30
6
$\begingroup$

You can use NestWhile but I doubt it will be much (any?) faster than the one @IstvánZachar gave you.

prime = NestWhile[# + 1 &, # + 1, ! PrimeQ@((# + 1)^6 + 1091) &] &
((# + 1)^6 + 1091) & /@ NestList[prime, 1, 6]~Drop~1

The pure function prime is easier to read if you change the middle argument of NestWhile i.e.

NestWhile[# + 1 &, 2, ! PrimeQ@((# + 1)^6 + 1091) &]

looks for the number (after 2) for which $ \text{number}^6 + 1091 $ is prime. So, turning this into a pure function, you can then apply it repeatedly for every number it spits out using NestList.

For some reason NestList produces its zeroth evaluation (the number 1) and I want to get rid of that from the list using Drop[ListofNumbersINeed,1] which in Infix notation is ListofNumbersINeed~Drop~1. (I learnt that from @MrWizard and it sometimes makes code easier to understand - not the case here:) ).

I then apply the pure function ((# + 1)^6 + 1091) & to the list to get the actual primes. There is bound to be a better way to do the whole thing in one go or with much more obscure notation or in a hundredth of the speed - give it a couple of hours/days and you'll see what I mean.

$\endgroup$
5
  • $\begingroup$ I think my Mathematica knowledge is not yet at the stage where I can parse that. What does the ~Drop~ do at the end? $\endgroup$ Commented Mar 18, 2013 at 11:33
  • $\begingroup$ I edited to make it a little clearer - NestWhile, NestList, Function and Infix are the relevant pages. $\endgroup$
    – gpap
    Commented Mar 18, 2013 at 11:54
  • $\begingroup$ Ah, so the ~Drop~ is Infix notation. Thanks very much for the clarification. $\endgroup$ Commented Mar 18, 2013 at 14:06
  • $\begingroup$ @nickjamesuk I'm largely the one to blame for the proliferation of that syntax around here. It apparently trips up a lot of people upon first seeing it, but after extensive use I find that it is often easier to read than the nested-bracket nightmares that can be the alternative. If you use a string of infix, as shown in Rojo's answer, it is very important IMHO to add space around each operator to distinguish these from arguments. Once this is done however code reads left to right in a logical fashion, e.g. a ~p~ b ~q~ c ~r~ d, which is equivalent to r[q[p[a, b], c], d]. $\endgroup$
    – Mr.Wizard
    Commented Mar 18, 2013 at 14:35
  • $\begingroup$ That doesn make sense. I'll be looking out for ways to utilise it. $\endgroup$ Commented Mar 18, 2013 at 17:06
5
$\begingroup$

Inspired by gpap's answer here is a use of NestWhileList that I cannot recommend it practice but which I had fun writing. (It's much slower than the vectorized methods in my other answer.)

n = 5;

NestWhileList[
 If[PrimeQ[(++i)^6 + 1091], i, ## &[]] &,
 i = 1, 
 Length@{##} < n &,
 All
]

The "vanishing function" ##&[] is used to silently remove any steps that don't yield a solution, and the All parameter passes all found solutions to the length check function, which stops the loop when n solutions are found.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.