Category Archives: Julia

A Fun Exploration of Perfect, Abundant, and Deficient Numbers

By: Jacob Zelko

Re-posted from: https://jacobzelko.com/05102023043333-perfect-abundant-deficit/index.html

A Fun Exploration of Perfect, Abundant, and Deficient Numbers

Date: May 10 2023

Summary: A computational treatment and exploration of abundant and deficient numbers

Keywords: #blog #abundant #deficient #number #theory #julia #programming #perfect #aliquot #sequence #archive

Bibliography

Not Available

Table of Contents

    1. Motivation
    2. What Are These Numbers?
    3. Examining Divisors of These Numbers
      1. Abundant Numbers
      2. Deficient Numbers
    4. Any Connection to Aliquot Sequences?
      1. Aliquot Sequences of Abundant Numbers
      2. Aliquot Sequences of Deficient Numbers
    5. Conclusion
  1. How To Cite
  2. References:
  3. Discussion:

Motivation

I was at the gym working out when I started thinking about locker numbers in the men's locker room. I was reminded of perfect numbers and was thinking about perfect number examples. I started testing random numbers in my mind and noticed numbers which had divisors that summed up to greater than their number and also less than their number. I had no idea about the existence of abundant and deficient numbers and got curious about these numbers and to see what characteristics I could find about them.

What Are These Numbers?

In Number Theory, there exist three species of numbers that depend on the divisors of a given number (excluding the number itself as a divisor). Here are the three species and their simple characteristics:

Deficient Numbers – these numbers have divisors whose sum is never greater than the number being examined. An example is the number \(4\) which has as divisors \(1\) and \(2\) – those divisors only sum up to \(3\).

Perfect Numbers – these numbers have divisors which sum to exactly to the number being examined. An example is the number \(6\) which has as divisors \(1, 2, 3\) which sum together to \(6\).

Abundant Numbers – these numbers have divisors whose sum is greater than the number being examined. An example is the number \(12\) whose divisors are \(1, 2, 3, 4, 6\) and sum to \(16\).

As it turns out, there are infinite deficient, perfect, and abundant numbers. However, only around 50 perfect numbers have ever been discovered to this day! \(6\) is the smallest perfect number but then perfect numbers grow to be hundreds of digits long! For that reason, this fun exploration will really only explore abundant and deficient numbers.

Examining Divisors of These Numbers

Out of curiosity, I wanted to know if there were any trends to be noticed in the divisors of the deficient and abundant number species. So, I whipped together some code to explore this within Julia (if you are not interested in the code, you can skip it and just go to the results for each section). To get started, I first defined a function to calculate divisors of a number:

import Primes: factorfunction divisors(n)    d = Int64[1]    for (p, e) in factor(n)        t = Int64[]
        r = 1        for i in 1:e
            r *= p
	    for u in d
	        push!(t, u * r)
	    end
	end	append!(d, t)
    end    return sort!(d)end

With this function defined, now, I am going to calculate some deficient and deficient numbers (since perfect numbers are hard to calculate, I am going to look up a few to explore). To do that, we will use the following snippet to find \(1000\) abundant and deficient numbers:

i = 1 
deficient_numbers = []
abundant_numbers = []
while true
  divisor_sum = divisors(i)[1:end-1] |> sum
  if divisor_sum < i && length(deficient_numbers) != 1000
    push!(deficient_numbers, i)
  elseif divisor_sum > i && length(abundant_numbers) != 1000
    push!(abundant_numbers, i)
  end  i += 1  length(abundant_numbers) == 1000 && length(deficient_numbers) == 1000 ? break : continue
end

We are set to explore further these numbers!

Abundant Numbers

As a first pass, let's calculate the divisors of the abundant numbers and plot their frequency:

import DataStructures: counter
import UnicodePlots: barplotabundant_divisors = vcat(divisors.(abundant_numbers)...) |> counter |> Dict |> sort;vs = collect(values(abundant_divisors));
ks = collect(keys(abundant_divisors));barplot(ks[1:20], vs[1:20], xlabel = "Count", ylabel = "Divisors", title = "Divisor Count for First 1000 Abundant Numbers")

Which gives the following plot:

Divisor Count for First 1000 Abundant Numbers 
               ┌                                        ┐ 
             1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 000   
             2 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 995     
             3 ┤■■■■■■■■■■■■■■■■■■■■■■ 677                
             4 ┤■■■■■■■■■■■■■■■■■■■■■ 623                 
             5 ┤■■■■■■■■■■ 308                            
             6 ┤■■■■■■■■■■■■■■■■■■■■■■ 672                
             7 ┤■■■■■■■ 216                               
             8 ┤■■■■■■■■■■■ 347                           
             9 ┤■■■■■■■■ 229                              
   Divisors 10 ┤■■■■■■■■■■ 303                            
            11 ┤■■■■ 116                                  
            12 ┤■■■■■■■■■■■ 336                           
            13 ┤■■■ 96                                    
            14 ┤■■■■■■■ 211                               
            15 ┤■■■■■ 139                                 
            16 ┤■■■■■■ 188                                
            17 ┤■■ 68                                     
            18 ┤■■■■■■■ 224                               
            19 ┤■■ 61                                     
            20 ┤■■■■■■■ 201                               
               └                                        ┘ 
                                  Count

Without any real methodology, what I notice is that there seems to be an interesting pattern where certain divisors are being repeated more than others as more and more divisors are found. It almost feels like a kind of decaying sequence where counts seems to spike on any multiple of \(3\) or \(4\) more consistently than any other number. Even though, it seems like multiples of \(3\) are not as consistent.

Deficient Numbers

Now, let's calculate the divisors of the deficient numbers and plot their frequency:

import DataStructures: counter
import UnicodePlots: barplotdeficient_divisors = vcat(divisors.(deficient_numbers)...) |> counter |> Dict |> sort;vs = collect(values(deficient_divisors));
ks = collect(keys(deficient_divisors));barplot(ks[1:20], vs[1:20], xlabel = "Count", ylabel = "Divisors", title = "Divisor Count for First 1000 Deficient Numbers")

Which gives the following plot:

Divisor Count for First 1000 Deficient Numbers 
               ┌                                        ┐ 
             1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 000   
             2 ┤■■■■■■■■■■■ 337                           
             3 ┤■■■■■■■ 220                               
             4 ┤■■■■ 124                                  
             5 ┤■■■■■■ 168                                
             7 ┤■■■■ 119                                  
             8 ┤■■ 51                                     
             9 ┤■■ 73                                     
            10 ┤■ 36                                      
   Divisors 11 ┤■■■ 82                                    
            13 ┤■■ 71                                     
            14 ┤■ 25                                      
            15 ┤■ 43                                      
            16 ┤■ 18                                      
            17 ┤■■ 56                                     
            19 ┤■■ 50                                     
            21 ┤■ 31                                      
            22 ┤■ 22                                      
            23 ┤■ 42                                      
            25 ┤■ 33                                      
               └                                        ┘ 
                                  Count

What's interesting here is that I did not see any immediate pattern or phenomena with these divisors at first glance. However, when I examined the plot using a log10 scale, I then saw that consistently, the counts for odd divisors for outnumber those for even divisors:

Divisor Count for First 1000 Deficient Numbers 
               ┌                                        ┐ 
             1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 000   
             2 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 337          
             3 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 220            
             4 ┤■■■■■■■■■■■■■■■■■■■■■■■ 124               
             5 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 168              
             7 ┤■■■■■■■■■■■■■■■■■■■■■■■ 119               
             8 ┤■■■■■■■■■■■■■■■■■■■ 51                    
             9 ┤■■■■■■■■■■■■■■■■■■■■ 73                   
            10 ┤■■■■■■■■■■■■■■■■■ 36                      
   Divisors 11 ┤■■■■■■■■■■■■■■■■■■■■■ 82                  
            13 ┤■■■■■■■■■■■■■■■■■■■■ 71                   
            14 ┤■■■■■■■■■■■■■■■ 25                        
            15 ┤■■■■■■■■■■■■■■■■■■ 43                     
            16 ┤■■■■■■■■■■■■■■ 18                         
            17 ┤■■■■■■■■■■■■■■■■■■■ 56                    
            19 ┤■■■■■■■■■■■■■■■■■■■ 50                    
            21 ┤■■■■■■■■■■■■■■■■ 31                       
            22 ┤■■■■■■■■■■■■■■■ 22                        
            23 ┤■■■■■■■■■■■■■■■■■■ 42                     
            25 ┤■■■■■■■■■■■■■■■■■ 33                      
               └                                        ┘ 
                           Count (Log10 Scale)

Any Connection to Aliquot Sequences?

Out of curiosity, I wondered if there could be any overlap of abundant and deficient numbers' divisors with their respective aliquot sequences. Now, an aliquot sequence is a rather fun thing. It has the following form:

\[
s_{0} = k
\]
\[
s_{n} = s(s_{n-1}) = \sigma_{1}(s_{n-1}) – s_{n-1} \text{if} s_{n-1} \gt 0
\]
\[
s_{n} = 0 \text{if} s_{n-1} = 0
\]

I decided to implement a small algorithm to compute the aliquot sequence for a given number as follows:

function aliquot_sequence(num; max_itrs = missing)
  sequence = [num]
  s = num  while true
    s = sum(divisors(s)) - s
    if !ismissing(aliquot_sequence) && length(sequence) == max_itrs
      return nothing
    elseif s == 0 
      push!(sequence, s)
      break
    elseif in(s, sequence)
      break
    else
      push!(sequence, s)
    end
    
  end  return sequenceend

In my implementation, I decided to limit the sequence to no repeating sequence values for a number. Let's plot these sequence values and see what could be seen as before.

NOTE: As a limitation, some of these sequences have an immensely high number of iterations which cause my computer to explode (looking at you, abundant number \(138\))! For that reason, I am only calculating sequences for an abundant number that has only 10 maximum iterations within their aliquot sequence.

Aliquot Sequences of Abundant Numbers

Let's calculate the aliquot sequences for \(500\) abundant numbers that have at most \(10\) terms within their sequence:

import DataStructures: counter
import UnicodePlots: barplotabundant_aliquot_sequences = []for i in 1:1000000
         divisor_sum = divisors(i)[1:end-1] |> sum
         if divisor_sum > i 
           seq = aliquot_sequence(i, 10)
           !isnothing(seq) ? push!(abundant_aliquot_sequences, seq) : continue
         end         i += 1         length(abundant_aliquot_sequences) == 500 ? break : continue
         
       end
        
abundant_aliquot_terms = vcat(abundant_aliquot_sequences...) |> counter |> Dict |> sort;vs = collect(values(abundant_aliquot_terms));
ks = collect(keys(abundant_aliquot_terms));barplot(ks[1:20], vs[1:20], xlabel = "Count", ylabel = "Terms", title = "Aliquot Term Count for 500 Abundant Numbers")

Which yields the plot:

Aliquot Term Count for 500 Abundant Numbers 
            ┌                                        ┐ 
          0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 448   
          1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 448   
          3 ┤ 3                                        
          4 ┤ 3                                        
          6 ┤ 3                                        
          7 ┤■ 9                                       
          8 ┤■ 9                                       
          9 ┤ 3                                        
         10 ┤ 1                                        
   Terms 11 ┤ 4                                        
         12 ┤ 1                                        
         13 ┤■ 11                                      
         14 ┤ 1                                        
         15 ┤ 3                                        
         16 ┤ 1                                        
         17 ┤ 3                                        
         18 ┤ 2                                        
         19 ┤■ 8                                       
         20 ┤ 1                                        
         21 ┤ 4                                        
            └                                        ┘ 
                               Count

Here, I really cannot discern any relatable pattern as well as significance that can be tied back to abundant numbers. I am not sure if there is a way to tie significance back to abundant numbers at all in this scenario.

Aliquot Sequences of Deficient Numbers

Let's calculate the aliquot sequences for \(500\) abundant numbers that have at most \(10\) terms within their sequence:

import DataStructures: counter
import UnicodePlots: barplotdeficient_aliquot_sequences = []for i in 1:1000000
         divisor_sum = divisors(i)[1:end-1] |> sum
         if divisor_sum < i 
           seq = aliquot_sequence(i, 10)
           !isnothing(seq) ? push!(deficient_aliquot_sequences, seq) : continue
         end         i += 1         length(deficient_aliquot_sequences) == 500 ? break : continue
         
       end
        
deficient_aliquot_terms = vcat(deficient_aliquot_sequences...) |> counter |> Dict |> sort;vs = collect(values(deficient_aliquot_terms));
ks = collect(keys(deficient_aliquot_terms));barplot(ks[1:20], vs[1:20], xlabel = "Count", ylabel = "Terms", title = "Aliquot Term Count for 500 Deficient Numbers")

Which yields the plot:

Aliquot Term Count for 500 Deficient Numbers 
            ┌                                        ┐ 
          0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 487   
          0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 487   
          1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 487   
          2 ┤ 1                                        
          3 ┤■■ 28                                     
          4 ┤■■ 27                                     
          5 ┤ 1                                        
          6 ┤■ 10                                      
          7 ┤■■ 24                                     
          8 ┤■■ 23                                     
   Terms  9 ┤■■ 26                                     
         10 ┤■ 10                                      
         11 ┤■ 17                                      
         12 ┤ 2                                        
         13 ┤■ 15                                      
         14 ┤■ 9                                       
         15 ┤■■ 25                                     
         16 ┤■ 7                                       
         17 ┤■ 13                                      
         18 ┤ 1                                        
         19 ┤■■ 25                                     
            └                                        ┘ 
                               Count

Again, I really cannot discern any relatable pattern as well as significance that can be tied back to deficient numbers.

Conclusion

This was a small exploration that I wanted to do of these numbers to see if I could find any patterns or significance within aspects of these numbers. It seems like there may be some present within the factors of abundant and deficient numbers, but when looking at their corresponding aliquot sequences, I am unable to determine anything from a computational sense. To that end, I was also curious about how effective computation can be in helping to derive or provide hints about what may underlie these numbers. In short, it would appear that computation is quite helpful to give rise to initial questions. For example, I'd be curious to what extent the patterns I noticed within abundant and deficient numbers prolong for and if they are actually legitimate observations. At that point, one could then start applying basic data science skills to group, explore, and summarize potential trends within these numbers.

For now, my curiosity is sated and it might be worth a return to in the future. One thing this blog post did make me think about is analogies. The idea of deficient, perfect, and abundant numbers are really fascinating as it lends itself to analogs within set theory relationships (like many-to-one -> deficient number, one-to-one -> perfect number, one-to-many -> abundant number). I wonder if it could be used as analogy outside of mathematics strictly and in terms like healthcare (sub-type of a disease -> deficient number, canonical disease diagnosis -> perfect number, disease family -> abundant number). Might be worth further exploration in the future.

How To Cite

Zelko, Jacob. A Fun Exploration of Perfect, Abundant, and Deficient Numbers. https://jacobzelko.com/05102023043333-perfect-abundant-deficit. May 10 2023.

References:

Discussion:


Hungarian meeting with Euler for the third anniversary

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2023/05/05/hungarian.html

Introduction

I have been writing this blog for three years now, so I was
thinking what to post about to celebrate this.

Recently I have learned about the ProjectEuler.jl package.
I like it very much. It gives access to problems presented in
the Project Euler website in Julia REPL.
Additionally, when reading the documentation of the package it
mentioned a problem that I have not seen before. Therefore
I thought to solve it in this post.

This post was written under Julia 1.9.0-rc2, HiGHS v1.5.1,
Hungarian v0.7.0, JuMP v1.10.0, ProjectEuler v0.1.1.

The puzzle

Let us use ProjectEuler.jl to get the description of the
problem we want to solve:

julia> import ProjectEuler

julia> ProjectEuler.question(345)

│             Source: The following problem is taken from Project Euler
│      Problem Title: Problem 345: Matrix Sum
│       Published On: Saturday, 3rd September 2011, 04:00 pm
│          Solved By: 5813
│  Difficulty Rating: 15%

Problem
≡≡≡≡≡≡≡≡≡≡
We define the Matrix Sum of a matrix as the maximum possible sum of matrix
elements such that none of the selected elements share the same row or column.

For example, the Matrix Sum of the matrix below equals
3315 ( = 863 + 383 + 343 + 959 + 767):

                                                   7  53 183 439 863
                                                 497 383 563  79 973
                                                 287  63 343 169 583
                                                 627 343 773 959 943
                                                 767 473 103 699 303

Find the Matrix Sum of:

                         7  53 183 439 863 497 383 563  79 973 287  63 343 169 583
                       627 343 773 959 943 767 473 103 699 303 957 703 583 639 913
                       447 283 463  29  23 487 463 993 119 883 327 493 423 159 743
                       217 623   3 399 853 407 103 983  89 463 290 516 212 462 350
                       960 376 682 962 300 780 486 502 912 800 250 346 172 812 350
                       870 456 192 162 593 473 915  45 989 873 823 965 425 329 803
                       973 965 905 919 133 673 665 235 509 613 673 815 165 992 326
                       322 148 972 962 286 255 941 541 265 323 925 281 601  95 973
                       445 721  11 525 473  65 511 164 138 672  18 428 154 448 848
                       414 456 310 312 798 104 566 520 302 248 694 976 430 392 198
                       184 829 373 181 631 101 969 613 840 740 778 458 284 760 390
                       821 461 843 513  17 901 711 993 293 157 274  94 192 156 574
                        34 124   4 878 450 476 712 914 838 669 875 299 823 329 699
                       815 559 813 459 522 788 168 586 966 232 308 833 251 631 107
                       813 883 451 509 615  77 281 613 459 205 380 274 302  35 805

Manual solution

To start let us define the matrix that gives specification of the problem
and bind it to the M variable:

M = [  7  53 183 439 863 497 383 563  79 973 287  63 343 169 583
     627 343 773 959 943 767 473 103 699 303 957 703 583 639 913
     447 283 463  29  23 487 463 993 119 883 327 493 423 159 743
     217 623   3 399 853 407 103 983  89 463 290 516 212 462 350
     960 376 682 962 300 780 486 502 912 800 250 346 172 812 350
     870 456 192 162 593 473 915  45 989 873 823 965 425 329 803
     973 965 905 919 133 673 665 235 509 613 673 815 165 992 326
     322 148 972 962 286 255 941 541 265 323 925 281 601  95 973
     445 721  11 525 473  65 511 164 138 672  18 428 154 448 848
     414 456 310 312 798 104 566 520 302 248 694 976 430 392 198
     184 829 373 181 631 101 969 613 840 740 778 458 284 760 390
     821 461 843 513  17 901 711 993 293 157 274  94 192 156 574
      34 124   4 878 450 476 712 914 838 669 875 299 823 329 699
     815 559 813 459 522 788 168 586 966 232 308 833 251 631 107
     813 883 451 509 615  77 281 613 459 205 380 274 302  35 805]

Note that it is easy to do in Julia REPL, by copy-pasting the text
from the problem specification and just wrapping it with M = [ and ].

To solve the problem manually let us make the following observations:

  • Since every column has to be picked exactly once subtracting
    the same value from each element of some column does not affect the
    solution (the same holds for rows).
  • If in every row maximal element is in a different column then we can
    just pick these maximal elements in each row and these entries
    are the solution to our problem.

Using these two facts we will try to solve our problem. First let us
check if in our initial matrix M each row has a unique column where
it has a maximum value:

julia> findall(==(0), M .- maximum(M, dims=2))
15-element Vector{CartesianIndex{2}}:
 CartesianIndex(15, 2)
 CartesianIndex(2, 4)
 CartesianIndex(5, 4)
 CartesianIndex(11, 7)
 CartesianIndex(3, 8)
 CartesianIndex(4, 8)
 CartesianIndex(12, 8)
 CartesianIndex(13, 8)
 CartesianIndex(6, 9)
 CartesianIndex(14, 9)
 CartesianIndex(1, 10)
 CartesianIndex(10, 12)
 CartesianIndex(7, 14)
 CartesianIndex(8, 15)
 CartesianIndex(9, 15)

Unfortunately, this is not the case. We see that e.g. rows 2 and 5 have
maximum in column 4. Therefore we cannot trivially solve our problem.

However, let us try subtracting some values from columns of our
matrix M hoping that we will get the desired uniqueness.

The values we subtract from each column are:

julia> sub = [55 0 23 56 40 0 101 171 175 62 53 151 0 0 26]
1×15 Matrix{Int64}:
 55  0  23  56  40  0  101  171  175  62  53  151  0  0  26

Let us check them:

julia> M2 = M .- sub
15×15 Matrix{Int64}:
 -48   53  160  383  823  497  282   392  -96  911  234  -88  343  169  557
 572  343  750  903  903  767  372   -68  524  241  904  552  583  639  887
 392  283  440  -27  -17  487  362   822  -56  821  274  342  423  159  717
 162  623  -20  343  813  407    2   812  -86  401  237  365  212  462  324
 905  376  659  906  260  780  385   331  737  738  197  195  172  812  324
 815  456  169  106  553  473  814  -126  814  811  770  814  425  329  777
 918  965  882  863   93  673  564    64  334  551  620  664  165  992  300
 267  148  949  906  246  255  840   370   90  261  872  130  601   95  947
 390  721  -12  469  433   65  410    -7  -37  610  -35  277  154  448  822
 359  456  287  256  758  104  465   349  127  186  641  825  430  392  172
 129  829  350  125  591  101  868   442  665  678  725  307  284  760  364
 766  461  820  457  -23  901  610   822  118   95  221  -57  192  156  548
 -21  124  -19  822  410  476  611   743  663  607  822  148  823  329  673
 760  559  790  403  482  788   67   415  791  170  255  682  251  631   81
 758  883  428  453  575   77  180   442  284  143  327  123  302   35  779

julia> sol = findall(==(0), M2 .- maximum(M2, dims=2))
15-element Vector{CartesianIndex{2}}:
 CartesianIndex(6, 1)
 CartesianIndex(15, 2)
 CartesianIndex(8, 3)
 CartesianIndex(5, 4)
 CartesianIndex(4, 5)
 CartesianIndex(12, 6)
 CartesianIndex(11, 7)
 CartesianIndex(3, 8)
 CartesianIndex(14, 9)
 CartesianIndex(1, 10)
 CartesianIndex(2, 11)
 CartesianIndex(10, 12)
 CartesianIndex(13, 13)
 CartesianIndex(7, 14)
 CartesianIndex(9, 15)

Now we see that we have exactly one maximum value in each row and each of these values
is in a different column. Thus the solution to the problem can be calculated as
(I do not show the solution to encourage you to try solving the problem yourself):

sum(M[sol])

Now you might ask how one could get the sub vector?
You could find it by trial and error, or use a more systematic way.
Interestingly the problem we solve today is an important question
in operations research, and a specialized algorithm was developed to solve it.

Hungarian solution

The algorithm that can be used to solve this class of problems is called
Hungarian algorithm. It is implemented in Julia in the Hungarian.jl
package. I encourage you to study it, however, let me just mention that it uses
a refined version of the two observations we have made when developing the manual
solution.

The package is easy to use. You just need to remember that by default it minimizes
the sum, so we need to use the -M matrix. Here is how you can get
the solution (I show you the indices, but drop displaying the value of the solution):

julia> using Hungarian

julia> hungarian(-M)[1]
15-element Vector{Int64}:
 10
 11
  8
  5
  4
  1
 14
  3
 15
 12
  7
  6
 13
  9
  2

You might ask how we could check if our manual solution and the solution obtained
using the package match. You can do it as follows:

julia> getindex.(sol, 1) == hungarian(-M')[1]
true

All matches as expected.

Note that for the check I used the hungarian function with the transposition
of the M matrix as our cartesian indices are sorted by column number
(the reason is that Julia uses column major storage order) and by default
hungarian returns column indices sorted by row number.

Solver solution

What if we did not have the Hungarian.jl package?
In this case the problem can be solved using mixed integer programming.
I have decided to use the JuMP.jl and HiGHS.jl packages to get the answer
(as usual – the value of the solution is not shown):

using JuMP
import HiGHS
model = Model(HiGHS.Optimizer)
@variable(model, x[1:15, 1:15], Bin)
for i in 1:15
    @constraint(model, sum(x[i, j] for j in 1:15) == 1)
    @constraint(model, sum(x[j, i] for j in 1:15) == 1)
end
@objective(model, Max, sum(x[i, j] * M[i, j] for i in 1:15, j in 1:15))
optimize!(model)
value.(x)

I really enjoy using the JuMP.jl API for solving optimization problems.

As above let us check if the solution matches the solution we obtained manually:

julia> findall(>=(0.5), value.(x)) == sol
true

Note that I use 0.5 to separate 0 from 1 solutions as this is a
safe boundary value even if there were some numerical inaccuracies in the
returned solution.

Conclusions

I really enjoy solving Project Euler puzzles using Julia.
The syntax and package ecosystem that I have at hand make it
quite convenient. The resulting codes are usually short and easy to read.

If you liked the problem let me give you a challenge. Notice that
the sum of values we subtracted in the manual solution was:

julia> sum(sub)
913

The challenge for you is to find such non-negative entries of sub
that uniquely solve our problem (in the manual approach) and
minimize the sum of entries of sub. I hope you will enjoy solving
this extra puzzle!

Ref ref.

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2023/04/28/ref.html

Introduction

Today I want to discuss the Ref type defined in Base Julia.
The reason is that it is often used in practice, but it is not immediately
obvious what the design behind Ref is.

I will focus on an entry-level introduction to the topic and leave out
more advanced issues that are typically not needed when working with
Julia.

This post is written under Julia 1.9.0-rc2.

Where can you see Ref?

As a normal Julia user there are two cases, where you might encounter Ref:
broadcasting and allowing mutation.

Broadcasting

The first case is in broadcasting when you want to store some object in a
0-dimensional container that protects its contents from being broadcasted over.
Here is a typical example:

julia> x = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> y = [2, 3, 4]
3-element Vector{Int64}:
 2
 3
 4

julia> Ref(x) .* y
3-element Vector{Vector{Int64}}:
 [2, 4, 6]
 [3, 6, 9]
 [4, 8, 12]

Note that I wrap x in Ref to ensure that the whole x vector is multiplied
by elements of y. If I omitted Ref I would get an elementwise product of
x and y:

julia> x .* y
3-element Vector{Int64}:
  2
  6
 12

The reason why Ref is used in such cases is that Ref makes a minimal impact on
the type of the result of the broadcasted operation. Consider this example:

julia> z = (2, 3, 4)
(2, 3, 4)

julia> [x] .* z
3-element Vector{Vector{Int64}}:
 [2, 4, 6]
 [3, 6, 9]
 [4, 8, 12]

julia> Ref(x) .* z
([2, 4, 6], [3, 6, 9], [4, 8, 12])

Here I protected x when multiplying it by elements of the tuple z.
I could protect x by wrapping it with a vector, but, as you can see
then the result of the operation would be vector of vectors. While
wrapping x in Ref produces a tuple of vectors as a result.
As you can see, using Ref made broadcasting mechanism use the type of
the other container to determine the output type, which is typically desirable.

Allowing mutation

The other use of Ref is when we have an immutable type that we want to be able
to mutate ?. This might sound strange, but sometimes indeed it is useful.

Let me give you a simple example:

julia> struct X
           value::Int
           callcount::Base.RefValue{Int}

           X(x) = new(Int(x), Ref(0))
       end

julia> f(x::X) = (x.callcount[] += 1; x)
f (generic function with 1 method)

julia> x = X(10)
X(10, Base.RefValue{Int64}(0))

julia> f(x)
X(10, Base.RefValue{Int64}(1))

julia> f(x)
X(10, Base.RefValue{Int64}(2))

julia> f(x)
X(10, Base.RefValue{Int64}(3))

Here I defined the X type that stores a value, which I want to be immutable,
and an extra field callcount that counts how many times the function f was
called on this object. Since Int is immutable, I needed to wrap it with Ref
to achieve the mutability of this field.

As a side note this is not the only way to get this kind of effect. For example,
I could define a mutable struct with const field value. Still in some
cases Ref is a useful because it is mutable. Note that I accessed and updated
the value stored in Ref using empty indexing x.callcount[] (i.e. brackets
with no value inside them).

So what is hard about Ref?

In the last example I said I am talking about Ref, but in the definition of
the X type I used callcount::Base.RefValue{Int} instead. This is the tricky
part. Ref is a parametric abstract type. This means that no object can have
Ref type. Ref is a non-leaf node in the type tree in Julia. Let us check its
subtypes:

julia> subtypes(Ref)
6-element Vector{Any}:
 Base.CFunction
 Base.RefArray
 Base.RefValue
 Core.Compiler.RefValue
 Core.LLVMPtr
 Ptr

As you can see there are six types that are subtypes of Ref. And here comes
why I have said that I want our post today to be entry-level. I will only talk
about RefValue and RefArray. I leave out other options as they are
rarely needed (unless you are doing low-level stuff in Julia, but then probably
you do not need to read this post ?).

The tricky thing is that when we write Ref(1) we do not get an object
whose type is Ref, but instead a RefValue (that is a subtype of Ref):

julia> v1 = Ref(1)
Base.RefValue{Int64}(1)

julia> v1[]
1

Similarly we can have a reference to an element of an array. In this case
we pass an array as a first argument to Ref and an index as a second one:

julia> v2 = Ref([2, 3, 4], 2)
Base.RefArray{Int64, Vector{Int64}, Nothing}([2, 3, 4], 2, nothing)

julia> v2[]
3

You can think of Ref as a convenient way to handle both cases (wrapping a value
and wrapping an element of an array) in a single syntax.

There is one difference between RefValue and RefArray though. RefValue
indeed guarantees mutability of the container as we have seen in the example
above with the X struct. Trying to mutate RefArray will try to mutate
the underlying array. Therefore the following code fails:

julia> v3 = Ref(2:4, 2)
Base.RefArray{Int64, UnitRange{Int64}, Nothing}(2:4, 2, nothing)

julia> v3[] = 10
ERROR: CanonicalIndexError: setindex! not defined for UnitRange{Int64}

While this code works:

julia> a = [2, 3, 4]
3-element Vector{Int64}:
 2
 3
 4

julia> v4 = Ref(a, 2)
Base.RefArray{Int64, Vector{Int64}, Nothing}([2, 3, 4], 2, nothing)

julia> v4[]
3

julia> v4[] = 100
100

julia> v4
Base.RefArray{Int64, Vector{Int64}, Nothing}([2, 100, 4], 2, nothing)

julia> a
3-element Vector{Int64}:
   2
 100
   4

and we can see that the a array was changed.

Conclusions

The major things to remember about Ref are:

  • Its main uses are in broadcasting and when we need a lightweight mutable container.
  • Ref is abstract, when you write Ref(x) you do not get a Ref instance. Instead
    you will get a RefValue (which is mutable).
  • There are other subtypes of Ref than just RefValue. You will rarely need them.
    Of the other options the one you might want to use most often is RefArray, which
    creates a reference to a single element of the underlying array.

I hope you found this post a useful ref. for Ref.