Комментарий код небольшой научный проект, гости массива и списка выполнить


Я новичок в Python, но не программирования в целом. Это мой первый проект за основу.

Мне было интересно, если я мог бы получить некоторую обратную связь на код в целом, в частности о всяких нехороших на Java/C++ и привычки, я могу быть тащащей.

У меня есть некоторые конкретные вопросы, касающиеся производительности массива списков и NumPy против. Как вы можете видеть из этого сравнения, я переписал часть кода, чтобы использовать массив класса, который, кажется, более "подходящие для Python" (код, кажется, читать лучше) но я считаю, что это иногда почти в два раза медленнее. Является ли это неизбежным производительности для забора, или я делаю что-то неправильно?

Чтобы получить правильную рабочую копию код:

git clone git://github.com/hemmer/pyDLA.git

И проверка массива использовать версию (потом):

git checkout numpyarray_slow

Любые советы, пусть и небольшой, с благодарностью!

Редактировать: вот источник старые (более быстрый код). Для изменения, которые я сделал, чтобы использовать массивы numpy, см. выше дифф.

#!/usr/bin/env python
import time
from math import pi, sqrt, cos, sin
from random import choice, random, seed

from pylab import pcolormesh, axes, show
from numpy import zeros, int32, arange

twopi = 2 * pi              # 2 pi for choosing starting position

hits = 0                    # how many seeds have stuck
birthradius = 5             # starting radius for walkers
deathradius = 10            # radius to kill off walkers
maxradius = -1              # the extent of the growth
numParticles = 1000         # how many walkers to release
seed(42)                    # fixed seed for debugging

L = 500                     # lattice goes from -L : L
size = (2 * L) + 1          # so total lattice width is 2L + 1

# preallocate and initialise centre point as "seed"
lattice = zeros((size, size), dtype=int32)
lattice[L, L] = -1

# possible (relative) nearest neighbour sites
nnsteps = ((0, 1), (0, -1), (1, 0), (-1, 0))


# returns whether site pos = (x, y) has
# an occupied nearest-neighbour site
def nnOccupied(pos):

    # convert from lattice coords to array coords
    latticepos = (pos[0] + L, pos[1] + L)

    for step in nnsteps:
        if lattice[latticepos[0] + step[0], latticepos[1] + step[1]] != 0:
            return True
    else:
        return False
# end of nnOccupied


# check if a point is within the
# allowed radius
def inCircle(pos):
    if (pos[0] ** 2 + pos[1] ** 2) > deathradius ** 2:  # faster than sqrt
        return False
    else:
        return True
# end of inCircle


# registers an extension on the seed
def registerHit(pos):
    global hits, birthradius, deathradius, maxradius

    # check if this "hit" extends the max radius
    norm2 = (pos[0] ** 2 + pos[1] ** 2)
    if norm2 > maxradius ** 2:
        maxradius = int(sqrt(norm2))
        birthradius = maxradius + 5 if (maxradius + 5) < L else L
        deathradius = maxradius + 20 if (maxradius + 20) < L else L

    hits += 1
    lattice[pos[0] + L, pos[1] + L] = hits
# end of registerHit


starttime = time.time()
print "Running", numParticles, "particles..."

for particle in xrange(numParticles):

    #print particle

    # find angle on [0, 2pi)
    angle = random() * twopi
    # and convert to a starting position, pos = (x, y),
    # on a circle of radius "birthradius" around the centre seed
    pos = [int(sin(angle) * birthradius), int(cos(angle) * birthradius)]

    isDead = False      # walker starts off alive

    while not isDead:

        # pick one of the nearest neighbour sites to explore
        moveDir = choice(nnsteps)
        # and apply the selected move to position coordinate, pos
        pos[0] += moveDir[0]
        pos[1] += moveDir[1]

        if not inCircle(pos):
            isDead = True
            break
        elif nnOccupied(pos):
            registerHit(pos)
            isDead = True
            break


endtime = time.time()
print "Ran in time:", (endtime - starttime)
print "Maximum radius:", maxradius

# select only the interesting parts
M = maxradius
grph = L - M, L + M

# and plot
axis = arange(-M, M + 1)
pcolormesh(axis, axis, lattice[grph[0]:grph[1], grph[0]:grph[1]])
axes().set_aspect('equal', 'datalim')
show()


515
7
задан 23 августа 2011 в 04:08 Источник Поделиться
Комментарии
1 ответ


  1. Использование глобальных переменных не приветствуется. Вы должны, вероятно, определить класс и хранения данных в качестве атрибутов на нем.

  2. Не ставьте логика на уровне модуля, т. е. ваш основной цикл. Что лучше поместить внутри функции main()

  3. В свой дифф, вы используете сумму в Python, а затем сумма включает в себя все. при общении с суммой библиотеки numpy и NumPy массива будет гораздо быстрее. В принципе, вы никогда не должны использовать математические функции Python на массивы numpy. Всегда использовать библиотеки numpy эквиваленты.

  4. И NumPy действительно не предназначен для малых массивов из двух элементов. Его действительно сосредоточен на большие массивы, так что вы будете иметь проблемы при получении скоростные преимущества.

  5. Вы должны избегать написания перебирает массивы numpy, вместо этого вы должны использовать векторные операции включает в

Чтобы сделать эффективное использование numpy, вы, вероятно, нужно, если это возможно, чтобы обрабатывать несколько частиц параллельно с тем, что вы работаете на больших массивах. Способности обработки и NumPy смогут помочь.

Редактировать: быстрая выборка векторных операций и NumPy

angles = numpy.random.rand(numParticles) * twopi
positions = numpy.empty((numParticles, 2))
positions[0,:] = numpy.sin(angles) * birthradius
positions[1,:] = numpy.cos(angles) * birthradius

Это создает 2Д массив позиций. Используется в Python циклы, вместо операций над массивами используются. Я не вижу хороший способ, чтобы использовать его в вашем случае. Ordinarilly, я бы использовать их для запуска всех частиц одновременно. Но у вас есть зависимость между разными трассами.

Больше мыслей:


  1. Сохраняя массив значений маркировки позиции рядом с "Хит" местах, вы можете избежать проверки соседей в nnOccupied. Его дешевле обновить "next_to" массив при обнаружении удар.

  2. Используя две системы координат с 0 и L на основе результатов путаницы. Стандартизировать на одном.

  3. Установка isDead и через перерыв является излишним. Выберите один и придерживайтесь его

  4. Не если expr: возвратите true, иначе возвращает false только вернуть его

  5. Почему nnsteps redecared в registerHit?

** Редактировать: мой переписанный код **

#!/usr/bin/env python
import time

from pylab import pcolormesh, axes, show
import numpy as np

L = 500 # lattice goes from -L : L
SIZE = (2 * L) + 1 # so total lattice width is 2L + 1
NNSTEPS = np.array([
(0, 1),
(0, -1),
(1, 0),
(-1, 0)
])
CHUNK_SIZE = 1000

class Simulation(object):
def __init__(self, num_particles):
self.hits = 0
self.birthradius = 5
self.deathradius = 10
self.maxradius = -1

self.lattice = np.zeros((SIZE, SIZE), dtype=np.int32)
self.lattice[L, L] = -1
# initialize center point as the seed

self.hit_zone = np.zeros((SIZE, SIZE), dtype=bool)
self.hit_zone[L, L] = True
self.hit_zone[ NNSTEPS[:,0] + L, NNSTEPS[:,1] + L ] = True
# the hit_zone is all of the position next to places that have
# actually hit, these count as hits

self.num_particles = num_particles

def register_hit(self, pos):
neighbors = NNSTEPS + pos
self.hit_zone[neighbors[:,0], neighbors[:,1]] = True
# update hit_zone

# check if this "hit" extends the max radius
norm2 = (pos[0] - L)**2 + (pos[1] - L)**2
if norm2 > self.maxradius ** 2:
self.maxradius = int(np.sqrt(norm2))
self.birthradius = min(self.maxradius + 5, L)
self.deathradius = min(self.maxradius + 20, L)

self.hits += 1
self.lattice[pos] = self.hits

def run(self):
for particle in xrange(self.num_particles):
# find angle on [0, 2pi)
angle = np.random.random() * 2 * np.pi
# and convert to a starting position, pos = (x, y),
# on a circle of radius "birthradius" around the centre seed
pos = (np.sin(angle) * self.birthradius + L, np.cos(angle) * self.birthradius + L)

while True:
moves = np.random.randint(0, 4, CHUNK_SIZE) # pick the move numbers
moves = np.cumsum(NNSTEPS[moves], axis = 0) # grab the move and do a sum

# add starting position to all of that
moves[:,0] += pos[0]
moves[:,1] += pos[1]

# calculate distance to center for all the points
from_center = moves - L
distances_to_center = from_center[:,0]**2 + from_center[:,1]**2
alive = distances_to_center < self.deathradius ** 2
alive = np.minimum.accumulate(alive)

particle_hits = self.hit_zone[moves[:,0], moves[:,1]]

if np.any(particle_hits):
first_hit = particle_hits.nonzero()[0][0]
if alive[first_hit]:
pos = tuple(moves[first_hit])
self.register_hit(pos)
break
else:
if np.all(alive):
pos = tuple(moves[-1])
else:
break
NUMBER_PARTICLES = 1000
def main():

np.random.seed(42)
simulation = Simulation(NUMBER_PARTICLES)
starttime = time.time()
print "Running", NUMBER_PARTICLES, "particles..."
simulation.run()
endtime = time.time()
print "Ran in time:", (endtime - starttime)
print "Maximum radius:", simulation.maxradius

# select only the interesting parts
M = simulation.maxradius
grph = L - M, L + M

# and plot
axis = np.arange(-M, M + 1)
pcolormesh(axis, axis, simulation.lattice[grph[0]:grph[1], grph[0]:grph[1]])
axes().set_aspect('equal', 'datalim')
show()

if __name__ == '__main__':
main()

Хитрость здесь состоит в том, как я мог и NumPy вектор операций, чтобы помочь в этой ситуации. Здесь я постараюсь объяснить, что.

                moves = np.random.randint(0, 4, CHUNK_SIZE) # pick the move numbers

Здесь мы выбираем 1000 различных цифр 0, 1, 2, или 3, соответствующую направлениям мы можем перемещать

                moves = np.cumsum(NNSTEPS[moves], axis = 0) # grab the move and do a sum

Мы используем эти цифры для индекса в массиве NNSTEPS, таким образом, у нас есть массив всех ходов. Cumsum добавляет в совокупности за весь массив. Так что N-й элемент является суммой первых n элементов.

                # add starting position to all of that
moves[:,0] += pos[0]
moves[:,1] += pos[1]

Добавить в положение Start, чтобы все это производить массив ближайшие 1000 позиций, которые будут посещены.

                # calculate distance to center for all the points
from_center = moves - L
distances_to_center = from_center[:,0]**2 + from_center[:,1]**2

Мы рассчитать все расстояния до центра. Поскольку нам не придется писать явные петли это довольно эффективно.

                alive = distances_to_center < self.deathradius ** 2
alive = np.minimum.accumulate(alive)

живы указывает, является ли конкретное живое течение определенного двигаться. Мы начинаем с маркировки этих элементов как живой, когда расстояние меньше, чем deathradius. Минимальный.накопить наборы n-го элемента к минимальной первых n элементов. Следствием является то, что после первого ложного (умерших) элемент, все остальные мертвы

                particle_hits = self.hit_zone[moves[:,0], moves[:,1]]

Здесь мы делаем векторные операции проверки hit_zone. particle_hits становится булевский массив каждый, что путь будет что-то ударило.

                if np.any(particle_hits):
first_hit = particle_hits.nonzero()[0][0]

Если что-нибудь из 1000 мы рассматриваем хиты, выяснить, где

                    if alive[first_hit]:

Убедитесь, что мы еще живы

                        pos = tuple(moves[first_hit])
self.register_hit(pos)

Запись, нажмите
перерыв
другое:
если НП.все(жив):
пос = кортежа(ходов[-1])

Если мы выжили, взять кортежа и запустить еще один раунд.

                    else:
break

В принципе, таким образом мы запускаем 1000 элементов пути в то время, позволит избежать многих издержек на Python.

2
ответ дан 23 августа 2011 в 05:08 Источник Поделиться