Matrix property X revisited (or the Joy of X)
C++, Score 23/12 25/13 27/14 28/14 31/15
Finally a result with ratio > 2:
rows=15,cols=31
1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1 1 0
1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1 1
1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0 1
1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 1
0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1
0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1
1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0 1
0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 0
0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0
0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0
1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1
0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0 0
0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 0
1 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1
I completely explored 1 to 14 rows. 15 would take too long to completely explore. The results are:
1/1 = 1
2/2 = 1
4/3 = 1.333
5/4 = 1.25
7/5 = 1.4
9/6 = 1.5
12/7 = 1.714
14/8 = 1.75
16/9 = 1.778
18/10 = 1.8
20/11 = 1.818
23/12 = 1.917
25/13 = 1.923
28/14 = 2
The code given below is an older version of the program. The newest version is at https://github.com/thospel/personal-propertyX.
/*
Compile using something like:
g++ -Wall -O3 -march=native -fstrict-aliasing -std=c++11 -g propertyX.cpp -lpthread -o propertyX
*/
#include <cstdint>
#include <climits>
#include <ctgmath>
#include <iostream>
#include <vector>
#include <array>
#include <chrono>
#include <mutex>
#include <atomic>
#include <thread>
using namespace std;
const int ELEMENTS = 2;
using uint = unsigned int;
using Element = uint64_t;
using Column = array<Element, ELEMENTS>;
using Set = vector<Column>;
using Sum = uint8_t;
using Index = uint32_t;
using sec = chrono::seconds;
int const PERIOD = 5*60;
int const MAX_ROWS = 54;
int const COL_FACTOR = (MAX_ROWS+1) | 1; // 55
int const ROW_ZERO = COL_FACTOR/2; // 27
int const ROWS_PER_ELEMENT = CHAR_BIT * sizeof(Element) / log2(COL_FACTOR); //11
Element constexpr ELEMENT_FILL(Element v = ROW_ZERO, int n = ROWS_PER_ELEMENT) {
return n ? ELEMENT_FILL(v, n-1) * COL_FACTOR + v : 0;
}
Element constexpr POWN(Element v, int n) {
return n ? POWN(v, n-1)*v : 1;
}
Element const ELEMENT_TOP = POWN(COL_FACTOR, ROWS_PER_ELEMENT -1);
int const MAX_COLS = ROWS_PER_ELEMENT * ELEMENTS; // 22
atomic<Index> col_next;
atomic<uint> period;
chrono::steady_clock::time_point start;
mutex period_mutex;
uint ratio_row;
uint ratio_col;
mutex ratio_mutex;
auto const nr_threads = thread::hardware_concurrency();
// auto const nr_threads = 1;
struct State {
State(uint cols);
void process(Index i);
void extend(uint row);
void print(uint rows);
Index nr_columns() const { return static_cast<Index>(1) << cols_; }
Column last_;
Element top_;
int top_offset_;
uint ratio_row_ = 0;
uint ratio_col_ = 1;
uint cols_;
array<Sum, MAX_ROWS + MAX_COLS -1> side;
vector<Set> sets;
};
ostream& operator<<(ostream& os, Column const& column) {
for (int i=0; i<ELEMENTS; ++i) {
auto v = column[i];
for (int j=0; j<ROWS_PER_ELEMENT; ++j) {
auto bit = v / ELEMENT_TOP;
cout << " " << bit;
v -= bit * ELEMENT_TOP;
v *= COL_FACTOR;
}
}
return os;
}
State::State(uint cols) : cols_{cols} {
sets.resize(MAX_ROWS+2);
for (int i=0; i<2; ++i) {
sets[i].resize(2);
for (int j=0; j < ELEMENTS; ++j) {
sets[i][0][j] = ELEMENT_FILL();
sets[i][1][j] = static_cast<Element>(-1) - ELEMENT_FILL(1);
}
}
top_ = POWN(COL_FACTOR, (cols_-1) % ROWS_PER_ELEMENT);
top_offset_ = ELEMENTS - 1 - (cols_-1) / ROWS_PER_ELEMENT;
}
void State::print(uint rows) {
for (auto c=0U; c<cols_;c++) {
for (auto r=0U; r<rows;r++) {
cout << static_cast<int>(side[cols_-c+r-1]) << " ";
}
cout << "\n";
}
cout << "----------" << endl;
}
void check(uint cols, uint t) {
State state(cols);
Index nr_columns = state.nr_columns();
while (1) {
Index col = col_next++;
if (col >= nr_columns) break;
state.process(col);
auto now = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<sec>(now-start).count();
if (elapsed >= period) {
lock_guard<mutex> lock{period_mutex};
if (elapsed >= period) {
cout << "col=" << col << "/" << nr_columns << " (" << 100.*col/nr_columns << "% " << elapsed << " s)" << endl;
period = (elapsed/PERIOD+1)*PERIOD;
}
}
}
}
void State::process(Index col) {
last_.fill(0);
for (uint i=0; i<cols_; ++i) {
Element bit = col >> i & 1;
side[i] = bit;
Element carry = 0;
for (int j=0; j<ELEMENTS; ++j) {
auto c = last_[j] % COL_FACTOR;
last_[j] = last_[j] / COL_FACTOR + carry * ELEMENT_TOP;
if (j == top_offset_ && bit) last_[j] += top_;
carry = c;
}
}
// cout << "col=" << col << ", value=" << last_ << "\n";
extend(0);
}
void State::extend(uint row) {
// cout << "Extend row " << row << " " << static_cast<int>(side[cols_+row-1]) << "\n";
if (row >= MAX_ROWS) throw(range_error("row out of range"));
// Execute subset sum. The new column is added to set {from} giving {to}
// {sum} is the other set.
auto const& set_from = sets[row];
auto const& set_sum = sets[row + 1];
auto & set_to = sets[row + 2];
if (set_to.size() == 0) {
auto size = 3 * set_from.size() - 2;
set_to.resize(size);
for (int j=0; j<ELEMENTS; ++j)
set_to[size-1][j] = static_cast<Element>(-1) - ELEMENT_FILL(1);
}
// Merge sort {set_from - last_} , {set_from} and {set_from + last_}
auto ptr_sum = &set_sum[1][0];
auto ptr_low = &set_from[0][0];
auto ptr_middle = &set_from[0][0];
auto ptr_high = &set_from[0][0];
Column col_low, col_high;
for (int j=0; j<ELEMENTS; ++j) {
col_low [j] = *ptr_low++ - last_[j];
col_high [j] = *ptr_high++ + last_[j];
}
auto ptr_end = &set_to[set_to.size()-1][0];
auto ptr_to = &set_to[0][0];
while (ptr_to < ptr_end) {
for (int j=0; j<ELEMENTS; ++j) {
if (col_low[j] < ptr_middle[j]) goto LOW;
if (col_low[j] > ptr_middle[j]) goto MIDDLE;
}
// low == middle
// cout << "low == middle\n";
return;
LOW:
// cout << "LOW\n";
for (int j=0; j<ELEMENTS; ++j) {
if (col_low[j] < col_high[j]) goto LOW0;
if (col_low[j] > col_high[j]) goto HIGH0;
}
// low == high
// cout << "low == high\n";
return;
MIDDLE:
// cout << "MIDDLE\n";
for (int j=0; j<ELEMENTS; ++j) {
if (ptr_middle[j] < col_high[j]) goto MIDDLE0;
if (ptr_middle[j] > col_high[j]) goto HIGH0;
}
// middle == high
// cout << "middle == high\n";
return;
LOW0:
// cout << "LOW0\n";
for (int j=0; j<ELEMENTS; ++j) {
*ptr_to++ = col_low[j];
col_low[j] = *ptr_low++ - last_[j];
}
goto SUM;
MIDDLE0:
// cout << "MIDDLE0\n";
for (int j=0; j<ELEMENTS; ++j)
*ptr_to++ = *ptr_middle++;
goto SUM;
HIGH0:
// cout << "HIGH0\n";
for (int j=0; j<ELEMENTS; ++j) {
*ptr_to++ = col_high[j];
col_high[j] = *ptr_high++ + last_[j];
}
goto SUM;
SUM:
for (int j=-ELEMENTS; j<0; ++j) {
if (ptr_to[j] > ptr_sum[j]) {
ptr_sum += ELEMENTS;
goto SUM;
}
if (ptr_to[j] < ptr_sum[j]) goto DONE;
}
// sum == to
for (int j=-ELEMENTS; j<0; ++j)
if (ptr_to[j] != ELEMENT_FILL()) {
// sum == to and to != 0
// cout << "sum == to\n";
// cout << set_sum[(ptr_sum - &set_sum[0][0])/ELEMENTS-1] << "\n";
return;
}
DONE:;
}
// cout << "Wee\n";
auto row1 = row+1;
if (0)
for (uint i=0; i<row1+2; ++i) {
cout << "Set " << i << "\n";
auto& set = sets[i];
for (auto& column: set)
cout << column << "\n";
}
if (row1 * ratio_col_ > ratio_row_ * cols_) {
ratio_row_ = row1;
ratio_col_ = cols_;
lock_guard<mutex> lock{ratio_mutex};
if (ratio_row_ * ratio_col > ratio_row * ratio_col_) {
auto now = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<sec>(now-start).count();
cout << "cols=" << cols_ << ",rows=" << row1 << " (" << elapsed << " s)\n";
print(row1);
ratio_row = ratio_row_;
ratio_col = ratio_col_;
}
}
auto last = last_;
Element carry = 0;
for (int j=0; j<ELEMENTS; ++j) {
auto c = last_[j] % COL_FACTOR;
last_[j] = last_[j] / COL_FACTOR + carry * ELEMENT_TOP;
carry = c;
}
side[cols_+row] = 0;
extend(row1);
last_[top_offset_] += top_;
side[cols_+row] = 1;
extend(row1);
last_ = last;
}
void my_main(int argc, char** argv) {
if (!col_next.is_lock_free()) cout << "col_next is not lock free\n";
if (!period. is_lock_free()) cout << "period is not lock free\n";
int min_col = 2;
int max_col = MAX_COLS;
if (argc > 1) {
min_col = atoi(argv[1]);
if (min_col < 2)
throw(range_error("Column must be >= 2"));
if (min_col > MAX_COLS)
throw(range_error("Column must be <= " + to_string(MAX_COLS)));
}
if (argc > 2) {
max_col = atoi(argv[2]);
if (max_col < min_col)
throw(range_error("Column must be >= " + to_string(min_col)));
if (max_col > MAX_COLS)
throw(range_error("Column must be <= " + to_string(MAX_COLS)));
}
for (int cols = min_col; cols <= max_col; ++cols) {
cout << "Trying " << cols << " columns" << endl;
ratio_row = 0;
ratio_col = 1;
col_next = 0;
period = PERIOD;
start = chrono::steady_clock::now();
vector<thread> threads;
for (auto t = 1U; t < nr_threads; t++)
threads.emplace_back(check, cols, t);
check(cols, 0);
for (auto& thread: threads)
thread.join();
}
}
int main(int argc, char** argv) {
try {
my_main(argc, argv);
} catch(exception& e) {
cerr << "Error: " << e.what() << endl;
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
Haskell 14/8 = 1.75
1 1 0 0 0 1 0 1 1 0 1 1 0 0
1 1 1 0 0 0 1 0 1 1 0 1 1 0
0 1 1 1 0 0 0 1 0 1 1 0 1 1
1 0 1 1 1 0 0 0 1 0 1 1 0 1
0 1 0 1 1 1 0 0 0 1 0 1 1 0
0 0 1 0 1 1 1 0 0 0 1 0 1 1
0 0 0 1 0 1 1 1 0 0 0 1 0 1
0 0 0 0 1 0 1 1 1 0 0 0 1 0
Previously 9/6 = 1.5
1 0 1 0 1 1 0 0 1
1 1 0 1 0 1 1 0 0
1 1 1 0 1 0 1 1 0
1 1 1 1 0 1 0 1 1
1 1 1 1 1 0 1 0 1
1 1 1 1 1 1 0 1 0
I wrote this, then looked at the answers to the other question and was... discouraged.
import Data.List
import Data.Hashable
import Control.Monad
import Control.Parallel.Strategies
import Control.Parallel
import qualified Data.HashSet as S
matrix§indices = [ matrix!!i | i<-indices ]
powerset :: [a] -> [[a]]
powerset = filterM (const [True, False])
hashNub :: (Hashable a, Eq a) => [a] -> [a]
hashNub l = go S.empty l
where
go _ [] = []
go s (x:xs) = if x `S.member` s
then go s xs
else x : go (S.insert x s) xs
getMatrix :: Int -> Int -> [Int] -> [[Int]]
getMatrix width height vector = [ vector § [x..x+width-1] | x<-[0..height-1] ]
hasDuplicate :: (Hashable a, Eq a) => [a] -> Bool
hasDuplicate m = go S.empty m
where
go _ [] = False
go s (x:xs) = if x `S.member` s
then True
else go (S.insert x s) xs
hasProperty :: [[Int]] -> Bool
hasProperty matrix =
let
base = replicate (length (matrix !! 0)) 0::[Int]
in
if elem base matrix then
False
else
if hasDuplicate matrix then
False
else
if hasDuplicate (map (foldl (zipWith (+)) base) (powerset matrix)) then
False
else
True
pmap = parMap rseq
matricesWithProperty :: Int -> Int -> [[[Int]]]
matricesWithProperty n m =
let
base = replicate n 0::[Int]
in
filter (hasProperty) $
map (getMatrix n m) $
sequence [ [0,1] | x<-[0..n+m-1] ]
firstMatrixWithProperty :: Int -> Int -> [[Int]]
firstMatrixWithProperty n m = head $ matricesWithProperty n m
main = mapM (putStrLn. show) $ map (firstMatrixWithProperty 8) [1..]