我是一名初级软件工程师,C++通常是我的主要障碍,但我开始为我在大学做的一个研究项目学习Python。我渴望尽可能多地学习Python语法、技巧、最佳实践或软件架构。这是我的项目~300行代码的一部分,它模拟了两种类型的蛋白质通过布朗扩散的运动:在小的圆形域中的扩散(纳米域模拟)和在有间隔的网络中的扩散(跳扩散)。我希望我能得到任何提示和反馈的代码!
.
├── main.py
├── plotGenerator.py
├── requirements-dev.txt
├── simulations
│ ├── __init__.py
│ ├── hopDiffusionSimulation.py
│ ├── nanodomainSimulation.py
│ └── simulation.py
└── util.py
from simulations.simulation import *
from simulations.nanodomainSimulation import *
from simulations.hopDiffusionSimulation import *
from plotGenerator import *
from util import *
#nanoDomain = Nanodomain()
hopDiffusion = HopDiffusion();
plot(hopDiffusion, SimulationType.HOPDIFFUSION)from typing import List, Tuple
import random
import numpy as np
from enum import Enum
class SimulationType(Enum):
BROWNIAN = 1
NANODOMAIN = 2
HOPDIFFUSION = 3
PATH = Tuple[List[float]]
DPI = 100
RADIUS_PADDING = 10
RADIUS = 250
CORRECTED_CANVAS_RADIUS = RADIUS - RADIUS_PADDING
TIME_PER_FRAME: float = 0.02 # 20 ms
DIFFUSION_SPEED_CORRECTION: int = 35 # arbitrary
MEMBRANE_DIFFUSION_COEFFICIENT: float = 0.1 # micrometer^2 / s
MEMBRANE_DIFFUSION_FACTOR: float = 2 * np.sqrt(MEMBRANE_DIFFUSION_COEFFICIENT * TIME_PER_FRAME)
MEMBRANE_DIFFUSION_FATOR_CORRECTED: float = MEMBRANE_DIFFUSION_FACTOR * DIFFUSION_SPEED_CORRECTION
class Simulation:
def __init__(self, n: int = 5):
self.numberOfParticles: int = n
self.particlesLocation: List[Tuple[int, int]] = []
self.paths: List[List[Tuple[int, int]]] = []
self.init_particles()
self.init_paths()
def init_paths(self):
self.paths.extend([[coordinate] for coordinate in self.particlesLocation])
def init_particles(self) -> None:
mem: List[Tuple] = []
def get_random_canvas_value(self) -> int:
return int(random.randint(-(CORRECTED_CANVAS_RADIUS), CORRECTED_CANVAS_RADIUS))
def rec(self, x: int = 0, y: int = 0) -> Tuple[int, int]:
x, y = [get_random_canvas_value(self) for _ in range(2)]
while (x, y) in mem:
return rec(self, x, y)
mem.append((x, y))
return x,y
self.particlesLocation.extend([rec(self) for _ in range(5)])from typing import List, Tuple
from simulations.simulation import *
from util import *
BOUNDARY_THICKNESS: int = 15
NUMBER_OF_COMPARTMENTS_PER_DIRECTION: int = 3
BOUNDARY_JUMP: int = BOUNDARY_THICKNESS
BOUNDARY_OVERFLOW: int = 20
HOP_PROBABILITY_PERCENTAGE: float = 0.15
class HopDiffusion(Simulation):
def __init__(self, n: int = 5):
self.boundary_coordinates_for_plot: List[List] = []
self.boundary_coordinates: List[Tuple[Tuple]] = []
self.generate_boundaries()
super().__init__(n)
def generate_boundaries(self):
step: int = int((RADIUS << 1) / NUMBER_OF_COMPARTMENTS_PER_DIRECTION)
for i in range(6):
if i % 3 == 0: continue
horizontal: bool = i < NUMBER_OF_COMPARTMENTS_PER_DIRECTION
curr = i * step if horizontal else (i - NUMBER_OF_COMPARTMENTS_PER_DIRECTION) * step
width = BOUNDARY_THICKNESS if horizontal else (RADIUS << 1) + (BOUNDARY_OVERFLOW << 1)
height = BOUNDARY_THICKNESS if not horizontal else (RADIUS << 1) + (BOUNDARY_OVERFLOW << 1)
x = curr - RADIUS - (BOUNDARY_THICKNESS >> 1) if horizontal else -RADIUS - BOUNDARY_OVERFLOW
y = curr - RADIUS - (BOUNDARY_THICKNESS >> 1) if not horizontal else -RADIUS - BOUNDARY_OVERFLOW
self.boundary_coordinates_for_plot.append(list([x, y, width, height]))
self.boundary_coordinates.append(list([tuple((x, x + width)), tuple((y, y + height))]))
@property
def get_boundary_coordinates(self):
return self.boundary_coordinates_for_plot
def can_particle_hop_boundary_probability(self) -> bool:
return random.random() < HOP_PROBABILITY_PERCENTAGE
def is_particle_on_specific_boudnary(self, pos: Tuple, idx: int):
return Util.is_point_within_bounds(pos, self.boundary_coordinates[idx])
def is_particle_on_boundary(self, pos: Tuple):
return any(
Util.is_point_within_bounds(pos, bounds_of_boundary)
for bounds_of_boundary in self.boundary_coordinates
)
def is_particle_in_compartment(self, particle) -> bool:
return not self.is_particle_on_boundary(particle)
def get_surrounding_boundary_of_particle(self, pos: Tuple) -> int:
for idx, bounds_of_boundary in enumerate(self.boundary_coordinates):
if Util.is_point_within_bounds(pos, bounds_of_boundary):
return idx
return -1
def make_particle_jump(self, newPos: Tuple, x_dir: int, y_dir: int):
surrounding_boundary_idx = self.get_surrounding_boundary_of_particle(newPos)
while (self.is_particle_on_specific_boudnary(newPos, surrounding_boundary_idx)):
newPos = Util.increment_tuple_by_val(
newPos, tuple((Util.sign(x_dir), Util.sign(y_dir)))
)
newPos = Util.increment_tuple_by_val(
newPos, tuple(
(Util.sign(x_dir) * BOUNDARY_JUMP,
Util.sign(y_dir) * BOUNDARY_JUMP)
)
)
# Special case: In some instances the jump may land the particle
# on a subsequent boundary so we repeat the function. We decrement
# the particle's coordinates until it is out.
new_surrounding_boundary_idx = self.get_surrounding_boundary_of_particle(newPos)
while (self.is_particle_on_boundary(newPos)):
newPos = Util.increment_tuple_by_val(
newPos, tuple((Util.sign(-x_dir), Util.sign(-y_dir)))
)
return newPos
def update_path(self, idx: int):
x, y = self.paths[idx][-1]
assert(not self.is_particle_on_boundary(tuple((x, y))))
diffusion_factor = MEMBRANE_DIFFUSION_FATOR_CORRECTED
x_dir, y_dir = [Util.get_random_normal_direction() * diffusion_factor for _ in range(2)]
newPos = tuple((x + x_dir, y + y_dir))
if self.is_particle_on_boundary(newPos):
if self.can_particle_hop_boundary_probability():
newPos = self.make_particle_jump(newPos, x_dir, y_dir)
else:
newPos = Util.change_direction(tuple((x, y)), tuple((x_dir, y_dir)))
self.paths[idx].append(newPos)
def update(self):
for i in range(self.numberOfParticles): self.update_path(i)
def init_particles(self) -> None:
mem: List[Tuple[int, int]] = []
def get_random_canvas_value(self) -> int:
return int(random.randint(-(CORRECTED_CANVAS_RADIUS), CORRECTED_CANVAS_RADIUS))
def rec(self, x: int = 0, y: int = 0) -> Tuple[int, int]:
x, y = [get_random_canvas_value(self) for _ in range(2)]
while (x, y) in mem or self.is_particle_on_boundary(tuple((x, y))):
return rec(self, x, y)
mem.append((x, y))
return x, y
self.particlesLocation.extend([rec(self) for _ in range(5)])from typing import List, Tuple
from simulations.simulation import *
from util import *
NANODOMAIN_DIFFUSION_FATOR_CORRECTED: float = MEMBRANE_DIFFUSION_FATOR_CORRECTED * 0.4 # type : ignore
class Nanodomain(Simulation):
def __init__(self, n: int = 5):
super().__init__(n)
self.nanodomain_coordinates: List[Tuple[int, int]] = [
(-100, 100), (0, 0), (150, -60), (-130, -160)
]
self.nanodomain_radii: List[int] = [80, 20, 50, 140]
@property
def get_nanodomain_coordinates(self) -> List[Tuple[int, int]]:
return self.nanodomain_coordinates
@property
def get_nanodomain_radii(self) -> List[int]:
return self.nanodomain_radii
def get_nanodomain_attributes(self) -> List[Tuple]:
return list(map(
lambda coord, radius: (coord, radius),
self.get_nanodomain_coordinates,
self.get_nanodomain_radii
))
def is_particle_in_nanodomain(self, particle: Tuple) -> bool:
return any(
Util.compute_distance(particle, circle_center) <= radius
for circle_center, radius in
zip(self.get_nanodomain_coordinates, self.get_nanodomain_radii)
)
def update_path(self, idx):
x, y = self.paths[idx][-1]
diffusion_factor = NANODOMAIN_DIFFUSION_FATOR_CORRECTED if (self.is_particle_in_nanodomain((x, y))) else MEMBRANE_DIFFUSION_FATOR_CORRECTED
x_dir, y_dir = [Util.get_random_normal_direction() * diffusion_factor for _ in range(2)]
self.paths[idx].append((x + x_dir, y + y_dir))
def update(self):
[self.update_path(i) for i in range(self.numberOfParticles)]from simulations.hopDiffusionSimulation import HopDiffusion
from simulations.nanodomainSimulation import Nanodomain
from simulations.simulation import *
from util import *
from matplotlib.animation import FuncAnimation # type: ignore
from matplotlib.pyplot import figure
import matplotlib.pyplot as plt
from typing import List, Tuple
import numpy as np
from matplotlib import rcParams # type: ignore
colors: List[str] = ['r', 'b', "orange", 'g', 'y', 'c']
markers: List[str] = ['o', 'v', '<', '>', 's', 'p']
def handle_nanodomain(ax, sim: Nanodomain):
nanodomains = [
plt.Circle( # type: ignore
*param,
color = 'black',
alpha = 0.2)
for param in sim.get_nanodomain_attributes()
]
[ax.add_patch(nanodomain) for nanodomain in nanodomains]
def handle_hop_diffusion(ax, sim: HopDiffusion):
compartments = [
plt.Rectangle( # type: ignore
tuple((param[0], param[1])),
param[2], param[3],
color = 'black',
alpha = 0.7,
clip_on = False)
for param in sim.boundary_coordinates_for_plot
]
[ax.add_patch(boundary) for boundary in compartments]
def get_coordinates_for_plot(sim, idx: int):
return Util.get_x_coordinates(sim.paths[idx]), Util.get_y_coordinates(sim.paths[idx])
def get_coordinates_for_heads(sim, idx: int):
return Util.get_last_point(sim.paths[idx])
def set_plot_parameters(ax):
ax.tick_params(axis = 'y', direction = "in", right = True, labelsize = 16, pad = 20)
ax.tick_params(axis = 'x', direction = "in", top = True, bottom = True, labelsize = 16, pad = 20)
## legends and utilities
ax.set_xlabel(r"nm", fontsize=16)
ax.set_ylabel(r"nm", fontsize=16)
## border colors
ax.patch.set_edgecolor('black')
ax.patch.set_linewidth('2')
ax.set_xlim(-RADIUS, RADIUS)
ax.set_ylim(-RADIUS, RADIUS)
def plot(sim: Simulation, type: SimulationType):
fig, ax = plt.subplots(figsize = [5, 5], dpi = DPI) # type: ignore
path_plots: List = [
ax.plot(
*get_coordinates_for_plot(sim, i),
markersize=15, color = colors[i])[0]
for i in range(5)
]
head_plots: List = [
ax.plot(
*get_coordinates_for_heads(sim, i),
markersize=7, color = colors[i], marker = markers[i],
markerfacecolor="white")[0]
for i in range(5)
]
def initialize_animation():
set_plot_parameters(ax)
if type == SimulationType.NANODOMAIN: handle_nanodomain(ax, sim)
elif type == SimulationType.HOPDIFFUSION: handle_hop_diffusion(ax, sim)
return path_plots
def update_animation(frame):
sim.update()
for i, plot in enumerate(path_plots):
plot.set_data(*get_coordinates_for_plot(sim, i))
for i, head_marker in enumerate(head_plots):
head_marker.set_data(*get_coordinates_for_heads(sim, i))
return path_plots
animation = FuncAnimation(
fig,
update_animation,
init_func = initialize_animation,
interval = 20
)
plt.show(block = True) # type: ignore
fig.tight_layout()
rcParams.update({'figure.autolayout': True})from typing import List, Tuple
import numpy as np
import random
class Util:
@staticmethod
def get_bounds(lists) -> Tuple[int, ...]:
x_min: int = min([min(elem[0]) for elem in lists])
x_max: int = max([max(elem[0]) for elem in lists])
y_min: int = min([min(elem[1]) for elem in lists])
y_max: int = max([max(elem[1]) for elem in lists])
return x_min, x_max, y_min, y_max
@staticmethod
def compute_distance(p1: Tuple, p2: Tuple) -> float:
return np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
@staticmethod
def get_last_point(path: List[Tuple]) -> Tuple[int, ...]:
return path[-1][0], path[-1][1]
@staticmethod
def get_x_coordinates(path) -> List:
return list(list(zip(*path))[0])
@staticmethod
def get_y_coordinates(path) -> List:
return list(list(zip(*path))[1])
@staticmethod
def get_random_normal_direction():
return np.random.normal() * np.random.choice([1, -1])
@staticmethod
def is_point_within_bounds(pos: Tuple, bounds: Tuple[Tuple, ...]):
x, y = pos[0], pos[1]
return x >= bounds[0][0] and x <= bounds[0][1] and y >= bounds[1][0] and y <= bounds[1][1]
@staticmethod
def sign(x):
return ((x >= 0) << 1) - 1
@staticmethod
def increment_tuple_by_val(tuple_object: Tuple, val):
tuple_object = tuple((tuple_object[0] + val[0], tuple_object[1] + val[1]))
return tuple_object
@staticmethod
def change_direction(tuple_object: Tuple, dir):
tuple_object = tuple((tuple_object[0] - dir[0], tuple_object[1] - dir[1]))
return tuple_object发布于 2022-10-02 15:59:05
拼写:FATOR -> FACTOR,boudnary -> boundary。
升级Python,然后直接使用list和tuple作为类型提示,而不是旧的List和Tuple。
简化链式比较;用于is_point_within_bounds的比较如下所示:
return (
bounds[0][0] <= x <= bounds[0][1] and
bounds[1][0] <= y <= bounds[1][1]
)有一个名为dir to change_direction的参数隐藏了内置的dir。将其命名为其他名称,在本例中为direction。
在许多地方都有不完整的类型提示。不要不指定list;在[]中填充它的元素类型。将Mypy配置为不允许未指定的类型。
Util不应该是一个类。只需将这些函数放在全局命名空间中,然后在依赖模块中编写没有import util的from来创建包含的命名空间。
tuple_object不是一个好名字。当然,这是一个元组:您在类型方面做了正确的事情--暗示它本身(尽管类型提示不完整)。根据内容的含义而不是类型来命名它。
您有一种不需要强制转换的序列转换习惯,如
self.boundary_coordinates.append(list([tuple((x, x + width)), tuple((y, y + height))]))那真的只是
self.boundary_coordinates.append(
[
(x, x + width), (y, y + height),
]
)这是:
def sign(x):
return ((x >= 0) << 1) - 1既棘手又没有必要。只需使用np.sign。
ax.patch.set_linewidth('2')将永远不能工作;该字符串需要是一个数字文字。
-(CORRECTED_CANVAS_RADIUS)不应该有父母。
rec存在于init_particles中,因此在闭包范围内已经可以访问self;不要将它再次写入到rec的签名中。签名中也从不使用x和y,所以也要删除它们。
get_random_canvas_value应该移到staticmethod of Simulation;不要重复它--停止复制和粘贴代码。
这个循环没有意义:
while (x, y) in mem:
return rec(self, x, y)那只是一个if,而不是一个while。
应该对rec进行重新设计,使其不再递归。这是等待发生的堆栈溢出。
本清单理解如下:
x, y = [self.get_random_canvas_value() for _ in range(2)]应该保留为生成器(),而不是列表[]。同样,get_bounds中的所有内部列表理解都应该转换为生成器;基本上删除[]。
colors和markers应该是元组。
numberOfParticles应该是PEP8的number_of_particles。
get_bounds未使用,所以请删除它。
你应该在这里使用楼层划分:
step: int = int((RADIUS << 1) // NUMBER_OF_COMPARTMENTS_PER_DIRECTION)您使用<<和>>来加速乘法和除法,这是不成熟的优化,而且您的程序中有些领域的效率相对较低。只需做一些可理解的事情,并编写2。总之:你有固定的框架。只要这个框架受到打击,尝试微观优化就没有任何意义。
让get_nanodomain_coordinates像您所拥有的那样成为一个属性是一个好主意,但是在这种情况下,删除"get_“,因为它的调用是类似变量的,而不是类似方法的。然而。您已经有了带有这些名称的变量,所以恰恰相反:从这些方法中删除@property。
您将内置的random与np.random混合使用。别干那事。可能只使用np.random。
不要写诸如[self.update_path(i) for i in range(self.n_particles)]这样的无作业理解。只需写一个循环。
理解在handle_hop_diffusion中起不了作用。只需将其展开为一个常规循环:
def handle_hop_diffusion(ax: plt.Axes, sim: HopDiffusion):
for param in sim.boundary_coordinates_for_plot:
boundary = plt.Rectangle( # type: ignore
tuple((param[0], param[1])),
param[2], param[3],
color='black',
alpha=0.7,
clip_on=False,
)
ax.add_patch(boundary)type作为一个参数需要消失。您已经知道了在isinstance()变量上使用sim的类型。
Simulation实际上是一个抽象类。您需要在上面添加一个update存根:
def update(self) -> None:
raise NotImplementedError()此语句无效,因此将其删除:
new_surrounding_boundary_idx = self.get_surrounding_boundary_of_particle(new_pos)你有潜在的窃听器。for i in range(6):硬编码隔间的数量,而这实际上应该是从NUMBER_OF_COMPARTMENTS_PER_DIRECTION得到的.一旦这种不断的变化,您的代码就不会做您想做的事情。同样,for _ in range(5):对粒子计数进行硬编码,而不是使用n_particles中设置的计数参数.
上面大部分内容的第一次通过如下所示:
import random
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import rcParams # type: ignore
from matplotlib.animation import FuncAnimation # type: ignore
DPI = 100
RADIUS_PADDING = 10
RADIUS = 250
CORRECTED_CANVAS_RADIUS = RADIUS - RADIUS_PADDING
TIME_PER_FRAME: float = 0.02 # 20 ms
DIFFUSION_SPEED_CORRECTION: int = 35 # arbitrary
MEMBRANE_DIFFUSION_COEFFICIENT: float = 0.1 # micrometer^2 / s
MEMBRANE_DIFFUSION_FACTOR: float = 2 * np.sqrt(MEMBRANE_DIFFUSION_COEFFICIENT * TIME_PER_FRAME)
MEMBRANE_DIFFUSION_FACTOR_CORRECTED: float = MEMBRANE_DIFFUSION_FACTOR * DIFFUSION_SPEED_CORRECTION
NANODOMAIN_DIFFUSION_FACTOR_CORRECTED: float = MEMBRANE_DIFFUSION_FACTOR_CORRECTED * 0.4
BOUNDARY_THICKNESS: int = 15
NUMBER_OF_COMPARTMENTS_PER_DIRECTION: int = 3
BOUNDARY_JUMP: int = BOUNDARY_THICKNESS
BOUNDARY_OVERFLOW: int = 20
HOP_PROBABILITY_PERCENTAGE: float = 0.15
colors: tuple[str, ...] = ('r', 'b', 'orange', 'g', 'y', 'c')
markers: tuple[str, ...] = ('o', 'v', '<', '>', 's', 'p')
class util: # convert this to an import of a module with global functions
@staticmethod
def compute_distance(p1: tuple[float, float], p2: tuple[float, float]) -> float:
return np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
@staticmethod
def get_last_point(path: list[tuple[float, float]]) -> tuple[float, float]:
return path[-1][0], path[-1][1]
@staticmethod
def get_x_coordinates(path: list[tuple[float, float]]) -> list[float]:
return list(list(zip(*path))[0])
@staticmethod
def get_y_coordinates(path: list[tuple[float, float]]) -> list[float]:
return list(list(zip(*path))[1])
@staticmethod
def get_random_normal_direction() -> float:
return np.random.normal() * np.random.choice([1, -1])
@staticmethod
def is_point_within_bounds(
pos: tuple[float, float],
bounds: tuple[tuple[float, float], tuple[float, float]],
) -> bool:
x, y = pos[0], pos[1]
return (
bounds[0][0] <= x <= bounds[0][1] and
bounds[1][0] <= y <= bounds[1][1]
)
@staticmethod
def increment_tuple_by_val(tuple_object: tuple[float, float], val: tuple[float, float]) -> tuple[float, float]:
return tuple_object[0] + val[0], tuple_object[1] + val[1]
@staticmethod
def change_direction(
tuple_object: tuple[float, float],
direction: tuple[float, float],
) -> tuple[float, float]:
return tuple_object[0] - direction[0], tuple_object[1] - direction[1]
class Simulation:
def __init__(self, n: int = 5) -> None:
self.n_particles: int = n
self.particle_locations: list[tuple[float, float]] = list(self.init_particles())
self.paths: list[list[tuple[float, float]]] = [
[coordinate] for coordinate in self.particle_locations
]
@staticmethod
def get_random_canvas_value() -> int:
return int(random.randint(-CORRECTED_CANVAS_RADIUS, CORRECTED_CANVAS_RADIUS))
def init_particles(self) -> set[tuple[float, float]]:
mem: set[tuple[float, float]] = set()
for _ in range(5):
while True:
pair = (self.get_random_canvas_value(), self.get_random_canvas_value())
if pair not in mem:
break
mem.add(pair)
return mem
def update(self) -> None:
raise NotImplementedError()
class HopDiffusion(Simulation):
def __init__(self, n: int = 5) -> None:
self.boundary_coordinates_for_plot: list[tuple[int, int, int, int]] = []
self.boundary_coordinates: list[tuple[tuple[float, float], tuple[float, float]]] = []
self.generate_boundaries()
super().__init__(n)
def generate_boundaries(self) -> None:
step: int = (RADIUS * 2) // NUMBER_OF_COMPARTMENTS_PER_DIRECTION
for i in range(6):
if i % 3 == 0:
continue
horizontal: bool = i < NUMBER_OF_COMPARTMENTS_PER_DIRECTION
curr = i * step if horizontal else (i - NUMBER_OF_COMPARTMENTS_PER_DIRECTION) * step
width = BOUNDARY_THICKNESS if horizontal else (RADIUS * 2) + (BOUNDARY_OVERFLOW * 2)
height = BOUNDARY_THICKNESS if not horizontal else (RADIUS * 2) + (BOUNDARY_OVERFLOW * 2)
x = curr - RADIUS - (BOUNDARY_THICKNESS // 2) if horizontal else -RADIUS - BOUNDARY_OVERFLOW
y = curr - RADIUS - (BOUNDARY_THICKNESS // 2) if not horizontal else -RADIUS - BOUNDARY_OVERFLOW
self.boundary_coordinates_for_plot.append((x, y, width, height))
self.boundary_coordinates.append(
((x, x + width), (y, y + height))
)
@staticmethod
def can_particle_hop_boundary_probability() -> bool:
return random.random() < HOP_PROBABILITY_PERCENTAGE
def is_particle_on_specific_boundary(self, pos: tuple[float, float], idx: int) -> bool:
return util.is_point_within_bounds(pos, self.boundary_coordinates[idx])
def is_particle_on_boundary(self, pos: tuple[float, float]) -> bool:
return any(
util.is_point_within_bounds(pos, bounds_of_boundary)
for bounds_of_boundary in self.boundary_coordinates
)
def is_particle_in_compartment(self, particle: tuple[float, float]) -> bool:
return not self.is_particle_on_boundary(particle)
def get_surrounding_boundary_of_particle(self, pos: tuple[float, float]) -> int:
for idx, bounds_of_boundary in enumerate(self.boundary_coordinates):
if util.is_point_within_bounds(pos, bounds_of_boundary):
return idx
return -1
def make_particle_jump(
self,
new_pos: tuple[float, float],
x_dir: float, y_dir: float,
) -> tuple[float, float]:
surrounding_boundary_idx = self.get_surrounding_boundary_of_particle(new_pos)
while self.is_particle_on_specific_boundary(new_pos, surrounding_boundary_idx):
new_pos = util.increment_tuple_by_val(
new_pos, (np.sign(x_dir), np.sign(y_dir))
)
new_pos = util.increment_tuple_by_val(
new_pos, (
np.sign(x_dir) * BOUNDARY_JUMP,
np.sign(y_dir) * BOUNDARY_JUMP,
),
)
# Special case: In some instances the jump may land the particle
# on a subsequent boundary so we repeat the function. We decrement
# the particle's coordinates until it is out.
while self.is_particle_on_boundary(new_pos):
new_pos = util.increment_tuple_by_val(
new_pos, (np.sign(-x_dir), np.sign(-y_dir))
)
return new_pos
def update_path(self, idx: int) -> None:
x, y = self.paths[idx][-1]
assert not self.is_particle_on_boundary((x, y))
diffusion_factor = MEMBRANE_DIFFUSION_FACTOR_CORRECTED
x_dir, y_dir = [util.get_random_normal_direction() * diffusion_factor for _ in range(2)]
new_pos = x + x_dir, y + y_dir
if self.is_particle_on_boundary(new_pos):
if self.can_particle_hop_boundary_probability():
new_pos = self.make_particle_jump(new_pos, x_dir, y_dir)
else:
new_pos = util.change_direction((x, y), (x_dir, y_dir))
self.paths[idx].append(new_pos)
def update(self) -> None:
for i in range(self.n_particles):
self.update_path(i)
def init_particles(self) -> set[tuple[float, float]]:
mem: set[tuple[float, float]] = set()
for _ in range(5):
while True:
pair = (self.get_random_canvas_value(), self.get_random_canvas_value())
if not (pair in mem or self.is_particle_on_boundary(pair)):
break
mem.add(pair)
return mem
class Nanodomain(Simulation):
def __init__(self, n: int = 5):
super().__init__(n)
self.nanodomain_coordinates: list[tuple[float, float]] = [
(-100, 100), (0, 0), (150, -60), (-130, -160)
]
self.nanodomain_radii: list[int] = [80, 20, 50, 140]
def get_nanodomain_attributes(self) -> list[tuple]:
return list(map(
lambda coord, radius: (coord, radius),
self.nanodomain_coordinates,
self.nanodomain_radii,
))
def is_particle_in_nanodomain(self, particle: tuple) -> bool:
return any(
util.compute_distance(particle, circle_center) <= radius
for circle_center, radius in
zip(self.nanodomain_coordinates, self.nanodomain_radii)
)
def update_path(self, idx: int) -> None:
x, y = self.paths[idx][-1]
diffusion_factor = NANODOMAIN_DIFFUSION_FACTOR_CORRECTED if (
self.is_particle_in_nanodomain((x, y))) else MEMBRANE_DIFFUSION_FACTOR_CORRECTED
x_dir, y_dir = [util.get_random_normal_direction() * diffusion_factor for _ in range(2)]
self.paths[idx].append((x + x_dir, y + y_dir))
def update(self) -> None:
for i in range(self.n_particles):
self.update_path(i)
def handle_nanodomain(ax: plt.Axes, sim: Nanodomain) -> None:
nanodomains = [
plt.Circle(
*param,
color='black',
alpha=0.2,
)
for param in sim.get_nanodomain_attributes()
]
for nanodomain in nanodomains:
ax.add_patch(nanodomain)
def handle_hop_diffusion(ax: plt.Axes, sim: HopDiffusion) -> None:
for param in sim.boundary_coordinates_for_plot:
boundary = plt.Rectangle(
tuple((param[0], param[1])),
param[2], param[3],
color='black',
alpha=0.7,
clip_on=False,
)
ax.add_patch(boundary)
def get_coordinates_for_plot(sim: Simulation, idx: int):
return util.get_x_coordinates(sim.paths[idx]), util.get_y_coordinates(sim.paths[idx])
def get_coordinates_for_heads(sim, idx: int):
return util.get_last_point(sim.paths[idx])
def set_plot_parameters(ax):
ax.tick_params(axis='y', direction='in', right=True, labelsize=16, pad=20)
ax.tick_params(axis='x', direction='in', top=True, bottom=True, labelsize=16, pad=20)
# legends and utilities
ax.set_xlabel(r'nm', fontsize=16)
ax.set_ylabel(r'nm', fontsize=16)
# border colors
ax.patch.set_edgecolor('black')
ax.patch.set_linewidth(2)
ax.set_xlim(-RADIUS, RADIUS)
ax.set_ylim(-RADIUS, RADIUS)
def plot(sim: Simulation) -> None:
fig, ax = plt.subplots(figsize=[5, 5], dpi=DPI)
path_plots: list[plt.Line2D] = [
ax.plot(
*get_coordinates_for_plot(sim, i),
markersize=15, color=colors[i],
)[0]
for i in range(5)
]
head_plots: list[plt.Line2D] = [
ax.plot(
*get_coordinates_for_heads(sim, i),
markersize=7, color=colors[i], marker=markers[i],
markerfacecolor='white')[0]
for i in range(5)
]
def initialize_animation() -> list[plt.Artist]:
set_plot_parameters(ax)
if isinstance(sim, Nanodomain):
handle_nanodomain(ax, sim)
elif isinstance(sim, HopDiffusion):
handle_hop_diffusion(ax, sim)
return path_plots
def update_animation(frame: int) -> list[plt.Artist]:
sim.update()
for i, axes in enumerate(path_plots):
coords = get_coordinates_for_plot(sim, i)
axes.set_data(*coords)
for i, head_marker in enumerate(head_plots):
coords = get_coordinates_for_heads(sim, i)
head_marker.set_data(*coords)
return path_plots
animation = FuncAnimation(
fig,
update_animation,
init_func=initialize_animation,
interval=20,
)
fig.tight_layout()
plt.show(block=True)
def main() -> None:
rcParams.update({'figure.autolayout': True})
sim = HopDiffusion() # Nanodomain()
plot(sim)
if __name__ == '__main__':
main()对于任何一种严肃的模拟,尤其是如果你需要将它从动画可视化转换成高吞吐量的无头模拟,你需要用Numpy进行矢量化。这是一个很长的过程,需要重写大部分代码。我将展示例子,但不是完整的程序。
generate_boundaries()成为一个生成两个三维数组的函数:
@staticmethod
def generate_boundaries() -> tuple[np.ndarray, np.ndarray]:
stop = RADIUS + BOUNDARY_OVERFLOW
inner_edges = np.linspace(
start=-RADIUS, stop=RADIUS, num=NUMBER_OF_COMPARTMENTS_PER_DIRECTION + 1,
)[1: -1]
n_edges = len(inner_edges)
# edges by x/y by start/stop
bound_coords = np.empty((2*n_edges, 2, 2))
bound_coords[:n_edges, 0, 0] = inner_edges - BOUNDARY_THICKNESS/2
bound_coords[:n_edges, 0, 1] = inner_edges + BOUNDARY_THICKNESS/2
bound_coords[:n_edges, 1, :] = -stop, stop
bound_coords[n_edges:, ...] = bound_coords[:n_edges, ::-1, :]
# edges by x/y by start/size
plot_coords = bound_coords.copy()
plot_coords[..., 1] = bound_coords[..., 1] - bound_coords[..., 0]
return plot_coords, bound_coords你的随机发生器变成
from numpy.random import default_rng
from numpy.random._generator import Generator
rand: Generator = default_rng(seed=0)然后,正态分布的布朗偏移量产生。
def get_random_normal_direction() -> float:
return rand.normal(scale=MEMBRANE_DIFFUSION_FACTOR_CORRECTED, size=2)边界检查变成
def is_point_within_bounds(
pos: np.ndarray,
bounds: np.ndarray,
) -> bool:
return (
(bounds[:, 0] <= pos) &
(bounds[:, 1] >= pos)
).all()你的初始粒子状态失去了它所有的唯一性关注点,只在浮子上运行,然后变成
def init_particles(self) -> np.ndarray:
return rand.uniform(
low=-CORRECTED_CANVAS_RADIUS,
high=CORRECTED_CANVAS_RADIUS,
size=(self.n_particles, 2),
)等。
发布于 2022-10-02 16:16:43
这些都有点挑剔;我注意到了一两件事,所以我针对代码运行了匹林特,并在这里总结一下,省略了前面提到的东西。
snake_case而不是camelCase。不过,PascalCase用于类。在约定之间转换是一件痛苦的事,但它比战斗的约定要痛苦得多:for循环使用列表理解。(plotGenerator.py:24和其他人)在理解中有副作用是很糟糕的形式。type at plotGenerator.py:59)frame)... = min(min(elem[0]) for elem in lists)是一个令人愉快的惊喜。因此,找到一个链接,你可以站起来,并配置它供您使用。遵守每一种格式约定可能是一项繁重的工作,但对任何同事来说都是很有价值的,除了类型检查程序会发现的之外,还有一些真正的问题是linter可以找到的。
我注意到的第一件事之一是您在simulation.py of PATH = Tuple[List[float]]中的声明。MyPy没有抱怨它,因为它从未被使用过,而linter也没有抱怨它从来没有被使用过,因为(我认为)它是模块中的一个全局定义(所以它可能对模块的使用者有用)。该类型表达式的问题是,它是一个长度为1的元组,这不太可能是您真正想要的(忽略您显然根本不想要PATH )。也许你是说 ``Tuple[列表浮动,.]
https://codereview.stackexchange.com/questions/280131
复制相似问题