Разбивка проблемы: есть достаточная мотивация, но давайте предположим, что нам дана матрица N
(код приведен внизу), и мы хотим решить матрично-векторное уравнение Nv = b
, где b
- двоичная строка с весом k
. В настоящее время я решаю эту проблему, используя numpy.linalg.lstsq
. Если «остаток» этого вычисления наименьших квадратов меньше 0,000001, я принимаю его как решение.
Во-первых, мне нужно сгенерировать все двоичные строки определенных length
и weight k
,Следующая функция делает это.
'''
This was provided in one of the answers here:
/12803730/naiti-vse-dvoichnye-stroki-opredelennogo-vesa-maksimalno-bystro
Given a value with weight k, you can get the (co)lexically next value as follows (using bit manipulations).
'''
def ksubsetcolexsuccessor(length,k):
limit=1<<length
val=(1<<k)-1
while val<limit:
yield "{0:0{1}b}".format(val,length)
minbit=val&-val
fillbit = (val+minbit)&~int(val)
val = val+minbit | (fillbit//(minbit<<1))-1
Так что для length = 6
и k = 2
мы имеем: 0
00011, 000101, 000110, 001001, 001010, 001100, 010001, 010010, 010100, 011000, 100001, 100010, 100100, 101000, 110000.
Мы подключаем каждый в Nv = b
и решаем:
import numpy as np
solutions = 0
for b in ksubsetcolexsuccessor(n, k):
v = np.linalg.lstsq(N,np.array(list(b), dtype = float))
if v[1] < 0.000001:
solutions += 1
Это общее количество решений. Моя проблема в том, что это довольно медленно. Я хотел бы в конечном итоге попробовать n = 72
и k = 8
. Я заметил, что используется только часть моего процессора, поэтому я бы хотел многопроцессорность. Я начал с выяснения того, как генерировать вышеупомянутые двоичные строки в чанках. Я написал функцию, которая дает мне значения для val
, которая позволяет мне запускать и останавливать генерацию вышеуказанной последовательности двоичных строк в любом месте. Так что мой план состоял в том, чтобы на моей машине было столько кусков, сколько у меня есть ядер. Например:
Процессы Core 1: 000011, 000101, 000110, 001001, 001010, 001100, 010001
Процессы Core 2: 010010, 010100, 011000, 100001, 100010, 100100, 101000, 110000
Я не знаком с multiprocessing
и multithreading
. Вот что я сделал:
from multiprocessing import Process
'''
A version where you can specify where to start and end by passing start_val and end_val to the function.
It performs the least-squares computation for each b and tallies the trials that work.
'''
def ksubsetcolexsuccessorSegmentComp(n, k, val, limit, num):
while val<limit:
b = "{0:0{1}b}".format(val,n)
v = np.linalg.lstsq(N,np.array(list(b), dtype = float))
if v[1] < 0.000001:
num += 1
minbit=val&-val
fillbit = (val+minbit)&~int(val)
val = val+minbit | (fillbit//(minbit<<1))-1
print(num)
solutions = 0
length = 6
weight = 2
start_val = (1<<k)-1
mid_val = 18 #If splitting into 2 processes
end_val = 1<<n
#Multiprocessing
p1 = Process(target=ksubsetcolexsuccessorSegmentComp, args=(length, weight, start_val, mid_val, solutions))
p2 = Process(target=ksubsetcolexsuccessorSegmentComp, args=(length, weight, mid_val, end_val, solutions))
p1.start()
p2.start()
p1.join()
p2.join()
Это действительно сокращает время вычислений примерно на 45% (с увеличением длины и веса). Мой процессор работает на уровне около 50% во время этого. Как ни странно, если я разделю на 4 процесса, это займет примерно столько же, сколько если бы я разделился на 2 (даже при том, что мой процессор работает примерно на 85% за это время). На ноутбуке, который я тестирую, у меня есть 2 физических ядра и 4 логических ядра. Сколько процессов является оптимальным на таком процессоре?
Вопросы:
- В моем отсутствии знаний о многопроцессорности, я сделал это правильно? то есть это самый быстрый способ многопроцессорной обработки этой программы?
- Может кто-нибудь увидеть какой-либо четкий способ улучшить время вычислений? Я смотрел на многопоточность внутри каждого процесса, но, похоже, он замедлял вычисления.
- Самая медленная часть этого, безусловно,
np.linalg.lsts
q. Есть ли какие-нибудь умные способы ускорить эту часть?
Если кто-то хочет проверить код, вот как я генерирую матрицу N
:
'''
Creating the matrix N
'''
def matrixgenerator(G, cardinality, degree):
matrix = np.zeros((size, (n-1)**2 + 1))
#Generate the matrix (it'll be missing stab_nn)
column_index = 0
for i in range(1,n):
for j in range(1,n):
for k in range(size):
if G[k](i) == j:
matrix[k][column_index] = 1
column_index += 1
#Determine the final column of N, which is determined by the characteristic vector of stab_nn
for k in range(size):
if G[k](n) == n:
matrix[k][(n-1)**2] = 1
return matrix
n = 3 #This will give length 6 and weight 2 binary strings
#Try n = 7 and change the second argument below to '4' - this takes roughly 14 min with 2 processes on my device
Group = TransitiveGroup(n, 2) #SageMath 8.8
length = Group.order()
weight = int(size / n)
N = matrixgenerator(Group, length, n)
Это требуетSageMath 8.8. Обратите внимание, что это единственная часть кода, которая требует SageMath. Все остальное эффективно написано на Python 2.7. Заранее спасибо.