Skip to content

Commit

Permalink
Visualizer bugfixes (#4686)
Browse files Browse the repository at this point in the history
Closes #4663

Description of changes:
- the `*_arrows_type_materials` options of the visualizer now have an effect on arrow materials
- the visualizer `freeglut` dependency has been added to the user guide
- issues with the exception handling mechanism have been addressed
- undefined behavior in OpenMPI triggered by the visualizer (#4663) now has a workaround for simulations that use only 1 MPI rank
  • Loading branch information
kodiakhq[bot] authored Mar 10, 2023
2 parents 35aeeac + 1d47c4e commit f9361c8
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 53 deletions.
2 changes: 1 addition & 1 deletion doc/sphinx/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ To compile |es| on Ubuntu 22.04 LTS, install the following dependencies:
sudo apt install build-essential cmake cython3 python3-pip python3-numpy \
libboost-all-dev openmpi-common fftw3-dev libhdf5-dev libhdf5-openmpi-dev \
python3-scipy python3-opengl libgsl-dev
python3-scipy python3-opengl libgsl-dev freeglut3
Optionally the ccmake utility can be installed for easier configuration:

Expand Down
6 changes: 4 additions & 2 deletions src/core/bond_breakage/bond_breakage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ void clear_queue() { queue.clear(); }
/** @brief Gathers combined queue from all mpi ranks */
Queue gather_global_queue(Queue const &local_queue) {
Queue res = local_queue;
Utils::Mpi::gather_buffer(res, comm_cart);
boost::mpi::broadcast(comm_cart, res, 0);
if (comm_cart.size() > 1) {
Utils::Mpi::gather_buffer(res, comm_cart);
boost::mpi::broadcast(comm_cart, res, 0);
}
return res;
}

Expand Down
7 changes: 4 additions & 3 deletions src/core/collision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,10 @@ void glue_to_surface_bind_part_to_vs(const Particle *const p1,

std::vector<CollisionPair> gather_global_collision_queue() {
std::vector<CollisionPair> res = local_collision_queue;
Utils::Mpi::gather_buffer(res, comm_cart);
boost::mpi::broadcast(comm_cart, res, 0);

if (comm_cart.size() > 1) {
Utils::Mpi::gather_buffer(res, comm_cart);
boost::mpi::broadcast(comm_cart, res, 0);
}
return res;
}

Expand Down
85 changes: 38 additions & 47 deletions src/python/espressomd/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,17 +590,9 @@ def screenshot(self, path):

# read the pixels
OpenGL.GL.glReadBuffer(OpenGL.GL.GL_COLOR_ATTACHMENT0)
data = OpenGL.GL.glReadPixels(
0,
0,
self.specs['window_size'][0],
self.specs['window_size'][1],
OpenGL.GL.GL_RGB,
OpenGL.GL.GL_FLOAT)

# save image
data = np.flipud(data.reshape((data.shape[1], data.shape[0], 3)))
matplotlib.pyplot.imsave(path, data)
self._make_screenshot(path)

def run(self, integration_steps=1):
"""Convenience method with a simple integration thread.
Expand Down Expand Up @@ -1005,14 +997,12 @@ def _draw_constraints(self):

def _determine_radius(self, part_type):
def radius_by_lj(part_type):
try:
radius = self.system.non_bonded_inter[part_type, part_type].lennard_jones.get_params()[
'sigma'] * 0.5
if radius == 0.0:
radius = self.system.non_bonded_inter[part_type, part_type].wca.get_params()[
'sigma'] * 0.5
except Exception:
radius = 0.5
ia = self.system.non_bonded_inter[part_type, part_type]
radius = 0.5
if hasattr(ia, "lennard_jones"):
radius = ia.lennard_jones.sigma * 0.5
if radius == 0.0 and hasattr(ia, "wca"):
radius = ia.wca.sigma * 0.5
if radius == 0.0:
radius = 0.5
return radius
Expand All @@ -1039,8 +1029,9 @@ def _draw_system_particles(self, color_by_id=False):

# Only change material if type/charge has changed, color_by_id or
# material was reset by arrows
if reset_material or color_by_id or not part_type == part_type_last or \
part_id == self.drag_id or part_id == self.info_id or self.specs['particle_coloring'] == 'node':
if reset_material or color_by_id or part_type != part_type_last or \
part_id == self.drag_id or part_id == self.info_id or \
self.specs['particle_coloring'] == 'node':
reset_material = False

radius = self._determine_radius(part_type)
Expand Down Expand Up @@ -1072,6 +1063,9 @@ def _draw_system_particles(self, color_by_id=False):
elif self.specs['particle_coloring'] == 'node':
color = self._modulo_indexing(
self.specs['particle_type_colors'], self.particles['node'][index])
else:
raise ValueError(
f"Cannot process particle_coloring={self.specs['particle_coloring']}")

# Invert color of highlighted particle
if part_id == self.drag_id or part_id == self.info_id:
Expand Down Expand Up @@ -1118,33 +1112,37 @@ def _draw_system_particles(self, color_by_id=False):
self._draw_arrow_property(
part_id, part_type, self.specs['velocity_arrows_type_scale'],
self.specs['velocity_arrows_type_colors'],
self.specs['velocity_arrows_type_materials'],
self.specs['velocity_arrows_type_radii'], 'velocity')
reset_material = True

if self.specs['force_arrows']:
self._draw_arrow_property(
part_id, part_type, self.specs['force_arrows_type_scale'],
self.specs['force_arrows_type_colors'],
self.specs['force_arrows_type_materials'],
self.specs['force_arrows_type_radii'], 'force')
reset_material = True

if self.specs['director_arrows']:
self._draw_arrow_property(
part_id, part_type, self.specs['director_arrows_type_scale'],
self.specs['director_arrows_type_colors'],
self.specs['director_arrows_type_materials'],
self.specs['director_arrows_type_radii'], 'director')
reset_material = True

def _draw_arrow_property(self, part_id, part_type,
type_scale, type_colors, type_radii, prop):
def _draw_arrow_property(self, part_id, part_type, type_scale,
type_colors, type_materials, type_radii, prop):
sc = self._modulo_indexing(type_scale, part_type)
if sc > 0:
v = self.particles[prop][self.index_from_id[part_id]]
col = self._modulo_indexing(type_colors, part_type)
radius = self._modulo_indexing(type_radii, part_type)
material = self._modulo_indexing(type_materials, part_type)
draw_arrow(self.particles['pos'][self.index_from_id[part_id]],
np.array(v, dtype=float) * sc, radius, col,
self.materials['chrome'], self.specs['quality_arrows'])
self.materials[material], self.specs['quality_arrows'])

def _draw_bonds(self):
half_box_l = self.system.box_l / 2.0
Expand All @@ -1161,8 +1159,9 @@ def _draw_bonds(self):
try:
x_a = self.particles['pos'][self.index_from_id[b[0]]]
x_b = self.particles['pos'][self.index_from_id[b[1]]]
except BaseException:
pass
except Exception:
# skip this bond
continue
dx = x_b - x_a

if np.all(np.abs(dx) < half_box_l):
Expand Down Expand Up @@ -1279,23 +1278,26 @@ def _update_charge_color_range(self):
def _handle_screenshot(self):
if self.take_screenshot:
self.take_screenshot = False
data = OpenGL.GL.glReadPixels(0, 0, self.specs['window_size'][0],
self.specs['window_size'][1],
OpenGL.GL.GL_RGB, OpenGL.GL.GL_FLOAT)
script_name = os.path.splitext(sys.argv[0])[0]

i = 0
while os.path.exists(f"{script_name}_{i:04d}.png"):
i += 1
file_name = f"{script_name}_{i:04d}.png"

data = np.flipud(data.reshape((data.shape[1], data.shape[0], 3)))
matplotlib.pyplot.imsave(file_name, data)
self._make_screenshot(file_name)

self.screenshot_captured = True
self.screenshot_capture_time = time.time()
self.screenshot_capture_txt = f"Saved screenshot {file_name}"

def _make_screenshot(self, filepath):
data = OpenGL.GL.glReadPixels(0, 0, self.specs['window_size'][0],
self.specs['window_size'][1],
OpenGL.GL.GL_RGB, OpenGL.GL.GL_FLOAT)
data = np.flipud(data.reshape((data.shape[1], data.shape[0], 3)))
matplotlib.pyplot.imsave(filepath, data)

def _display_all(self):

OpenGL.GL.glClear(
Expand Down Expand Up @@ -1403,36 +1405,30 @@ def _init_OpenGL_callbacks(self):
def display():
if self.hasParticleData and self.glut_main_loop_started:
self._display_all()
return

# pylint: disable=unused-argument
def keyboard_up(button, x, y):
if isinstance(button, bytes):
button = button.decode("utf-8")
self.keyboard_manager.keyboard_up(button)
return

# pylint: disable=unused-argument
def keyboard_down(button, x, y):
if isinstance(button, bytes):
button = button.decode("utf-8")
self.keyboard_manager.keyboard_down(button)
return

def mouse(button, state, x, y):
self.mouse_manager.mouse_click(button, state, x, y)
return

def motion(x, y):
self.mouse_manager.mouse_move(x, y)
return

def redraw_on_idle():
# don't repost faster than 60 fps
if (time.time() - self.last_draw) > 1.0 / 60.0:
OpenGL.GLUT.glutPostRedisplay()
self.last_draw = time.time()
return

def reshape_callback(w, h):
self._reshape_window(w, h)
Expand All @@ -1449,7 +1445,6 @@ def close_window():
OpenGL.GLUT.glutReshapeFunc(reshape_callback)
OpenGL.GLUT.glutMotionFunc(motion)
OpenGL.GLUT.glutWMCloseFunc(close_window)

OpenGL.GLUT.glutIdleFunc(redraw_on_idle)

def _init_timers(self):
Expand Down Expand Up @@ -1546,10 +1541,9 @@ def _id_to_fcolor(part_id):
def _fcolor_to_id(fcolor):
if (fcolor == [0, 0, 0]).all():
return -1
else:
return int(fcolor[0] * 255) * 256 ** 2 + \
int(fcolor[1] * 255) * 256 + \
int(fcolor[2] * 255) - 1
return int(fcolor[0] * 255) * 256 ** 2 + \
int(fcolor[1] * 255) * 256 + \
int(fcolor[2] * 255) - 1

# pylint: disable=unused-argument
def _set_particle_drag(self, pos, pos_old):
Expand Down Expand Up @@ -1898,7 +1892,6 @@ def __init__(self, shape, particle_type, color, material,
self.box_l = box_l
self.rasterize_resolution = rasterize_resolution
self.pointsize = rasterize_pointsize

self.rasterized_surface_points = None

def draw(self):
Expand Down Expand Up @@ -1928,16 +1921,15 @@ def _rasterize_shape(self):
for i in range(int(resolution[0])):
for j in range(int(resolution[1])):
for k in range(int(resolution[2])):
# some shapes may not have a well-defined distance function in the whole domain
# and may throw upon asking for a distance
# some shapes may not have a well-defined distance function
# in the whole domain and may throw a ValueError
try:
p = np.array([i, j, k]) * spacing
dist, vec = self.shape.call_method(
"calc_distance", position=p.tolist())
if not np.isnan(vec).any() and not np.isnan(
dist) and abs(dist) < spacing:
if not (np.isnan(vec).any() or np.isnan(dist)) \
and abs(dist) < spacing:
points.append((p - vec).tolist())
# domain error translates to ValueError (cython)
except ValueError:
continue
return points
Expand Down Expand Up @@ -2476,7 +2468,6 @@ def draw_cylinder(posA, posB, radius, color, material, quality,

d = posB - posA

# angle,t,length = calcAngle(d)
length = np.linalg.norm(d)
OpenGL.GL.glTranslatef(posA[0], posA[1], posA[2])

Expand Down

0 comments on commit f9361c8

Please sign in to comment.