From 23e9350fd7da2c7f8a2696de096f571aaf9a476a Mon Sep 17 00:00:00 2001 From: CalCraven Date: Sat, 9 Jan 2021 16:28:33 -0600 Subject: [PATCH 01/62] Include angle and dihedral information on conversion of topology to networkx --- gmso/external/convert_networkx.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gmso/external/convert_networkx.py b/gmso/external/convert_networkx.py index 88db8e117..578259e47 100644 --- a/gmso/external/convert_networkx.py +++ b/gmso/external/convert_networkx.py @@ -88,4 +88,18 @@ def to_networkx(top): for b in top.bonds: graph.add_edge(b.connection_members[0], b.connection_members[1], connection=b) + for node in graph.nodes: + angles = [] + for angle in list(top.angles): + if node in angle.connection_members: + angles.append(angle) + graph.nodes[node]['angles'] = angles + + for node in graph.nodes: + dihedrals = [] + for dihedral in list(top.dihedrals): + if node in dihedral.connection_members: + dihedrals.append(dihedral) + graph.nodes[node]['dihedrals'] = dihedrals + return graph From 905263e6fd46f00fac266d8e4b51a94568713c8e Mon Sep 17 00:00:00 2001 From: CalCraven Date: Sat, 9 Jan 2021 18:18:27 -0600 Subject: [PATCH 02/62] create gmso/formats/networkx.py --- gmso/formats/__init__.py | 1 + gmso/formats/networkx.py | 333 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 gmso/formats/networkx.py diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 3ea0c9f9e..5cd0566a2 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,3 +3,4 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata +from .networx import * diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py new file mode 100644 index 000000000..fd5f9cd53 --- /dev/null +++ b/gmso/formats/networkx.py @@ -0,0 +1,333 @@ +import numpy as np +import unyt as u +import matplotlib.pyplot as plt +import networkx as nx + +from gmso.core.topology import Topology +from gmso.external.convert_networkx import to_networkx + +def plot_networkx_atomtypes(topology,atom_name=None): + """Get a networkx plot showing the atom types in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name : The atom name which will have larger node sizes. + When drawing the networkx graph, all atoms with this name will be 3X as large. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + node_sizes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + node_sizes.append(900) + else: + node_sizes.append(300) + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) + pos= {} + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + +def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): + """Get a networkx plot showing the bonds in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name1 : The atom name to of which bonds you want to have selected + When drawing the networkx graph, all bonds connected to atoms with + this name will be indicated. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + If no atom_name is given, then only bonds missing bond information will be + indicated + atom_name2 : A second atom name to restrict what bonds will be selected. only bonds + between the two given atoms will be indicated. + This input will be of type string. + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + #Color and weight edges between particular atoms. If both atom_names are none, plot missing bond types. + from gmso.external import convert_networkx + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + networkx_graph = to_networkx(topology) + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + node_colors.append(node_color_dict[node.name]) + + edge_weights = {} + edge_colors = {} + mia_bond_ind = 0 + if atom_name1 and atom_name2: + for edge in networkx_graph.edges: + if edge[0].name == atom_name1 and edge[1].name == atom_name2: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + elif edge[0].name == atom_name2 and edge[1].name == atom_name1: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + elif atom_name1: + for edge in networkx_graph.edges: + if edge[0].name == atom_name1 or edge[1].name == atom_name1: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + else: + for bond in list(networkx_graph.edges.items()): + if bond[1]['connection'].bond_type == None: + edge_weights[bond[0]] = 5 + edge_colors[bond[0]] = 'red' + mia_bond_ind = 1 + else: + edge_weights[bond[0]] = 1 + edge_colors[bond[0]] = 'k' + if not mia_bond_ind: + print('All bonds are typed') + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + pos= {} + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + return(fig,ax) + + +def plot_networkx_angles(topology,center_atom_index = None): + """Get a networkx plot showing the angles in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the angle types + that have been parameterized. + center_atom_index : The central atom to of which angles you want to have selected + When drawing the networkx graph, all angles connected to atoms with + this index will be indicated. + This input will be of type int. To see what atoms correspond to these indices, see + gmso.formats.networkx.plot_networkx_atomtypes + If no atom_name is given, then only atoms missing angle information will be + indicated + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + edge_weights = {} + edge_colors = {} + + for edge in networkx_graph.edges: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + + node = list(networkx_graph.nodes)[center_atom_index] + for angle in networkx_graph.nodes[node]['angles']: + if node == angle.connection_members[1]: + for i in [0,1]: + node1 = list(angle.connection_members)[i+1] + node0 = list(angle.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + + +def plot_networkx_dihedrals(topology,center_atom_index = None): + """Get a networkx plot showing the dihedrals in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the dihedral types + that have been parameterized. + center_atom_index : The second or third atom of the dihedrals you want to have selected + When drawing the networkx graph, all dihedrals connected to atoms with + this index will be indicated. + This input will be of type int. To see what atoms correspond to these indices, see + gmso.formats.networkx.plot_networkx_atomtypes + If no atom_name is given, then only atoms missing dihedral information will be + indicated + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + edge_weights = {} + edge_colors = {} + + for edge in networkx_graph.edges: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + + node = list(networkx_graph.nodes)[center_atom_index] + for dihedral in networkx_graph.nodes[node]['dihedrals']: + if (node == list(dihedral.connection_members)[1] or + node == list(dihedral.connection_members)[2]): + i=0 + node1 = list(dihedral.connection_members)[i+1] + node0 = list(dihedral.connection_members)[i] + #order is switched around when naming edges for the first + #edge in the dihedral + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + for i in [1,2]: + node1 = list(dihedral.connection_members)[i+1] + node0 = list(dihedral.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + From 07095b32ebf11f9f7c59ee011b185e5ad4737f20 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Tue, 12 Jan 2021 11:13:39 -0600 Subject: [PATCH 03/62] Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site --- gmso/core/topology.py | 16 ++++++++++++++++ gmso/external/convert_networkx.py | 12 ++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 474a87ef6..64837b735 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -580,6 +580,22 @@ def update_topology(self): self.update_connection_types() self.is_typed(updated=True) + def _get_angles_for(self, site): + """return a list of the angles that contain Site""" + angles = [] + for angle in self.angles: + if site in angle.connection_members: + angles.append(angle) + return angles + + def _get_dihedrals_for(self, site): + """return a list of the dihedrals that contain Site""" + dihedrals = [] + for dihedral in self.dihedrals: + if site in dihedral.connection_members: + dihedrals.append(dihedral) + return dihedrals + def get_index(self, member): """Get index of a member in the topology diff --git a/gmso/external/convert_networkx.py b/gmso/external/convert_networkx.py index 578259e47..c932d3623 100644 --- a/gmso/external/convert_networkx.py +++ b/gmso/external/convert_networkx.py @@ -89,17 +89,9 @@ def to_networkx(top): graph.add_edge(b.connection_members[0], b.connection_members[1], connection=b) for node in graph.nodes: - angles = [] - for angle in list(top.angles): - if node in angle.connection_members: - angles.append(angle) - graph.nodes[node]['angles'] = angles + graph.nodes[node]['angles'] = top._get_angles_for(node) for node in graph.nodes: - dihedrals = [] - for dihedral in list(top.dihedrals): - if node in dihedral.connection_members: - dihedrals.append(dihedral) - graph.nodes[node]['dihedrals'] = dihedrals + graph.nodes[node]['dihedrals'] = top._get_dihedrals_for(node) return graph From 3d6e35b2922ce9e66e3a3e556013447b74a1220b Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 14 Jan 2021 12:11:14 -0600 Subject: [PATCH 04/62] Add functions to clean up networkx plotting, and plot missing angle and bond types --- gmso/formats/__init__.py | 2 +- gmso/formats/networkx.py | 193 +++++++++++++-------------------------- 2 files changed, 64 insertions(+), 131 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 5cd0566a2..0e5313219 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,4 +3,4 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networx import * +from .networkx import * diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index fd5f9cd53..3fe0e8be6 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -33,37 +33,13 @@ def plot_networkx_atomtypes(topology,atom_name=None): fig,ax = plt.subplots(1,1,figsize=(8,8)) networkx_graph = to_networkx(topology) - - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] node_sizes = [] for node in networkx_graph.nodes: if node.name == atom_name: node_sizes.append(900) else: node_sizes.append(300) - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append('black') - - nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) - pos= {} - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) + ax = plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = node_sizes) return(fig,ax) @@ -103,16 +79,6 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): fig,ax = plt.subplots(1,1,figsize=(8,8)) networkx_graph = to_networkx(topology) - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] - for node in networkx_graph.nodes: - node_colors.append(node_color_dict[node.name]) edge_weights = {} edge_colors = {} @@ -148,22 +114,8 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): if not mia_bond_ind: print('All bonds are typed') - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - - pos= {} - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) - return(fig,ax) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + return fig, ax def plot_networkx_angles(topology,center_atom_index = None): @@ -188,58 +140,17 @@ def plot_networkx_angles(topology,center_atom_index = None): The drawn networkx plot of the topology on a matplotlib editable figures matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be + The axis information that corresponds to a networkx drawn figure. This output can be shown using matplotlib.pyplot.show() """ networkx_graph = to_networkx(topology) - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) fig,ax = plt.subplots(1,1,figsize=(8,8)) - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] - for node in networkx_graph.nodes: - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append('black') - - edge_weights = {} - edge_colors = {} - - for edge in networkx_graph.edges: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' + edge_weights, edge_colors = highlight_bonds(networkx_graph,'angles',atom_index=center_atom_index) - node = list(networkx_graph.nodes)[center_atom_index] - for angle in networkx_graph.nodes[node]['angles']: - if node == angle.connection_members[1]: - for i in [0,1]: - node1 = list(angle.connection_members)[i+1] - node0 = list(angle.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) return(fig,ax) @@ -272,12 +183,60 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): """ networkx_graph = to_networkx(topology) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + edge_weights, edge_colors = highlight_bonds(networkx_graph,'dihedrals',atom_index=center_atom_index) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + + return(fig,ax) + + +def highlight_bonds(networkx_graph,attribute,atom_index=None): + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' + + def_param = 1 + if atom_index == None: + for node in networkx_graph.nodes: + for parameter in networkx_graph.nodes[node][attribute]: + var = attribute[:-1] + '_type' + if getattr(parameter,var) == None: + def_param = 0 + members = list(parameter.connection_members) + for i in np.arange(len(members)-1): + edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 + edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' + + if def_param: + print('No {} selected, and all {} typed'.format(attribute,attribute)) + + elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: + node = list(networkx_graph.nodes)[atom_index] + for parameter in networkx_graph.nodes[node][attribute]: + if (node == parameter.connection_members[1] or + node == parameter.connection_members[ + len(networkx_graph.nodes[node][attribute][0].connection_members)-2]): + for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): + node1 = list(parameter.connection_members)[i+1] + node0 = list(parameter.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + print('Invalid input for atom or node index') + + return edge_weights, edge_colors + +def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): pos={} for node in networkx_graph.nodes: pos[node] = node.position.value[0:2] - + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - fig,ax = plt.subplots(1,1,figsize=(8,8)) node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} node_colors = [] @@ -287,47 +246,21 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): else: node_colors.append('black') - edge_weights = {} - edge_colors = {} - - for edge in networkx_graph.edges: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - - node = list(networkx_graph.nodes)[center_atom_index] - for dihedral in networkx_graph.nodes[node]['dihedrals']: - if (node == list(dihedral.connection_members)[1] or - node == list(dihedral.connection_members)[2]): - i=0 - node1 = list(dihedral.connection_members)[i+1] - node0 = list(dihedral.connection_members)[i] - #order is switched around when naming edges for the first - #edge in the dihedral - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - for i in [1,2]: - node1 = list(dihedral.connection_members)[i+1] - node0 = list(dihedral.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - - nx.draw(networkx_graph,layout,ax,node_color=node_colors, + if node_sizes: + nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) + else: + nx.draw(networkx_graph,layout,ax,node_color=node_colors, width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - labels = {} i=0 for node in list(networkx_graph.nodes()): node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label + labels[node] = node.label i+=1 for atom,pos in layout.items(): layout[atom] = pos + [0.09,0] nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') ax.margins(.3,.3) - - return(fig,ax) - + + return ax From 3b7568e08daacc9283d1eeda4146faa5d7f50c74 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Fri, 15 Jan 2021 13:22:47 -0600 Subject: [PATCH 05/62] Add unit tests for _get_x_for where x is bonds, angles, and dihedrals. Add _get_bonds_for method in topology.py --- gmso/core/topology.py | 8 ++++++++ gmso/tests/test_convert_networkx.py | 5 +++++ gmso/tests/test_topology.py | 30 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 64837b735..356bfdaa6 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -580,6 +580,14 @@ def update_topology(self): self.update_connection_types() self.is_typed(updated=True) + def _get_bonds_for(self, site): + """return a list of the bonds that contain Site""" + bonds = [] + for bond in self.bonds: + if site in bond.connection_members: + bonds.append(bond) + return bonds + def _get_angles_for(self, site): """return a list of the angles that contain Site""" angles = [] diff --git a/gmso/tests/test_convert_networkx.py b/gmso/tests/test_convert_networkx.py index fcdb6749f..aa7b93607 100644 --- a/gmso/tests/test_convert_networkx.py +++ b/gmso/tests/test_convert_networkx.py @@ -16,7 +16,12 @@ def test_to_networkx_ethane(self, ethane): assert ethane.n_sites == ethane_to_nx.number_of_nodes() assert ethane.n_bonds == ethane_to_nx.number_of_edges() + + assert set(ethane.sites) == set(ethane_to_nx.nodes) + for site in ethane.sites: + assert set(ethane_to_nx.nodes[site]['angles']) == set(ethane._get_angles_for(site)) + assert set(ethane_to_nx.nodes[site]['dihedrals']) == set(ethane._get_dihedrals_for(site)) def test_from_networkx_ethane(self, ethane): ethane_to_nx = to_networkx(ethane) diff --git a/gmso/tests/test_topology.py b/gmso/tests/test_topology.py index 51bbf20ef..e17a07dfb 100644 --- a/gmso/tests/test_topology.py +++ b/gmso/tests/test_topology.py @@ -536,3 +536,33 @@ def test_topology_get_index_dihedral_type_after_change(self, typed_methylnitroan prev_idx = typed_methylnitroaniline.get_index(dihedral_type_to_test) typed_methylnitroaniline.dihedrals[0].connection_type.name = 'changed name' assert typed_methylnitroaniline.get_index(dihedral_type_to_test) != prev_idx + + def test_topology_get_bonds_for(self, typed_methylnitroaniline): + site = list(typed_methylnitroaniline.sites)[0] + converted_bonds_list = typed_methylnitroaniline._get_bonds_for(site) + top_bonds_containing_site = [] + for bond in typed_methylnitroaniline.bonds: + if site in bond.connection_members: + assert bond in converted_bonds_list + top_bonds_containing_site.append(bond) + assert len(top_bonds_containing_site) == len(converted_bonds_list) + + def test_topology_get_angles_for(self, typed_methylnitroaniline): + site = list(typed_methylnitroaniline.sites)[0] + converted_angles_list = typed_methylnitroaniline._get_angles_for(site) + top_angles_containing_site = [] + for angle in typed_methylnitroaniline.angles: + if site in angle.connection_members: + assert angle in converted_angles_list + top_angles_containing_site.append(angle) + assert len(top_angles_containing_site) == len(converted_angles_list) + + def test_topology_get_dihedrals_for(self, typed_methylnitroaniline): + site = list(typed_methylnitroaniline.sites)[0] + converted_dihedrals_list = typed_methylnitroaniline._get_dihedrals_for(site) + top_dihedrals_containing_site = [] + for dihedral in typed_methylnitroaniline.dihedrals: + if site in dihedral.connection_members: + assert dihedral in converted_dihedrals_list + top_dihedrals_containing_site.append(dihedral) + assert len(top_dihedrals_containing_site) == len(converted_dihedrals_list) From b573ebec980bd3ea4fd863245b16de0ab361c792 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 20 Jan 2021 13:26:56 -0600 Subject: [PATCH 06/62] Fix import * and add soft import for matplotlib --- gmso/formats/__init__.py | 5 ++++- gmso/formats/networkx.py | 2 -- requirements-test.txt | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 0e5313219..7e11c6f89 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,4 +3,7 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networkx import * +from .networkx import (plot_networkx_atomtypes, + plot_networkx_bonds, + plot_networkx_angles, + plot_networkx_dihedrals) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 3fe0e8be6..ee65517bd 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,9 +1,7 @@ import numpy as np -import unyt as u import matplotlib.pyplot as plt import networkx as nx -from gmso.core.topology import Topology from gmso.external.convert_networkx import to_networkx def plot_networkx_atomtypes(topology,atom_name=None): diff --git a/requirements-test.txt b/requirements-test.txt index 2411332a1..e161bb56a 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -13,3 +13,4 @@ parmed pytest-cov codecov pydantic +matplotlib From 9a4d40c29541ab88076dfd5e0b8c27bbcea78e1d Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 20 Jan 2021 19:28:51 -0600 Subject: [PATCH 07/62] Add unit tests for selecting atomtype parameters, and soft imports for matplotlib and networkx to io.py --- gmso/formats/networkx.py | 70 +++++++++++++++++++++--------------- gmso/tests/test_networkx.py | 72 +++++++++++++++++++++++++++++++++++++ gmso/utils/io.py | 14 ++++++++ 3 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 gmso/tests/test_networkx.py diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index ee65517bd..f2a418a9b 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -190,45 +190,57 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): def highlight_bonds(networkx_graph,attribute,atom_index=None): - edge_weights={};edge_colors={} - for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' - + edge_weights, edge_colors = initialize_edge_params(networkx_graph) def_param = 1 if atom_index == None: - for node in networkx_graph.nodes: - for parameter in networkx_graph.nodes[node][attribute]: - var = attribute[:-1] + '_type' - if getattr(parameter,var) == None: - def_param = 0 - members = list(parameter.connection_members) - for i in np.arange(len(members)-1): - edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 - edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' - + edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) if def_param: print('No {} selected, and all {} typed'.format(attribute,attribute)) - elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: - node = list(networkx_graph.nodes)[atom_index] - for parameter in networkx_graph.nodes[node][attribute]: - if (node == parameter.connection_members[1] or - node == parameter.connection_members[ - len(networkx_graph.nodes[node][attribute][0].connection_members)-2]): - for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): - node1 = list(parameter.connection_members)[i+1] - node0 = list(parameter.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' + edge_weights, edge_colors = sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors) else: print('Invalid input for atom or node index') return edge_weights, edge_colors +def sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors): + node = list(networkx_graph.nodes)[atom_index] + for parameter in networkx_graph.nodes[node][attribute]: + if (node == parameter.connection_members[1] or + node == parameter.connection_members[len( + networkx_graph.nodes[node][attribute][0].connection_members)-2]): + for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): + node1 = list(parameter.connection_members)[i+1] + node0 = list(parameter.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + return edge_weights, edge_colors + +def initialize_edge_params(networkx_graph): + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' + + return edge_weights, edge_colors + +def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors): + for node in networkx_graph.nodes: + for parameter in networkx_graph.nodes[node][attribute]: + var = attribute[:-1] + '_type' + if getattr(parameter,var) == None: + def_param = 0 + members = list(parameter.connection_members) + for i in np.arange(len(members)-1): + edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 + edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' + + return edge_weights, edge_colors, def_param + def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): pos={} for node in networkx_graph.nodes: diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py new file mode 100644 index 000000000..4cf74f89b --- /dev/null +++ b/gmso/tests/test_networkx.py @@ -0,0 +1,72 @@ +import unyt as u +import numpy as np +#import matplotlib.pyplot as plt +#import networkx as nx +import pytest + +from gmso.formats.networkx import (plot_networkx_atomtypes, +plot_networkx_bonds, plot_networkx_angles, plot_networkx_dihedrals, +highlight_bonds, sel_input_params, initialize_edge_params) +from gmso.tests.base_test import BaseTest +from unyt.testing import assert_allclose_units +from gmso.utils.io import import_, has_networkx, has_pyplot +from gmso.external.convert_networkx import to_networkx + +if has_networkx: + networkx = import_('networkx') + +if has_pyplot: + plt = import_('matplotlib.pyplot') + +@pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") +@pytest.mark.skipif(not has_pyplot, reason="Matplotlib.pyplot is not installed") +class TestNetworkx(BaseTest): + def test_highlight_bonds(self, typed_ethane): + list(typed_ethane.angles)[0].angle_type = None + list(typed_ethane.dihedrals)[0].dihedral_type = None + + graph = to_networkx(typed_ethane) + test_edge_weights, test_edge_colors = highlight_bonds(graph, 'angles') + nodes = list(graph.nodes) + assert test_edge_weights[nodes[0],nodes[4]] == 5 + assert test_edge_weights[nodes[4],nodes[5]] == 5 + assert test_edge_weights[nodes[0],nodes[1]] == 1 + + test_edge_weights, test_edge_colors = highlight_bonds(graph, 'dihedrals') + assert test_edge_weights[nodes[0],nodes[4]] == 5 + assert test_edge_weights[nodes[4],nodes[5]] == 5 + assert test_edge_weights[nodes[0],nodes[1]] == 5 + assert test_edge_weights[nodes[0],nodes[3]] == 1 + + + + def test_sel_input_params(self,typed_ethane): + graph = to_networkx(typed_ethane) + edge_weights, edge_colors = initialize_edge_params(graph) + test_edge_weights, test_edge_colors = sel_input_params(graph, 0, 'angles', edge_weights, edge_colors) + + assert all([a == b for a, b in zip(list(test_edge_weights.values()), [5, 5, 5, 5, 1, 1, 1])]) + assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['red', 'red', 'red', 'red', 'k', 'k', 'k'])]) + + def test_initialize_edge_params(self, typed_ethane): + graph = to_networkx(typed_ethane) + test_edge_weights, test_edge_colors = initialize_edge_params(graph) + + assert all([a == b for a, b in zip(list(test_edge_weights.values()), [1, 1, 1, 1, 1, 1, 1])]) + assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['k', 'k', 'k', 'k', 'k', 'k', 'k'])]) + + def test_plot_networkx_atomtypes(self,typed_ethane): + graph = to_networkx(typed_ethane) + fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) + test_fig, test_ax = plt.subplots(1) + + assert isinstance(fig, test_fig.__class__) + assert isinstance(ax, test_ax.__class__) + + def test_plot_networkx_bonds(self,typed_ethane): + graph = to_networkx(typed_ethane) + fig, ax = plot_networkx_bonds(typed_ethane) + test_fig, test_ax = plt.subplots(1) + + assert isinstance(fig, test_fig.__class__) + assert isinstance(ax, test_ax.__class__) diff --git a/gmso/utils/io.py b/gmso/utils/io.py index f78d5bb5c..fc326c39a 100644 --- a/gmso/utils/io.py +++ b/gmso/utils/io.py @@ -119,3 +119,17 @@ def import_(module): del unit except ImportError: has_simtk_unit = False + +try: + import networkx + has_networkx = True + del networkx +except ImportError: + has_networkx = False + +try: + from matplotlib import pyplot + has_pyplot = True + del pyplot +except ImportError: + has_pyplot = False From db5933cbb8ee6f0e90e66a36c8169f69208e22b8 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 21 Jan 2021 14:42:51 -0600 Subject: [PATCH 08/62] change to --- gmso/formats/networkx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index f2a418a9b..ddb55338d 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -102,7 +102,7 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): edge_colors[edge] = 'k' else: for bond in list(networkx_graph.edges.items()): - if bond[1]['connection'].bond_type == None: + if bond[1]['connection'].bond_type is None: edge_weights[bond[0]] = 5 edge_colors[bond[0]] = 'red' mia_bond_ind = 1 @@ -192,7 +192,7 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): def highlight_bonds(networkx_graph,attribute,atom_index=None): edge_weights, edge_colors = initialize_edge_params(networkx_graph) def_param = 1 - if atom_index == None: + if atom_index is None: edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) if def_param: print('No {} selected, and all {} typed'.format(attribute,attribute)) @@ -232,7 +232,7 @@ def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) for node in networkx_graph.nodes: for parameter in networkx_graph.nodes[node][attribute]: var = attribute[:-1] + '_type' - if getattr(parameter,var) == None: + if getattr(parameter,var) is None: def_param = 0 members = list(parameter.connection_members) for i in np.arange(len(members)-1): From 5cc1c0bbbe39462b4334628a4224a606fe7a0af8 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 28 Jan 2021 16:11:20 -0600 Subject: [PATCH 09/62] add interactive features to networkx.py --- gmso/formats/networkx.py | 874 ++++++++++++++++++++++++++++++--------- 1 file changed, 687 insertions(+), 187 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index ddb55338d..5bd10175c 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,22 +1,22 @@ -import numpy as np -import matplotlib.pyplot as plt +import matplotlib as plot import networkx as nx +from ipywidgets import interact, fixed, +import ipywidgets as widgets +from IPython.display import display + from gmso.external.convert_networkx import to_networkx -def plot_networkx_atomtypes(topology,atom_name=None): - """Get a networkx plot showing the atom types in a topology object. +def plot_networkx_params(networkx_graph, list_of_edges): + """Get a networkx plot showing the specified edges in a networkx graph object. Parameters ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - atom_name : The atom name which will have larger node sizes. - When drawing the networkx graph, all atoms with this name will be 3X as large. - This input will be of type string. To see what atom names are available, use - for site in topology.sites: - print(site.name) + networkx_graph : A networkx.Graph object + This is a networkx graph object that can be created from a topology using the to_networkx + function found in gmso.external.convert_networkx + list_of_edges : a list of edges that should be shown on the plot + Will be of the shape [(node1,node2), (node2,node3), etc...] Returns ------- @@ -24,45 +24,39 @@ def plot_networkx_atomtypes(topology,atom_name=None): The drawn networkx plot of the topology on a matplotlib editable figures matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be + The axis information that corresponds to a networkx drawn figure. This output can be shown using matplotlib.pyplot.show() """ - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - networkx_graph = to_networkx(topology) - node_sizes = [] - for node in networkx_graph.nodes: - if node.name == atom_name: - node_sizes.append(900) - else: - node_sizes.append(300) - ax = plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = node_sizes) + fig,ax = plt.subplots(1, 1, figsize=(8,8)) + edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) + ax = plot_networkx_nodes(networkx_graph, ax, + edge_weights = edge_weights, + edge_colors = edge_colors + ) return(fig,ax) -def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): - """Get a networkx plot showing the bonds in a topology object. +def interactive_networkx_atomtypes(topology, list_of_labels = None): + """Get an interactive networkx plot showing the atom types of a topology object. Parameters ---------- topology : A gmso.core.topology.Topology object This should be a gmso topology object that you want to visualize the atom types that have been parameterized. - atom_name1 : The atom name to of which bonds you want to have selected - When drawing the networkx graph, all bonds connected to atoms with - this name will be indicated. - This input will be of type string. To see what atom names are available, use - for site in topology.sites: - print(site.name) - If no atom_name is given, then only bonds missing bond information will be - indicated - atom_name2 : A second atom name to restrict what bonds will be selected. only bonds - between the two given atoms will be indicated. - This input will be of type string. + list_of_labels : Additional labels you would like to have on the plot. + Default labels are ['atom_type.name','charge','mass','element','label','position']. + Any additonal labels can be appended from the list of: + `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` Returns ------- + ipywidgets.Dropdown() + Two dropdown options, corresponding to: + Label = What labels are shown on the networkx graph + Atom_Name = Which sites will show these labels + matplotlib.pyplot.figure The drawn networkx plot of the topology on a matplotlib editable figures @@ -71,184 +65,502 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): shown using matplotlib.pyplot.show() """ - - #Color and weight edges between particular atoms. If both atom_names are none, plot missing bond types. - from gmso.external import convert_networkx - fig,ax = plt.subplots(1,1,figsize=(8,8)) - networkx_graph = to_networkx(topology) + #get a unique list of site names + site_names = [] + for node in graph.nodes: + site_names.append(node.name) + site_names = set(site_names) + + # create a tuple of selectable names for each site + names_tuple = [] + names_tuple.append(('All Sites',None)) + for name in site_names: + names_tuple.append((name,name)) + + #Create list of labels to put on plot + if not list_of_labels: + list_of_labels = [] + base_list = ['atom_type.name','charge','mass','element','label','position'] + list_of_labels += base_list + + @interact + def get_interactive_sites(Label = list_of_labels, Atom_Name = names_tuple): + # Plot atom types for the widget inputs + plot_networkx_atomtypes(topology, Atom_Name, [Label]) + + return - edge_weights = {} - edge_colors = {} - mia_bond_ind = 0 - if atom_name1 and atom_name2: - for edge in networkx_graph.edges: - if edge[0].name == atom_name1 and edge[1].name == atom_name2: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - elif edge[0].name == atom_name2 and edge[1].name == atom_name1: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - else: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - elif atom_name1: - for edge in networkx_graph.edges: - if edge[0].name == atom_name1 or edge[1].name == atom_name1: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - else: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - else: - for bond in list(networkx_graph.edges.items()): - if bond[1]['connection'].bond_type is None: - edge_weights[bond[0]] = 5 - edge_colors[bond[0]] = 'red' - mia_bond_ind = 1 - else: - edge_weights[bond[0]] = 1 - edge_colors[bond[0]] = 'k' - if not mia_bond_ind: - print('All bonds are typed') - - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) - return fig, ax + return +def interactive_networkx_bonds(topology, additional_labels = None): + """Get an interactive networkx plot showing the bond types of a topology object. -def plot_networkx_angles(topology,center_atom_index = None): - """Get a networkx plot showing the angles in a topology object. - - Parameters + Parameters ---------- topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the angle types + This should be a gmso topology object that you want to visualize the atom types that have been parameterized. - center_atom_index : The central atom to of which angles you want to have selected - When drawing the networkx graph, all angles connected to atoms with - this index will be indicated. - This input will be of type int. To see what atoms correspond to these indices, see - gmso.formats.networkx.plot_networkx_atomtypes - If no atom_name is given, then only atoms missing angle information will be - indicated - + additonal_labels : Labels at each site to be included on the plot. + Default labels are ['atom_type.name']. + Any additonal labels can be appended from the list of: + `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` + Returns - ------- + ------- + ipywidgets.Dropdown() + Three dropdown options, corresponding to: + Atom1 = The first site for which bonds will be listed. If all sites are selected for both + atoms, then only bonds with missing types will be shown. + Atom2 = A second site for which bonds will be listed. With both specified + only bonds between Atom1 and Atom2 will be shown. + list_of_bonds = The bonds that can be selected corresponding to the Atom1 and Atom2 + selections in the two above dropdown menus. + matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to a networkx drawn figure. This output can be - shown using - matplotlib.pyplot.show() + The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines + indicate which bonds have been selected. + + ipywidgets.Checkbox() + Show parameters = True or False + Will determine if the necessary bond parameters for the selected bond type are shown + below the figure. """ networkx_graph = to_networkx(topology) - fig,ax = plt.subplots(1,1,figsize=(8,8)) - - edge_weights, edge_colors = highlight_bonds(networkx_graph,'angles',atom_index=center_atom_index) + # Create a list of labels to go on the nodes + if not additional_labels: + additional_labels = [] + base_list = ['atom_type.name'] + list_of_labels = base_list + additional_labels + + # Create list of nodes to plot + site_names = [] + for node in graph.nodes: + site_names.append(node.name) + site_names = set(site_names) + + # Create a tuple of keys for each selected site + names_tuple = [] + names_tuple.append(('All Sites',None)) + for name in site_names: + names_tuple.append((name,name)) + + # Call recursive interacts. The top level determines what bonds can be selected + @interact + def call_interactive_sites(Atom1 = names_tuple, Atom2 = names_tuple): + list_of_edges = get_edges(networkx_graph,Atom1,Atom2) + if list_of_edges: + # Call the second level, which takes the selected bonds and shows them on the figure + @interact + def select_edges(list_of_bonds = list_of_edges): + + plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels, + list_of_bonds = list_of_bonds) + + # The final level prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value = False, description = 'Show parameters') + @interact(w=checkbox) + def show_bond_info(w): + if w: + report_bond_parameters(topology, list_of_bonds) + else: + #TODO: Should be able to remove this blank print statement so that deselecting the + #checkbox removes the listed parameters. + print('') - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + else: + plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels) - return(fig,ax) + return + +def interactive_networkx_angles(topology): + """Get an interactive networkx plot showing the angle types of a topology object. + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the angle types + that have been parameterized. -def plot_networkx_dihedrals(topology,center_atom_index = None): - """Get a networkx plot showing the dihedrals in a topology object. + Returns + ------- + ipywidgets.Dropdown() + Three dropdown options, corresponding to: + Central Atom1 = The central site for which the angle can be visualized. + If it is not specified, missing angles will be shown. + Atom2 = An optional atom that will specify one end of the angle. + Atom3 = An optional atom that will specify the second end of the angle. Atom2 + must be selected first. + Selected Angle = The dropdown will show all angles that match the above site + criteria. Angles are listed by three characterstic atomtypes. Multiple angles + may satisfy the selected angle, and so more than three edges may be selected + on the plot. Use the atomtype definitions to verify the correct angles. + + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines + indicate which angles have been selected. + + ipywidgets.Checkbox() + Show parameters = True or False + Will determine if the necessary angle parameters for the selected angle type are shown + below the figure. + """ + networkx_graph = to_networkx(topology) + + # Create list of nodes to plot + site_names = [] + for node in networkx_graph.nodes: + site_names.append(node.name) + site_names = set(site_names) + + # Create a tuple of keys for each selected site + names_tuple = [] + names_tuple.append(('All Sites',None)) + for name in site_names: + names_tuple.append((name,name)) + + # Call recursive interacts. The top level determines what bonds can be selected + atom_selection = [] + descriptions = ['Central Atom1 (req)', 'Atom2 (opt)', 'Atom3 (opt)'] + for i in np.arange(3): + atom_selection.append(widgets.Dropdown( + options = names_tuple, + layout = widgets.Layout(width = '30%'), + style = dict(description_width='initial'), + description = descriptions[i] + ) + ) + interact(select_angles_from_sites, + networkx_graph = fixed(networkx_graph), + top = fixed(topology), + Atom1 = atom_selection[0], + Atom2 = atom_selection[1], + Atom3 = atom_selection[2], + ) + + return + +def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): + params_list = select_params_on_networkx(networkx_graph,[Atom1,Atom2,Atom3]) + if params_list: + edges_widget = widgets.Dropdown(options = params_list, + layout = widgets.Layout(width = '60%'), + style = dict(description_width = 'initial'), + description = "Selected Edge" + ) + interact(select_edges_on_networkx, networkx_graph = fixed(networkx_graph), + top = fixed(top), list_of_params = edges_widget + ) + else: + plot_networkx_params(networkx_graph, list_of_edges = []) + + return + +def interactive_networkx_dihedrals(topology): + """Get an interactive networkx plot showing the dihedral types of a topology object. + Parameters ---------- topology : A gmso.core.topology.Topology object This should be a gmso topology object that you want to visualize the dihedral types that have been parameterized. - center_atom_index : The second or third atom of the dihedrals you want to have selected - When drawing the networkx graph, all dihedrals connected to atoms with - this index will be indicated. - This input will be of type int. To see what atoms correspond to these indices, see - gmso.formats.networkx.plot_networkx_atomtypes - If no atom_name is given, then only atoms missing dihedral information will be - indicated - + Returns ------- + ipywidgets.Dropdown() + Three dropdown options, corresponding to: + Central Atom1 = One of the two central sites for which dihedrals can be visualized. + If it is not specified, missing dihedrals will be shown. + Central Atom2 = The second central site to select dihedrals. All dihedrals that + contain these two atom sites in their center will be listed. If it is not + specified, missing dihedrals will be shown. + Atom3 = An optional atom that will specify one end of the dihedral. + Atom4 = An optional atom that will specify the second end of the dihedral. Atom3 + must be selected. + Selected Dihedral = The dropdown will show all dihedrals that match the above site + criteria. Dihedrals are listed by four characterstic atomtypes. Multiple dihedrals + may satisfy the selected dihedral, and so more than four edges may be selected + on the plot. Use the atomtype definitions to verify the correct dihedrals. + + matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures + The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines + indicate which dihedrals have been selected. - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() + ipywidgets.Checkbox() + Show parameters = True or False + Will determine if the necessary dihedral parameters for the selected dihedral type are shown + below the figure. """ networkx_graph = to_networkx(topology) + + # Create list of nodes to plot + site_names = [] + for node in networkx_graph.nodes: + site_names.append(node.name) + site_names = set(site_names) + + # Create a tuple of keys for each selected site + names_tuple = [] + names_tuple.append(('All Sites',None)) + for name in site_names: + names_tuple.append((name,name)) + + # Call recursive interacts. The top level determines what bonds can be selected + atom_selection = [] + descriptions = ['Central Atom1 (req)', 'Central Atom2 (req)', 'Atom3 (opt)', 'Atom4 (opt)'] + for i in np.arange(4): + atom_selection.append(widgets.Dropdown( + options = (names_tuple), + layout = widgets.Layout(width = '30%'), + style = dict(description_width='initial'), + description = descriptions[i] + ) + ) + interact(select_dihedrals_from_sites, + networkx_graph = fixed(networkx_graph), + top = fixed(topology), + Atom1 = atom_selection[0], + Atom2 = atom_selection[1], + Atom3 = atom_selection[2], + Atom4 = atom_selection[3] + ) + + return + +def select_dihedrals_from_sites(networkx_graph, top, Atom1 = None, Atom2 = None, Atom3 = None, Atom4 = None): - fig,ax = plt.subplots(1,1,figsize=(8,8)) + params_list = select_params_on_networkx(networkx_graph,[Atom1,Atom2,Atom3,Atom4]) + if params_list: + edges_widget = widgets.Dropdown(options = params_list, + layout = widgets.Layout(width = '60%'), + style = dict(description_width = 'initial'), + description = "Selected Edge" + ) + interact(select_edges_on_networkx, networkx_graph = fixed(networkx_graph), + top = fixed(top), list_of_params = edges_widget + ) + else: + plot_networkx_params(networkx_graph, list_of_edges = []) + + return + +def select_params_on_networkx(networkx_graph, atoms): + # Create a list of the edges that have been selected corresponding to the selected + # list of sites names. If all sites are None, then select edges that are missing parameter types. + # Works for selecting angles or dihedals based on the number of atoms sent in the `atoms` variable. + + list_of_params = [] + list_of_names = [] + mia_angle_flag = 0 + if len(atoms) == 3: + if all(atoms): + for node in list(networkx_graph.nodes): + for angle in networkx_graph.nodes[node]['angles']: + names = [] + for member in angle.connection_members: + names.append(member.name) + if ((atoms[0] == names[0] and + atoms[1] == names[1] and + atoms[2] == names[2]) or + (atoms[0] == names[2] and + atoms[1] == names[1] and + atoms[2] == names[0])): + list_of_params.append(angle.connection_members) + list_of_names.append(angle.connection_members[0].atom_type.name + + ' --- ' + angle.connection_members[1].atom_type.name + + ' --- ' + angle.connection_members[2].atom_type.name) + elif all(atoms[0:2]): + for node in list(networkx_graph.nodes): + for angle in networkx_graph.nodes[node]['angles']: + names = [] + for member in angle.connection_members: + names.append(member.name) + if ((atoms[0] == names[1] and + atoms[1] == names[2]) or + (atoms[0] == names[1] and + atoms[1] == names[0])): + list_of_params.append(angle.connection_members) + list_of_names.append(angle.connection_members[0].atom_type.name + + ' --- ' + angle.connection_members[1].atom_type.name + + ' --- ' + angle.connection_members[2].atom_type.name) + elif atoms[0]: + for node in list(networkx_graph.nodes): + for angle in networkx_graph.nodes[node]['angles']: + names = [] + for member in angle.connection_members: + names.append(member.name) + if (atoms[0] == names[1]): + list_of_params.append(angle.connection_members) + list_of_names.append(angle.connection_members[0].atom_type.name + + ' --- ' + angle.connection_members[1].atom_type.name + + ' --- ' + angle.connection_members[2].atom_type.name) + else: + for node in networkx_graph.nodes: + for angle in networkx_graph.nodes[node]['angles']: + if angle.angle_type is None: + list_of_params.append(angle.connection_members) + list_of_names.append(angle.connection_members[0].atom_type.name + + ' --- ' + angle.connection_members[1].atom_type.name + + ' --- ' + angle.connection_members[2].atom_type.name) + mia_angle_flag = 1 + if not mia_angle_flag: + return print('Since no sites are input, angles with missing types are shown') + elif len(atoms) == 4: + #select a list of dihedrals + if all(atoms): + for node in list(networkx_graph.nodes): + for dihedral in networkx_graph.nodes[node]['dihedrals']: + names = [] + for member in dihedral.connection_members: + names.append(member.name) + if ((atoms[0] == names[1] and + atoms[1] == names[2] and + atoms[2] == names[3] and + atoms[3] == names[0]) or + (atoms[0] == names[1] and + atoms[1] == names[2] and + atoms[2] == names[0] and + atoms[3] == names[3])): + list_of_params.append(dihedral.connection_members) + list_of_names.append(dihedral.connection_members[0].atom_type.name + + ' --- ' + dihedral.connection_members[1].atom_type.name + + ' --- ' + dihedral.connection_members[2].atom_type.name + + ' --- ' + dihedral.connection_members[3].atom_type.name) + elif all(atoms[0:3]): + for node in list(networkx_graph.nodes): + for dihedral in networkx_graph.nodes[node]['dihedrals']: + names = [] + for member in dihedral.connection_members: + names.append(member.name) + if ((atoms[0] == names[1] and + atoms[1] == names[2] and + atoms[2] == names[3]) or + (atoms[0] == names[2] and + atoms[1] == names[1] and + atoms[2] == names[3]) or + (atoms[0] == names[1] and + atoms[1] == names[2] and + atoms[2] == names[0]) or + (atoms[0] == names[2] and + atoms[1] == names[1] and + atoms[2] == names[0])): + list_of_params.append(dihedral.connection_members) + list_of_names.append(dihedral.connection_members[0].atom_type.name + + ' --- ' + dihedral.connection_members[1].atom_type.name + + ' --- ' + dihedral.connection_members[2].atom_type.name + + ' --- ' + dihedral.connection_members[3].atom_type.name) + elif all(atoms[0:2]): + for node in list(networkx_graph.nodes): + for dihedral in networkx_graph.nodes[node]['dihedrals']: + names = [] + for member in dihedral.connection_members: + names.append(member.name) + if ((atoms[0] == names[1] and + atoms[1] == names[2]) or + (atoms[0] == names[2] and + atoms[1] == names[1])): + list_of_params.append(dihedral.connection_members) + list_of_names.append(dihedral.connection_members[0].atom_type.name + + ' --- ' + dihedral.connection_members[1].atom_type.name + + ' --- ' + dihedral.connection_members[2].atom_type.name + + ' --- ' + dihedral.connection_members[3].atom_type.name) + else: + for node in networkx_graph.nodes: + for dihedral in networkx_graph.nodes[node]['dihedrals']: + if dihedral.dihedral_type is None: + list_of_params.append(dihedral.connection_members) + list_of_names.append(dihedral.connection_members[0].atom_type.name + + ' --- ' + dihedral.connection_members[1].atom_type.name + + ' --- ' + dihedral.connection_members[2].atom_type.name + + ' --- ' + dihedral.connection_members[3].atom_type.name) + mia_angle_flag = 1 + + if not mia_angle_flag: + print('All dihedrals are typed. Select two central atoms to see associated dihedrals.') + else: + print('Since no sites are input, dihedrals with missing types are shown') + + + else: + print('invalid atom selections') - edge_weights, edge_colors = highlight_bonds(networkx_graph,'dihedrals',atom_index=center_atom_index) - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + labeled_params = zip(list_of_names,list_of_params) + + #create a dict so each selected bond selects the proper edges on the networkx.graph object. + selectable_list = {} + for label,param in labeled_params: + if label in selectable_list.keys(): + selectable_list[label].append(param) + else: + selectable_list[label] = [] + selectable_list[label].append(param) - return(fig,ax) + # turn the dict selectable list into a list of tuples. + list_of_edges = [] + for key in selectable_list: + list_of_edges.append((key,selectable_list[key])) + return list_of_edges -def highlight_bonds(networkx_graph,attribute,atom_index=None): - edge_weights, edge_colors = initialize_edge_params(networkx_graph) - def_param = 1 - if atom_index is None: - edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) - if def_param: - print('No {} selected, and all {} typed'.format(attribute,attribute)) - elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: - edge_weights, edge_colors = sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors) - else: - print('Invalid input for atom or node index') +def select_edges_on_networkx(networkx_graph, top, list_of_params): + list_of_edges = get_networkx_edges(list_of_params) + plot_networkx_params(networkx_graph, list_of_edges = list_of_edges) + + # The interact prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value = False, description = 'Show parameters') + interact(show_parameter_values, topology = fixed(top), + list_of_params = fixed(list_of_params), + checkbox=checkbox) + + return + +def get_networkx_edges(list_of_params): + # Return a list of edges within a given dihedral or angle + # Both orientations of every edge are saved, to guarentee the edge can be + #found on a networkx_graph.edge object. + list_of_edges = [] + if len(list_of_params[0]) == 4: + for param in list_of_params: + edge1 = (param[0],param[1]) + edge2 = (param[1],param[2]) + edge3 = (param[1],param[0]) + edge4 = (param[2],param[1]) + edge5 = (param[2],param[3]) + edge6 = (param[3],param[2]) + list_of_edges.append(edge1) + list_of_edges.append(edge2) + list_of_edges.append(edge3) + list_of_edges.append(edge4) + list_of_edges.append(edge5) + list_of_edges.append(edge6) + elif len(list_of_params[0]) == 3: + for param in list_of_params: + edge1 = (param[0],param[1]) + edge2 = (param[1],param[2]) + edge3 = (param[1],param[0]) + edge4 = (param[2],param[1]) + list_of_edges.append(edge1) + list_of_edges.append(edge2) + list_of_edges.append(edge3) + list_of_edges.append(edge4) + else: + raise ValueError('The parameters are not proper angles or dihedrals. Connection members are missing.') - return edge_weights, edge_colors - -def sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors): - node = list(networkx_graph.nodes)[atom_index] - for parameter in networkx_graph.nodes[node][attribute]: - if (node == parameter.connection_members[1] or - node == parameter.connection_members[len( - networkx_graph.nodes[node][attribute][0].connection_members)-2]): - for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): - node1 = list(parameter.connection_members)[i+1] - node0 = list(parameter.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - return edge_weights, edge_colors - -def initialize_edge_params(networkx_graph): - edge_weights={};edge_colors={} - for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' - - return edge_weights, edge_colors - -def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors): - for node in networkx_graph.nodes: - for parameter in networkx_graph.nodes[node][attribute]: - var = attribute[:-1] + '_type' - if getattr(parameter,var) is None: - def_param = 0 - members = list(parameter.connection_members) - for i in np.arange(len(members)-1): - edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 - edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' - - return edge_weights, edge_colors, def_param - -def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): + return list_of_edges + +def plot_networkx_nodes(networkx_graph, ax, atom_name = None, edge_weights = None, + edge_colors = None, node_sizes = None, list_of_labels = ['atom_type.name']): + # Place nodes at 2D positions related to position in the topology pos={} for node in networkx_graph.nodes: pos[node] = node.position.value[0:2] - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + + # Use this dictionary to color specific atoms + node_color_dict = {'C':'grey', 'H':'silver', 'O':'red', 'N':'blue', 'Cl':'green'} node_colors = [] for node in networkx_graph.nodes: if node.name in list(node_color_dict.keys()): @@ -256,21 +568,209 @@ def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = else: node_colors.append('black') + # Node sizes determines if looking at just sites. Bonds, angles, dihedrals have edge_weights + # and edge_colors as identifiers if node_sizes: - nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) + nx.draw(networkx_graph, layout, ax, node_color = node_colors, + node_size = node_sizes) else: - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 + nx.draw(networkx_graph, layout, ax, node_color = node_colors, + width = list(edge_weights.values()), + edge_color = list(edge_colors.values())) + # Offset positions to place labels for atom,pos in layout.items(): layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + + # Get a list of labels to plot + labels = identify_labels(networkx_graph, list_of_labels, atom_name) + # Plot labels on current figure + nx.draw_networkx_labels(networkx_graph, layout, labels, + horizontalalignment = 'left') ax.margins(.3,.3) - + return ax + +def identify_labels(networkx_graph, list_of_labels, atom_name): + # If atom_name specified, only show labels on that site. + # Otherwise, show labels for every atom from the label list. + if atom_name: + list_of_nodes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + list_of_nodes.append(node) + labels = return_labels_for_nodes(list_of_nodes, list_of_labels) + else: + labels = return_labels_for_nodes(list(networkx_graph.nodes), list_of_labels) + + return labels + +def return_labels_for_nodes(list_of_nodes, list_of_labels): + # Get the label values for the sites specified. + # labels is a dic of each node and the labels to put there + labels = {} + for i,node in enumerate(list_of_nodes): + node.label = str(i) + ': ' + str(node.name) + for label in list_of_labels: + if '.' in label: + label1,label2 = label.split('.') + node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) + else: + node.label = node.label + '\n' + str(getattr(node, label)) + labels[node] = node.label + + return labels + +def show_parameter_values(topology, list_of_params, checkbox): + if checkbox: + try: + report_parameter_expression(topology, list_of_params[0]) + except AttributeError: + print("There are no values for the parameter expression") + else: + #TODO: Should be able to remove this blank print statement so that deselecting the + #checkbox removes the listed parameters. + print('') + + return + +def report_parameter_expression(topology, param): + # return nicely printed parameters for a given edge. + if len(param) == 4: + for dihedral in list(topology.dihedrals): + if dihedral.connection_members == (param[0], param[1], param[2], param[3]): + print(dihedral.dihedral_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in dihedral.dihedral_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + elif len(param) == 3: + for angle in list(topology.angles): + if angle.connection_members == (param[0], param[1], param[2]): + print(angle.angle_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in angle.angle_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + else: + raise ValueError('Parameters are not proper angles or dihedrals. Connection members are missing') + + return + +def get_edges(networkx_graph, atom_name1, atom_name2): + + # Create a list of the edges that have been selected corresponding to the selected atom_name1 + # and atom_name2. If both are None, then select edges that are missing bond types. + list_of_edges = [] + list_of_bonds = [] + mia_bond_flag = 0 + if atom_name1 and atom_name2: + for edge in list(networkx_graph.edges): + if (edge[0].name == atom_name1 and edge[1].name == atom_name2 or + edge[0].name == atom_name2 and edge[1].name == atom_name1): + list_of_bonds.append(edge[0].atom_type.name + ' --- ' + edge[1].atom_type.name) + list_of_edges.append(edge) + elif atom_name1: + for edge in list(networkx_graph.edges): + if edge[0].name == atom_name1 or edge[1].name == atom_name1: + list_of_bonds.append(edge[0].atom_type.name + ' --- ' + edge[1].atom_type.name) + list_of_edges.append(edge) + else: + for nodes in list(networkx_graph.edges.items()): + if nodes[1]['connection'].bond_type is None: + list_of_bonds.append(nodes[0].atom_type.name + ' --- ' + nodes[1].atom_type.name) + list_of_edges.append((nodes[0],nodes[1])) + mia_bond_flag = 1 + if not mia_bond_flag: + return print('All bonds are typed') + + labeled_bonds = zip(list_of_bonds,list_of_edges) + + #create a dic so each selected bond selects the proper edges on the networkx.graph object. + selectable_list = {} + for label,bond in labeled_bonds: + if label in selectable_list.keys(): + selectable_list[label].append(bond) + else: + selectable_list[label] = [] + selectable_list[label].append(bond) + + # turn the dic selectable list into a list of tuples. + list_of_edges = [] + for key in selectable_list: + list_of_edges.append((key,selectable_list[key])) + + return list_of_edges + +def plot_networkx_bonds(networkx_graph, atom_name1=None, atom_name2=None, + list_of_labels = ['atom_type.name'], list_of_bonds = []): + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + # Create dictionaries of edges that correspond red thick lines for the selected bonds + edge_weights = {} + edge_colors = {} + for bond in list(networkx_graph.edges): + if bond in list_of_bonds: + edge_weights[bond] = 5 + edge_colors[bond] = 'red' + else: + edge_weights[bond] = 1 + edge_colors[bond] = 'k' + + ax = plot_nodes(networkx_graph,ax,edge_weights = edge_weights, + edge_colors = edge_colors, list_of_labels = list_of_labels, + atom_name = atom_name1) + return fig, ax + +def report_bond_parameters(topology, edge): + # return nicely printed bond parameters for a given edge. + for bond in list(topology.bonds): + if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): + print(bond.bond_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in bond.bond_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + + return + +def plot_networkx_atomtypes(topology,atom_name=None,list_of_labels = ['atom_type.name']): + """Get a networkx plot showing the atom types in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name : The atom name which will have larger node sizes. + When drawing the networkx graph, all atoms with this name will be 3X as large. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + networkx_graph = to_networkx(topology) + + #larger nodes for the atom selected + node_sizes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + node_sizes.append(900) + else: + node_sizes.append(300) + + ax = plot_nodes(networkx_graph, ax, edge_weights = None, edge_colors = None, + node_sizes = node_sizes, list_of_labels = list_of_labels, + atom_name = atom_name) + + return(fig,ax) + From 7fe4440fc8c2e719c0c1ec73114a5272b11bf179 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 28 Jan 2021 16:34:10 -0600 Subject: [PATCH 10/62] update to master --- gmso/core/topology.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index c1a7a9bb4..b307623c6 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -581,11 +581,7 @@ def update_topology(self): self.is_typed(updated=True) def _get_bonds_for(self, site): -<<<<<<< HEAD - """return a list of the bonds that contain Site""" -======= """Return a list of bonds in this Topology that the site is a part of""" ->>>>>>> cbb547d9d85488764f4b03cf2e3656b1cd36e0a0 bonds = [] for bond in self.bonds: if site in bond.connection_members: @@ -593,12 +589,7 @@ def _get_bonds_for(self, site): return bonds def _get_angles_for(self, site): -<<<<<<< HEAD - """return a list of the angles that contain Site""" -======= """Return a list of angles in this Topology that the site is a part of""" - ->>>>>>> cbb547d9d85488764f4b03cf2e3656b1cd36e0a0 angles = [] for angle in self.angles: if site in angle.connection_members: @@ -606,11 +597,7 @@ def _get_angles_for(self, site): return angles def _get_dihedrals_for(self, site): -<<<<<<< HEAD - """return a list of the dihedrals that contain Site""" -======= """Return a list of dihedrals in this Topology that the site is a part of""" ->>>>>>> cbb547d9d85488764f4b03cf2e3656b1cd36e0a0 dihedrals = [] for dihedral in self.dihedrals: if site in dihedral.connection_members: From cc07db24789bcfb41f0a0ddf2dfea723472adb07 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 28 Jan 2021 16:51:26 -0600 Subject: [PATCH 11/62] fix bugs with importing networkx.py functions --- gmso/formats/__init__.py | 4 ---- gmso/formats/networkx.py | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 7e11c6f89..3ea0c9f9e 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,7 +3,3 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networkx import (plot_networkx_atomtypes, - plot_networkx_bonds, - plot_networkx_angles, - plot_networkx_dihedrals) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 5bd10175c..84ba95829 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,7 +1,7 @@ -import matplotlib as plot +import matplotlib.pyplot as plt import networkx as nx -from ipywidgets import interact, fixed, +from ipywidgets import interact, fixed import ipywidgets as widgets from IPython.display import display @@ -65,10 +65,11 @@ def interactive_networkx_atomtypes(topology, list_of_labels = None): shown using matplotlib.pyplot.show() """ - + + networkx_graph = to_networkx(topology) #get a unique list of site names site_names = [] - for node in graph.nodes: + for node in networkx_graph.nodes: site_names.append(node.name) site_names = set(site_names) @@ -136,7 +137,7 @@ def interactive_networkx_bonds(topology, additional_labels = None): # Create list of nodes to plot site_names = [] - for node in graph.nodes: + for node in networkx_graph.nodes: site_names.append(node.name) site_names = set(site_names) @@ -224,7 +225,7 @@ def interactive_networkx_angles(topology): # Call recursive interacts. The top level determines what bonds can be selected atom_selection = [] descriptions = ['Central Atom1 (req)', 'Atom2 (opt)', 'Atom3 (opt)'] - for i in np.arange(3): + for i in [0, 1, 2]: atom_selection.append(widgets.Dropdown( options = names_tuple, layout = widgets.Layout(width = '30%'), @@ -311,7 +312,7 @@ def interactive_networkx_dihedrals(topology): # Call recursive interacts. The top level determines what bonds can be selected atom_selection = [] descriptions = ['Central Atom1 (req)', 'Central Atom2 (req)', 'Atom3 (opt)', 'Atom4 (opt)'] - for i in np.arange(4): + for i in [0, 1, 2, 3]: atom_selection.append(widgets.Dropdown( options = (names_tuple), layout = widgets.Layout(width = '30%'), @@ -716,7 +717,7 @@ def plot_networkx_bonds(networkx_graph, atom_name1=None, atom_name2=None, edge_weights[bond] = 1 edge_colors[bond] = 'k' - ax = plot_nodes(networkx_graph,ax,edge_weights = edge_weights, + ax = plot_networkx_nodes(networkx_graph,ax,edge_weights = edge_weights, edge_colors = edge_colors, list_of_labels = list_of_labels, atom_name = atom_name1) return fig, ax @@ -768,9 +769,27 @@ def plot_networkx_atomtypes(topology,atom_name=None,list_of_labels = ['atom_type else: node_sizes.append(300) - ax = plot_nodes(networkx_graph, ax, edge_weights = None, edge_colors = None, + ax = plot_networkx_nodes(networkx_graph, ax, edge_weights = None, edge_colors = None, node_sizes = node_sizes, list_of_labels = list_of_labels, atom_name = atom_name) return(fig,ax) +def highlight_networkx_edges(networkx_graph, list_of_edges): + #return edge parameters for the specified networkx edge list. + edge_weights, edge_colors = initialize_edge_params(networkx_graph) + def_param = 1 + for edge in networkx_graph.edges: + if edge in list_of_edges: + edge_weights[edge] = 5 + edge_colors[edge] = 'r' + + return edge_weights, edge_colors + +def initialize_edge_params(networkx_graph): + #create dictionaries of base values for each edge in the graph + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' + + return edge_weights, edge_colors From 3bbab126ae98d9918f1aaf4da5bd3f7de976ae19 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 4 Feb 2021 16:32:57 -0600 Subject: [PATCH 12/62] Convert charge units, clean up if statements for selecting angles and dihedrals --- gmso/formats/networkx.py | 209 +++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 117 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 84ba95829..b6a7c1916 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -358,125 +358,89 @@ def select_params_on_networkx(networkx_graph, atoms): mia_angle_flag = 0 if len(atoms) == 3: if all(atoms): - for node in list(networkx_graph.nodes): - for angle in networkx_graph.nodes[node]['angles']: - names = [] - for member in angle.connection_members: - names.append(member.name) - if ((atoms[0] == names[0] and - atoms[1] == names[1] and - atoms[2] == names[2]) or - (atoms[0] == names[2] and - atoms[1] == names[1] and - atoms[2] == names[0])): + for node, angles in networkx_graph.nodes(data='angles'): + for angle in angles: + names = [member.name for member in angle.connection_members] + if (atoms == [names[i] for i in [1,0,2]] or + atoms == [names[i] for i in [2,0,1]]): list_of_params.append(angle.connection_members) - list_of_names.append(angle.connection_members[0].atom_type.name + - ' --- ' + angle.connection_members[1].atom_type.name + - ' --- ' + angle.connection_members[2].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(angle) + ) elif all(atoms[0:2]): - for node in list(networkx_graph.nodes): - for angle in networkx_graph.nodes[node]['angles']: - names = [] - for member in angle.connection_members: - names.append(member.name) - if ((atoms[0] == names[1] and - atoms[1] == names[2]) or - (atoms[0] == names[1] and - atoms[1] == names[0])): + for node, angles in networkx_graph.nodes(data='angles'): + for angle in angles: + names = [member.name for member in angle.connection_members] + if (atoms[0:2] == names[1:3] or + atoms[0:2] == reversed(names[0:2])): list_of_params.append(angle.connection_members) - list_of_names.append(angle.connection_members[0].atom_type.name + - ' --- ' + angle.connection_members[1].atom_type.name + - ' --- ' + angle.connection_members[2].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(angle) + ) elif atoms[0]: - for node in list(networkx_graph.nodes): - for angle in networkx_graph.nodes[node]['angles']: - names = [] - for member in angle.connection_members: - names.append(member.name) - if (atoms[0] == names[1]): + for node, angles in networkx_graph.nodes(data='angles'): + for angle in angles: + names = [member.name for member in angle.connection_members] + if atoms[0] == names[1]: list_of_params.append(angle.connection_members) - list_of_names.append(angle.connection_members[0].atom_type.name + - ' --- ' + angle.connection_members[1].atom_type.name + - ' --- ' + angle.connection_members[2].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(angle) + ) else: - for node in networkx_graph.nodes: - for angle in networkx_graph.nodes[node]['angles']: - if angle.angle_type is None: - list_of_params.append(angle.connection_members) - list_of_names.append(angle.connection_members[0].atom_type.name + - ' --- ' + angle.connection_members[1].atom_type.name + - ' --- ' + angle.connection_members[2].atom_type.name) - mia_angle_flag = 1 + for node, angles in networkx_graph.nodes(data='angles'): + for angle in angles: + if angle.angle_type is None: + list_of_params.append(angle.connection_members) + list_of_names.append( + _get_formatted_atom_types_names_for(angle) + ) + mia_angle_flag = 1 if not mia_angle_flag: return print('Since no sites are input, angles with missing types are shown') elif len(atoms) == 4: #select a list of dihedrals if all(atoms): - for node in list(networkx_graph.nodes): - for dihedral in networkx_graph.nodes[node]['dihedrals']: - names = [] - for member in dihedral.connection_members: - names.append(member.name) - if ((atoms[0] == names[1] and - atoms[1] == names[2] and - atoms[2] == names[3] and - atoms[3] == names[0]) or - (atoms[0] == names[1] and - atoms[1] == names[2] and - atoms[2] == names[0] and - atoms[3] == names[3])): + for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if (atoms == names or + atoms == [names[i] for i in [0,2,1,3]] or + atoms == [names[i] for i in [3,1,2,0]] or + atoms == [names[i] for i in [3,2,1,0]]): list_of_params.append(dihedral.connection_members) - list_of_names.append(dihedral.connection_members[0].atom_type.name + - ' --- ' + dihedral.connection_members[1].atom_type.name + - ' --- ' + dihedral.connection_members[2].atom_type.name + - ' --- ' + dihedral.connection_members[3].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) elif all(atoms[0:3]): - for node in list(networkx_graph.nodes): - for dihedral in networkx_graph.nodes[node]['dihedrals']: - names = [] - for member in dihedral.connection_members: - names.append(member.name) - if ((atoms[0] == names[1] and - atoms[1] == names[2] and - atoms[2] == names[3]) or - (atoms[0] == names[2] and - atoms[1] == names[1] and - atoms[2] == names[3]) or - (atoms[0] == names[1] and - atoms[1] == names[2] and - atoms[2] == names[0]) or - (atoms[0] == names[2] and - atoms[1] == names[1] and - atoms[2] == names[0])): + for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if (atoms[0:3] == [names[i] for i in [1,2,3]] or + atoms[0:3] == [names[i] for i in [2,1,3]] or + atoms[0:3] == [names[i] for i in [1,2,0]] or + atoms[0:3] == [names[i] for i in [2,1,0]]): list_of_params.append(dihedral.connection_members) - list_of_names.append(dihedral.connection_members[0].atom_type.name + - ' --- ' + dihedral.connection_members[1].atom_type.name + - ' --- ' + dihedral.connection_members[2].atom_type.name + - ' --- ' + dihedral.connection_members[3].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) elif all(atoms[0:2]): - for node in list(networkx_graph.nodes): - for dihedral in networkx_graph.nodes[node]['dihedrals']: - names = [] - for member in dihedral.connection_members: - names.append(member.name) - if ((atoms[0] == names[1] and - atoms[1] == names[2]) or - (atoms[0] == names[2] and - atoms[1] == names[1])): + for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if (atoms[0:2] == names[1:3] or + atoms[0:2] == [names[2],names[1]]): list_of_params.append(dihedral.connection_members) - list_of_names.append(dihedral.connection_members[0].atom_type.name + - ' --- ' + dihedral.connection_members[1].atom_type.name + - ' --- ' + dihedral.connection_members[2].atom_type.name + - ' --- ' + dihedral.connection_members[3].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) else: - for node in networkx_graph.nodes: - for dihedral in networkx_graph.nodes[node]['dihedrals']: + for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for dihedral in dihedrals: if dihedral.dihedral_type is None: list_of_params.append(dihedral.connection_members) - list_of_names.append(dihedral.connection_members[0].atom_type.name + - ' --- ' + dihedral.connection_members[1].atom_type.name + - ' --- ' + dihedral.connection_members[2].atom_type.name + - ' --- ' + dihedral.connection_members[3].atom_type.name) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) mia_angle_flag = 1 if not mia_angle_flag: @@ -506,6 +470,13 @@ def select_params_on_networkx(networkx_graph, atoms): return list_of_edges +def _get_formatted_atom_types_names_for(connection): + assert all(map(lambda atom: atom.atom_type, connection.connection_members)) + names = (member.atom_type.name for member in connection.connection_members) + + return ' --- '.join(names) + + def select_edges_on_networkx(networkx_graph, top, list_of_params): list_of_edges = get_networkx_edges(list_of_params) plot_networkx_params(networkx_graph, list_of_edges = list_of_edges) @@ -557,7 +528,7 @@ def plot_networkx_nodes(networkx_graph, ax, atom_name = None, edge_weights = Non # Place nodes at 2D positions related to position in the topology pos={} for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] + pos[node] = node.position.in_units('nm').value[0:2] layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) # Use this dictionary to color specific atoms @@ -592,7 +563,7 @@ def plot_networkx_nodes(networkx_graph, ax, atom_name = None, edge_weights = Non return ax -def identify_labels(networkx_graph, list_of_labels, atom_name): +def identify_labels(networkx_graph, list_of_labels, atom_name = None): # If atom_name specified, only show labels on that site. # Otherwise, show labels for every atom from the label list. if atom_name: @@ -608,7 +579,7 @@ def identify_labels(networkx_graph, list_of_labels, atom_name): def return_labels_for_nodes(list_of_nodes, list_of_labels): # Get the label values for the sites specified. - # labels is a dic of each node and the labels to put there + # labels is a dict of each node and the labels to put there labels = {} for i,node in enumerate(list_of_nodes): node.label = str(i) + ': ' + str(node.name) @@ -616,6 +587,12 @@ def return_labels_for_nodes(list_of_nodes, list_of_labels): if '.' in label: label1,label2 = label.split('.') node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) + elif label == "charge": + label = getattr(node,label)/1.602/10**(-19) + if label < 0: + node.label = node.label + '\n' + str(label)[0:6] + else: + node.label = node.label + '\n' + str(label)[0:5] else: node.label = node.label + '\n' + str(getattr(node, label)) labels[node] = node.label @@ -709,7 +686,7 @@ def plot_networkx_bonds(networkx_graph, atom_name1=None, atom_name2=None, # Create dictionaries of edges that correspond red thick lines for the selected bonds edge_weights = {} edge_colors = {} - for bond in list(networkx_graph.edges): + for bond in networkx_graph.edges: if bond in list_of_bonds: edge_weights[bond] = 5 edge_colors[bond] = 'red' @@ -725,11 +702,14 @@ def plot_networkx_bonds(networkx_graph, atom_name1=None, atom_name2=None, def report_bond_parameters(topology, edge): # return nicely printed bond parameters for a given edge. for bond in list(topology.bonds): - if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): - print(bond.bond_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) - for k, v in bond.bond_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) + if bond: + if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): + print(bond.bond_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in bond.bond_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + else: + print("This bond has no parameters associated with it") return @@ -777,7 +757,9 @@ def plot_networkx_atomtypes(topology,atom_name=None,list_of_labels = ['atom_type def highlight_networkx_edges(networkx_graph, list_of_edges): #return edge parameters for the specified networkx edge list. - edge_weights, edge_colors = initialize_edge_params(networkx_graph) + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' def_param = 1 for edge in networkx_graph.edges: if edge in list_of_edges: @@ -786,10 +768,3 @@ def highlight_networkx_edges(networkx_graph, list_of_edges): return edge_weights, edge_colors -def initialize_edge_params(networkx_graph): - #create dictionaries of base values for each edge in the graph - edge_weights={};edge_colors={} - for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' - - return edge_weights, edge_colors From f77e3d633947898891b659b30920d4a82f2093a1 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 10 Feb 2021 11:37:57 -0600 Subject: [PATCH 13/62] Clean up layout for plotting molecules --- gmso/formats/networkx.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index b6a7c1916..de8ff9952 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -526,10 +526,7 @@ def get_networkx_edges(list_of_params): def plot_networkx_nodes(networkx_graph, ax, atom_name = None, edge_weights = None, edge_colors = None, node_sizes = None, list_of_labels = ['atom_type.name']): # Place nodes at 2D positions related to position in the topology - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.in_units('nm').value[0:2] - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + layout = nx.drawing.layout.kamada_kawai_layout(networkx_graph) # Use this dictionary to color specific atoms node_color_dict = {'C':'grey', 'H':'silver', 'O':'red', 'N':'blue', 'Cl':'green'} From 38cb18c05395756bf07a0876193b6fcc693da44d Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 10 Feb 2021 16:10:01 -0600 Subject: [PATCH 14/62] update charges --- gmso/formats/networkx.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index de8ff9952..08ff9abe9 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,5 +1,6 @@ import matplotlib.pyplot as plt import networkx as nx +import unyt from ipywidgets import interact, fixed import ipywidgets as widgets @@ -585,11 +586,7 @@ def return_labels_for_nodes(list_of_nodes, list_of_labels): label1,label2 = label.split('.') node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) elif label == "charge": - label = getattr(node,label)/1.602/10**(-19) - if label < 0: - node.label = node.label + '\n' + str(label)[0:6] - else: - node.label = node.label + '\n' + str(label)[0:5] + node.label = node.label + '\n' + str((getattr(node,label)/unyt.electron_charge).round(4)*unyt.electron_charge.units) else: node.label = node.label + '\n' + str(getattr(node, label)) labels[node] = node.label From 842c458264c40ae0ecd2873c7f91a8f37c700adc Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 10 Feb 2021 16:17:27 -0600 Subject: [PATCH 15/62] remove Ipython.Display import --- gmso/formats/networkx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 08ff9abe9..44712b80c 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -4,7 +4,6 @@ from ipywidgets import interact, fixed import ipywidgets as widgets -from IPython.display import display from gmso.external.convert_networkx import to_networkx From a55532307708540dc5a09cdc5571dda1f4eb6b13 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 15 Feb 2021 16:47:28 -0600 Subject: [PATCH 16/62] Add messages and support for gmso.topology objects that are missing attributes such as parameters and atomtypes --- gmso/formats/networkx.py | 91 ++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 44712b80c..f814e79f2 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -388,6 +388,9 @@ def select_params_on_networkx(networkx_graph, atoms): ) else: for node, angles in networkx_graph.nodes(data='angles'): + if not angles: + print("No angle parameters have been applied to this topology") + return for angle in angles: if angle.angle_type is None: list_of_params.append(angle.connection_members) @@ -396,7 +399,10 @@ def select_params_on_networkx(networkx_graph, atoms): ) mia_angle_flag = 1 if not mia_angle_flag: - return print('Since no sites are input, angles with missing types are shown') + print('All angles are typed. Select a central atom to look at the different angle_types.') + else: + print('Since no sites are input, angles with missing types are shown.') + elif len(atoms) == 4: #select a list of dihedrals if all(atoms): @@ -435,6 +441,9 @@ def select_params_on_networkx(networkx_graph, atoms): ) else: for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + if not dihedrals: + print("No dihedral parameters have been applied to this topology") + return for dihedral in dihedrals: if dihedral.dihedral_type is None: list_of_params.append(dihedral.connection_members) @@ -446,7 +455,7 @@ def select_params_on_networkx(networkx_graph, atoms): if not mia_angle_flag: print('All dihedrals are typed. Select two central atoms to see associated dihedrals.') else: - print('Since no sites are input, dihedrals with missing types are shown') + print('Since no sites are input, dihedrals with missing types are shown.') else: @@ -579,15 +588,31 @@ def return_labels_for_nodes(list_of_nodes, list_of_labels): # labels is a dict of each node and the labels to put there labels = {} for i,node in enumerate(list_of_nodes): - node.label = str(i) + ': ' + str(node.name) + node.label = str(i) + ':' + str(node.name) for label in list_of_labels: if '.' in label: label1,label2 = label.split('.') - node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) + try: + node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) + except AttributeError: + node.label = node.label + '\nNoneType' elif label == "charge": - node.label = node.label + '\n' + str((getattr(node,label)/unyt.electron_charge).round(4)*unyt.electron_charge.units) + if isinstance(getattr(node,label),unyt.array.unyt_quantity): + node.label = node.label + '\n' + str((getattr(node,label)/unyt.electron_charge).round(4)) + ' e' + else: + node.label = node.label + '\nNone' + elif label == "position": + if isinstance(getattr(node,label)[0],unyt.array.unyt_quantity): + node.label = node.label + '\n' + str(getattr(node,label).to('angstrom').round(2)*unyt.angstrom) + else: + node.label = node.label + '\nNone' else: - node.label = node.label + '\n' + str(getattr(node, label)) + try: + node.label = node.label + '\n' + str(getattr(node, label)) + except AttributeError: + node.label = node.label + '\nNoneType' + if len(node.label) > 12: + node.label = "".join([line + '\n' for line in node.label.split()]) labels[node] = node.label return labels @@ -608,19 +633,25 @@ def show_parameter_values(topology, list_of_params, checkbox): def report_parameter_expression(topology, param): # return nicely printed parameters for a given edge. if len(param) == 4: - for dihedral in list(topology.dihedrals): - if dihedral.connection_members == (param[0], param[1], param[2], param[3]): - print(dihedral.dihedral_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) - for k, v in dihedral.dihedral_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) + try: + for dihedral in list(topology.dihedrals): + if dihedral.connection_members == (param[0], param[1], param[2], param[3]): + print(dihedral.dihedral_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in dihedral.dihedral_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("Dihedral not typed") elif len(param) == 3: - for angle in list(topology.angles): - if angle.connection_members == (param[0], param[1], param[2]): - print(angle.angle_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) - for k, v in angle.angle_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) + try: + for angle in list(topology.angles): + if angle.connection_members == (param[0], param[1], param[2]): + print(angle.angle_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in angle.angle_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("Angle not typed") else: raise ValueError('Parameters are not proper angles or dihedrals. Connection members are missing') @@ -629,9 +660,14 @@ def report_parameter_expression(topology, param): def get_edges(networkx_graph, atom_name1, atom_name2): # Create a list of the edges that have been selected corresponding to the selected atom_name1 - # and atom_name2. If both are None, then select edges that are missing bond types. + #and atom_name2. If both are None, then select edges that are missing bond types. list_of_edges = [] list_of_bonds = [] + # Check for whether you want to plot bonds based on the atom types, or their node labels. + try: + [node.atom_type.name for node in networkx_graph.nodes] + except AttributeError: + return print("Atomtypes are missing, so no bond types can be selected") mia_bond_flag = 0 if atom_name1 and atom_name2: for edge in list(networkx_graph.edges): @@ -647,8 +683,8 @@ def get_edges(networkx_graph, atom_name1, atom_name2): else: for nodes in list(networkx_graph.edges.items()): if nodes[1]['connection'].bond_type is None: - list_of_bonds.append(nodes[0].atom_type.name + ' --- ' + nodes[1].atom_type.name) - list_of_edges.append((nodes[0],nodes[1])) + list_of_bonds.append(nodes[0][0].atom_type.name + ' --- ' + nodes[0][1].atom_type.name) + list_of_edges.append((nodes[0][0],nodes[0][1])) mia_bond_flag = 1 if not mia_bond_flag: return print('All bonds are typed') @@ -696,11 +732,14 @@ def report_bond_parameters(topology, edge): # return nicely printed bond parameters for a given edge. for bond in list(topology.bonds): if bond: - if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): - print(bond.bond_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) - for k, v in bond.bond_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) + try: + if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): + print(bond.bond_type.expression, '\n') + print("{:<12} {:<15}".format('Parameter','Value')) + for k, v in bond.bond_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("This bond has no parameters associated with it") else: print("This bond has no parameters associated with it") From bc3b24a8bc25b3fa8171f82b5a1f1b10a2ed053e Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 22 Feb 2021 15:29:11 -0600 Subject: [PATCH 17/62] Unit tests that are still failing --- gmso/tests/test_networkx.py | 54 ++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 4cf74f89b..d17672a34 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -4,9 +4,7 @@ #import networkx as nx import pytest -from gmso.formats.networkx import (plot_networkx_atomtypes, -plot_networkx_bonds, plot_networkx_angles, plot_networkx_dihedrals, -highlight_bonds, sel_input_params, initialize_edge_params) +from gmso.formats.networkx import * from gmso.tests.base_test import BaseTest from unyt.testing import assert_allclose_units from gmso.utils.io import import_, has_networkx, has_pyplot @@ -21,40 +19,23 @@ @pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") @pytest.mark.skipif(not has_pyplot, reason="Matplotlib.pyplot is not installed") class TestNetworkx(BaseTest): - def test_highlight_bonds(self, typed_ethane): + def test_highlight_networkx_edges(self, typed_ethane): list(typed_ethane.angles)[0].angle_type = None list(typed_ethane.dihedrals)[0].dihedral_type = None graph = to_networkx(typed_ethane) - test_edge_weights, test_edge_colors = highlight_bonds(graph, 'angles') + test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, 'angles') nodes = list(graph.nodes) assert test_edge_weights[nodes[0],nodes[4]] == 5 assert test_edge_weights[nodes[4],nodes[5]] == 5 assert test_edge_weights[nodes[0],nodes[1]] == 1 - test_edge_weights, test_edge_colors = highlight_bonds(graph, 'dihedrals') + test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, 'dihedrals') assert test_edge_weights[nodes[0],nodes[4]] == 5 assert test_edge_weights[nodes[4],nodes[5]] == 5 assert test_edge_weights[nodes[0],nodes[1]] == 5 assert test_edge_weights[nodes[0],nodes[3]] == 1 - - - def test_sel_input_params(self,typed_ethane): - graph = to_networkx(typed_ethane) - edge_weights, edge_colors = initialize_edge_params(graph) - test_edge_weights, test_edge_colors = sel_input_params(graph, 0, 'angles', edge_weights, edge_colors) - - assert all([a == b for a, b in zip(list(test_edge_weights.values()), [5, 5, 5, 5, 1, 1, 1])]) - assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['red', 'red', 'red', 'red', 'k', 'k', 'k'])]) - - def test_initialize_edge_params(self, typed_ethane): - graph = to_networkx(typed_ethane) - test_edge_weights, test_edge_colors = initialize_edge_params(graph) - - assert all([a == b for a, b in zip(list(test_edge_weights.values()), [1, 1, 1, 1, 1, 1, 1])]) - assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['k', 'k', 'k', 'k', 'k', 'k', 'k'])]) - def test_plot_networkx_atomtypes(self,typed_ethane): graph = to_networkx(typed_ethane) fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) @@ -65,8 +46,33 @@ def test_plot_networkx_atomtypes(self,typed_ethane): def test_plot_networkx_bonds(self,typed_ethane): graph = to_networkx(typed_ethane) - fig, ax = plot_networkx_bonds(typed_ethane) + fig, ax = plot_networkx_bonds(graph) test_fig, test_ax = plt.subplots(1) assert isinstance(fig, test_fig.__class__) assert isinstance(ax, test_ax.__class__) + + def test_select_params_on_networkx(self,typed_ethane): + graph = to_networkx(typed_ethane) + assert len(select_params_on_networkx(graph,[None,None,None,None])) == 0 + assert len(select_params_on_networkx(graph,['C','H','H'])) == 1 + assert len(select_params_on_networkx(graph,['C','C','H','H'])) == 1 + assert len(select_params_on_networkx(graph,[None,None,None])) == 0 + + def test__get_formatted_atom_types_names_for(self,typed_ethane): + graph = to_networkx(typed_ethane) + for node, dihedrals in graph.nodes(data='angles'): + assert isinstance(_get_formatted_atom_types_names_for(dihedrals[0]),str) + + def test_get_networkx_edges(self): + with pytest.raises(ValueError): + get_networkx_edges(list_of_params = ['C','C']) + + def test_identify_labels(self,typed_ethane): + graph = to_networkx(typed_ethane) + assert len(identify_labels(graph,['name'],atom_name = 'C')) == 2 + + def test_show_parameter_values(self,typed_ethane): + parameters = list(typed_ethane.angles[0].connection_members[0:2]) + with pytest.raises(ValueError): + show_parameter_values(typed_ethane, [parameters], True) From efb299ba808e20cc428263ab8627d41c9260c824 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 22 Feb 2021 16:22:59 -0600 Subject: [PATCH 18/62] Passing test_highlight_edges --- gmso/formats/networkx.py | 2 +- gmso/tests/test_networkx.py | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index f814e79f2..f586084a5 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -404,7 +404,7 @@ def select_params_on_networkx(networkx_graph, atoms): print('Since no sites are input, angles with missing types are shown.') elif len(atoms) == 4: - #select a list of dihedrals + #select a list of dihedrals if all(atoms): for node, dihedrals in networkx_graph.nodes(data='dihedrals'): for dihedral in dihedrals: diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index d17672a34..41c724970 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -24,17 +24,15 @@ def test_highlight_networkx_edges(self, typed_ethane): list(typed_ethane.dihedrals)[0].dihedral_type = None graph = to_networkx(typed_ethane) - test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, 'angles') - nodes = list(graph.nodes) - assert test_edge_weights[nodes[0],nodes[4]] == 5 - assert test_edge_weights[nodes[4],nodes[5]] == 5 - assert test_edge_weights[nodes[0],nodes[1]] == 1 + list_edges = list(graph.edges)[0:3] + test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, list_edges[0:2]) + assert test_edge_weights[list_edges[0]] == 5 + assert test_edge_weights[list_edges[1]] == 5 + assert test_edge_weights[list_edges[2]] == 1 + assert test_edge_colors[list_edges[0]] == 'r' + assert test_edge_colors[list_edges[1]] == 'r' + assert test_edge_colors[list_edges[2]] == 'k' - test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, 'dihedrals') - assert test_edge_weights[nodes[0],nodes[4]] == 5 - assert test_edge_weights[nodes[4],nodes[5]] == 5 - assert test_edge_weights[nodes[0],nodes[1]] == 5 - assert test_edge_weights[nodes[0],nodes[3]] == 1 def test_plot_networkx_atomtypes(self,typed_ethane): graph = to_networkx(typed_ethane) From 0a5236f140cbe5176b1627a03344cc1102e7a671 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 22 Feb 2021 16:38:52 -0600 Subject: [PATCH 19/62] unit tests passing for selecting_parameters --- gmso/formats/networkx.py | 8 ++++---- gmso/tests/test_networkx.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index f586084a5..bd71b92b5 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -409,10 +409,10 @@ def select_params_on_networkx(networkx_graph, atoms): for node, dihedrals in networkx_graph.nodes(data='dihedrals'): for dihedral in dihedrals: names = [member.name for member in dihedral.connection_members] - if (atoms == names or - atoms == [names[i] for i in [0,2,1,3]] or - atoms == [names[i] for i in [3,1,2,0]] or - atoms == [names[i] for i in [3,2,1,0]]): + if (atoms == [names[i] for i in [2,1,0,3]] or + atoms == [names[i] for i in [1,2,3,0]] or + atoms == [names[i] for i in [1,2,0,3]] or + atoms == [names[i] for i in [2,1,3,0]]): list_of_params.append(dihedral.connection_members) list_of_names.append( _get_formatted_atom_types_names_for(dihedral) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 41c724970..2ce0e00a5 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -5,6 +5,7 @@ import pytest from gmso.formats.networkx import * +from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest from unyt.testing import assert_allclose_units from gmso.utils.io import import_, has_networkx, has_pyplot From f242e48e45029d14d2cc47ff4ec0efdd571586d3 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 24 Feb 2021 10:02:32 -0600 Subject: [PATCH 20/62] Fix unused import, and improve import of test_networkx.py functions --- gmso/formats/networkx.py | 1 - gmso/tests/test_networkx.py | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index bd71b92b5..bbf61517e 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -792,7 +792,6 @@ def highlight_networkx_edges(networkx_graph, list_of_edges): edge_weights={};edge_colors={} for edge in networkx_graph.edges: edge_weights[edge] = 1; edge_colors[edge] = 'k' - def_param = 1 for edge in networkx_graph.edges: if edge in list_of_edges: edge_weights[edge] = 5 diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 2ce0e00a5..080720dcd 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -1,13 +1,10 @@ -import unyt as u -import numpy as np -#import matplotlib.pyplot as plt -#import networkx as nx import pytest -from gmso.formats.networkx import * +from gmso.formats.networkx import (highlight_networkx_edges, plot_networkx_atomtypes, +plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, +show_parameter_values) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest -from unyt.testing import assert_allclose_units from gmso.utils.io import import_, has_networkx, has_pyplot from gmso.external.convert_networkx import to_networkx @@ -36,7 +33,6 @@ def test_highlight_networkx_edges(self, typed_ethane): def test_plot_networkx_atomtypes(self,typed_ethane): - graph = to_networkx(typed_ethane) fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) test_fig, test_ax = plt.subplots(1) From 25bd67f94e88f77912599ece9ce0a269e3aaae84 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 24 Feb 2021 10:40:49 -0600 Subject: [PATCH 21/62] increase unit test coverage --- gmso/tests/test_networkx.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 080720dcd..20d17fbc7 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -2,8 +2,11 @@ from gmso.formats.networkx import (highlight_networkx_edges, plot_networkx_atomtypes, plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, -show_parameter_values) +show_parameter_values, interactive_networkx_atomtypes, interactive_networkx_bonds, +interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, +plot_networkx_nodes) from gmso.formats.networkx import _get_formatted_atom_types_names_for + from gmso.tests.base_test import BaseTest from gmso.utils.io import import_, has_networkx, has_pyplot from gmso.external.convert_networkx import to_networkx @@ -71,3 +74,31 @@ def test_show_parameter_values(self,typed_ethane): parameters = list(typed_ethane.angles[0].connection_members[0:2]) with pytest.raises(ValueError): show_parameter_values(typed_ethane, [parameters], True) + + def test_interactive_networkx_atomtypes(self): + with pytest.raises(Exception): + interactive_networkx_atomtypes() + + def test_interactive_networkx_bonds(self): + with pytest.raises(Exception): + interactive_networkx_bonds() + + def test_interactive_networkx_angles(self): + with pytest.raises(Exception): + interactive_networkx_angles() + + def test_interactive_networkx_dihedrals(self): + with pytest.raises(Exception): + interactive_networkx_dihedrals() + + def test_select_dihedrals_from_sites(self,typed_ethane,capsys): + graph = to_networkx(typed_ethane) + select_dihedrals_from_sites(graph,typed_ethane,'C','C') + captured = capsys.readouterr() + assert isinstance(captured.out,str) + + def test_plot_networkx_nodes(self,typed_ethane): + graph = to_networkx(typed_ethane) + fig, ax = plt.subplots(1,1) + plot_networkx_nodes(graph,ax,edge_weights = {1:5}, + edge_colors = {1:'r'}) From 5d9c4e32efe5ddb4461a6ca8f4e96d8e24b36872 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 24 Feb 2021 15:23:25 -0600 Subject: [PATCH 22/62] Add ipywidgets to requirements-test.txt --- requirements-test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-test.txt b/requirements-test.txt index e161bb56a..bae56a6ff 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -14,3 +14,4 @@ pytest-cov codecov pydantic matplotlib +ipywidgets From 211917beb0ed1d640816afbf53fbdc06a20720e1 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 25 Feb 2021 12:49:39 -0600 Subject: [PATCH 23/62] Fill out unit tests --- gmso/tests/test_networkx.py | 76 +++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 20d17fbc7..de8cec166 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -4,7 +4,8 @@ plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, show_parameter_values, interactive_networkx_atomtypes, interactive_networkx_bonds, interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, -plot_networkx_nodes) +plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, +report_parameter_expression) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest @@ -56,15 +57,19 @@ def test_select_params_on_networkx(self,typed_ethane): assert len(select_params_on_networkx(graph,['C','H','H'])) == 1 assert len(select_params_on_networkx(graph,['C','C','H','H'])) == 1 assert len(select_params_on_networkx(graph,[None,None,None])) == 0 + assert len(select_params_on_networkx(graph,['C','H',None])) == 2 + assert len(select_params_on_networkx(graph,['C',None,None])) == 3 + assert len(select_params_on_networkx(graph,['C','C','H', None])) == 1 + assert len(select_params_on_networkx(graph,['C','C', None, None])) == 1 def test__get_formatted_atom_types_names_for(self,typed_ethane): graph = to_networkx(typed_ethane) for node, dihedrals in graph.nodes(data='angles'): assert isinstance(_get_formatted_atom_types_names_for(dihedrals[0]),str) - def test_get_networkx_edges(self): - with pytest.raises(ValueError): - get_networkx_edges(list_of_params = ['C','C']) + def test_get_networkx_edges(self,typed_ethane,capsys): + assert len(get_networkx_edges([list(typed_ethane.dihedrals[0].connection_members)])) == 6 + assert len(get_networkx_edges([list(typed_ethane.angles[0].connection_members)])) == 4 def test_identify_labels(self,typed_ethane): graph = to_networkx(typed_ethane) @@ -75,30 +80,63 @@ def test_show_parameter_values(self,typed_ethane): with pytest.raises(ValueError): show_parameter_values(typed_ethane, [parameters], True) - def test_interactive_networkx_atomtypes(self): - with pytest.raises(Exception): - interactive_networkx_atomtypes() + def test_interactive_networkx_atomtypes(self,typed_ethane,capsys): + interactive_networkx_atomtypes(typed_ethane) + captured, err = capsys.readouterr() + assert isinstance(err,str) + + def test_interactive_networkx_bonds(self,typed_ethane,capsys): + interactive_networkx_bonds(typed_ethane) + captured, err = capsys.readouterr() + assert isinstance(err,str) - def test_interactive_networkx_bonds(self): - with pytest.raises(Exception): - interactive_networkx_bonds() + def test_interactive_networkx_angles(self,typed_ethane,capsys): + interactive_networkx_angles(typed_ethane) + captured, err = capsys.readouterr() + assert isinstance(err,str) - def test_interactive_networkx_angles(self): - with pytest.raises(Exception): - interactive_networkx_angles() + def test_interactive_networkx_dihedrals(self,typed_ethane,capsys): + interactive_networkx_dihedrals(typed_ethane) + captured, err = capsys.readouterr() + assert isinstance(err,str) - def test_interactive_networkx_dihedrals(self): - with pytest.raises(Exception): - interactive_networkx_dihedrals() + def test_select_dihedrals_from_sites(self,typed_ethane,capsys): + graph = to_networkx(typed_ethane) + select_dihedrals_from_sites(graph,typed_ethane) + captured,err = capsys.readouterr() + assert isinstance(err,str) def test_select_dihedrals_from_sites(self,typed_ethane,capsys): graph = to_networkx(typed_ethane) - select_dihedrals_from_sites(graph,typed_ethane,'C','C') - captured = capsys.readouterr() - assert isinstance(captured.out,str) + select_dihedrals_from_sites(graph,typed_ethane) + captured, err = capsys.readouterr() + assert isinstance(err,str) def test_plot_networkx_nodes(self,typed_ethane): graph = to_networkx(typed_ethane) fig, ax = plt.subplots(1,1) plot_networkx_nodes(graph,ax,edge_weights = {1:5}, edge_colors = {1:'r'}) + + def test_plot_networkx_params(self,typed_ethane): + graph = to_networkx(typed_ethane) + list_of_edges = get_networkx_edges(list_of_params = [typed_ethane.dihedrals[0].connection_members, + typed_ethane.dihedrals[1].connection_members]) + fig, ax = plot_networkx_params(graph, list_of_edges) + test_fig, test_ax = plt.subplots(1) + + assert isinstance(fig, test_fig.__class__) + assert isinstance(ax, test_ax.__class__) + + def test_select_edges_on_networkx(self,typed_ethane,capsys): + graph = to_networkx(typed_ethane) + edges = select_params_on_networkx(graph, ['C','C','H']) + select_edges_on_networkx(graph,typed_ethane, edges[0][1]) + captured, err = capsys.readouterr() + assert isinstance(err,str) + + def test_report_parameter_expression(self,typed_ethane,capsys): + report_parameter_expression(typed_ethane,list(typed_ethane.dihedrals[0].connection_members)) + report_parameter_expression(typed_ethane,list(typed_ethane.angles[0].connection_members)) + captured, err = capsys.readouterr() + assert isinstance(err,str) From dc4f323395b713ecfde969939ce3a306450de380 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Fri, 26 Feb 2021 13:56:34 -0600 Subject: [PATCH 24/62] parameter and get_edges unit tests --- gmso/tests/test_networkx.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index de8cec166..48a9faac4 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -4,8 +4,8 @@ plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, show_parameter_values, interactive_networkx_atomtypes, interactive_networkx_bonds, interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, -plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, -report_parameter_expression) +plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, get_edges, +report_parameter_expression, report_bond_parameters) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest @@ -140,3 +140,14 @@ def test_report_parameter_expression(self,typed_ethane,capsys): report_parameter_expression(typed_ethane,list(typed_ethane.angles[0].connection_members)) captured, err = capsys.readouterr() assert isinstance(err,str) + + def test_get_edges(self,typed_ethane): + graph = to_networkx(typed_ethane) + assert len(get_edges(graph, 'C', 'C')[0][1]) == 1 + assert len(get_edges(graph, 'H', None)[0][1]) == 6 + assert not get_edges(graph, None, None) + + def test_report_bond_parameters(self,typed_ethane,capsys): + report_bond_parameters(typed_ethane,[typed_ethane.bonds[0].connection_members]) + captured, err = capsys.readouterr() + assert isinstance(err,str) From 354544f80b06ff35d843fdb514ad5d0be9856e19 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 11 Mar 2021 12:54:32 -0600 Subject: [PATCH 25/62] Fix merge issue, update test_return_labels_for_nodes --- gmso/core/topology.py | 4 ---- gmso/tests/test_networkx.py | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index f2757090c..2a5b2ce80 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -587,10 +587,6 @@ def _get_bonds_for(self, site): def _get_angles_for(self, site): """Return a list of angles in this Topology that the site is a part of""" -<<<<<<< HEAD -======= - ->>>>>>> fee3df261a74e39977a5f5d51ca4abd408aca2db angles = [] for angle in self.angles: if site in angle.connection_members: diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index c88084b1e..98750a45f 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -154,6 +154,6 @@ def test_report_bond_parameters(self,typed_ethane,capsys): def test_return_labels_for_nodes(self,typed_ethane): graph = to_networkx(typed_ethane) - assert len(return_labels_for_nodes(graph.nodes,['atom_type.name','charge',positions])) == 8 - assert return_labels_for_nodes(graph.nodes,['atom_type.error'])[-8:] == 'NoneType' - assert return_labels_for_nodes(graph.nodes,['error'])[-8:] == 'NoneType' + assert len(return_labels_for_nodes(graph.nodes,['atom_type.name','charge','positions'])) == 8 + assert list(return_labels_for_nodes(graph.nodes,['atom_type.error']).values())[0][-8:] == 'NoneType' + assert list(return_labels_for_nodes(graph.nodes,['error']).values())[0][-8:] == 'NoneType' From c8fbae04c30a1835b80315342499ea874930ef2f Mon Sep 17 00:00:00 2001 From: CalCraven Date: Sat, 9 Jan 2021 18:18:27 -0600 Subject: [PATCH 26/62] create gmso/formats/networkx.py --- gmso/formats/__init__.py | 1 + gmso/formats/networkx.py | 333 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 gmso/formats/networkx.py diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 3ea0c9f9e..5cd0566a2 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,3 +3,4 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata +from .networx import * diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py new file mode 100644 index 000000000..fd5f9cd53 --- /dev/null +++ b/gmso/formats/networkx.py @@ -0,0 +1,333 @@ +import numpy as np +import unyt as u +import matplotlib.pyplot as plt +import networkx as nx + +from gmso.core.topology import Topology +from gmso.external.convert_networkx import to_networkx + +def plot_networkx_atomtypes(topology,atom_name=None): + """Get a networkx plot showing the atom types in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name : The atom name which will have larger node sizes. + When drawing the networkx graph, all atoms with this name will be 3X as large. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + node_sizes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + node_sizes.append(900) + else: + node_sizes.append(300) + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) + pos= {} + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + +def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): + """Get a networkx plot showing the bonds in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name1 : The atom name to of which bonds you want to have selected + When drawing the networkx graph, all bonds connected to atoms with + this name will be indicated. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + If no atom_name is given, then only bonds missing bond information will be + indicated + atom_name2 : A second atom name to restrict what bonds will be selected. only bonds + between the two given atoms will be indicated. + This input will be of type string. + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + #Color and weight edges between particular atoms. If both atom_names are none, plot missing bond types. + from gmso.external import convert_networkx + + fig,ax = plt.subplots(1,1,figsize=(8,8)) + networkx_graph = to_networkx(topology) + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + node_colors.append(node_color_dict[node.name]) + + edge_weights = {} + edge_colors = {} + mia_bond_ind = 0 + if atom_name1 and atom_name2: + for edge in networkx_graph.edges: + if edge[0].name == atom_name1 and edge[1].name == atom_name2: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + elif edge[0].name == atom_name2 and edge[1].name == atom_name1: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + elif atom_name1: + for edge in networkx_graph.edges: + if edge[0].name == atom_name1 or edge[1].name == atom_name1: + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + else: + for bond in list(networkx_graph.edges.items()): + if bond[1]['connection'].bond_type == None: + edge_weights[bond[0]] = 5 + edge_colors[bond[0]] = 'red' + mia_bond_ind = 1 + else: + edge_weights[bond[0]] = 1 + edge_colors[bond[0]] = 'k' + if not mia_bond_ind: + print('All bonds are typed') + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + pos= {} + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + return(fig,ax) + + +def plot_networkx_angles(topology,center_atom_index = None): + """Get a networkx plot showing the angles in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the angle types + that have been parameterized. + center_atom_index : The central atom to of which angles you want to have selected + When drawing the networkx graph, all angles connected to atoms with + this index will be indicated. + This input will be of type int. To see what atoms correspond to these indices, see + gmso.formats.networkx.plot_networkx_atomtypes + If no atom_name is given, then only atoms missing angle information will be + indicated + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + edge_weights = {} + edge_colors = {} + + for edge in networkx_graph.edges: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + + node = list(networkx_graph.nodes)[center_atom_index] + for angle in networkx_graph.nodes[node]['angles']: + if node == angle.connection_members[1]: + for i in [0,1]: + node1 = list(angle.connection_members)[i+1] + node0 = list(angle.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + + +def plot_networkx_dihedrals(topology,center_atom_index = None): + """Get a networkx plot showing the dihedrals in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the dihedral types + that have been parameterized. + center_atom_index : The second or third atom of the dihedrals you want to have selected + When drawing the networkx graph, all dihedrals connected to atoms with + this index will be indicated. + This input will be of type int. To see what atoms correspond to these indices, see + gmso.formats.networkx.plot_networkx_atomtypes + If no atom_name is given, then only atoms missing dihedral information will be + indicated + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + networkx_graph = to_networkx(topology) + + pos={} + for node in networkx_graph.nodes: + pos[node] = node.position.value[0:2] + + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} + node_colors = [] + for node in networkx_graph.nodes: + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append('black') + + edge_weights = {} + edge_colors = {} + + for edge in networkx_graph.edges: + edge_weights[edge] = 1 + edge_colors[edge] = 'k' + + node = list(networkx_graph.nodes)[center_atom_index] + for dihedral in networkx_graph.nodes[node]['dihedrals']: + if (node == list(dihedral.connection_members)[1] or + node == list(dihedral.connection_members)[2]): + i=0 + node1 = list(dihedral.connection_members)[i+1] + node0 = list(dihedral.connection_members)[i] + #order is switched around when naming edges for the first + #edge in the dihedral + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + for i in [1,2]: + node1 = list(dihedral.connection_members)[i+1] + node0 = list(dihedral.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + + nx.draw(networkx_graph,layout,ax,node_color=node_colors, + width=list(edge_weights.values()),edge_color=list(edge_colors.values())) + + labels = {} + i=0 + for node in list(networkx_graph.nodes()): + node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name + labels[node] = node.label + i+=1 + + for atom,pos in layout.items(): + layout[atom] = pos + [0.09,0] + nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') + ax.margins(.3,.3) + + return(fig,ax) + From 2b37062253a50badc70dfe653ca95efd6261cb4f Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 14 Jan 2021 12:11:14 -0600 Subject: [PATCH 27/62] Add functions to clean up networkx plotting, and plot missing angle and bond types --- gmso/formats/__init__.py | 2 +- gmso/formats/networkx.py | 193 +++++++++++++-------------------------- 2 files changed, 64 insertions(+), 131 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 5cd0566a2..0e5313219 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,4 +3,4 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networx import * +from .networkx import * diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index fd5f9cd53..3fe0e8be6 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -33,37 +33,13 @@ def plot_networkx_atomtypes(topology,atom_name=None): fig,ax = plt.subplots(1,1,figsize=(8,8)) networkx_graph = to_networkx(topology) - - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] node_sizes = [] for node in networkx_graph.nodes: if node.name == atom_name: node_sizes.append(900) else: node_sizes.append(300) - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append('black') - - nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) - pos= {} - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) + ax = plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = node_sizes) return(fig,ax) @@ -103,16 +79,6 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): fig,ax = plt.subplots(1,1,figsize=(8,8)) networkx_graph = to_networkx(topology) - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] - for node in networkx_graph.nodes: - node_colors.append(node_color_dict[node.name]) edge_weights = {} edge_colors = {} @@ -148,22 +114,8 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): if not mia_bond_ind: print('All bonds are typed') - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - - pos= {} - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) - return(fig,ax) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + return fig, ax def plot_networkx_angles(topology,center_atom_index = None): @@ -188,58 +140,17 @@ def plot_networkx_angles(topology,center_atom_index = None): The drawn networkx plot of the topology on a matplotlib editable figures matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be + The axis information that corresponds to a networkx drawn figure. This output can be shown using matplotlib.pyplot.show() """ networkx_graph = to_networkx(topology) - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) fig,ax = plt.subplots(1,1,figsize=(8,8)) - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] - for node in networkx_graph.nodes: - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append('black') - - edge_weights = {} - edge_colors = {} - - for edge in networkx_graph.edges: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' + edge_weights, edge_colors = highlight_bonds(networkx_graph,'angles',atom_index=center_atom_index) - node = list(networkx_graph.nodes)[center_atom_index] - for angle in networkx_graph.nodes[node]['angles']: - if node == angle.connection_members[1]: - for i in [0,1]: - node1 = list(angle.connection_members)[i+1] - node0 = list(angle.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) return(fig,ax) @@ -272,12 +183,60 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): """ networkx_graph = to_networkx(topology) + fig,ax = plt.subplots(1,1,figsize=(8,8)) + + edge_weights, edge_colors = highlight_bonds(networkx_graph,'dihedrals',atom_index=center_atom_index) + ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) + + return(fig,ax) + + +def highlight_bonds(networkx_graph,attribute,atom_index=None): + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' + + def_param = 1 + if atom_index == None: + for node in networkx_graph.nodes: + for parameter in networkx_graph.nodes[node][attribute]: + var = attribute[:-1] + '_type' + if getattr(parameter,var) == None: + def_param = 0 + members = list(parameter.connection_members) + for i in np.arange(len(members)-1): + edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 + edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' + + if def_param: + print('No {} selected, and all {} typed'.format(attribute,attribute)) + + elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: + node = list(networkx_graph.nodes)[atom_index] + for parameter in networkx_graph.nodes[node][attribute]: + if (node == parameter.connection_members[1] or + node == parameter.connection_members[ + len(networkx_graph.nodes[node][attribute][0].connection_members)-2]): + for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): + node1 = list(parameter.connection_members)[i+1] + node0 = list(parameter.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + else: + print('Invalid input for atom or node index') + + return edge_weights, edge_colors + +def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): pos={} for node in networkx_graph.nodes: pos[node] = node.position.value[0:2] - + layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - fig,ax = plt.subplots(1,1,figsize=(8,8)) node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} node_colors = [] @@ -287,47 +246,21 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): else: node_colors.append('black') - edge_weights = {} - edge_colors = {} - - for edge in networkx_graph.edges: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - - node = list(networkx_graph.nodes)[center_atom_index] - for dihedral in networkx_graph.nodes[node]['dihedrals']: - if (node == list(dihedral.connection_members)[1] or - node == list(dihedral.connection_members)[2]): - i=0 - node1 = list(dihedral.connection_members)[i+1] - node0 = list(dihedral.connection_members)[i] - #order is switched around when naming edges for the first - #edge in the dihedral - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - for i in [1,2]: - node1 = list(dihedral.connection_members)[i+1] - node0 = list(dihedral.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - - nx.draw(networkx_graph,layout,ax,node_color=node_colors, + if node_sizes: + nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) + else: + nx.draw(networkx_graph,layout,ax,node_color=node_colors, width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - labels = {} i=0 for node in list(networkx_graph.nodes()): node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label + labels[node] = node.label i+=1 for atom,pos in layout.items(): layout[atom] = pos + [0.09,0] nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') ax.margins(.3,.3) - - return(fig,ax) - + + return ax From 51e3962ea5359bc8638d0ce6f7f33ce5614cd856 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Tue, 12 Jan 2021 11:13:39 -0600 Subject: [PATCH 28/62] Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site --- gmso/core/topology.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 68c50b8c2..fb87ae52f 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -577,6 +577,7 @@ def update_topology(self): self.update_connection_types() self.is_typed(updated=True) +<<<<<<< HEAD def _get_bonds_for(self, site): """Return a list of bonds in this Topology that the site is a part of""" bonds = [] @@ -588,6 +589,10 @@ def _get_bonds_for(self, site): def _get_angles_for(self, site): """Return a list of angles in this Topology that the site is a part of""" +======= + def _get_angles_for(self, site): + """return a list of the angles that contain Site""" +>>>>>>> Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site angles = [] for angle in self.angles: if site in angle.connection_members: @@ -595,7 +600,11 @@ def _get_angles_for(self, site): return angles def _get_dihedrals_for(self, site): +<<<<<<< HEAD """Return a list of dihedrals in this Topology that the site is a part of""" +======= + """return a list of the dihedrals that contain Site""" +>>>>>>> Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site dihedrals = [] for dihedral in self.dihedrals: if site in dihedral.connection_members: From 4a85cd283ba4a88de3b78d0209f19b0279801c3c Mon Sep 17 00:00:00 2001 From: CalCraven Date: Fri, 15 Jan 2021 13:22:47 -0600 Subject: [PATCH 29/62] Add unit tests for _get_x_for where x is bonds, angles, and dihedrals. Add _get_bonds_for method in topology.py --- gmso/core/topology.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index fb87ae52f..252d7e823 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -577,22 +577,16 @@ def update_topology(self): self.update_connection_types() self.is_typed(updated=True) -<<<<<<< HEAD def _get_bonds_for(self, site): - """Return a list of bonds in this Topology that the site is a part of""" + """return a list of the bonds that contain Site""" bonds = [] for bond in self.bonds: if site in bond.connection_members: bonds.append(bond) return bonds - def _get_angles_for(self, site): - """Return a list of angles in this Topology that the site is a part of""" - -======= def _get_angles_for(self, site): """return a list of the angles that contain Site""" ->>>>>>> Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site angles = [] for angle in self.angles: if site in angle.connection_members: @@ -600,11 +594,7 @@ def _get_angles_for(self, site): return angles def _get_dihedrals_for(self, site): -<<<<<<< HEAD - """Return a list of dihedrals in this Topology that the site is a part of""" -======= """return a list of the dihedrals that contain Site""" ->>>>>>> Add _get_angles_for and _get_dihedrals_for methods for a topology. Selects angles/dihedrals for a particular Site dihedrals = [] for dihedral in self.dihedrals: if site in dihedral.connection_members: From 035c32b30055985f7e74f25a3043c2735b67dddb Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 20 Jan 2021 13:26:56 -0600 Subject: [PATCH 30/62] Fix import * and add soft import for matplotlib --- gmso/formats/__init__.py | 5 ++++- gmso/formats/networkx.py | 2 -- requirements-test.txt | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 0e5313219..7e11c6f89 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,4 +3,7 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networkx import * +from .networkx import (plot_networkx_atomtypes, + plot_networkx_bonds, + plot_networkx_angles, + plot_networkx_dihedrals) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 3fe0e8be6..ee65517bd 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,9 +1,7 @@ import numpy as np -import unyt as u import matplotlib.pyplot as plt import networkx as nx -from gmso.core.topology import Topology from gmso.external.convert_networkx import to_networkx def plot_networkx_atomtypes(topology,atom_name=None): diff --git a/requirements-test.txt b/requirements-test.txt index 2411332a1..e161bb56a 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -13,3 +13,4 @@ parmed pytest-cov codecov pydantic +matplotlib From b4704409eaed5e3d8f9b0d7edb011af869a16d9c Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 20 Jan 2021 19:28:51 -0600 Subject: [PATCH 31/62] Add unit tests for selecting atomtype parameters, and soft imports for matplotlib and networkx to io.py --- gmso/formats/networkx.py | 70 +++++++++++++++++++++--------------- gmso/tests/test_networkx.py | 72 +++++++++++++++++++++++++++++++++++++ gmso/utils/io.py | 14 ++++++++ 3 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 gmso/tests/test_networkx.py diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index ee65517bd..f2a418a9b 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -190,45 +190,57 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): def highlight_bonds(networkx_graph,attribute,atom_index=None): - edge_weights={};edge_colors={} - for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' - + edge_weights, edge_colors = initialize_edge_params(networkx_graph) def_param = 1 if atom_index == None: - for node in networkx_graph.nodes: - for parameter in networkx_graph.nodes[node][attribute]: - var = attribute[:-1] + '_type' - if getattr(parameter,var) == None: - def_param = 0 - members = list(parameter.connection_members) - for i in np.arange(len(members)-1): - edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 - edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' - + edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) if def_param: print('No {} selected, and all {} typed'.format(attribute,attribute)) - elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: - node = list(networkx_graph.nodes)[atom_index] - for parameter in networkx_graph.nodes[node][attribute]: - if (node == parameter.connection_members[1] or - node == parameter.connection_members[ - len(networkx_graph.nodes[node][attribute][0].connection_members)-2]): - for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): - node1 = list(parameter.connection_members)[i+1] - node0 = list(parameter.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' + edge_weights, edge_colors = sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors) else: print('Invalid input for atom or node index') return edge_weights, edge_colors +def sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors): + node = list(networkx_graph.nodes)[atom_index] + for parameter in networkx_graph.nodes[node][attribute]: + if (node == parameter.connection_members[1] or + node == parameter.connection_members[len( + networkx_graph.nodes[node][attribute][0].connection_members)-2]): + for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): + node1 = list(parameter.connection_members)[i+1] + node0 = list(parameter.connection_members)[i] + edge = (node0,node1) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + edge = (node1,node0) + edge_weights[edge] = 5 + edge_colors[edge] = 'red' + + return edge_weights, edge_colors + +def initialize_edge_params(networkx_graph): + edge_weights={};edge_colors={} + for edge in networkx_graph.edges: + edge_weights[edge] = 1; edge_colors[edge] = 'k' + + return edge_weights, edge_colors + +def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors): + for node in networkx_graph.nodes: + for parameter in networkx_graph.nodes[node][attribute]: + var = attribute[:-1] + '_type' + if getattr(parameter,var) == None: + def_param = 0 + members = list(parameter.connection_members) + for i in np.arange(len(members)-1): + edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 + edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' + + return edge_weights, edge_colors, def_param + def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): pos={} for node in networkx_graph.nodes: diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py new file mode 100644 index 000000000..4cf74f89b --- /dev/null +++ b/gmso/tests/test_networkx.py @@ -0,0 +1,72 @@ +import unyt as u +import numpy as np +#import matplotlib.pyplot as plt +#import networkx as nx +import pytest + +from gmso.formats.networkx import (plot_networkx_atomtypes, +plot_networkx_bonds, plot_networkx_angles, plot_networkx_dihedrals, +highlight_bonds, sel_input_params, initialize_edge_params) +from gmso.tests.base_test import BaseTest +from unyt.testing import assert_allclose_units +from gmso.utils.io import import_, has_networkx, has_pyplot +from gmso.external.convert_networkx import to_networkx + +if has_networkx: + networkx = import_('networkx') + +if has_pyplot: + plt = import_('matplotlib.pyplot') + +@pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") +@pytest.mark.skipif(not has_pyplot, reason="Matplotlib.pyplot is not installed") +class TestNetworkx(BaseTest): + def test_highlight_bonds(self, typed_ethane): + list(typed_ethane.angles)[0].angle_type = None + list(typed_ethane.dihedrals)[0].dihedral_type = None + + graph = to_networkx(typed_ethane) + test_edge_weights, test_edge_colors = highlight_bonds(graph, 'angles') + nodes = list(graph.nodes) + assert test_edge_weights[nodes[0],nodes[4]] == 5 + assert test_edge_weights[nodes[4],nodes[5]] == 5 + assert test_edge_weights[nodes[0],nodes[1]] == 1 + + test_edge_weights, test_edge_colors = highlight_bonds(graph, 'dihedrals') + assert test_edge_weights[nodes[0],nodes[4]] == 5 + assert test_edge_weights[nodes[4],nodes[5]] == 5 + assert test_edge_weights[nodes[0],nodes[1]] == 5 + assert test_edge_weights[nodes[0],nodes[3]] == 1 + + + + def test_sel_input_params(self,typed_ethane): + graph = to_networkx(typed_ethane) + edge_weights, edge_colors = initialize_edge_params(graph) + test_edge_weights, test_edge_colors = sel_input_params(graph, 0, 'angles', edge_weights, edge_colors) + + assert all([a == b for a, b in zip(list(test_edge_weights.values()), [5, 5, 5, 5, 1, 1, 1])]) + assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['red', 'red', 'red', 'red', 'k', 'k', 'k'])]) + + def test_initialize_edge_params(self, typed_ethane): + graph = to_networkx(typed_ethane) + test_edge_weights, test_edge_colors = initialize_edge_params(graph) + + assert all([a == b for a, b in zip(list(test_edge_weights.values()), [1, 1, 1, 1, 1, 1, 1])]) + assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['k', 'k', 'k', 'k', 'k', 'k', 'k'])]) + + def test_plot_networkx_atomtypes(self,typed_ethane): + graph = to_networkx(typed_ethane) + fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) + test_fig, test_ax = plt.subplots(1) + + assert isinstance(fig, test_fig.__class__) + assert isinstance(ax, test_ax.__class__) + + def test_plot_networkx_bonds(self,typed_ethane): + graph = to_networkx(typed_ethane) + fig, ax = plot_networkx_bonds(typed_ethane) + test_fig, test_ax = plt.subplots(1) + + assert isinstance(fig, test_fig.__class__) + assert isinstance(ax, test_ax.__class__) diff --git a/gmso/utils/io.py b/gmso/utils/io.py index fef0d5e8c..c397948f5 100644 --- a/gmso/utils/io.py +++ b/gmso/utils/io.py @@ -126,3 +126,17 @@ def import_(module): del unit except ImportError: has_simtk_unit = False + +try: + import networkx + has_networkx = True + del networkx +except ImportError: + has_networkx = False + +try: + from matplotlib import pyplot + has_pyplot = True + del pyplot +except ImportError: + has_pyplot = False From c37e189ad00eeb52c341a0c75a5b114fce7770c1 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 21 Jan 2021 14:42:51 -0600 Subject: [PATCH 32/62] change to --- gmso/formats/networkx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index f2a418a9b..ddb55338d 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -102,7 +102,7 @@ def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): edge_colors[edge] = 'k' else: for bond in list(networkx_graph.edges.items()): - if bond[1]['connection'].bond_type == None: + if bond[1]['connection'].bond_type is None: edge_weights[bond[0]] = 5 edge_colors[bond[0]] = 'red' mia_bond_ind = 1 @@ -192,7 +192,7 @@ def plot_networkx_dihedrals(topology,center_atom_index = None): def highlight_bonds(networkx_graph,attribute,atom_index=None): edge_weights, edge_colors = initialize_edge_params(networkx_graph) def_param = 1 - if atom_index == None: + if atom_index is None: edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) if def_param: print('No {} selected, and all {} typed'.format(attribute,attribute)) @@ -232,7 +232,7 @@ def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) for node in networkx_graph.nodes: for parameter in networkx_graph.nodes[node][attribute]: var = attribute[:-1] + '_type' - if getattr(parameter,var) == None: + if getattr(parameter,var) is None: def_param = 0 members = list(parameter.connection_members) for i in np.arange(len(members)-1): From 3582c9e5463fd239717a24974e6415d440558492 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 11 Mar 2021 16:09:48 -0600 Subject: [PATCH 33/62] remove merge conflicts --- gmso/core/topology.py | 659 ------------------------------------------ 1 file changed, 659 deletions(-) delete mode 100644 gmso/core/topology.py diff --git a/gmso/core/topology.py b/gmso/core/topology.py deleted file mode 100644 index 252d7e823..000000000 --- a/gmso/core/topology.py +++ /dev/null @@ -1,659 +0,0 @@ -import warnings - -import numpy as np -import unyt as u -from boltons.setutils import IndexedSet - -from gmso.core.atom import Atom -from gmso.core.bond import Bond -from gmso.core.angle import Angle -from gmso.core.dihedral import Dihedral -from gmso.core.improper import Improper -from gmso.core.parametric_potential import ParametricPotential -from gmso.core.atom_type import AtomType -from gmso.core.bond_type import BondType -from gmso.core.angle_type import AngleType -from gmso.core.dihedral_type import DihedralType -from gmso.core.improper_type import ImproperType -from gmso.utils.connectivity import identify_connections as _identify_connections -from gmso.utils._constants import ATOM_TYPE_DICT, BOND_TYPE_DICT, ANGLE_TYPE_DICT, DIHEDRAL_TYPE_DICT, IMPROPER_TYPE_DICT -from gmso.exceptions import GMSOError - - -class Topology(object): - """A topology. - - A topology represents a chemical structure wherein lie the collection - of sites which together form a chemical structure containing connections - (gmso.Bond, gmso.Angle and gmso.Dihedral (along with their associated types). - A topology is the fundamental data structure in GMSO, from which we can gather - various information about the chemical structure and apply a forcefield - before converting the structure into a format familiar to various simulation - engines. - - Parameters - ---------- - name : str, optional, default='Topology' - A name for the Topology. - box : gmso.Box, optional, default=None - A gmso.Box object bounding the topology - - Attributes - ---------- - typed : bool - True if the topology is typed - - combining_rule : str, ['lorentz', 'geometric'] - The combining rule for the topology, can be either 'lorentz' or 'geometric' - - n_sites : int - Number of sites in the topology - - n_connections : int - Number of connections in the topology (Bonds, Angles, Dihedrals, Impropers) - - n_bonds : int - Number of bonds in the topology - - n_angles: int - Number of angles in the topology - - n_dihedrals : int - Number of dihedrals in the topology - - n_impropers : int - Number of impropers in the topology - - n_subtops : int - Number of subtopolgies in the topology - - connections : tuple of gmso.Connection objects - A collection of bonds, angles, dihedrals, and impropers in the topology - - bonds : tuple of gmso.Bond objects - A collection of bonds in the topology - - angles : tuple of gmso.Angle objects - A collection of angles in the topology - - dihedrals : tuple of gmso.Dihedral objects - A collection of dihedrals in the topology - - impropers : tuple of gmso.Improper objects - A collection of impropers in the topology - - connection_types : tuple of gmso.Potential objects - A collection of BondTypes, AngleTypes, DihedralTypes, and ImproperTypes in the topology - - atom_types : tuple of gmso.AtomType objects - A collection of AtomTypes in the topology - - bond_types : tuple of gmso.BondType objects - A collection of BondTypes in the topology - - angle_types : tuple of gmso.AngleType objects - A collection go AngleTypes in the topology - - dihedral_types : tuple of gmso.DihedralType objects - A collection of DihedralTypes in the topology - - improper_types : tuple of gmso.ImproperType objects - A collection of ImproperTypes in the topology - - atom_type_expressions : list of gmso.AtomType.expression objects - A collection of all the expressions for the AtomTypes in topology - - connection_type_expressions : list of gmso.Potential.expression objects - A collection of all the expressions for the Potential objects in the topology that represent a connection type - - bond_type_expressions : list of gmso.BondType.expression objects - A collection of all the expressions for the BondTypes in topology - - angle_type_expressions : list of gmso.AngleType.expression objects - A collection of all the expressions for the AngleTypes in topology - - dihedral_type_expressions : list of gmso.DihedralType.expression objects - A collection of all the expression for the DihedralTypes in the topology - - improper_type_expressions : list of gmso.ImproperType.expression objects - A collection of all the expression for the ImproperTypes in the topology - - See Also - -------- - gmso.SubTopology : - A topology within a topology - """ - def __init__(self, name="Topology", box=None): - - self.name = name - self._box = box - self._sites = IndexedSet() - self._typed = False - self._connections = IndexedSet() - self._bonds = IndexedSet() - self._angles = IndexedSet() - self._dihedrals = IndexedSet() - self._impropers = IndexedSet() - self._subtops = IndexedSet() - self._atom_types = {} - self._atom_types_idx = {} - self._connection_types = {} - self._bond_types = {} - self._bond_types_idx = {} - self._angle_types = {} - self._angle_types_idx = {} - self._dihedral_types = {} - self._dihedral_types_idx = {} - self._improper_types = {} - self._improper_types_idx = {} - self._combining_rule = 'lorentz' - self._set_refs = { - ATOM_TYPE_DICT: self._atom_types, - BOND_TYPE_DICT: self._bond_types, - ANGLE_TYPE_DICT: self._angle_types, - DIHEDRAL_TYPE_DICT: self._dihedral_types, - IMPROPER_TYPE_DICT: self._improper_types, - } - - self._index_refs = { - ATOM_TYPE_DICT: self._atom_types_idx, - BOND_TYPE_DICT: self._bond_types_idx, - ANGLE_TYPE_DICT: self._angle_types_idx, - DIHEDRAL_TYPE_DICT: self._dihedral_types_idx, - IMPROPER_TYPE_DICT: self._improper_types_idx - } - - self._unique_connections = {} - - - @property - def name(self): - return self._name - - @name.setter - def name(self, name): - self._name = str(name) if name else 'Topology' - - @property - def box(self): - return self._box - - @box.setter - def box(self, box): - self._box = box - - @property - def typed(self): - return self._typed - - @typed.setter - def typed(self, typed): - self._typed = typed - - @property - def combining_rule(self): - return self._combining_rule - - @combining_rule.setter - def combining_rule(self, rule): - if rule not in ['lorentz', 'geometric']: - raise GMSOError('Combining rule must be `lorentz` or `geometric`') - self._combining_rule = rule - - @property - def positions(self): - xyz = np.empty(shape=(self.n_sites, 3)) * u.nm - for i, site in enumerate(self._sites): - xyz[i, :] = site.position - return xyz - - @property - def n_sites(self): - return len(self.sites) - - @property - def n_connections(self): - return len(self.connections) - - @property - def n_bonds(self): - return len(self.bonds) - - @property - def n_angles(self): - return len(self.angles) - - @property - def n_dihedrals(self): - return len(self.dihedrals) - - @property - def n_impropers(self): - return len(self.impropers) - - @property - def subtops(self): - return self._subtops - - @property - def n_subtops(self): - return len(self._subtops) - - @property - def sites(self): - return tuple(self._sites) - - @property - def connections(self): - return tuple(self._connections) - - @property - def bonds(self): - return tuple(self._bonds) - - @property - def angles(self): - return tuple(self._angles) - - @property - def dihedrals(self): - return tuple(self._dihedrals) - - @property - def impropers(self): - return tuple(self._impropers) - - @property - def atom_types(self): - return tuple(self._atom_types.values()) - - @property - def connection_types(self): - return tuple(self._connection_types.values()) - - @property - def bond_types(self): - return tuple(self._bond_types.values()) - - @property - def angle_types(self): - return tuple(self._angle_types.values()) - - @property - def dihedral_types(self): - return tuple(self._dihedral_types.values()) - - @property - def improper_types(self): - return tuple(self._improper_types.values()) - - @property - def atom_type_expressions(self): - return list(set([atype.expression for atype in self.atom_types])) - - @property - def connection_type_expressions(self): - return list(set([contype.expression for contype in self.connection_types])) - - @property - def bond_type_expressions(self): - return list(set([btype.expression for btype in self.bond_types])) - - @property - def angle_type_expressions(self): - return list(set([atype.expression for atype in self.angle_types])) - - @property - def dihedral_type_expressions(self): - return list(set([atype.expression for atype in self.dihedral_types])) - - @property - def improper_type_expressions(self): - return list(set([atype.expression for atype in self.improper_types])) - - def add_site(self, site, update_types=True): - """Add a site to the topology - - This method will add a site to the existing topology, since - sites are stored in an indexed set, adding redundant site - will have no effect. If the update_types parameter is set to - true (default behavior), this method will also check if there - is an gmso.AtomType associated with the site and it to the - topology's AtomTypes collection. - - Parameters - ----------- - site : gmso.core.Site - Site to be added to this topology - update_types : (bool), default=True - If true, add this site's atom type to the topology's set of AtomTypes - """ - self._sites.add(site) - if update_types and site.atom_type: - site.atom_type.topology = self - if site.atom_type in self._atom_types: - site.atom_type = self._atom_types[site.atom_type] - else: - self._atom_types[site.atom_type] = site.atom_type - self._atom_types_idx[site.atom_type] = len(self._atom_types) - 1 - self.is_typed(updated=False) - - def update_sites(self): - """Update the sites of the topology. - - This method will update the sites in the topology - based on the connection members, For example- if you - add a bond to a topology, without adding the constituent - sites, this method can be called to add the sites which are the - connection members of the bond as shown below. - - >>> import gmso - >>> site1 = gmso.Site(name='MySite1') - >>> site2 = gmso.Site(name='MySite2') - >>> bond1 = gmso.Bond(name='site1-site2', connection_members=[site1, site2]) - >>> this_topology = gmso.Topology('TwoSitesTopology') - >>> this_topology.add_connection(bond1) - >>> this_topology.update_sites() - - See Also - -------- - gmso.Topology.add_site : Add a site to the topology. - gmso.Topology.add_connection : Add a Bond, an Angle or a Dihedral to the topology. - gmso.Topology.update_topology : Update the entire topology. - """ - for connection in self.connections: - for member in connection.connection_members: - if member not in self._sites: - self.add_site(member) - - def add_connection(self, connection, update_types=True): - """Add a gmso.Connection object to the topology. - - This method will add a gmso.Connection object to the - topology, it can be used to generically include any - Connection object i.e. Bond or Angle or Dihedral to - the topology. According to the type of object added, - the equivalent collection in the topology is updated. - For example- If you add a Bond, this method will update - topology.connections and topology.bonds object. Additionally, - if update_types is True (default behavior), it will also - update any Potential objects associated with the connection. - - Parameters - ---------- - connection : one of gmso.Connection, gmso.Bond, gmso.Angle, gmso.Dihedral, or gmso.Improper object - update_types : bool, default=True - If True also add any Potential object associated with connection to the - topology. - - Returns - _______ - gmso.Connection - The Connection object or equivalent Connection object that - is in the topology - """ - # Check if an equivalent connection is in the topology - equivalent_members = connection._equivalent_members_hash() - if equivalent_members in self._unique_connections: - warnings.warn('An equivalent connection already exists. ' - 'Providing the existing equivalent Connection.') - connection = self._unique_connections[equivalent_members] - - for conn_member in connection.connection_members: - if conn_member not in self.sites: - self.add_site(conn_member) - self._connections.add(connection) - self._unique_connections.update( - {equivalent_members : connection}) - if isinstance(connection, Bond): - self._bonds.add(connection) - if isinstance(connection, Angle): - self._angles.add(connection) - if isinstance(connection, Dihedral): - self._dihedrals.add(connection) - if isinstance(connection, Improper): - self._impropers.add(connection) - if update_types: - self.update_connection_types() - - return connection - - def identify_connections(self): - _identify_connections(self) - - def update_connection_types(self): - """Update the connection types based on the connection collection in the topology. - - This method looks into all the connection objects (Bonds, Angles, Dihedrals, Impropers) to - check if any Potential object (BondType, AngleType, DihedralType, ImproperType) is not in the - topology's respective collection and will add those objects there. - - See Also - -------- - gmso.Topology.update_atom_types : Update atom types in the topology. - """ - for c in self.connections: - if c.connection_type is None: - warnings.warn('Non-parametrized Connection {} detected'.format(c)) - elif not isinstance(c.connection_type, ParametricPotential): - raise GMSOError('Non-Potential {} found' - 'in Connection {}'.format(c.connection_type, c)) - elif c.connection_type not in self._connection_types: - c.connection_type.topology = self - self._connection_types[c.connection_type] = c.connection_type - if isinstance(c.connection_type, BondType): - self._bond_types[c.connection_type] = c.connection_type - self._bond_types_idx[c.connection_type] = len(self._bond_types) - 1 - if isinstance(c.connection_type, AngleType): - self._angle_types[c.connection_type] = c.connection_type - self._angle_types_idx[c.connection_type] = len(self._angle_types) - 1 - if isinstance(c.connection_type, DihedralType): - self._dihedral_types[c.connection_type] = c.connection_type - self._dihedral_types_idx[c.connection_type] = len(self._dihedral_types) - 1 - if isinstance(c.connection_type, ImproperType): - self._improper_types[c.connection_type] = c.connection_type - self._improper_types_idx[c.connection_type] = len(self._improper_types) - 1 - elif c.connection_type in self.connection_types: - if isinstance(c.connection_type, BondType): - c.connection_type = self._bond_types[c.connection_type] - if isinstance(c.connection_type, AngleType): - c.connection_type = self._angle_types[c.connection_type] - if isinstance(c.connection_type, DihedralType): - c.connection_type = self._dihedral_types[c.connection_type] - if isinstance(c.connection_type, ImproperType): - c.connection_type = self._improper_types[c.connection_type] - - def update_atom_types(self): - """Update atom types in the topology - - This method checks all the sites in the topology which have an - associated AtomType and if that AtomType is not in the topology's - AtomTypes collection, it will add it there. - - See Also: - --------- - gmso.Topology.update_connection_types : - Update the connection types based on the connection collection in the topology - """ - for site in self._sites: - if site.atom_type is None: - warnings.warn('Non-parametrized site detected {}'.format(site)) - elif not isinstance(site.atom_type, AtomType): - raise GMSOError('Non AtomType instance found in site {}'.format(site)) - elif site.atom_type not in self._atom_types: - site.atom_type.topology = self - self._atom_types[site.atom_type] = site.atom_type - self._atom_types_idx[site.atom_type] = len(self._atom_types) - 1 - elif site.atom_type in self._atom_types: - site.atom_type = self._atom_types[site.atom_type] - self.is_typed(updated=True) - - def add_subtopology(self, subtop, update=True): - """Add a sub-topology to this topology - - This methods adds a gmso.Core.SubTopology object to the topology - All the sites in this sub-topology are added to the collection of current - sites in this topology. - - Parameters - ---------- - subtop : gmso.SubTopology - The sub-topology object to be added. - update : bool, default=True - - See Also - -------- - gmso.SubTopology : A topology within a topology - """ - self._subtops.add(subtop) - subtop.parent = self - self._sites.union(subtop.sites) - if update: - self.update_topology() - - def is_typed(self, updated=False): - if not updated: - self.update_connection_types() - self.update_atom_types() - - if len(self.atom_types) > 0 or len(self.connection_types) > 0: - self._typed = True - else: - self._typed = False - return self._typed - - def update_angle_types(self): - """Uses gmso.Topology.update_connection_types to update AngleTypes in the topology. - - This method is an alias for gmso.Topology.update_connection_types. - - See Also - -------- - gmso.Topology.update_connection_types : - Update the connection types based on the connection collection in the topology. - """ - self.update_connection_types() - - def update_bond_types(self): - """Uses gmso.Topology.update_connection_types to update BondTypes in the topology. - - This method is an alias for gmso.Topology.update_connection_types. - - See Also - -------- - gmso.Topology.update_connection_types : - Update the connection types based on the connection collection in the topology. - """ - self.update_connection_types() - - def update_dihedral_types(self): - """Uses gmso.Topology.update_connection_types to update DihedralTypes in the topology. - - This method is an alias for gmso.Topology.update_connection_types. - - See Also - -------- - gmso.Topology.update_connection_types : - Update the connection types based on the connection collection in the topology. - """ - self.update_connection_types() - - def update_improper_types(self): - """Uses gmso.Topology.update_connection_types to update ImproperTypes in the topology. - - This method is an alias for gmso.Topology.update_connection_types. - - See Also - -------- - gmso.Topology.update_connection_types : - Update the connection types based on the connection collection in the topology. - """ - self.update_connection_types() - - def update_topology(self): - """Update the entire topology""" - self.update_sites() - self.update_atom_types() - self.update_connection_types() - self.is_typed(updated=True) - - def _get_bonds_for(self, site): - """return a list of the bonds that contain Site""" - bonds = [] - for bond in self.bonds: - if site in bond.connection_members: - bonds.append(bond) - return bonds - - def _get_angles_for(self, site): - """return a list of the angles that contain Site""" - angles = [] - for angle in self.angles: - if site in angle.connection_members: - angles.append(angle) - return angles - - def _get_dihedrals_for(self, site): - """return a list of the dihedrals that contain Site""" - dihedrals = [] - for dihedral in self.dihedrals: - if site in dihedral.connection_members: - dihedrals.append(dihedral) - return dihedrals - - def get_index(self, member): - """Get index of a member in the topology - - Parameters - ---------- - member : gmso Topology objects - The member to for which to return index for. - `member` can be of type gmso.Site, gmso.Bond, gmso.Angle, gmso.Dihedral, gmso.Improper, - gmso.AtomType, gmso.BondType, gmso.AngleType, gmso.DihedralType or gmso.ImproperType. - - Returns - ------- - int - The index of the member in the topology's collection objects - """ - refs = { - Atom: self._sites, - Bond: self._bonds, - Angle: self._angles, - Dihedral: self._dihedrals, - Improper: self._impropers, - AtomType: self._atom_types_idx, - BondType: self._bond_types_idx, - AngleType: self._angle_types_idx, - DihedralType: self._dihedral_types_idx, - ImproperType: self._improper_types_idx - } - - member_type = type(member) - - if member_type not in refs.keys(): - raise TypeError(f'Cannot index member of type {member_type.__name__}') - - try: - index = refs[member_type].index(member) - except AttributeError: - index = refs[member_type][member] - - return index - - def _reindex_connection_types(self, ref): - if ref not in self._index_refs: - raise GMSOError(f'cannot reindex {ref}. It should be one of ' - f'{ANGLE_TYPE_DICT}, {BOND_TYPE_DICT}, ' - f'{ANGLE_TYPE_DICT}, {DIHEDRAL_TYPE_DICT}, {IMPROPER_TYPE_DICT}') - for i, ref_member in enumerate(self._set_refs[ref].keys()): - self._index_refs[ref][ref_member] = i - - def __repr__(self): - return f"" - - def __str__(self): - return f"" From 07606fac6597bf62f476f06ed60314c9c357ed74 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 11 Mar 2021 16:27:55 -0600 Subject: [PATCH 34/62] rebase to master --- gmso/core/topology.py | 660 +++++++++++++++++++++++++++++++++++++++ gmso/formats/__init__.py | 4 - gmso/utils/io.py | 14 - requirements-test.txt | 1 - 4 files changed, 660 insertions(+), 19 deletions(-) create mode 100644 gmso/core/topology.py diff --git a/gmso/core/topology.py b/gmso/core/topology.py new file mode 100644 index 000000000..68c50b8c2 --- /dev/null +++ b/gmso/core/topology.py @@ -0,0 +1,660 @@ +import warnings + +import numpy as np +import unyt as u +from boltons.setutils import IndexedSet + +from gmso.core.atom import Atom +from gmso.core.bond import Bond +from gmso.core.angle import Angle +from gmso.core.dihedral import Dihedral +from gmso.core.improper import Improper +from gmso.core.parametric_potential import ParametricPotential +from gmso.core.atom_type import AtomType +from gmso.core.bond_type import BondType +from gmso.core.angle_type import AngleType +from gmso.core.dihedral_type import DihedralType +from gmso.core.improper_type import ImproperType +from gmso.utils.connectivity import identify_connections as _identify_connections +from gmso.utils._constants import ATOM_TYPE_DICT, BOND_TYPE_DICT, ANGLE_TYPE_DICT, DIHEDRAL_TYPE_DICT, IMPROPER_TYPE_DICT +from gmso.exceptions import GMSOError + + +class Topology(object): + """A topology. + + A topology represents a chemical structure wherein lie the collection + of sites which together form a chemical structure containing connections + (gmso.Bond, gmso.Angle and gmso.Dihedral (along with their associated types). + A topology is the fundamental data structure in GMSO, from which we can gather + various information about the chemical structure and apply a forcefield + before converting the structure into a format familiar to various simulation + engines. + + Parameters + ---------- + name : str, optional, default='Topology' + A name for the Topology. + box : gmso.Box, optional, default=None + A gmso.Box object bounding the topology + + Attributes + ---------- + typed : bool + True if the topology is typed + + combining_rule : str, ['lorentz', 'geometric'] + The combining rule for the topology, can be either 'lorentz' or 'geometric' + + n_sites : int + Number of sites in the topology + + n_connections : int + Number of connections in the topology (Bonds, Angles, Dihedrals, Impropers) + + n_bonds : int + Number of bonds in the topology + + n_angles: int + Number of angles in the topology + + n_dihedrals : int + Number of dihedrals in the topology + + n_impropers : int + Number of impropers in the topology + + n_subtops : int + Number of subtopolgies in the topology + + connections : tuple of gmso.Connection objects + A collection of bonds, angles, dihedrals, and impropers in the topology + + bonds : tuple of gmso.Bond objects + A collection of bonds in the topology + + angles : tuple of gmso.Angle objects + A collection of angles in the topology + + dihedrals : tuple of gmso.Dihedral objects + A collection of dihedrals in the topology + + impropers : tuple of gmso.Improper objects + A collection of impropers in the topology + + connection_types : tuple of gmso.Potential objects + A collection of BondTypes, AngleTypes, DihedralTypes, and ImproperTypes in the topology + + atom_types : tuple of gmso.AtomType objects + A collection of AtomTypes in the topology + + bond_types : tuple of gmso.BondType objects + A collection of BondTypes in the topology + + angle_types : tuple of gmso.AngleType objects + A collection go AngleTypes in the topology + + dihedral_types : tuple of gmso.DihedralType objects + A collection of DihedralTypes in the topology + + improper_types : tuple of gmso.ImproperType objects + A collection of ImproperTypes in the topology + + atom_type_expressions : list of gmso.AtomType.expression objects + A collection of all the expressions for the AtomTypes in topology + + connection_type_expressions : list of gmso.Potential.expression objects + A collection of all the expressions for the Potential objects in the topology that represent a connection type + + bond_type_expressions : list of gmso.BondType.expression objects + A collection of all the expressions for the BondTypes in topology + + angle_type_expressions : list of gmso.AngleType.expression objects + A collection of all the expressions for the AngleTypes in topology + + dihedral_type_expressions : list of gmso.DihedralType.expression objects + A collection of all the expression for the DihedralTypes in the topology + + improper_type_expressions : list of gmso.ImproperType.expression objects + A collection of all the expression for the ImproperTypes in the topology + + See Also + -------- + gmso.SubTopology : + A topology within a topology + """ + def __init__(self, name="Topology", box=None): + + self.name = name + self._box = box + self._sites = IndexedSet() + self._typed = False + self._connections = IndexedSet() + self._bonds = IndexedSet() + self._angles = IndexedSet() + self._dihedrals = IndexedSet() + self._impropers = IndexedSet() + self._subtops = IndexedSet() + self._atom_types = {} + self._atom_types_idx = {} + self._connection_types = {} + self._bond_types = {} + self._bond_types_idx = {} + self._angle_types = {} + self._angle_types_idx = {} + self._dihedral_types = {} + self._dihedral_types_idx = {} + self._improper_types = {} + self._improper_types_idx = {} + self._combining_rule = 'lorentz' + self._set_refs = { + ATOM_TYPE_DICT: self._atom_types, + BOND_TYPE_DICT: self._bond_types, + ANGLE_TYPE_DICT: self._angle_types, + DIHEDRAL_TYPE_DICT: self._dihedral_types, + IMPROPER_TYPE_DICT: self._improper_types, + } + + self._index_refs = { + ATOM_TYPE_DICT: self._atom_types_idx, + BOND_TYPE_DICT: self._bond_types_idx, + ANGLE_TYPE_DICT: self._angle_types_idx, + DIHEDRAL_TYPE_DICT: self._dihedral_types_idx, + IMPROPER_TYPE_DICT: self._improper_types_idx + } + + self._unique_connections = {} + + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = str(name) if name else 'Topology' + + @property + def box(self): + return self._box + + @box.setter + def box(self, box): + self._box = box + + @property + def typed(self): + return self._typed + + @typed.setter + def typed(self, typed): + self._typed = typed + + @property + def combining_rule(self): + return self._combining_rule + + @combining_rule.setter + def combining_rule(self, rule): + if rule not in ['lorentz', 'geometric']: + raise GMSOError('Combining rule must be `lorentz` or `geometric`') + self._combining_rule = rule + + @property + def positions(self): + xyz = np.empty(shape=(self.n_sites, 3)) * u.nm + for i, site in enumerate(self._sites): + xyz[i, :] = site.position + return xyz + + @property + def n_sites(self): + return len(self.sites) + + @property + def n_connections(self): + return len(self.connections) + + @property + def n_bonds(self): + return len(self.bonds) + + @property + def n_angles(self): + return len(self.angles) + + @property + def n_dihedrals(self): + return len(self.dihedrals) + + @property + def n_impropers(self): + return len(self.impropers) + + @property + def subtops(self): + return self._subtops + + @property + def n_subtops(self): + return len(self._subtops) + + @property + def sites(self): + return tuple(self._sites) + + @property + def connections(self): + return tuple(self._connections) + + @property + def bonds(self): + return tuple(self._bonds) + + @property + def angles(self): + return tuple(self._angles) + + @property + def dihedrals(self): + return tuple(self._dihedrals) + + @property + def impropers(self): + return tuple(self._impropers) + + @property + def atom_types(self): + return tuple(self._atom_types.values()) + + @property + def connection_types(self): + return tuple(self._connection_types.values()) + + @property + def bond_types(self): + return tuple(self._bond_types.values()) + + @property + def angle_types(self): + return tuple(self._angle_types.values()) + + @property + def dihedral_types(self): + return tuple(self._dihedral_types.values()) + + @property + def improper_types(self): + return tuple(self._improper_types.values()) + + @property + def atom_type_expressions(self): + return list(set([atype.expression for atype in self.atom_types])) + + @property + def connection_type_expressions(self): + return list(set([contype.expression for contype in self.connection_types])) + + @property + def bond_type_expressions(self): + return list(set([btype.expression for btype in self.bond_types])) + + @property + def angle_type_expressions(self): + return list(set([atype.expression for atype in self.angle_types])) + + @property + def dihedral_type_expressions(self): + return list(set([atype.expression for atype in self.dihedral_types])) + + @property + def improper_type_expressions(self): + return list(set([atype.expression for atype in self.improper_types])) + + def add_site(self, site, update_types=True): + """Add a site to the topology + + This method will add a site to the existing topology, since + sites are stored in an indexed set, adding redundant site + will have no effect. If the update_types parameter is set to + true (default behavior), this method will also check if there + is an gmso.AtomType associated with the site and it to the + topology's AtomTypes collection. + + Parameters + ----------- + site : gmso.core.Site + Site to be added to this topology + update_types : (bool), default=True + If true, add this site's atom type to the topology's set of AtomTypes + """ + self._sites.add(site) + if update_types and site.atom_type: + site.atom_type.topology = self + if site.atom_type in self._atom_types: + site.atom_type = self._atom_types[site.atom_type] + else: + self._atom_types[site.atom_type] = site.atom_type + self._atom_types_idx[site.atom_type] = len(self._atom_types) - 1 + self.is_typed(updated=False) + + def update_sites(self): + """Update the sites of the topology. + + This method will update the sites in the topology + based on the connection members, For example- if you + add a bond to a topology, without adding the constituent + sites, this method can be called to add the sites which are the + connection members of the bond as shown below. + + >>> import gmso + >>> site1 = gmso.Site(name='MySite1') + >>> site2 = gmso.Site(name='MySite2') + >>> bond1 = gmso.Bond(name='site1-site2', connection_members=[site1, site2]) + >>> this_topology = gmso.Topology('TwoSitesTopology') + >>> this_topology.add_connection(bond1) + >>> this_topology.update_sites() + + See Also + -------- + gmso.Topology.add_site : Add a site to the topology. + gmso.Topology.add_connection : Add a Bond, an Angle or a Dihedral to the topology. + gmso.Topology.update_topology : Update the entire topology. + """ + for connection in self.connections: + for member in connection.connection_members: + if member not in self._sites: + self.add_site(member) + + def add_connection(self, connection, update_types=True): + """Add a gmso.Connection object to the topology. + + This method will add a gmso.Connection object to the + topology, it can be used to generically include any + Connection object i.e. Bond or Angle or Dihedral to + the topology. According to the type of object added, + the equivalent collection in the topology is updated. + For example- If you add a Bond, this method will update + topology.connections and topology.bonds object. Additionally, + if update_types is True (default behavior), it will also + update any Potential objects associated with the connection. + + Parameters + ---------- + connection : one of gmso.Connection, gmso.Bond, gmso.Angle, gmso.Dihedral, or gmso.Improper object + update_types : bool, default=True + If True also add any Potential object associated with connection to the + topology. + + Returns + _______ + gmso.Connection + The Connection object or equivalent Connection object that + is in the topology + """ + # Check if an equivalent connection is in the topology + equivalent_members = connection._equivalent_members_hash() + if equivalent_members in self._unique_connections: + warnings.warn('An equivalent connection already exists. ' + 'Providing the existing equivalent Connection.') + connection = self._unique_connections[equivalent_members] + + for conn_member in connection.connection_members: + if conn_member not in self.sites: + self.add_site(conn_member) + self._connections.add(connection) + self._unique_connections.update( + {equivalent_members : connection}) + if isinstance(connection, Bond): + self._bonds.add(connection) + if isinstance(connection, Angle): + self._angles.add(connection) + if isinstance(connection, Dihedral): + self._dihedrals.add(connection) + if isinstance(connection, Improper): + self._impropers.add(connection) + if update_types: + self.update_connection_types() + + return connection + + def identify_connections(self): + _identify_connections(self) + + def update_connection_types(self): + """Update the connection types based on the connection collection in the topology. + + This method looks into all the connection objects (Bonds, Angles, Dihedrals, Impropers) to + check if any Potential object (BondType, AngleType, DihedralType, ImproperType) is not in the + topology's respective collection and will add those objects there. + + See Also + -------- + gmso.Topology.update_atom_types : Update atom types in the topology. + """ + for c in self.connections: + if c.connection_type is None: + warnings.warn('Non-parametrized Connection {} detected'.format(c)) + elif not isinstance(c.connection_type, ParametricPotential): + raise GMSOError('Non-Potential {} found' + 'in Connection {}'.format(c.connection_type, c)) + elif c.connection_type not in self._connection_types: + c.connection_type.topology = self + self._connection_types[c.connection_type] = c.connection_type + if isinstance(c.connection_type, BondType): + self._bond_types[c.connection_type] = c.connection_type + self._bond_types_idx[c.connection_type] = len(self._bond_types) - 1 + if isinstance(c.connection_type, AngleType): + self._angle_types[c.connection_type] = c.connection_type + self._angle_types_idx[c.connection_type] = len(self._angle_types) - 1 + if isinstance(c.connection_type, DihedralType): + self._dihedral_types[c.connection_type] = c.connection_type + self._dihedral_types_idx[c.connection_type] = len(self._dihedral_types) - 1 + if isinstance(c.connection_type, ImproperType): + self._improper_types[c.connection_type] = c.connection_type + self._improper_types_idx[c.connection_type] = len(self._improper_types) - 1 + elif c.connection_type in self.connection_types: + if isinstance(c.connection_type, BondType): + c.connection_type = self._bond_types[c.connection_type] + if isinstance(c.connection_type, AngleType): + c.connection_type = self._angle_types[c.connection_type] + if isinstance(c.connection_type, DihedralType): + c.connection_type = self._dihedral_types[c.connection_type] + if isinstance(c.connection_type, ImproperType): + c.connection_type = self._improper_types[c.connection_type] + + def update_atom_types(self): + """Update atom types in the topology + + This method checks all the sites in the topology which have an + associated AtomType and if that AtomType is not in the topology's + AtomTypes collection, it will add it there. + + See Also: + --------- + gmso.Topology.update_connection_types : + Update the connection types based on the connection collection in the topology + """ + for site in self._sites: + if site.atom_type is None: + warnings.warn('Non-parametrized site detected {}'.format(site)) + elif not isinstance(site.atom_type, AtomType): + raise GMSOError('Non AtomType instance found in site {}'.format(site)) + elif site.atom_type not in self._atom_types: + site.atom_type.topology = self + self._atom_types[site.atom_type] = site.atom_type + self._atom_types_idx[site.atom_type] = len(self._atom_types) - 1 + elif site.atom_type in self._atom_types: + site.atom_type = self._atom_types[site.atom_type] + self.is_typed(updated=True) + + def add_subtopology(self, subtop, update=True): + """Add a sub-topology to this topology + + This methods adds a gmso.Core.SubTopology object to the topology + All the sites in this sub-topology are added to the collection of current + sites in this topology. + + Parameters + ---------- + subtop : gmso.SubTopology + The sub-topology object to be added. + update : bool, default=True + + See Also + -------- + gmso.SubTopology : A topology within a topology + """ + self._subtops.add(subtop) + subtop.parent = self + self._sites.union(subtop.sites) + if update: + self.update_topology() + + def is_typed(self, updated=False): + if not updated: + self.update_connection_types() + self.update_atom_types() + + if len(self.atom_types) > 0 or len(self.connection_types) > 0: + self._typed = True + else: + self._typed = False + return self._typed + + def update_angle_types(self): + """Uses gmso.Topology.update_connection_types to update AngleTypes in the topology. + + This method is an alias for gmso.Topology.update_connection_types. + + See Also + -------- + gmso.Topology.update_connection_types : + Update the connection types based on the connection collection in the topology. + """ + self.update_connection_types() + + def update_bond_types(self): + """Uses gmso.Topology.update_connection_types to update BondTypes in the topology. + + This method is an alias for gmso.Topology.update_connection_types. + + See Also + -------- + gmso.Topology.update_connection_types : + Update the connection types based on the connection collection in the topology. + """ + self.update_connection_types() + + def update_dihedral_types(self): + """Uses gmso.Topology.update_connection_types to update DihedralTypes in the topology. + + This method is an alias for gmso.Topology.update_connection_types. + + See Also + -------- + gmso.Topology.update_connection_types : + Update the connection types based on the connection collection in the topology. + """ + self.update_connection_types() + + def update_improper_types(self): + """Uses gmso.Topology.update_connection_types to update ImproperTypes in the topology. + + This method is an alias for gmso.Topology.update_connection_types. + + See Also + -------- + gmso.Topology.update_connection_types : + Update the connection types based on the connection collection in the topology. + """ + self.update_connection_types() + + def update_topology(self): + """Update the entire topology""" + self.update_sites() + self.update_atom_types() + self.update_connection_types() + self.is_typed(updated=True) + + def _get_bonds_for(self, site): + """Return a list of bonds in this Topology that the site is a part of""" + bonds = [] + for bond in self.bonds: + if site in bond.connection_members: + bonds.append(bond) + return bonds + + def _get_angles_for(self, site): + """Return a list of angles in this Topology that the site is a part of""" + + angles = [] + for angle in self.angles: + if site in angle.connection_members: + angles.append(angle) + return angles + + def _get_dihedrals_for(self, site): + """Return a list of dihedrals in this Topology that the site is a part of""" + dihedrals = [] + for dihedral in self.dihedrals: + if site in dihedral.connection_members: + dihedrals.append(dihedral) + return dihedrals + + def get_index(self, member): + """Get index of a member in the topology + + Parameters + ---------- + member : gmso Topology objects + The member to for which to return index for. + `member` can be of type gmso.Site, gmso.Bond, gmso.Angle, gmso.Dihedral, gmso.Improper, + gmso.AtomType, gmso.BondType, gmso.AngleType, gmso.DihedralType or gmso.ImproperType. + + Returns + ------- + int + The index of the member in the topology's collection objects + """ + refs = { + Atom: self._sites, + Bond: self._bonds, + Angle: self._angles, + Dihedral: self._dihedrals, + Improper: self._impropers, + AtomType: self._atom_types_idx, + BondType: self._bond_types_idx, + AngleType: self._angle_types_idx, + DihedralType: self._dihedral_types_idx, + ImproperType: self._improper_types_idx + } + + member_type = type(member) + + if member_type not in refs.keys(): + raise TypeError(f'Cannot index member of type {member_type.__name__}') + + try: + index = refs[member_type].index(member) + except AttributeError: + index = refs[member_type][member] + + return index + + def _reindex_connection_types(self, ref): + if ref not in self._index_refs: + raise GMSOError(f'cannot reindex {ref}. It should be one of ' + f'{ANGLE_TYPE_DICT}, {BOND_TYPE_DICT}, ' + f'{ANGLE_TYPE_DICT}, {DIHEDRAL_TYPE_DICT}, {IMPROPER_TYPE_DICT}') + for i, ref_member in enumerate(self._set_refs[ref].keys()): + self._index_refs[ref][ref_member] = i + + def __repr__(self): + return f"" + + def __str__(self): + return f"" diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 7e11c6f89..3ea0c9f9e 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,7 +3,3 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networkx import (plot_networkx_atomtypes, - plot_networkx_bonds, - plot_networkx_angles, - plot_networkx_dihedrals) diff --git a/gmso/utils/io.py b/gmso/utils/io.py index c397948f5..fef0d5e8c 100644 --- a/gmso/utils/io.py +++ b/gmso/utils/io.py @@ -126,17 +126,3 @@ def import_(module): del unit except ImportError: has_simtk_unit = False - -try: - import networkx - has_networkx = True - del networkx -except ImportError: - has_networkx = False - -try: - from matplotlib import pyplot - has_pyplot = True - del pyplot -except ImportError: - has_pyplot = False diff --git a/requirements-test.txt b/requirements-test.txt index e161bb56a..2411332a1 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -13,4 +13,3 @@ parmed pytest-cov codecov pydantic -matplotlib From 31b8eacfc089d1db90a3f0eaef0f6ddd627e2f7e Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 11 Mar 2021 16:32:41 -0600 Subject: [PATCH 35/62] rebase to fee3df2 --- gmso/formats/networkx.py | 276 ------------------------------------ gmso/tests/test_networkx.py | 72 ---------- 2 files changed, 348 deletions(-) delete mode 100644 gmso/formats/networkx.py delete mode 100644 gmso/tests/test_networkx.py diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py deleted file mode 100644 index ddb55338d..000000000 --- a/gmso/formats/networkx.py +++ /dev/null @@ -1,276 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import networkx as nx - -from gmso.external.convert_networkx import to_networkx - -def plot_networkx_atomtypes(topology,atom_name=None): - """Get a networkx plot showing the atom types in a topology object. - - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - atom_name : The atom name which will have larger node sizes. - When drawing the networkx graph, all atoms with this name will be 3X as large. - This input will be of type string. To see what atom names are available, use - for site in topology.sites: - print(site.name) - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() - """ - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - networkx_graph = to_networkx(topology) - node_sizes = [] - for node in networkx_graph.nodes: - if node.name == atom_name: - node_sizes.append(900) - else: - node_sizes.append(300) - ax = plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = node_sizes) - - return(fig,ax) - -def plot_networkx_bonds(topology,atom_name1=None,atom_name2=None): - """Get a networkx plot showing the bonds in a topology object. - - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - atom_name1 : The atom name to of which bonds you want to have selected - When drawing the networkx graph, all bonds connected to atoms with - this name will be indicated. - This input will be of type string. To see what atom names are available, use - for site in topology.sites: - print(site.name) - If no atom_name is given, then only bonds missing bond information will be - indicated - atom_name2 : A second atom name to restrict what bonds will be selected. only bonds - between the two given atoms will be indicated. - This input will be of type string. - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() - """ - - #Color and weight edges between particular atoms. If both atom_names are none, plot missing bond types. - from gmso.external import convert_networkx - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - networkx_graph = to_networkx(topology) - - edge_weights = {} - edge_colors = {} - mia_bond_ind = 0 - if atom_name1 and atom_name2: - for edge in networkx_graph.edges: - if edge[0].name == atom_name1 and edge[1].name == atom_name2: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - elif edge[0].name == atom_name2 and edge[1].name == atom_name1: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - else: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - elif atom_name1: - for edge in networkx_graph.edges: - if edge[0].name == atom_name1 or edge[1].name == atom_name1: - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - else: - edge_weights[edge] = 1 - edge_colors[edge] = 'k' - else: - for bond in list(networkx_graph.edges.items()): - if bond[1]['connection'].bond_type is None: - edge_weights[bond[0]] = 5 - edge_colors[bond[0]] = 'red' - mia_bond_ind = 1 - else: - edge_weights[bond[0]] = 1 - edge_colors[bond[0]] = 'k' - if not mia_bond_ind: - print('All bonds are typed') - - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) - return fig, ax - - -def plot_networkx_angles(topology,center_atom_index = None): - """Get a networkx plot showing the angles in a topology object. - - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the angle types - that have been parameterized. - center_atom_index : The central atom to of which angles you want to have selected - When drawing the networkx graph, all angles connected to atoms with - this index will be indicated. - This input will be of type int. To see what atoms correspond to these indices, see - gmso.formats.networkx.plot_networkx_atomtypes - If no atom_name is given, then only atoms missing angle information will be - indicated - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to a networkx drawn figure. This output can be - shown using - matplotlib.pyplot.show() - """ - networkx_graph = to_networkx(topology) - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - - edge_weights, edge_colors = highlight_bonds(networkx_graph,'angles',atom_index=center_atom_index) - - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) - - return(fig,ax) - - -def plot_networkx_dihedrals(topology,center_atom_index = None): - """Get a networkx plot showing the dihedrals in a topology object. - - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the dihedral types - that have been parameterized. - center_atom_index : The second or third atom of the dihedrals you want to have selected - When drawing the networkx graph, all dihedrals connected to atoms with - this index will be indicated. - This input will be of type int. To see what atoms correspond to these indices, see - gmso.formats.networkx.plot_networkx_atomtypes - If no atom_name is given, then only atoms missing dihedral information will be - indicated - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() - """ - networkx_graph = to_networkx(topology) - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - - edge_weights, edge_colors = highlight_bonds(networkx_graph,'dihedrals',atom_index=center_atom_index) - ax = plot_nodes(networkx_graph,ax,edge_weights,edge_colors) - - return(fig,ax) - - -def highlight_bonds(networkx_graph,attribute,atom_index=None): - edge_weights, edge_colors = initialize_edge_params(networkx_graph) - def_param = 1 - if atom_index is None: - edge_weights, edge_members, def_param = sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors) - if def_param: - print('No {} selected, and all {} typed'.format(attribute,attribute)) - elif isinstance(atom_index,int) and len(list(networkx_graph.nodes)) > atom_index: - edge_weights, edge_colors = sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors) - else: - print('Invalid input for atom or node index') - - return edge_weights, edge_colors - -def sel_input_params(networkx_graph,atom_index,attribute,edge_weights,edge_colors): - node = list(networkx_graph.nodes)[atom_index] - for parameter in networkx_graph.nodes[node][attribute]: - if (node == parameter.connection_members[1] or - node == parameter.connection_members[len( - networkx_graph.nodes[node][attribute][0].connection_members)-2]): - for i in np.arange(len(networkx_graph.nodes[node][attribute][0].connection_members)-1): - node1 = list(parameter.connection_members)[i+1] - node0 = list(parameter.connection_members)[i] - edge = (node0,node1) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - edge = (node1,node0) - edge_weights[edge] = 5 - edge_colors[edge] = 'red' - - return edge_weights, edge_colors - -def initialize_edge_params(networkx_graph): - edge_weights={};edge_colors={} - for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' - - return edge_weights, edge_colors - -def sel_miss_params(networkx_graph,attribute,def_param,edge_weights,edge_colors): - for node in networkx_graph.nodes: - for parameter in networkx_graph.nodes[node][attribute]: - var = attribute[:-1] + '_type' - if getattr(parameter,var) is None: - def_param = 0 - members = list(parameter.connection_members) - for i in np.arange(len(members)-1): - edge_weights[(members[i],members[i+1])] = 5; edge_weights[(members[i+1],members[i])] = 5 - edge_colors[(members[i],members[i+1])] = 'red'; edge_colors[(members[i+1],members[i])] = 'red' - - return edge_weights, edge_colors, def_param - -def plot_nodes(networkx_graph,ax,edge_weights=None,edge_colors=None,node_sizes = None): - pos={} - for node in networkx_graph.nodes: - pos[node] = node.position.value[0:2] - - layout = nx.drawing.layout.spring_layout(networkx_graph,k=.5,pos=pos) - - node_color_dict = {'C':'grey','H':'silver','O':'red','N':'blue','Cl':'green'} - node_colors = [] - for node in networkx_graph.nodes: - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append('black') - - if node_sizes: - nx.draw(networkx_graph,layout,ax,node_color=node_colors,node_size=node_sizes) - else: - nx.draw(networkx_graph,layout,ax,node_color=node_colors, - width=list(edge_weights.values()),edge_color=list(edge_colors.values())) - labels = {} - i=0 - for node in list(networkx_graph.nodes()): - node.label = str(i) + ': ' + node.name + '\n' + node.atom_type.name - labels[node] = node.label - i+=1 - - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - nx.draw_networkx_labels(networkx_graph,layout,labels,horizontalalignment='left') - ax.margins(.3,.3) - - return ax diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py deleted file mode 100644 index 4cf74f89b..000000000 --- a/gmso/tests/test_networkx.py +++ /dev/null @@ -1,72 +0,0 @@ -import unyt as u -import numpy as np -#import matplotlib.pyplot as plt -#import networkx as nx -import pytest - -from gmso.formats.networkx import (plot_networkx_atomtypes, -plot_networkx_bonds, plot_networkx_angles, plot_networkx_dihedrals, -highlight_bonds, sel_input_params, initialize_edge_params) -from gmso.tests.base_test import BaseTest -from unyt.testing import assert_allclose_units -from gmso.utils.io import import_, has_networkx, has_pyplot -from gmso.external.convert_networkx import to_networkx - -if has_networkx: - networkx = import_('networkx') - -if has_pyplot: - plt = import_('matplotlib.pyplot') - -@pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") -@pytest.mark.skipif(not has_pyplot, reason="Matplotlib.pyplot is not installed") -class TestNetworkx(BaseTest): - def test_highlight_bonds(self, typed_ethane): - list(typed_ethane.angles)[0].angle_type = None - list(typed_ethane.dihedrals)[0].dihedral_type = None - - graph = to_networkx(typed_ethane) - test_edge_weights, test_edge_colors = highlight_bonds(graph, 'angles') - nodes = list(graph.nodes) - assert test_edge_weights[nodes[0],nodes[4]] == 5 - assert test_edge_weights[nodes[4],nodes[5]] == 5 - assert test_edge_weights[nodes[0],nodes[1]] == 1 - - test_edge_weights, test_edge_colors = highlight_bonds(graph, 'dihedrals') - assert test_edge_weights[nodes[0],nodes[4]] == 5 - assert test_edge_weights[nodes[4],nodes[5]] == 5 - assert test_edge_weights[nodes[0],nodes[1]] == 5 - assert test_edge_weights[nodes[0],nodes[3]] == 1 - - - - def test_sel_input_params(self,typed_ethane): - graph = to_networkx(typed_ethane) - edge_weights, edge_colors = initialize_edge_params(graph) - test_edge_weights, test_edge_colors = sel_input_params(graph, 0, 'angles', edge_weights, edge_colors) - - assert all([a == b for a, b in zip(list(test_edge_weights.values()), [5, 5, 5, 5, 1, 1, 1])]) - assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['red', 'red', 'red', 'red', 'k', 'k', 'k'])]) - - def test_initialize_edge_params(self, typed_ethane): - graph = to_networkx(typed_ethane) - test_edge_weights, test_edge_colors = initialize_edge_params(graph) - - assert all([a == b for a, b in zip(list(test_edge_weights.values()), [1, 1, 1, 1, 1, 1, 1])]) - assert all([a == b for a, b in zip(list(test_edge_colors.values()), ['k', 'k', 'k', 'k', 'k', 'k', 'k'])]) - - def test_plot_networkx_atomtypes(self,typed_ethane): - graph = to_networkx(typed_ethane) - fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) - test_fig, test_ax = plt.subplots(1) - - assert isinstance(fig, test_fig.__class__) - assert isinstance(ax, test_ax.__class__) - - def test_plot_networkx_bonds(self,typed_ethane): - graph = to_networkx(typed_ethane) - fig, ax = plot_networkx_bonds(typed_ethane) - test_fig, test_ax = plt.subplots(1) - - assert isinstance(fig, test_fig.__class__) - assert isinstance(ax, test_ax.__class__) From 29f9ed459382e4b5f6b4c575d11378412d971db4 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 15 Mar 2021 15:17:48 -0500 Subject: [PATCH 36/62] Add tests for select_params_on_networkx and select_angles_from_sites --- gmso/tests/test_networkx.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 98750a45f..164be5fc6 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -5,7 +5,8 @@ show_parameter_values, interactive_networkx_atomtypes, interactive_networkx_bonds, interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, get_edges, -report_parameter_expression, report_bond_parameters, return_labels_for_nodes) +report_parameter_expression, report_bond_parameters, return_labels_for_nodes, +select_angles_from_sites) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest @@ -51,7 +52,7 @@ def test_plot_networkx_bonds(self,typed_ethane): assert isinstance(fig, test_fig.__class__) assert isinstance(ax, test_ax.__class__) - def test_select_params_on_networkx(self,typed_ethane): + def test_select_params_on_networkx(self,typed_ethane,capsys): graph = to_networkx(typed_ethane) assert len(select_params_on_networkx(graph,[None,None,None,None])) == 0 assert len(select_params_on_networkx(graph,['C','H','H'])) == 1 @@ -61,6 +62,15 @@ def test_select_params_on_networkx(self,typed_ethane): assert len(select_params_on_networkx(graph,['C',None,None])) == 3 assert len(select_params_on_networkx(graph,['C','C','H', None])) == 1 assert len(select_params_on_networkx(graph,['C','C', None, None])) == 1 + for node, angles in graph.nodes(data='angles'): + for angle in angles: + angle.angle_type = None + select_params_on_networkx(graph,['C','C','H']) + for node, angles in graph.nodes(data='angles'): + angles = None + select_params_on_networkx(graph,['C','C','H']) + captured, err = capsys.readouterr() + assert isinstance(err,str) def test__get_formatted_atom_types_names_for(self,typed_ethane): graph = to_networkx(typed_ethane) @@ -70,6 +80,8 @@ def test__get_formatted_atom_types_names_for(self,typed_ethane): def test_get_networkx_edges(self,typed_ethane,capsys): assert len(get_networkx_edges([list(typed_ethane.dihedrals[0].connection_members)])) == 6 assert len(get_networkx_edges([list(typed_ethane.angles[0].connection_members)])) == 4 + with pytest.raises(ValueError): + assert get_networkx_edges(['C','C']) def test_identify_labels(self,typed_ethane): graph = to_networkx(typed_ethane) @@ -157,3 +169,11 @@ def test_return_labels_for_nodes(self,typed_ethane): assert len(return_labels_for_nodes(graph.nodes,['atom_type.name','charge','positions'])) == 8 assert list(return_labels_for_nodes(graph.nodes,['atom_type.error']).values())[0][-8:] == 'NoneType' assert list(return_labels_for_nodes(graph.nodes,['error']).values())[0][-8:] == 'NoneType' + + def test_select_angles_from_sites(self,typed_ethane,capsys): + graph = to_networkx(typed_ethane) + select_angles_from_sites(graph, typed_ethane, Atom1='C', Atom2='H', Atom3='C') + select_angles_from_sites(graph, typed_ethane, Atom1='O', Atom2='H', Atom3='C') + captured, err = capsys.readouterr() + assert isinstance(err,str) + From 1a8bbf87ffe27012318a7f992515cf1c9d27c69f Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 15 Mar 2021 18:37:10 -0500 Subject: [PATCH 37/62] unit tests for select_parameters_on_networkx missing angles and dihedrals --- gmso/tests/test_networkx.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 164be5fc6..e0780dc10 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -62,13 +62,22 @@ def test_select_params_on_networkx(self,typed_ethane,capsys): assert len(select_params_on_networkx(graph,['C',None,None])) == 3 assert len(select_params_on_networkx(graph,['C','C','H', None])) == 1 assert len(select_params_on_networkx(graph,['C','C', None, None])) == 1 - for node, angles in graph.nodes(data='angles'): - for angle in angles: - angle.angle_type = None + for angle in typed_ethane.angles: + angle.angle_type = None + graph = to_networkx(typed_ethane) + select_params_on_networkx(graph,['C','C','H']) + for angle in typed_ethane.angles: + angle = None + graph = to_networkx(typed_ethane) + select_params_on_networkx(graph,['C','C','H']) + for dihedral in typed_ethane.dihedrals: + dihedral.dihedral_type = None + graph = to_networkx(typed_ethane) + select_params_on_networkx(graph,['C','C','H']) + for dihedral in typed_ethane.dihedrals: + dihedral = None + graph = to_networkx(typed_ethane) select_params_on_networkx(graph,['C','C','H']) - for node, angles in graph.nodes(data='angles'): - angles = None - select_params_on_networkx(graph,['C','C','H']) captured, err = capsys.readouterr() assert isinstance(err,str) @@ -115,6 +124,7 @@ def test_interactive_networkx_dihedrals(self,typed_ethane,capsys): def test_select_dihedrals_from_sites(self,typed_ethane,capsys): graph = to_networkx(typed_ethane) select_dihedrals_from_sites(graph,typed_ethane) + select_dihedrals_from_sites(graph, top, 'C', 'C', 'H', 'H') captured,err = capsys.readouterr() assert isinstance(err,str) From 2b245ce4e0d90d5e95e25aeaf33b65d631d0ef26 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 18 Mar 2021 12:59:07 -0500 Subject: [PATCH 38/62] remove interact fixtures to improve ease for writing unit tests --- gmso/formats/networkx.py | 67 +++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index bbf61517e..29ea0f9ab 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -146,35 +146,52 @@ def interactive_networkx_bonds(topology, additional_labels = None): names_tuple.append(('All Sites',None)) for name in site_names: names_tuple.append((name,name)) - - # Call recursive interacts. The top level determines what bonds can be selected - @interact - def call_interactive_sites(Atom1 = names_tuple, Atom2 = names_tuple): - list_of_edges = get_edges(networkx_graph,Atom1,Atom2) - if list_of_edges: - # Call the second level, which takes the selected bonds and shows them on the figure - @interact - def select_edges(list_of_bonds = list_of_edges): - - plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels, - list_of_bonds = list_of_bonds) - - # The final level prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value = False, description = 'Show parameters') - @interact(w=checkbox) - def show_bond_info(w): - if w: - report_bond_parameters(topology, list_of_bonds) - else: - #TODO: Should be able to remove this blank print statement so that deselecting the - #checkbox removes the listed parameters. - print('') + atom_selection = [] + descriptions = ["Atom1 (req)", "Atom2 (opt)"] + for i in [0, 1]: + atom_selection.append(widgets.Dropdown( + options = names_tuple, + layout = widgets.Layout(width = '30%'), + style = dict(description_width='initial'), + description = descriptions[i]) + ) + interact(call_interactive_sites, + Atom1 = atom_selection[0], + Atom2 = atom_selection[1], + list_of_labels = fixed(list_of_labels), + networkx_graph = fixed(networkx_graph), + topology = fixed(topology)) - else: - plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels) + return + +def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): + list_of_edges = get_edges(networkx_graph,Atom1,Atom2) + if list_of_edges: + # Call the second level, which takes the selected bonds and shows them on the figure + interact(select_edges, list_of_bonds = list_of_edges, list_of_labels = fixed(list_of_labels), + networkx_graph = fixed(networkx_graph), topology = fixed(topology)) + else: + plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels) return + +def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): + plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels, + list_of_bonds = list_of_bonds) + # The final level prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value = False, description = 'Show parameters') + interact(show_bond_info, w=checkbox, topology = fixed(topology), list_of_bonds = fixed(list_of_bonds)) + + +def show_bond_info(w, topology, list_of_bonds): + if w: + report_bond_parameters(topology, list_of_bonds) + else: + #TODO: Should be able to remove this blank print statement so that deselecting the + #checkbox removes the listed parameters. + print('') + def interactive_networkx_angles(topology): """Get an interactive networkx plot showing the angle types of a topology object. From 857ddc0d14a160abb8a4854b0768b6adc6bd2895 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 18 Mar 2021 13:29:29 -0500 Subject: [PATCH 39/62] unit tests for call_interactive_sites --- gmso/tests/test_networkx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index e0780dc10..b65f854b0 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -6,7 +6,7 @@ interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, get_edges, report_parameter_expression, report_bond_parameters, return_labels_for_nodes, -select_angles_from_sites) +select_angles_from_sites, call_interactive_sites) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest @@ -187,3 +187,6 @@ def test_select_angles_from_sites(self,typed_ethane,capsys): captured, err = capsys.readouterr() assert isinstance(err,str) + def test_call_interactive_sites(self,typed_ethane): + graph = to_networkx(typed_ethane) + assert not call_interactive_sites('C','C',graph,typed_ethane, list_of_labels = ['name']) From 353eeffff5f08c0786761e155ed3579c63d8a0cf Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 25 Mar 2021 07:35:30 -0500 Subject: [PATCH 40/62] add tests for select_edges and show_bond_info --- gmso/formats/networkx.py | 3 +++ gmso/tests/test_networkx.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 29ea0f9ab..4f9afb112 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -183,6 +183,7 @@ def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): checkbox = widgets.Checkbox(value = False, description = 'Show parameters') interact(show_bond_info, w=checkbox, topology = fixed(topology), list_of_bonds = fixed(list_of_bonds)) + return def show_bond_info(w, topology, list_of_bonds): if w: @@ -192,6 +193,8 @@ def show_bond_info(w, topology, list_of_bonds): #checkbox removes the listed parameters. print('') + return + def interactive_networkx_angles(topology): """Get an interactive networkx plot showing the angle types of a topology object. diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index b65f854b0..b39985368 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -6,7 +6,7 @@ interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, get_edges, report_parameter_expression, report_bond_parameters, return_labels_for_nodes, -select_angles_from_sites, call_interactive_sites) +select_angles_from_sites, call_interactive_sites, select_edges, show_bond_info) from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest @@ -190,3 +190,13 @@ def test_select_angles_from_sites(self,typed_ethane,capsys): def test_call_interactive_sites(self,typed_ethane): graph = to_networkx(typed_ethane) assert not call_interactive_sites('C','C',graph,typed_ethane, list_of_labels = ['name']) + + def test_select_edges(self,typed_ethane): + graph = to_networkx(typed_ethane) + list_of_edges = get_edges(graph,'C','C') + assert not select_edges(graph, typed_ethane, list_of_edges, ['name']) + + def test_show_bond_info(self,typed_ethane): + graph = to_networkx(typed_ethane) + list_of_edges = get_edges(graph,'C','C') + assert not show_bond_info(True,typed_ethane,list_of_edges) From 303cae7b6a0448058fb6ea0326a3c5cfd6f8043c Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 25 Mar 2021 08:34:22 -0500 Subject: [PATCH 41/62] add tests to call_interactive sites --- gmso/tests/test_networkx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index b39985368..3034b605e 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -190,6 +190,7 @@ def test_select_angles_from_sites(self,typed_ethane,capsys): def test_call_interactive_sites(self,typed_ethane): graph = to_networkx(typed_ethane) assert not call_interactive_sites('C','C',graph,typed_ethane, list_of_labels = ['name']) + assert not call_interactive_sites('C','O',graph,typed_ethane, list_of_labels = ['name']) def test_select_edges(self,typed_ethane): graph = to_networkx(typed_ethane) @@ -200,3 +201,4 @@ def test_show_bond_info(self,typed_ethane): graph = to_networkx(typed_ethane) list_of_edges = get_edges(graph,'C','C') assert not show_bond_info(True,typed_ethane,list_of_edges) + assert not show_bond_info(False, typed_ethane, list_of_edges) From 55f1aa03e3b85f1b10493399eb677e59b320a7a2 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Fri, 2 Apr 2021 13:35:00 -0500 Subject: [PATCH 42/62] apply black to clean up code formatting --- gmso/formats/networkx.py | 732 +++++++++++++++++++++++---------------- 1 file changed, 432 insertions(+), 300 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 4f9afb112..0b82668f2 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -7,6 +7,7 @@ from gmso.external.convert_networkx import to_networkx + def plot_networkx_params(networkx_graph, list_of_edges): """Get a networkx plot showing the specified edges in a networkx graph object. @@ -28,16 +29,16 @@ def plot_networkx_params(networkx_graph, list_of_edges): shown using matplotlib.pyplot.show() """ - fig,ax = plt.subplots(1, 1, figsize=(8,8)) + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) - ax = plot_networkx_nodes(networkx_graph, ax, - edge_weights = edge_weights, - edge_colors = edge_colors - ) + ax = plot_networkx_nodes( + networkx_graph, ax, edge_weights=edge_weights, edge_colors=edge_colors + ) + + return (fig, ax) - return(fig,ax) -def interactive_networkx_atomtypes(topology, list_of_labels = None): +def interactive_networkx_atomtypes(topology, list_of_labels=None): """Get an interactive networkx plot showing the atom types of a topology object. Parameters @@ -46,7 +47,7 @@ def interactive_networkx_atomtypes(topology, list_of_labels = None): This should be a gmso topology object that you want to visualize the atom types that have been parameterized. list_of_labels : Additional labels you would like to have on the plot. - Default labels are ['atom_type.name','charge','mass','element','label','position']. + Default labels are ['atom_type.name','charge','mass','element','label','position']. Any additonal labels can be appended from the list of: `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` @@ -56,7 +57,7 @@ def interactive_networkx_atomtypes(topology, list_of_labels = None): Two dropdown options, corresponding to: Label = What labels are shown on the networkx graph Atom_Name = Which sites will show these labels - + matplotlib.pyplot.figure The drawn networkx plot of the topology on a matplotlib editable figures @@ -65,9 +66,9 @@ def interactive_networkx_atomtypes(topology, list_of_labels = None): shown using matplotlib.pyplot.show() """ - - networkx_graph = to_networkx(topology) - #get a unique list of site names + + networkx_graph = to_networkx(topology) + # get a unique list of site names site_names = [] for node in networkx_graph.nodes: site_names.append(node.name) @@ -75,26 +76,27 @@ def interactive_networkx_atomtypes(topology, list_of_labels = None): # create a tuple of selectable names for each site names_tuple = [] - names_tuple.append(('All Sites',None)) + names_tuple.append(("All Sites", None)) for name in site_names: - names_tuple.append((name,name)) - - #Create list of labels to put on plot + names_tuple.append((name, name)) + + # Create list of labels to put on plot if not list_of_labels: list_of_labels = [] - base_list = ['atom_type.name','charge','mass','element','label','position'] + base_list = ["atom_type.name", "charge", "mass", "element", "label", "position"] list_of_labels += base_list - + @interact - def get_interactive_sites(Label = list_of_labels, Atom_Name = names_tuple): + def get_interactive_sites(Label=list_of_labels, Atom_Name=names_tuple): # Plot atom types for the widget inputs plot_networkx_atomtypes(topology, Atom_Name, [Label]) - + return return -def interactive_networkx_bonds(topology, additional_labels = None): + +def interactive_networkx_bonds(topology, additional_labels=None): """Get an interactive networkx plot showing the bond types of a topology object. Parameters @@ -103,7 +105,7 @@ def interactive_networkx_bonds(topology, additional_labels = None): This should be a gmso topology object that you want to visualize the atom types that have been parameterized. additonal_labels : Labels at each site to be included on the plot. - Default labels are ['atom_type.name']. + Default labels are ['atom_type.name']. Any additonal labels can be appended from the list of: `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` @@ -115,9 +117,9 @@ def interactive_networkx_bonds(topology, additional_labels = None): atoms, then only bonds with missing types will be shown. Atom2 = A second site for which bonds will be listed. With both specified only bonds between Atom1 and Atom2 will be shown. - list_of_bonds = The bonds that can be selected corresponding to the Atom1 and Atom2 + list_of_bonds = The bonds that can be selected corresponding to the Atom1 and Atom2 selections in the two above dropdown menus. - + matplotlib.pyplot.figure The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines indicate which bonds have been selected. @@ -128,13 +130,13 @@ def interactive_networkx_bonds(topology, additional_labels = None): below the figure. """ networkx_graph = to_networkx(topology) - + # Create a list of labels to go on the nodes if not additional_labels: additional_labels = [] - base_list = ['atom_type.name'] + base_list = ["atom_type.name"] list_of_labels = base_list + additional_labels - + # Create list of nodes to plot site_names = [] for node in networkx_graph.nodes: @@ -143,58 +145,76 @@ def interactive_networkx_bonds(topology, additional_labels = None): # Create a tuple of keys for each selected site names_tuple = [] - names_tuple.append(('All Sites',None)) + names_tuple.append(("All Sites", None)) for name in site_names: - names_tuple.append((name,name)) - atom_selection = [] - descriptions = ["Atom1 (req)", "Atom2 (opt)"] + names_tuple.append((name, name)) + atom_selection = [] + descriptions = ["Atom1 (req)", "Atom2 (opt)"] for i in [0, 1]: - atom_selection.append(widgets.Dropdown( - options = names_tuple, - layout = widgets.Layout(width = '30%'), - style = dict(description_width='initial'), - description = descriptions[i]) - ) - interact(call_interactive_sites, - Atom1 = atom_selection[0], - Atom2 = atom_selection[1], - list_of_labels = fixed(list_of_labels), - networkx_graph = fixed(networkx_graph), - topology = fixed(topology)) + atom_selection.append( + widgets.Dropdown( + options=names_tuple, + layout=widgets.Layout(width="30%"), + style=dict(description_width="initial"), + description=descriptions[i], + ) + ) + interact( + call_interactive_sites, + Atom1=atom_selection[0], + Atom2=atom_selection[1], + list_of_labels=fixed(list_of_labels), + networkx_graph=fixed(networkx_graph), + topology=fixed(topology), + ) return -def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): - list_of_edges = get_edges(networkx_graph,Atom1,Atom2) + +def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): + list_of_edges = get_edges(networkx_graph, Atom1, Atom2) if list_of_edges: # Call the second level, which takes the selected bonds and shows them on the figure - interact(select_edges, list_of_bonds = list_of_edges, list_of_labels = fixed(list_of_labels), - networkx_graph = fixed(networkx_graph), topology = fixed(topology)) + interact( + select_edges, + list_of_bonds=list_of_edges, + list_of_labels=fixed(list_of_labels), + networkx_graph=fixed(networkx_graph), + topology=fixed(topology), + ) else: - plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels) + plot_networkx_bonds(networkx_graph, list_of_labels=list_of_labels) return def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): - plot_networkx_bonds(networkx_graph, list_of_labels = list_of_labels, - list_of_bonds = list_of_bonds) + plot_networkx_bonds( + networkx_graph, list_of_labels=list_of_labels, list_of_bonds=list_of_bonds + ) # The final level prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value = False, description = 'Show parameters') - interact(show_bond_info, w=checkbox, topology = fixed(topology), list_of_bonds = fixed(list_of_bonds)) + checkbox = widgets.Checkbox(value=False, description="Show parameters") + interact( + show_bond_info, + w=checkbox, + topology=fixed(topology), + list_of_bonds=fixed(list_of_bonds), + ) return + def show_bond_info(w, topology, list_of_bonds): if w: report_bond_parameters(topology, list_of_bonds) else: - #TODO: Should be able to remove this blank print statement so that deselecting the - #checkbox removes the listed parameters. - print('') + # TODO: Should be able to remove this blank print statement so that deselecting the + # checkbox removes the listed parameters. + print("") return + def interactive_networkx_angles(topology): """Get an interactive networkx plot showing the angle types of a topology object. @@ -211,14 +231,14 @@ def interactive_networkx_angles(topology): Central Atom1 = The central site for which the angle can be visualized. If it is not specified, missing angles will be shown. Atom2 = An optional atom that will specify one end of the angle. - Atom3 = An optional atom that will specify the second end of the angle. Atom2 + Atom3 = An optional atom that will specify the second end of the angle. Atom2 must be selected first. - Selected Angle = The dropdown will show all angles that match the above site + Selected Angle = The dropdown will show all angles that match the above site criteria. Angles are listed by three characterstic atomtypes. Multiple angles - may satisfy the selected angle, and so more than three edges may be selected + may satisfy the selected angle, and so more than three edges may be selected on the plot. Use the atomtype definitions to verify the correct angles. - - + + matplotlib.pyplot.figure The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines indicate which angles have been selected. @@ -229,7 +249,7 @@ def interactive_networkx_angles(topology): below the figure. """ networkx_graph = to_networkx(topology) - + # Create list of nodes to plot site_names = [] for node in networkx_graph.nodes: @@ -238,47 +258,55 @@ def interactive_networkx_angles(topology): # Create a tuple of keys for each selected site names_tuple = [] - names_tuple.append(('All Sites',None)) + names_tuple.append(("All Sites", None)) for name in site_names: - names_tuple.append((name,name)) - + names_tuple.append((name, name)) + # Call recursive interacts. The top level determines what bonds can be selected atom_selection = [] - descriptions = ['Central Atom1 (req)', 'Atom2 (opt)', 'Atom3 (opt)'] + descriptions = ["Central Atom1 (req)", "Atom2 (opt)", "Atom3 (opt)"] for i in [0, 1, 2]: - atom_selection.append(widgets.Dropdown( - options = names_tuple, - layout = widgets.Layout(width = '30%'), - style = dict(description_width='initial'), - description = descriptions[i] - ) - ) - interact(select_angles_from_sites, - networkx_graph = fixed(networkx_graph), - top = fixed(topology), - Atom1 = atom_selection[0], - Atom2 = atom_selection[1], - Atom3 = atom_selection[2], + atom_selection.append( + widgets.Dropdown( + options=names_tuple, + layout=widgets.Layout(width="30%"), + style=dict(description_width="initial"), + description=descriptions[i], ) + ) + interact( + select_angles_from_sites, + networkx_graph=fixed(networkx_graph), + top=fixed(topology), + Atom1=atom_selection[0], + Atom2=atom_selection[1], + Atom3=atom_selection[2], + ) return + def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): - params_list = select_params_on_networkx(networkx_graph,[Atom1,Atom2,Atom3]) + params_list = select_params_on_networkx(networkx_graph, [Atom1, Atom2, Atom3]) if params_list: - edges_widget = widgets.Dropdown(options = params_list, - layout = widgets.Layout(width = '60%'), - style = dict(description_width = 'initial'), - description = "Selected Edge" - ) - interact(select_edges_on_networkx, networkx_graph = fixed(networkx_graph), - top = fixed(top), list_of_params = edges_widget - ) + edges_widget = widgets.Dropdown( + options=params_list, + layout=widgets.Layout(width="60%"), + style=dict(description_width="initial"), + description="Selected Edge", + ) + interact( + select_edges_on_networkx, + networkx_graph=fixed(networkx_graph), + top=fixed(top), + list_of_params=edges_widget, + ) else: - plot_networkx_params(networkx_graph, list_of_edges = []) - + plot_networkx_params(networkx_graph, list_of_edges=[]) + return + def interactive_networkx_dihedrals(topology): """Get an interactive networkx plot showing the dihedral types of a topology object. @@ -295,17 +323,17 @@ def interactive_networkx_dihedrals(topology): Central Atom1 = One of the two central sites for which dihedrals can be visualized. If it is not specified, missing dihedrals will be shown. Central Atom2 = The second central site to select dihedrals. All dihedrals that - contain these two atom sites in their center will be listed. If it is not + contain these two atom sites in their center will be listed. If it is not specified, missing dihedrals will be shown. Atom3 = An optional atom that will specify one end of the dihedral. - Atom4 = An optional atom that will specify the second end of the dihedral. Atom3 + Atom4 = An optional atom that will specify the second end of the dihedral. Atom3 must be selected. - Selected Dihedral = The dropdown will show all dihedrals that match the above site + Selected Dihedral = The dropdown will show all dihedrals that match the above site criteria. Dihedrals are listed by four characterstic atomtypes. Multiple dihedrals - may satisfy the selected dihedral, and so more than four edges may be selected + may satisfy the selected dihedral, and so more than four edges may be selected on the plot. Use the atomtype definitions to verify the correct dihedrals. - - + + matplotlib.pyplot.figure The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines indicate which dihedrals have been selected. @@ -316,7 +344,7 @@ def interactive_networkx_dihedrals(topology): below the figure. """ networkx_graph = to_networkx(topology) - + # Create list of nodes to plot site_names = [] for node in networkx_graph.nodes: @@ -325,142 +353,156 @@ def interactive_networkx_dihedrals(topology): # Create a tuple of keys for each selected site names_tuple = [] - names_tuple.append(('All Sites',None)) + names_tuple.append(("All Sites", None)) for name in site_names: - names_tuple.append((name,name)) - + names_tuple.append((name, name)) + # Call recursive interacts. The top level determines what bonds can be selected atom_selection = [] - descriptions = ['Central Atom1 (req)', 'Central Atom2 (req)', 'Atom3 (opt)', 'Atom4 (opt)'] + descriptions = [ + "Central Atom1 (req)", + "Central Atom2 (req)", + "Atom3 (opt)", + "Atom4 (opt)", + ] for i in [0, 1, 2, 3]: - atom_selection.append(widgets.Dropdown( - options = (names_tuple), - layout = widgets.Layout(width = '30%'), - style = dict(description_width='initial'), - description = descriptions[i] - ) - ) - interact(select_dihedrals_from_sites, - networkx_graph = fixed(networkx_graph), - top = fixed(topology), - Atom1 = atom_selection[0], - Atom2 = atom_selection[1], - Atom3 = atom_selection[2], - Atom4 = atom_selection[3] + atom_selection.append( + widgets.Dropdown( + options=(names_tuple), + layout=widgets.Layout(width="30%"), + style=dict(description_width="initial"), + description=descriptions[i], ) + ) + interact( + select_dihedrals_from_sites, + networkx_graph=fixed(networkx_graph), + top=fixed(topology), + Atom1=atom_selection[0], + Atom2=atom_selection[1], + Atom3=atom_selection[2], + Atom4=atom_selection[3], + ) return -def select_dihedrals_from_sites(networkx_graph, top, Atom1 = None, Atom2 = None, Atom3 = None, Atom4 = None): - - params_list = select_params_on_networkx(networkx_graph,[Atom1,Atom2,Atom3,Atom4]) + +def select_dihedrals_from_sites( + networkx_graph, top, Atom1=None, Atom2=None, Atom3=None, Atom4=None +): + + params_list = select_params_on_networkx( + networkx_graph, [Atom1, Atom2, Atom3, Atom4] + ) if params_list: - edges_widget = widgets.Dropdown(options = params_list, - layout = widgets.Layout(width = '60%'), - style = dict(description_width = 'initial'), - description = "Selected Edge" - ) - interact(select_edges_on_networkx, networkx_graph = fixed(networkx_graph), - top = fixed(top), list_of_params = edges_widget - ) + edges_widget = widgets.Dropdown( + options=params_list, + layout=widgets.Layout(width="60%"), + style=dict(description_width="initial"), + description="Selected Edge", + ) + interact( + select_edges_on_networkx, + networkx_graph=fixed(networkx_graph), + top=fixed(top), + list_of_params=edges_widget, + ) else: - plot_networkx_params(networkx_graph, list_of_edges = []) - + plot_networkx_params(networkx_graph, list_of_edges=[]) + return + def select_params_on_networkx(networkx_graph, atoms): - # Create a list of the edges that have been selected corresponding to the selected + # Create a list of the edges that have been selected corresponding to the selected # list of sites names. If all sites are None, then select edges that are missing parameter types. # Works for selecting angles or dihedals based on the number of atoms sent in the `atoms` variable. - + list_of_params = [] list_of_names = [] mia_angle_flag = 0 if len(atoms) == 3: if all(atoms): - for node, angles in networkx_graph.nodes(data='angles'): + for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] - if (atoms == [names[i] for i in [1,0,2]] or - atoms == [names[i] for i in [2,0,1]]): + if atoms == [names[i] for i in [1, 0, 2]] or atoms == [ + names[i] for i in [2, 0, 1] + ]: list_of_params.append(angle.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(angle) - ) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) elif all(atoms[0:2]): - for node, angles in networkx_graph.nodes(data='angles'): + for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] - if (atoms[0:2] == names[1:3] or - atoms[0:2] == reversed(names[0:2])): + if atoms[0:2] == names[1:3] or atoms[0:2] == reversed(names[0:2]): list_of_params.append(angle.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(angle) - ) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) elif atoms[0]: - for node, angles in networkx_graph.nodes(data='angles'): + for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] if atoms[0] == names[1]: list_of_params.append(angle.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(angle) - ) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) else: - for node, angles in networkx_graph.nodes(data='angles'): + for node, angles in networkx_graph.nodes(data="angles"): if not angles: print("No angle parameters have been applied to this topology") return for angle in angles: - if angle.angle_type is None: - list_of_params.append(angle.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(angle) - ) - mia_angle_flag = 1 + if angle.angle_type is None: + list_of_params.append(angle.connection_members) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) + mia_angle_flag = 1 if not mia_angle_flag: - print('All angles are typed. Select a central atom to look at the different angle_types.') - else: - print('Since no sites are input, angles with missing types are shown.') + print( + "All angles are typed. Select a central atom to look at the different angle_types." + ) + else: + print("Since no sites are input, angles with missing types are shown.") elif len(atoms) == 4: - #select a list of dihedrals + # select a list of dihedrals if all(atoms): - for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): for dihedral in dihedrals: names = [member.name for member in dihedral.connection_members] - if (atoms == [names[i] for i in [2,1,0,3]] or - atoms == [names[i] for i in [1,2,3,0]] or - atoms == [names[i] for i in [1,2,0,3]] or - atoms == [names[i] for i in [2,1,3,0]]): + if ( + atoms == [names[i] for i in [2, 1, 0, 3]] + or atoms == [names[i] for i in [1, 2, 3, 0]] + or atoms == [names[i] for i in [1, 2, 0, 3]] + or atoms == [names[i] for i in [2, 1, 3, 0]] + ): list_of_params.append(dihedral.connection_members) list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) + _get_formatted_atom_types_names_for(dihedral) ) elif all(atoms[0:3]): - for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): for dihedral in dihedrals: names = [member.name for member in dihedral.connection_members] - if (atoms[0:3] == [names[i] for i in [1,2,3]] or - atoms[0:3] == [names[i] for i in [2,1,3]] or - atoms[0:3] == [names[i] for i in [1,2,0]] or - atoms[0:3] == [names[i] for i in [2,1,0]]): + if ( + atoms[0:3] == [names[i] for i in [1, 2, 3]] + or atoms[0:3] == [names[i] for i in [2, 1, 3]] + or atoms[0:3] == [names[i] for i in [1, 2, 0]] + or atoms[0:3] == [names[i] for i in [2, 1, 0]] + ): list_of_params.append(dihedral.connection_members) list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) + _get_formatted_atom_types_names_for(dihedral) ) elif all(atoms[0:2]): - for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): for dihedral in dihedrals: names = [member.name for member in dihedral.connection_members] - if (atoms[0:2] == names[1:3] or - atoms[0:2] == [names[2],names[1]]): + if atoms[0:2] == names[1:3] or atoms[0:2] == [names[2], names[1]]: list_of_params.append(dihedral.connection_members) list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) + _get_formatted_atom_types_names_for(dihedral) ) else: - for node, dihedrals in networkx_graph.nodes(data='dihedrals'): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): if not dihedrals: print("No dihedral parameters have been applied to this topology") return @@ -468,24 +510,27 @@ def select_params_on_networkx(networkx_graph, atoms): if dihedral.dihedral_type is None: list_of_params.append(dihedral.connection_members) list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) + _get_formatted_atom_types_names_for(dihedral) ) mia_angle_flag = 1 - + if not mia_angle_flag: - print('All dihedrals are typed. Select two central atoms to see associated dihedrals.') - else: - print('Since no sites are input, dihedrals with missing types are shown.') - - + print( + "All dihedrals are typed. Select two central atoms to see associated dihedrals." + ) + else: + print( + "Since no sites are input, dihedrals with missing types are shown." + ) + else: - print('invalid atom selections') - - labeled_params = zip(list_of_names,list_of_params) - - #create a dict so each selected bond selects the proper edges on the networkx.graph object. + print("invalid atom selections") + + labeled_params = zip(list_of_names, list_of_params) + + # create a dict so each selected bond selects the proper edges on the networkx.graph object. selectable_list = {} - for label,param in labeled_params: + for label, param in labeled_params: if label in selectable_list.keys(): selectable_list[label].append(param) else: @@ -495,42 +540,47 @@ def select_params_on_networkx(networkx_graph, atoms): # turn the dict selectable list into a list of tuples. list_of_edges = [] for key in selectable_list: - list_of_edges.append((key,selectable_list[key])) + list_of_edges.append((key, selectable_list[key])) return list_of_edges + def _get_formatted_atom_types_names_for(connection): assert all(map(lambda atom: atom.atom_type, connection.connection_members)) names = (member.atom_type.name for member in connection.connection_members) - - return ' --- '.join(names) + + return " --- ".join(names) def select_edges_on_networkx(networkx_graph, top, list_of_params): list_of_edges = get_networkx_edges(list_of_params) - plot_networkx_params(networkx_graph, list_of_edges = list_of_edges) + plot_networkx_params(networkx_graph, list_of_edges=list_of_edges) # The interact prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value = False, description = 'Show parameters') - interact(show_parameter_values, topology = fixed(top), - list_of_params = fixed(list_of_params), - checkbox=checkbox) - + checkbox = widgets.Checkbox(value=False, description="Show parameters") + interact( + show_parameter_values, + topology=fixed(top), + list_of_params=fixed(list_of_params), + checkbox=checkbox, + ) + return + def get_networkx_edges(list_of_params): # Return a list of edges within a given dihedral or angle - # Both orientations of every edge are saved, to guarentee the edge can be - #found on a networkx_graph.edge object. + # Both orientations of every edge are saved, to guarentee the edge can be + # found on a networkx_graph.edge object. list_of_edges = [] if len(list_of_params[0]) == 4: for param in list_of_params: - edge1 = (param[0],param[1]) - edge2 = (param[1],param[2]) - edge3 = (param[1],param[0]) - edge4 = (param[2],param[1]) - edge5 = (param[2],param[3]) - edge6 = (param[3],param[2]) + edge1 = (param[0], param[1]) + edge2 = (param[1], param[2]) + edge3 = (param[1], param[0]) + edge4 = (param[2], param[1]) + edge5 = (param[2], param[3]) + edge6 = (param[3], param[2]) list_of_edges.append(edge1) list_of_edges.append(edge2) list_of_edges.append(edge3) @@ -539,57 +589,79 @@ def get_networkx_edges(list_of_params): list_of_edges.append(edge6) elif len(list_of_params[0]) == 3: for param in list_of_params: - edge1 = (param[0],param[1]) - edge2 = (param[1],param[2]) - edge3 = (param[1],param[0]) - edge4 = (param[2],param[1]) + edge1 = (param[0], param[1]) + edge2 = (param[1], param[2]) + edge3 = (param[1], param[0]) + edge4 = (param[2], param[1]) list_of_edges.append(edge1) list_of_edges.append(edge2) list_of_edges.append(edge3) list_of_edges.append(edge4) - else: - raise ValueError('The parameters are not proper angles or dihedrals. Connection members are missing.') - + else: + raise ValueError( + "The parameters are not proper angles or dihedrals. Connection members are missing." + ) + return list_of_edges -def plot_networkx_nodes(networkx_graph, ax, atom_name = None, edge_weights = None, - edge_colors = None, node_sizes = None, list_of_labels = ['atom_type.name']): + +def plot_networkx_nodes( + networkx_graph, + ax, + atom_name=None, + edge_weights=None, + edge_colors=None, + node_sizes=None, + list_of_labels=["atom_type.name"], +): # Place nodes at 2D positions related to position in the topology layout = nx.drawing.layout.kamada_kawai_layout(networkx_graph) # Use this dictionary to color specific atoms - node_color_dict = {'C':'grey', 'H':'silver', 'O':'red', 'N':'blue', 'Cl':'green'} + node_color_dict = { + "C": "grey", + "H": "silver", + "O": "red", + "N": "blue", + "Cl": "green", + } node_colors = [] for node in networkx_graph.nodes: if node.name in list(node_color_dict.keys()): node_colors.append(node_color_dict[node.name]) else: - node_colors.append('black') + node_colors.append("black") - # Node sizes determines if looking at just sites. Bonds, angles, dihedrals have edge_weights + # Node sizes determines if looking at just sites. Bonds, angles, dihedrals have edge_weights # and edge_colors as identifiers if node_sizes: - nx.draw(networkx_graph, layout, ax, node_color = node_colors, - node_size = node_sizes) + nx.draw( + networkx_graph, layout, ax, node_color=node_colors, node_size=node_sizes + ) else: - nx.draw(networkx_graph, layout, ax, node_color = node_colors, - width = list(edge_weights.values()), - edge_color = list(edge_colors.values())) + nx.draw( + networkx_graph, + layout, + ax, + node_color=node_colors, + width=list(edge_weights.values()), + edge_color=list(edge_colors.values()), + ) # Offset positions to place labels - for atom,pos in layout.items(): - layout[atom] = pos + [0.09,0] - + for atom, pos in layout.items(): + layout[atom] = pos + [0.09, 0] + # Get a list of labels to plot labels = identify_labels(networkx_graph, list_of_labels, atom_name) # Plot labels on current figure - nx.draw_networkx_labels(networkx_graph, layout, labels, - horizontalalignment = 'left') - ax.margins(.3,.3) + nx.draw_networkx_labels(networkx_graph, layout, labels, horizontalalignment="left") + ax.margins(0.3, 0.3) return ax -def identify_labels(networkx_graph, list_of_labels, atom_name = None): + +def identify_labels(networkx_graph, list_of_labels, atom_name=None): # If atom_name specified, only show labels on that site. # Otherwise, show labels for every atom from the label list. if atom_name: @@ -600,43 +672,58 @@ def identify_labels(networkx_graph, list_of_labels, atom_name = None): labels = return_labels_for_nodes(list_of_nodes, list_of_labels) else: labels = return_labels_for_nodes(list(networkx_graph.nodes), list_of_labels) - + return labels + def return_labels_for_nodes(list_of_nodes, list_of_labels): # Get the label values for the sites specified. # labels is a dict of each node and the labels to put there labels = {} - for i,node in enumerate(list_of_nodes): - node.label = str(i) + ':' + str(node.name) + for i, node in enumerate(list_of_nodes): + node.label = str(i) + ":" + str(node.name) for label in list_of_labels: - if '.' in label: - label1,label2 = label.split('.') + if "." in label: + label1, label2 = label.split(".") try: - node.label = node.label + '\n' + str(getattr(getattr(node, label1), label2)) + node.label = ( + node.label + "\n" + str(getattr(getattr(node, label1), label2)) + ) except AttributeError: - node.label = node.label + '\nNoneType' + node.label = node.label + "\nNoneType" elif label == "charge": - if isinstance(getattr(node,label),unyt.array.unyt_quantity): - node.label = node.label + '\n' + str((getattr(node,label)/unyt.electron_charge).round(4)) + ' e' + if isinstance(getattr(node, label), unyt.array.unyt_quantity): + node.label = ( + node.label + + "\n" + + str((getattr(node, label) / unyt.electron_charge).round(4)) + + " e" + ) else: - node.label = node.label + '\nNone' + node.label = node.label + "\nNone" elif label == "position": - if isinstance(getattr(node,label)[0],unyt.array.unyt_quantity): - node.label = node.label + '\n' + str(getattr(node,label).to('angstrom').round(2)*unyt.angstrom) + if isinstance(getattr(node, label)[0], unyt.array.unyt_quantity): + node.label = ( + node.label + + "\n" + + str( + getattr(node, label).to("angstrom").round(2) * unyt.angstrom + ) + ) else: - node.label = node.label + '\nNone' - else: + node.label = node.label + "\nNone" + else: try: - node.label = node.label + '\n' + str(getattr(node, label)) + node.label = node.label + "\n" + str(getattr(node, label)) except AttributeError: - node.label = node.label + '\nNoneType' + node.label = node.label + "\nNoneType" if len(node.label) > 12: - node.label = "".join([line + '\n' for line in node.label.split()]) + node.label = "".join([line + "\n" for line in node.label.split()]) labels[node] = node.label - + return labels + def show_parameter_values(topology, list_of_params, checkbox): if checkbox: try: @@ -644,20 +731,26 @@ def show_parameter_values(topology, list_of_params, checkbox): except AttributeError: print("There are no values for the parameter expression") else: - #TODO: Should be able to remove this blank print statement so that deselecting the - #checkbox removes the listed parameters. - print('') - + # TODO: Should be able to remove this blank print statement so that deselecting the + # checkbox removes the listed parameters. + print("") + return + def report_parameter_expression(topology, param): # return nicely printed parameters for a given edge. if len(param) == 4: try: for dihedral in list(topology.dihedrals): - if dihedral.connection_members == (param[0], param[1], param[2], param[3]): - print(dihedral.dihedral_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) + if dihedral.connection_members == ( + param[0], + param[1], + param[2], + param[3], + ): + print(dihedral.dihedral_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) for k, v in dihedral.dihedral_type.parameters.items(): print("{:<12} {:<15}".format(k, v)) except AttributeError: @@ -666,21 +759,24 @@ def report_parameter_expression(topology, param): try: for angle in list(topology.angles): if angle.connection_members == (param[0], param[1], param[2]): - print(angle.angle_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) + print(angle.angle_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) for k, v in angle.angle_type.parameters.items(): print("{:<12} {:<15}".format(k, v)) except AttributeError: print("Angle not typed") else: - raise ValueError('Parameters are not proper angles or dihedrals. Connection members are missing') + raise ValueError( + "Parameters are not proper angles or dihedrals. Connection members are missing" + ) return + def get_edges(networkx_graph, atom_name1, atom_name2): - + # Create a list of the edges that have been selected corresponding to the selected atom_name1 - #and atom_name2. If both are None, then select edges that are missing bond types. + # and atom_name2. If both are None, then select edges that are missing bond types. list_of_edges = [] list_of_bonds = [] # Check for whether you want to plot bonds based on the atom types, or their node labels. @@ -691,29 +787,39 @@ def get_edges(networkx_graph, atom_name1, atom_name2): mia_bond_flag = 0 if atom_name1 and atom_name2: for edge in list(networkx_graph.edges): - if (edge[0].name == atom_name1 and edge[1].name == atom_name2 or - edge[0].name == atom_name2 and edge[1].name == atom_name1): - list_of_bonds.append(edge[0].atom_type.name + ' --- ' + edge[1].atom_type.name) + if ( + edge[0].name == atom_name1 + and edge[1].name == atom_name2 + or edge[0].name == atom_name2 + and edge[1].name == atom_name1 + ): + list_of_bonds.append( + edge[0].atom_type.name + " --- " + edge[1].atom_type.name + ) list_of_edges.append(edge) elif atom_name1: for edge in list(networkx_graph.edges): if edge[0].name == atom_name1 or edge[1].name == atom_name1: - list_of_bonds.append(edge[0].atom_type.name + ' --- ' + edge[1].atom_type.name) + list_of_bonds.append( + edge[0].atom_type.name + " --- " + edge[1].atom_type.name + ) list_of_edges.append(edge) else: for nodes in list(networkx_graph.edges.items()): - if nodes[1]['connection'].bond_type is None: - list_of_bonds.append(nodes[0][0].atom_type.name + ' --- ' + nodes[0][1].atom_type.name) - list_of_edges.append((nodes[0][0],nodes[0][1])) + if nodes[1]["connection"].bond_type is None: + list_of_bonds.append( + nodes[0][0].atom_type.name + " --- " + nodes[0][1].atom_type.name + ) + list_of_edges.append((nodes[0][0], nodes[0][1])) mia_bond_flag = 1 if not mia_bond_flag: - return print('All bonds are typed') - - labeled_bonds = zip(list_of_bonds,list_of_edges) - - #create a dic so each selected bond selects the proper edges on the networkx.graph object. + return print("All bonds are typed") + + labeled_bonds = zip(list_of_bonds, list_of_edges) + + # create a dic so each selected bond selects the proper edges on the networkx.graph object. selectable_list = {} - for label,bond in labeled_bonds: + for label, bond in labeled_bonds: if label in selectable_list.keys(): selectable_list[label].append(bond) else: @@ -723,49 +829,67 @@ def get_edges(networkx_graph, atom_name1, atom_name2): # turn the dic selectable list into a list of tuples. list_of_edges = [] for key in selectable_list: - list_of_edges.append((key,selectable_list[key])) + list_of_edges.append((key, selectable_list[key])) return list_of_edges -def plot_networkx_bonds(networkx_graph, atom_name1=None, atom_name2=None, - list_of_labels = ['atom_type.name'], list_of_bonds = []): - - fig,ax = plt.subplots(1,1,figsize=(8,8)) - + +def plot_networkx_bonds( + networkx_graph, + atom_name1=None, + atom_name2=None, + list_of_labels=["atom_type.name"], + list_of_bonds=[], +): + + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + # Create dictionaries of edges that correspond red thick lines for the selected bonds edge_weights = {} edge_colors = {} for bond in networkx_graph.edges: if bond in list_of_bonds: edge_weights[bond] = 5 - edge_colors[bond] = 'red' + edge_colors[bond] = "red" else: edge_weights[bond] = 1 - edge_colors[bond] = 'k' - - ax = plot_networkx_nodes(networkx_graph,ax,edge_weights = edge_weights, - edge_colors = edge_colors, list_of_labels = list_of_labels, - atom_name = atom_name1) + edge_colors[bond] = "k" + + ax = plot_networkx_nodes( + networkx_graph, + ax, + edge_weights=edge_weights, + edge_colors=edge_colors, + list_of_labels=list_of_labels, + atom_name=atom_name1, + ) return fig, ax + def report_bond_parameters(topology, edge): # return nicely printed bond parameters for a given edge. for bond in list(topology.bonds): if bond: try: - if bond.connection_members == edge[0] or bond.connection_members == (edge[0][1],edge[0][0]): - print(bond.bond_type.expression, '\n') - print("{:<12} {:<15}".format('Parameter','Value')) + if bond.connection_members == edge[0] or bond.connection_members == ( + edge[0][1], + edge[0][0], + ): + print(bond.bond_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) for k, v in bond.bond_type.parameters.items(): print("{:<12} {:<15}".format(k, v)) except AttributeError: print("This bond has no parameters associated with it") else: - print("This bond has no parameters associated with it") + print("This bond has no parameters associated with it") return -def plot_networkx_atomtypes(topology,atom_name=None,list_of_labels = ['atom_type.name']): + +def plot_networkx_atomtypes( + topology, atom_name=None, list_of_labels=["atom_type.name"] +): """Get a networkx plot showing the atom types in a topology object. Parameters @@ -790,32 +914,40 @@ def plot_networkx_atomtypes(topology,atom_name=None,list_of_labels = ['atom_type matplotlib.pyplot.show() """ - fig,ax = plt.subplots(1,1,figsize=(8,8)) + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) networkx_graph = to_networkx(topology) - - #larger nodes for the atom selected + + # larger nodes for the atom selected node_sizes = [] for node in networkx_graph.nodes: if node.name == atom_name: node_sizes.append(900) else: node_sizes.append(300) - - ax = plot_networkx_nodes(networkx_graph, ax, edge_weights = None, edge_colors = None, - node_sizes = node_sizes, list_of_labels = list_of_labels, - atom_name = atom_name) - return(fig,ax) + ax = plot_networkx_nodes( + networkx_graph, + ax, + edge_weights=None, + edge_colors=None, + node_sizes=node_sizes, + list_of_labels=list_of_labels, + atom_name=atom_name, + ) + + return (fig, ax) + def highlight_networkx_edges(networkx_graph, list_of_edges): - #return edge parameters for the specified networkx edge list. - edge_weights={};edge_colors={} + # return edge parameters for the specified networkx edge list. + edge_weights = {} + edge_colors = {} for edge in networkx_graph.edges: - edge_weights[edge] = 1; edge_colors[edge] = 'k' + edge_weights[edge] = 1 + edge_colors[edge] = "k" for edge in networkx_graph.edges: if edge in list_of_edges: edge_weights[edge] = 5 - edge_colors[edge] = 'r' + edge_colors[edge] = "r" return edge_weights, edge_colors - From fae850479128ea55f94bdb452793fa90b02fb000 Mon Sep 17 00:00:00 2001 From: CalCraven <54594941+CalCraven@users.noreply.github.com> Date: Mon, 5 Apr 2021 19:14:34 -0500 Subject: [PATCH 43/62] Update gmso/formats/networkx.py update docstrings Co-authored-by: Umesh Timalsina --- gmso/formats/networkx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 0b82668f2..521e18207 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -43,7 +43,7 @@ def interactive_networkx_atomtypes(topology, list_of_labels=None): Parameters ---------- - topology : A gmso.core.topology.Topology object + topology : gmso.Topology This should be a gmso topology object that you want to visualize the atom types that have been parameterized. list_of_labels : Additional labels you would like to have on the plot. From d471f5ba840bddc415ef10d1d0f184d9f9e690f6 Mon Sep 17 00:00:00 2001 From: CalCraven <54594941+CalCraven@users.noreply.github.com> Date: Mon, 5 Apr 2021 19:14:55 -0500 Subject: [PATCH 44/62] Update gmso/formats/networkx.py update docstrings Co-authored-by: Umesh Timalsina --- gmso/formats/networkx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 521e18207..b119bff90 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -46,7 +46,7 @@ def interactive_networkx_atomtypes(topology, list_of_labels=None): topology : gmso.Topology This should be a gmso topology object that you want to visualize the atom types that have been parameterized. - list_of_labels : Additional labels you would like to have on the plot. + list_of_labels : List[str] Default labels are ['atom_type.name','charge','mass','element','label','position']. Any additonal labels can be appended from the list of: `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` From 9fe8f322d688032a3b16084817ae092f74275762 Mon Sep 17 00:00:00 2001 From: CalCraven <54594941+CalCraven@users.noreply.github.com> Date: Mon, 5 Apr 2021 19:17:07 -0500 Subject: [PATCH 45/62] Update gmso/formats/networkx.py use .items method to simplify code Co-authored-by: Umesh Timalsina --- gmso/formats/networkx.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index b119bff90..0644c2423 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -827,11 +827,7 @@ def get_edges(networkx_graph, atom_name1, atom_name2): selectable_list[label].append(bond) # turn the dic selectable list into a list of tuples. - list_of_edges = [] - for key in selectable_list: - list_of_edges.append((key, selectable_list[key])) - - return list_of_edges + return tuple(selectable_list.items()) def plot_networkx_bonds( From 55972aa7edc19bb0b22c5a276f188f0475546ea4 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 5 Apr 2021 20:28:03 -0500 Subject: [PATCH 46/62] add selectable_dict_for_edges_from_labels function --- gmso/formats/networkx.py | 75 +++++++++++++++------------------------- 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 0644c2423..6c0f5d18c 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -777,13 +777,8 @@ def get_edges(networkx_graph, atom_name1, atom_name2): # Create a list of the edges that have been selected corresponding to the selected atom_name1 # and atom_name2. If both are None, then select edges that are missing bond types. - list_of_edges = [] - list_of_bonds = [] - # Check for whether you want to plot bonds based on the atom types, or their node labels. - try: - [node.atom_type.name for node in networkx_graph.nodes] - except AttributeError: - return print("Atomtypes are missing, so no bond types can be selected") + labeled_bonds = [] + selectable_dict = {} mia_bond_flag = 0 if atom_name1 and atom_name2: for edge in list(networkx_graph.edges): @@ -793,42 +788,29 @@ def get_edges(networkx_graph, atom_name1, atom_name2): or edge[0].name == atom_name2 and edge[1].name == atom_name1 ): - list_of_bonds.append( - edge[0].atom_type.name + " --- " + edge[1].atom_type.name - ) - list_of_edges.append(edge) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) elif atom_name1: for edge in list(networkx_graph.edges): if edge[0].name == atom_name1 or edge[1].name == atom_name1: - list_of_bonds.append( - edge[0].atom_type.name + " --- " + edge[1].atom_type.name - ) - list_of_edges.append(edge) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) else: for nodes in list(networkx_graph.edges.items()): if nodes[1]["connection"].bond_type is None: - list_of_bonds.append( - nodes[0][0].atom_type.name + " --- " + nodes[0][1].atom_type.name - ) - list_of_edges.append((nodes[0][0], nodes[0][1])) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) mia_bond_flag = 1 if not mia_bond_flag: return print("All bonds are typed") + # turn the dic selectable dict into a list of tuples. + return tuple(selectable_dict.items()) - labeled_bonds = zip(list_of_bonds, list_of_edges) - - # create a dic so each selected bond selects the proper edges on the networkx.graph object. - selectable_list = {} - for label, bond in labeled_bonds: - if label in selectable_list.keys(): - selectable_list[label].append(bond) - else: - selectable_list[label] = [] - selectable_list[label].append(bond) - - # turn the dic selectable list into a list of tuples. - return tuple(selectable_list.items()) - +def create_dict_of_labels_for_edges(selectable_dict,edge): + label = edge[0].atom_type.name + " --- " + edge[1].atom_type.name + if label in selectable_dict.keys(): + selectable_dict[label].append(edge) + else: + selectable_dict[label] = [] + selectable_dict[label].append(edge) + return selectable_dict def plot_networkx_bonds( networkx_graph, @@ -864,21 +846,18 @@ def plot_networkx_bonds( def report_bond_parameters(topology, edge): # return nicely printed bond parameters for a given edge. - for bond in list(topology.bonds): - if bond: - try: - if bond.connection_members == edge[0] or bond.connection_members == ( - edge[0][1], - edge[0][0], - ): - print(bond.bond_type.expression, "\n") - print("{:<12} {:<15}".format("Parameter", "Value")) - for k, v in bond.bond_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) - except AttributeError: - print("This bond has no parameters associated with it") - else: - print("This bond has no parameters associated with it") + for bond in topology.bonds: + try: + if bond.connection_members == edge[0] or bond.connection_members == ( + edge[0][1], + edge[0][0], + ): + print(bond.bond_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) + for k, v in bond.bond_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("The bond between {} is missing parameters".format(edge)) return From f0a69d298ee8d406ac0bc0ab4ff9fd0d8e38a4be Mon Sep 17 00:00:00 2001 From: CalCraven Date: Tue, 6 Apr 2021 14:59:38 -0500 Subject: [PATCH 47/62] fixed an issue with the widgets dropdown not being selectable as a list of tuples, and fixed the selection methods for angles from two and three specified atoms --- gmso/formats/networkx.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 6c0f5d18c..64bf109ff 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -427,7 +427,7 @@ def select_params_on_networkx(networkx_graph, atoms): for angle in angles: names = [member.name for member in angle.connection_members] if atoms == [names[i] for i in [1, 0, 2]] or atoms == [ - names[i] for i in [2, 0, 1] + names[i] for i in [1, 2, 0] ]: list_of_params.append(angle.connection_members) list_of_names.append(_get_formatted_atom_types_names_for(angle)) @@ -435,7 +435,8 @@ def select_params_on_networkx(networkx_graph, atoms): for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] - if atoms[0:2] == names[1:3] or atoms[0:2] == reversed(names[0:2]): + print(names[1:3],list(reversed(names[0:2]))) + if atoms[0:2] == names[1:3] or atoms[0:2] == list(reversed(names[0:2])): list_of_params.append(angle.connection_members) list_of_names.append(_get_formatted_atom_types_names_for(angle)) elif atoms[0]: @@ -801,7 +802,7 @@ def get_edges(networkx_graph, atom_name1, atom_name2): if not mia_bond_flag: return print("All bonds are typed") # turn the dic selectable dict into a list of tuples. - return tuple(selectable_dict.items()) + return list(selectable_dict.items()) def create_dict_of_labels_for_edges(selectable_dict,edge): label = edge[0].atom_type.name + " --- " + edge[1].atom_type.name From 3dde79be72774a914ec5dd762f719d0167a6427d Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 7 Apr 2021 13:05:47 -0500 Subject: [PATCH 48/62] fix behavior with missing atom_types so parameters can still be visualized --- gmso/formats/networkx.py | 22 ++++++++++++++++++---- gmso/tests/test_networkx.py | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 64bf109ff..cf745fe3a 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -435,7 +435,6 @@ def select_params_on_networkx(networkx_graph, atoms): for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] - print(names[1:3],list(reversed(names[0:2]))) if atoms[0:2] == names[1:3] or atoms[0:2] == list(reversed(names[0:2])): list_of_params.append(angle.connection_members) list_of_names.append(_get_formatted_atom_types_names_for(angle)) @@ -547,8 +546,13 @@ def select_params_on_networkx(networkx_graph, atoms): def _get_formatted_atom_types_names_for(connection): - assert all(map(lambda atom: atom.atom_type, connection.connection_members)) - names = (member.atom_type.name for member in connection.connection_members) + names = [] + for member in connection.connection_members: + if not member.atom_type: + label = "" + else: + label = member.atom_type.name + names.append(label) return " --- ".join(names) @@ -805,7 +809,17 @@ def get_edges(networkx_graph, atom_name1, atom_name2): return list(selectable_dict.items()) def create_dict_of_labels_for_edges(selectable_dict,edge): - label = edge[0].atom_type.name + " --- " + edge[1].atom_type.name + label0 = "" + label1 = "" + try: + label0 = edge[0].atom_type.name + except AttributeError: + print("An atomtype for {} is missing".format(edge[0].label)) + try: + label1 = edge[1].atom_type.name + except AttributeError: + print("An atomtype for {} is missing".format(edge[1].label)) + label = label0 + " --- " + label1 if label in selectable_dict.keys(): selectable_dict[label].append(edge) else: diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 3034b605e..69c8e7c9f 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -58,7 +58,7 @@ def test_select_params_on_networkx(self,typed_ethane,capsys): assert len(select_params_on_networkx(graph,['C','H','H'])) == 1 assert len(select_params_on_networkx(graph,['C','C','H','H'])) == 1 assert len(select_params_on_networkx(graph,[None,None,None])) == 0 - assert len(select_params_on_networkx(graph,['C','H',None])) == 2 + assert len(select_params_on_networkx(graph,['C','H',None])) == 3 assert len(select_params_on_networkx(graph,['C',None,None])) == 3 assert len(select_params_on_networkx(graph,['C','C','H', None])) == 1 assert len(select_params_on_networkx(graph,['C','C', None, None])) == 1 From d2038885bc7d3f6bd892849fdbab9a897b8531c4 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 7 Apr 2021 16:58:45 -0500 Subject: [PATCH 49/62] update docstrings for interactive methods --- gmso/formats/networkx.py | 152 ++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 57 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index cf745fe3a..bb1db102a 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -51,20 +51,22 @@ def interactive_networkx_atomtypes(topology, list_of_labels=None): Any additonal labels can be appended from the list of: `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` - Returns + Notes ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atomtypes that should be shown on the networkx graph object below. This + graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of labels labels to be included on the graph. + See: + gmso.formats.networkx.interactive_networkx_bonds, gmso.formats.networkx.interactive_networkx_angles, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + ipywidgets.Dropdown() Two dropdown options, corresponding to: - Label = What labels are shown on the networkx graph - Atom_Name = Which sites will show these labels - - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() + Label = What labels are shown on the networkx graph. + Atom_Name = Which sites will show these labels. """ networkx_graph = to_networkx(topology) @@ -109,7 +111,34 @@ def interactive_networkx_bonds(topology, additional_labels=None): Any additonal labels can be appended from the list of: `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` - Returns + Notes + ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the bonds shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object, and the bonds that makeup that bondtype are highlighted in red. + Select from a list of available sites, the sites that make up bonds to be highlighted on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_angles, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + + ipywidgets.Dropdown() + Two dropdown options, corresponding to: + Atom1 (req) = Filters through all bonds to find bonds that contain this site. If not specified, + only bonds with missing bondtypes will be highlighted (in red). + Atom2 (opt) = A second site to specify which bonds can be selected on the graph. + A third dropdown option: + list_of_bonds = a list of the bonds labeled by the types that makeup the two sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted edges corresponding to the + selected bonds + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected bond in the list_of_bonds + dropdown option. + """ ------- ipywidgets.Dropdown() Three dropdown options, corresponding to: @@ -224,30 +253,36 @@ def interactive_networkx_angles(topology): This should be a gmso topology object that you want to visualize the angle types that have been parameterized. - Returns + Notes ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the angles shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of available sites, the sites that make up angles to be highlighted (in red) on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + ipywidgets.Dropdown() Three dropdown options, corresponding to: - Central Atom1 = The central site for which the angle can be visualized. - If it is not specified, missing angles will be shown. - Atom2 = An optional atom that will specify one end of the angle. - Atom3 = An optional atom that will specify the second end of the angle. Atom2 - must be selected first. - Selected Angle = The dropdown will show all angles that match the above site - criteria. Angles are listed by three characterstic atomtypes. Multiple angles - may satisfy the selected angle, and so more than three edges may be selected - on the plot. Use the atomtype definitions to verify the correct angles. - - - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines - indicate which angles have been selected. - - ipywidgets.Checkbox() - Show parameters = True or False - Will determine if the necessary angle parameters for the selected angle type are shown - below the figure. + Central Atom1 (req) = Filters through all angles to find angles that contain this site as the center of the angle. + If not specified only angles with missing angletypes will be highlighted. + Atom2 (opt) = A second site to filter which angles can be selected on the graph. + Atom3 (opt) = A third site to filter which angles can be selected on the graph. + A fourth dropdown option: + Selected Edge = a list of the angles labeled by the types that makeup the three sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the + selected angles. + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected angle in the Selected Edge + dropdown option. """ + networkx_graph = to_networkx(topology) # Create list of nodes to plot @@ -316,32 +351,36 @@ def interactive_networkx_dihedrals(topology): This should be a gmso topology object that you want to visualize the dihedral types that have been parameterized. - Returns + Notes ------- - ipywidgets.Dropdown() - Three dropdown options, corresponding to: - Central Atom1 = One of the two central sites for which dihedrals can be visualized. - If it is not specified, missing dihedrals will be shown. - Central Atom2 = The second central site to select dihedrals. All dihedrals that - contain these two atom sites in their center will be listed. If it is not - specified, missing dihedrals will be shown. - Atom3 = An optional atom that will specify one end of the dihedral. - Atom4 = An optional atom that will specify the second end of the dihedral. Atom3 - must be selected. - Selected Dihedral = The dropdown will show all dihedrals that match the above site - criteria. Dihedrals are listed by four characterstic atomtypes. Multiple dihedrals - may satisfy the selected dihedral, and so more than four edges may be selected - on the plot. Use the atomtype definitions to verify the correct dihedrals. - - - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines - indicate which dihedrals have been selected. + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the dihedrals shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of available sites, the sites that make up dihedrals to be highlighted (in red) on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, + gmso.formats.networkx.interactive_networkx_angles + for other interactive visualization methods. - ipywidgets.Checkbox() - Show parameters = True or False - Will determine if the necessary dihedral parameters for the selected dihedral type are shown - below the figure. + ipywidgets.Dropdown() + Four dropdown options, corresponding to: + Central Atom1 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central + two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. + Central Atom2 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central + two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. + Atom3 (opt) = A third site to filter which dihedrals can be selected on the graph. + Atom4 (opt) = A fourth site to filter which dihedrals can be selected on the graph. + A fourth dropdown option: + Selected Edge = a list of the angles labeled by the types that makeup the three sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the + selected dihedrals. + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected dihedral in the Selected Edge + dropdown option. """ networkx_graph = to_networkx(topology) @@ -782,7 +821,6 @@ def get_edges(networkx_graph, atom_name1, atom_name2): # Create a list of the edges that have been selected corresponding to the selected atom_name1 # and atom_name2. If both are None, then select edges that are missing bond types. - labeled_bonds = [] selectable_dict = {} mia_bond_flag = 0 if atom_name1 and atom_name2: From 64e1a58752658ffc0f151d83866a182104bb7fac Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 7 Apr 2021 17:01:01 -0500 Subject: [PATCH 50/62] apply black --- gmso/formats/networkx.py | 303 +++++++++++++++++++-------------------- 1 file changed, 144 insertions(+), 159 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index bb1db102a..356fa246a 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -41,32 +41,32 @@ def plot_networkx_params(networkx_graph, list_of_edges): def interactive_networkx_atomtypes(topology, list_of_labels=None): """Get an interactive networkx plot showing the atom types of a topology object. - Parameters - ---------- - topology : gmso.Topology - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - list_of_labels : List[str] - Default labels are ['atom_type.name','charge','mass','element','label','position']. - Any additonal labels can be appended from the list of: - `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` - - Notes - ------- - This function will output interactive ipywidgets. An ipywidgets dropdown object will - select which atomtypes that should be shown on the networkx graph object below. This - graph object is a visual representation for the connections within a gmso.Topology - object. - Select from a list of labels labels to be included on the graph. - See: - gmso.formats.networkx.interactive_networkx_bonds, gmso.formats.networkx.interactive_networkx_angles, - gmso.formats.networkx.interactive_networkx_dihedrals - for other interactive visualization methods. - - ipywidgets.Dropdown() - Two dropdown options, corresponding to: - Label = What labels are shown on the networkx graph. - Atom_Name = Which sites will show these labels. + Parameters + ---------- + topology : gmso.Topology + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + list_of_labels : List[str] + Default labels are ['atom_type.name','charge','mass','element','label','position']. + Any additonal labels can be appended from the list of: + `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` + + Notes + ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atomtypes that should be shown on the networkx graph object below. This + graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of labels labels to be included on the graph. + See: + gmso.formats.networkx.interactive_networkx_bonds, gmso.formats.networkx.interactive_networkx_angles, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + + ipywidgets.Dropdown() + Two dropdown options, corresponding to: + Label = What labels are shown on the networkx graph. + Atom_Name = Which sites will show these labels. """ networkx_graph = to_networkx(topology) @@ -101,62 +101,43 @@ def get_interactive_sites(Label=list_of_labels, Atom_Name=names_tuple): def interactive_networkx_bonds(topology, additional_labels=None): """Get an interactive networkx plot showing the bond types of a topology object. - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - additonal_labels : Labels at each site to be included on the plot. - Default labels are ['atom_type.name']. - Any additonal labels can be appended from the list of: - `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` - - Notes - ------- - This function will output interactive ipywidgets. An ipywidgets dropdown object will - select which atom names that will make up the bonds shown on the networkx graph object below. - This graph object is a visual representation for the connections within a gmso.Topology - object, and the bonds that makeup that bondtype are highlighted in red. - Select from a list of available sites, the sites that make up bonds to be highlighted on the graph. - See: - gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_angles, - gmso.formats.networkx.interactive_networkx_dihedrals - for other interactive visualization methods. - - ipywidgets.Dropdown() - Two dropdown options, corresponding to: - Atom1 (req) = Filters through all bonds to find bonds that contain this site. If not specified, - only bonds with missing bondtypes will be highlighted (in red). - Atom2 (opt) = A second site to specify which bonds can be selected on the graph. - A third dropdown option: - list_of_bonds = a list of the bonds labeled by the types that makeup the two sites. - - matplotlib.pyplot.figure() - A figure showing the networkx.graph object of the molecule with highlighted edges corresponding to the - selected bonds - - ipywidgets.checkbox() - A checkbox that will allow you to print the parameters associated with the selected bond in the list_of_bonds - dropdown option. - """ - ------- - ipywidgets.Dropdown() - Three dropdown options, corresponding to: - Atom1 = The first site for which bonds will be listed. If all sites are selected for both - atoms, then only bonds with missing types will be shown. - Atom2 = A second site for which bonds will be listed. With both specified - only bonds between Atom1 and Atom2 will be shown. - list_of_bonds = The bonds that can be selected corresponding to the Atom1 and Atom2 - selections in the two above dropdown menus. - - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures. Thick dark red lines - indicate which bonds have been selected. - - ipywidgets.Checkbox() - Show parameters = True or False - Will determine if the necessary bond parameters for the selected bond type are shown - below the figure. + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + additonal_labels : Labels at each site to be included on the plot. + Default labels are ['atom_type.name']. + Any additonal labels can be appended from the list of: + `dir(list(topology.sites)[0])` or `dir(list(topology.sites)[0].atom_type)` + + Notes + ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the bonds shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object, and the bonds that makeup that bondtype are highlighted in red. + Select from a list of available sites, the sites that make up bonds to be highlighted on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_angles, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + + ipywidgets.Dropdown() + Two dropdown options, corresponding to: + Atom1 (req) = Filters through all bonds to find bonds that contain this site. If not specified, + only bonds with missing bondtypes will be highlighted (in red). + Atom2 (opt) = A second site to specify which bonds can be selected on the graph. + A third dropdown option: + list_of_bonds = a list of the bonds labeled by the types that makeup the two sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted edges corresponding to the + selected bonds + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected bond in the list_of_bonds + dropdown option. """ networkx_graph = to_networkx(topology) @@ -247,40 +228,40 @@ def show_bond_info(w, topology, list_of_bonds): def interactive_networkx_angles(topology): """Get an interactive networkx plot showing the angle types of a topology object. - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the angle types - that have been parameterized. - - Notes - ------- - This function will output interactive ipywidgets. An ipywidgets dropdown object will - select which atom names that will make up the angles shown on the networkx graph object below. - This graph object is a visual representation for the connections within a gmso.Topology - object. - Select from a list of available sites, the sites that make up angles to be highlighted (in red) on the graph. - See: - gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, - gmso.formats.networkx.interactive_networkx_dihedrals - for other interactive visualization methods. - - ipywidgets.Dropdown() - Three dropdown options, corresponding to: - Central Atom1 (req) = Filters through all angles to find angles that contain this site as the center of the angle. - If not specified only angles with missing angletypes will be highlighted. - Atom2 (opt) = A second site to filter which angles can be selected on the graph. - Atom3 (opt) = A third site to filter which angles can be selected on the graph. - A fourth dropdown option: - Selected Edge = a list of the angles labeled by the types that makeup the three sites. - - matplotlib.pyplot.figure() - A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the - selected angles. - - ipywidgets.checkbox() - A checkbox that will allow you to print the parameters associated with the selected angle in the Selected Edge - dropdown option. + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the angle types + that have been parameterized. + + Notes + ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the angles shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of available sites, the sites that make up angles to be highlighted (in red) on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, + gmso.formats.networkx.interactive_networkx_dihedrals + for other interactive visualization methods. + + ipywidgets.Dropdown() + Three dropdown options, corresponding to: + Central Atom1 (req) = Filters through all angles to find angles that contain this site as the center of the angle. + If not specified only angles with missing angletypes will be highlighted. + Atom2 (opt) = A second site to filter which angles can be selected on the graph. + Atom3 (opt) = A third site to filter which angles can be selected on the graph. + A fourth dropdown option: + Selected Edge = a list of the angles labeled by the types that makeup the three sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the + selected angles. + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected angle in the Selected Edge + dropdown option. """ networkx_graph = to_networkx(topology) @@ -345,42 +326,42 @@ def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): def interactive_networkx_dihedrals(topology): """Get an interactive networkx plot showing the dihedral types of a topology object. - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the dihedral types - that have been parameterized. - - Notes - ------- - This function will output interactive ipywidgets. An ipywidgets dropdown object will - select which atom names that will make up the dihedrals shown on the networkx graph object below. - This graph object is a visual representation for the connections within a gmso.Topology - object. - Select from a list of available sites, the sites that make up dihedrals to be highlighted (in red) on the graph. - See: - gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, - gmso.formats.networkx.interactive_networkx_angles - for other interactive visualization methods. - - ipywidgets.Dropdown() - Four dropdown options, corresponding to: - Central Atom1 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central - two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. - Central Atom2 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central - two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. - Atom3 (opt) = A third site to filter which dihedrals can be selected on the graph. - Atom4 (opt) = A fourth site to filter which dihedrals can be selected on the graph. - A fourth dropdown option: - Selected Edge = a list of the angles labeled by the types that makeup the three sites. - - matplotlib.pyplot.figure() - A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the - selected dihedrals. - - ipywidgets.checkbox() - A checkbox that will allow you to print the parameters associated with the selected dihedral in the Selected Edge - dropdown option. + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the dihedral types + that have been parameterized. + + Notes + ------- + This function will output interactive ipywidgets. An ipywidgets dropdown object will + select which atom names that will make up the dihedrals shown on the networkx graph object below. + This graph object is a visual representation for the connections within a gmso.Topology + object. + Select from a list of available sites, the sites that make up dihedrals to be highlighted (in red) on the graph. + See: + gmso.formats.networkx.interactive_networkx_atomtypes, gmso.formats.networkx.interactive_networkx_bonds, + gmso.formats.networkx.interactive_networkx_angles + for other interactive visualization methods. + + ipywidgets.Dropdown() + Four dropdown options, corresponding to: + Central Atom1 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central + two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. + Central Atom2 (req) = Filters through all bonds to find dihedrals that contain this site in one of its central + two positions. If not specified, only dihedrals with missing dihedraltypes will be highlighted. + Atom3 (opt) = A third site to filter which dihedrals can be selected on the graph. + Atom4 (opt) = A fourth site to filter which dihedrals can be selected on the graph. + A fourth dropdown option: + Selected Edge = a list of the angles labeled by the types that makeup the three sites. + + matplotlib.pyplot.figure() + A figure showing the networkx.graph object of the molecule with highlighted (in red) edges corresponding to the + selected dihedrals. + + ipywidgets.checkbox() + A checkbox that will allow you to print the parameters associated with the selected dihedral in the Selected Edge + dropdown option. """ networkx_graph = to_networkx(topology) @@ -474,7 +455,9 @@ def select_params_on_networkx(networkx_graph, atoms): for node, angles in networkx_graph.nodes(data="angles"): for angle in angles: names = [member.name for member in angle.connection_members] - if atoms[0:2] == names[1:3] or atoms[0:2] == list(reversed(names[0:2])): + if atoms[0:2] == names[1:3] or atoms[0:2] == list( + reversed(names[0:2]) + ): list_of_params.append(angle.connection_members) list_of_names.append(_get_formatted_atom_types_names_for(angle)) elif atoms[0]: @@ -831,30 +814,31 @@ def get_edges(networkx_graph, atom_name1, atom_name2): or edge[0].name == atom_name2 and edge[1].name == atom_name1 ): - selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) elif atom_name1: for edge in list(networkx_graph.edges): if edge[0].name == atom_name1 or edge[1].name == atom_name1: - selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) else: for nodes in list(networkx_graph.edges.items()): if nodes[1]["connection"].bond_type is None: - selectable_dict = create_dict_of_labels_for_edges(selectable_dict,edge) + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) mia_bond_flag = 1 if not mia_bond_flag: return print("All bonds are typed") # turn the dic selectable dict into a list of tuples. return list(selectable_dict.items()) -def create_dict_of_labels_for_edges(selectable_dict,edge): + +def create_dict_of_labels_for_edges(selectable_dict, edge): label0 = "" label1 = "" try: - label0 = edge[0].atom_type.name + label0 = edge[0].atom_type.name except AttributeError: print("An atomtype for {} is missing".format(edge[0].label)) try: - label1 = edge[1].atom_type.name + label1 = edge[1].atom_type.name except AttributeError: print("An atomtype for {} is missing".format(edge[1].label)) label = label0 + " --- " + label1 @@ -865,6 +849,7 @@ def create_dict_of_labels_for_edges(selectable_dict,edge): selectable_dict[label].append(edge) return selectable_dict + def plot_networkx_bonds( networkx_graph, atom_name1=None, From c69539abffbee48a375358b8c221b38e2f9bc92f Mon Sep 17 00:00:00 2001 From: CalCraven <54594941+CalCraven@users.noreply.github.com> Date: Thu, 8 Apr 2021 10:47:36 -0500 Subject: [PATCH 51/62] Update gmso/formats/networkx.py Co-authored-by: Umesh Timalsina --- gmso/formats/networkx.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 356fa246a..a84b832ad 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -809,10 +809,10 @@ def get_edges(networkx_graph, atom_name1, atom_name2): if atom_name1 and atom_name2: for edge in list(networkx_graph.edges): if ( - edge[0].name == atom_name1 - and edge[1].name == atom_name2 - or edge[0].name == atom_name2 - and edge[1].name == atom_name1 + (edge[0].name == atom_name1 + and edge[1].name == atom_name2) + or (edge[0].name == atom_name2 + and edge[1].name == atom_name1) ): selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) elif atom_name1: From bfbbc55d84a8238a28056e7623e744c8eb6ce803 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 10:45:08 -0500 Subject: [PATCH 52/62] remove requirements-test.txt --- requirements-test.txt | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index bae56a6ff..000000000 --- a/requirements-test.txt +++ /dev/null @@ -1,17 +0,0 @@ -numpy -sympy -unyt >= 2.4 -boltons -lxml -pytest -networkx -mbuild >= 0.10.6 -openbabel >= 3.0.0 -foyer -gsd >= 2.0 -parmed -pytest-cov -codecov -pydantic -matplotlib -ipywidgets From 85c372a9d35167297f6fbf3e73eefe004eeec2e0 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 13:03:05 -0500 Subject: [PATCH 53/62] checks for installation of matplotlib and ipywidgets --- gmso/formats/networkx.py | 8 +++++--- gmso/tests/test_networkx.py | 7 ++----- gmso/utils/io.py | 39 +++++++++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index a84b832ad..72e824f85 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,9 +1,11 @@ -import matplotlib.pyplot as plt import networkx as nx import unyt -from ipywidgets import interact, fixed -import ipywidgets as widgets +from gmso.utils.io import import_, has_ipywidgets +ipywidgets = import_('ipywidgets') +plt = import_('matplotlib.pyplot') +if has_ipywidgets: + from ipywidgets import interact, fixed from gmso.external.convert_networkx import to_networkx diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 69c8e7c9f..80407be6f 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -10,13 +10,10 @@ from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest -from gmso.utils.io import import_, has_networkx, has_pyplot +from gmso.utils.io import import_, has_matplotlib from gmso.external.convert_networkx import to_networkx -if has_networkx: - networkx = import_('networkx') - -if has_pyplot: +if has_matplotlib: plt = import_('matplotlib.pyplot') @pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") diff --git a/gmso/utils/io.py b/gmso/utils/io.py index c397948f5..171aeae94 100644 --- a/gmso/utils/io.py +++ b/gmso/utils/io.py @@ -6,6 +6,22 @@ import textwrap from unittest import SkipTest +MESSAGES = dict() +MESSAGES['matplotlib.pyplot'] = ''' +The code at {filename}:{line_number} requires the "matplotlib" package +matplotlib can be installed using: +# conda install -c conda-forge matplotlib +or +# pip install matplotlib +''' + +MESSAGES['matplotlib'] = ''' +The code at {filename}:{line_number} requires the "matplotlib" package +matplotlib can be installed using: +# conda install -c conda-forge matplotlib +or +# pip install matplotlib +''' class DelayImportError(ImportError, SkipTest): pass @@ -60,8 +76,11 @@ def import_(module): try: return importlib.import_module(module) except ImportError as e: - message = 'The code at {filename}:{line_number} requires the ' + module + ' package' - e = ImportError('No module named %s' % module) + try: + message = MESSAGES[module] + except KeyError: + message = 'The code at {filename}:{line_number} requires the ' + module + ' package' + e = ImportError('No module named %s' % module) frame, filename, line_number, function_name, lines, index = \ inspect.getouterframes(inspect.currentframe())[1] @@ -128,15 +147,15 @@ def import_(module): has_simtk_unit = False try: - import networkx - has_networkx = True - del networkx + import ipywidgets + has_ipywidgets = True + del ipywidgets except ImportError: - has_networkx = False + has_ipywidgets = False try: - from matplotlib import pyplot - has_pyplot = True - del pyplot + import matplotlib + has_matplotlib = True + del matplotlib except ImportError: - has_pyplot = False + has_matplotlib = False From 75a5399517b4bfe05a02b0f712e181f5c796ce08 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 13:17:07 -0500 Subject: [PATCH 54/62] change ipywidgets to widgets --- gmso/formats/networkx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 72e824f85..bc475ae9f 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -2,7 +2,7 @@ import unyt from gmso.utils.io import import_, has_ipywidgets -ipywidgets = import_('ipywidgets') +widgets = import_('ipywidgets') plt = import_('matplotlib.pyplot') if has_ipywidgets: from ipywidgets import interact, fixed From 2232aec66980439204f5d5ebb4c95df25f8859ff Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 13:19:30 -0500 Subject: [PATCH 55/62] update testing with soft imports --- gmso/tests/test_networkx.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 80407be6f..6906c4a31 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -10,14 +10,16 @@ from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest -from gmso.utils.io import import_, has_matplotlib +from gmso.utils.io import import_, has_matplotlib, has_ipywidgets from gmso.external.convert_networkx import to_networkx if has_matplotlib: plt = import_('matplotlib.pyplot') +if has_ipywidgets: + widgets = import_('ipywidgets') -@pytest.mark.skipif(not has_networkx, reason="Networkx is not installed") -@pytest.mark.skipif(not has_pyplot, reason="Matplotlib.pyplot is not installed") +@pytest.mark.skipif(not has_ipywidgets, reason="Ipywidgets is not installed") +@pytest.mark.skipif(not has_matplotlib, reason="Matplotlib is not installed") class TestNetworkx(BaseTest): def test_highlight_networkx_edges(self, typed_ethane): list(typed_ethane.angles)[0].angle_type = None From cbc9d68b249237c744ff236c2851b23657e33fe8 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 13:29:58 -0500 Subject: [PATCH 56/62] put functions meant to be implemented by users on top, add checks for operations within a jupyter notebook --- gmso/formats/networkx.py | 202 ++++++++++++++++++++------------------- gmso/utils/io.py | 7 ++ 2 files changed, 111 insertions(+), 98 deletions(-) diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index bc475ae9f..7b4a83343 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,7 +1,7 @@ import networkx as nx import unyt -from gmso.utils.io import import_, has_ipywidgets +from gmso.utils.io import import_, has_ipywidgets, run_from_ipython widgets = import_('ipywidgets') plt = import_('matplotlib.pyplot') if has_ipywidgets: @@ -9,37 +9,6 @@ from gmso.external.convert_networkx import to_networkx - -def plot_networkx_params(networkx_graph, list_of_edges): - """Get a networkx plot showing the specified edges in a networkx graph object. - - Parameters - ---------- - networkx_graph : A networkx.Graph object - This is a networkx graph object that can be created from a topology using the to_networkx - function found in gmso.external.convert_networkx - list_of_edges : a list of edges that should be shown on the plot - Will be of the shape [(node1,node2), (node2,node3), etc...] - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to a networkx drawn figure. This output can be - shown using - matplotlib.pyplot.show() - """ - fig, ax = plt.subplots(1, 1, figsize=(8, 8)) - edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) - ax = plot_networkx_nodes( - networkx_graph, ax, edge_weights=edge_weights, edge_colors=edge_colors - ) - - return (fig, ax) - - def interactive_networkx_atomtypes(topology, list_of_labels=None): """Get an interactive networkx plot showing the atom types of a topology object. @@ -71,6 +40,8 @@ def interactive_networkx_atomtypes(topology, list_of_labels=None): Atom_Name = Which sites will show these labels. """ + if not run_from_ipython(): + raise RuntimeError("Unsupported visualization outside of jupyter notebooks.") networkx_graph = to_networkx(topology) # get a unique list of site names site_names = [] @@ -141,6 +112,8 @@ def interactive_networkx_bonds(topology, additional_labels=None): A checkbox that will allow you to print the parameters associated with the selected bond in the list_of_bonds dropdown option. """ + if not run_from_ipython(): + raise RuntimeError("Unsupported visualization outside of jupyter notebooks.") networkx_graph = to_networkx(topology) # Create a list of labels to go on the nodes @@ -183,50 +156,6 @@ def interactive_networkx_bonds(topology, additional_labels=None): return -def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): - list_of_edges = get_edges(networkx_graph, Atom1, Atom2) - if list_of_edges: - # Call the second level, which takes the selected bonds and shows them on the figure - interact( - select_edges, - list_of_bonds=list_of_edges, - list_of_labels=fixed(list_of_labels), - networkx_graph=fixed(networkx_graph), - topology=fixed(topology), - ) - else: - plot_networkx_bonds(networkx_graph, list_of_labels=list_of_labels) - - return - - -def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): - plot_networkx_bonds( - networkx_graph, list_of_labels=list_of_labels, list_of_bonds=list_of_bonds - ) - # The final level prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value=False, description="Show parameters") - interact( - show_bond_info, - w=checkbox, - topology=fixed(topology), - list_of_bonds=fixed(list_of_bonds), - ) - - return - - -def show_bond_info(w, topology, list_of_bonds): - if w: - report_bond_parameters(topology, list_of_bonds) - else: - # TODO: Should be able to remove this blank print statement so that deselecting the - # checkbox removes the listed parameters. - print("") - - return - - def interactive_networkx_angles(topology): """Get an interactive networkx plot showing the angle types of a topology object. @@ -265,6 +194,8 @@ def interactive_networkx_angles(topology): A checkbox that will allow you to print the parameters associated with the selected angle in the Selected Edge dropdown option. """ + if not run_from_ipython(): + raise RuntimeError("Unsupported visualization outside of jupyter notebooks.") networkx_graph = to_networkx(topology) @@ -304,27 +235,6 @@ def interactive_networkx_angles(topology): return -def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): - params_list = select_params_on_networkx(networkx_graph, [Atom1, Atom2, Atom3]) - if params_list: - edges_widget = widgets.Dropdown( - options=params_list, - layout=widgets.Layout(width="60%"), - style=dict(description_width="initial"), - description="Selected Edge", - ) - interact( - select_edges_on_networkx, - networkx_graph=fixed(networkx_graph), - top=fixed(top), - list_of_params=edges_widget, - ) - else: - plot_networkx_params(networkx_graph, list_of_edges=[]) - - return - - def interactive_networkx_dihedrals(topology): """Get an interactive networkx plot showing the dihedral types of a topology object. @@ -365,8 +275,10 @@ def interactive_networkx_dihedrals(topology): A checkbox that will allow you to print the parameters associated with the selected dihedral in the Selected Edge dropdown option. """ - networkx_graph = to_networkx(topology) + if not run_from_ipython(): + raise RuntimeError("Unsupported visualization outside of jupyter notebooks.") + networkx_graph = to_networkx(topology) # Create list of nodes to plot site_names = [] for node in networkx_graph.nodes: @@ -409,6 +321,100 @@ def interactive_networkx_dihedrals(topology): return +def plot_networkx_params(networkx_graph, list_of_edges): + """Get a networkx plot showing the specified edges in a networkx graph object. + + Parameters + ---------- + networkx_graph : A networkx.Graph object + This is a networkx graph object that can be created from a topology using the to_networkx + function found in gmso.external.convert_networkx + list_of_edges : a list of edges that should be shown on the plot + Will be of the shape [(node1,node2), (node2,node3), etc...] + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to a networkx drawn figure. This output can be + shown using + matplotlib.pyplot.show() + """ + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) + ax = plot_networkx_nodes( + networkx_graph, ax, edge_weights=edge_weights, edge_colors=edge_colors + ) + + return (fig, ax) + + +def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): + list_of_edges = get_edges(networkx_graph, Atom1, Atom2) + if list_of_edges: + # Call the second level, which takes the selected bonds and shows them on the figure + interact( + select_edges, + list_of_bonds=list_of_edges, + list_of_labels=fixed(list_of_labels), + networkx_graph=fixed(networkx_graph), + topology=fixed(topology), + ) + else: + plot_networkx_bonds(networkx_graph, list_of_labels=list_of_labels) + + return + + +def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): + plot_networkx_bonds( + networkx_graph, list_of_labels=list_of_labels, list_of_bonds=list_of_bonds + ) + # The final level prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value=False, description="Show parameters") + interact( + show_bond_info, + w=checkbox, + topology=fixed(topology), + list_of_bonds=fixed(list_of_bonds), + ) + + return + +def show_bond_info(w, topology, list_of_bonds): + if w: + report_bond_parameters(topology, list_of_bonds) + else: + # TODO: Should be able to remove this blank print statement so that deselecting the + # checkbox removes the listed parameters. + print("") + + return + + +def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): + params_list = select_params_on_networkx(networkx_graph, [Atom1, Atom2, Atom3]) + if params_list: + edges_widget = widgets.Dropdown( + options=params_list, + layout=widgets.Layout(width="60%"), + style=dict(description_width="initial"), + description="Selected Edge", + ) + interact( + select_edges_on_networkx, + networkx_graph=fixed(networkx_graph), + top=fixed(top), + list_of_params=edges_widget, + ) + else: + plot_networkx_params(networkx_graph, list_of_edges=[]) + + return + + def select_dihedrals_from_sites( networkx_graph, top, Atom1=None, Atom2=None, Atom3=None, Atom4=None ): diff --git a/gmso/utils/io.py b/gmso/utils/io.py index 171aeae94..f2520b81e 100644 --- a/gmso/utils/io.py +++ b/gmso/utils/io.py @@ -159,3 +159,10 @@ def import_(module): del matplotlib except ImportError: has_matplotlib = False + +def run_from_ipython(): + try: + __IPYTHON__ + return True + except NameError: + return False From 70ef0dc4a817844b164808dc87315658f8bda78f Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 13:45:03 -0500 Subject: [PATCH 57/62] add matplotlib and ipywidgets for unit testing --- environment-dev.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environment-dev.yml b/environment-dev.yml index 315a0ec43..02f372395 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -18,3 +18,5 @@ dependencies: - pytest-cov - codecov - bump2version + - matplotlib + - ipywidgets From 3c007d71ac35d38ceab59d860a500c7f3df06a8c Mon Sep 17 00:00:00 2001 From: CalCraven Date: Wed, 28 Apr 2021 14:12:07 -0500 Subject: [PATCH 58/62] skip tests that need a jupyter notebook to run --- gmso/tests/test_networkx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 6906c4a31..dd0f00029 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -10,7 +10,7 @@ from gmso.formats.networkx import _get_formatted_atom_types_names_for from gmso.tests.base_test import BaseTest -from gmso.utils.io import import_, has_matplotlib, has_ipywidgets +from gmso.utils.io import import_, has_matplotlib, has_ipywidgets, run_from_ipython from gmso.external.convert_networkx import to_networkx if has_matplotlib: @@ -100,21 +100,25 @@ def test_show_parameter_values(self,typed_ethane): with pytest.raises(ValueError): show_parameter_values(typed_ethane, [parameters], True) + @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") def test_interactive_networkx_atomtypes(self,typed_ethane,capsys): interactive_networkx_atomtypes(typed_ethane) captured, err = capsys.readouterr() assert isinstance(err,str) + @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") def test_interactive_networkx_bonds(self,typed_ethane,capsys): interactive_networkx_bonds(typed_ethane) captured, err = capsys.readouterr() assert isinstance(err,str) + @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") def test_interactive_networkx_angles(self,typed_ethane,capsys): interactive_networkx_angles(typed_ethane) captured, err = capsys.readouterr() assert isinstance(err,str) + @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") def test_interactive_networkx_dihedrals(self,typed_ethane,capsys): interactive_networkx_dihedrals(typed_ethane) captured, err = capsys.readouterr() From 334d5d08003442ba82f8e9e38debaa40a318813d Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 29 Apr 2021 17:08:36 -0500 Subject: [PATCH 59/62] switch interactive functions to not be covered by codecov, create gmso/utils/nx_utils.py for functions used by interactive visualization --- codecov.yml | 1 + gmso/formats/networkx.py | 657 +---------------------------------- gmso/tests/test_networkx.py | 35 +- gmso/tests/test_utils.py | 6 +- gmso/utils/nx_utils.py | 662 ++++++++++++++++++++++++++++++++++++ 5 files changed, 674 insertions(+), 687 deletions(-) create mode 100644 gmso/utils/nx_utils.py diff --git a/codecov.yml b/codecov.yml index 228866658..ae1555d26 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,4 @@ ignore: - "gmso/examples" - "gmso/tests" + - "gmso/formats/networkx.py" diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 7b4a83343..85b184198 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -1,7 +1,5 @@ -import networkx as nx -import unyt - from gmso.utils.io import import_, has_ipywidgets, run_from_ipython +from gmso.utils import nx_utils widgets = import_('ipywidgets') plt = import_('matplotlib.pyplot') if has_ipywidgets: @@ -319,656 +317,3 @@ def interactive_networkx_dihedrals(topology): ) return - - -def plot_networkx_params(networkx_graph, list_of_edges): - """Get a networkx plot showing the specified edges in a networkx graph object. - - Parameters - ---------- - networkx_graph : A networkx.Graph object - This is a networkx graph object that can be created from a topology using the to_networkx - function found in gmso.external.convert_networkx - list_of_edges : a list of edges that should be shown on the plot - Will be of the shape [(node1,node2), (node2,node3), etc...] - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to a networkx drawn figure. This output can be - shown using - matplotlib.pyplot.show() - """ - fig, ax = plt.subplots(1, 1, figsize=(8, 8)) - edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) - ax = plot_networkx_nodes( - networkx_graph, ax, edge_weights=edge_weights, edge_colors=edge_colors - ) - - return (fig, ax) - - -def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): - list_of_edges = get_edges(networkx_graph, Atom1, Atom2) - if list_of_edges: - # Call the second level, which takes the selected bonds and shows them on the figure - interact( - select_edges, - list_of_bonds=list_of_edges, - list_of_labels=fixed(list_of_labels), - networkx_graph=fixed(networkx_graph), - topology=fixed(topology), - ) - else: - plot_networkx_bonds(networkx_graph, list_of_labels=list_of_labels) - - return - - -def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): - plot_networkx_bonds( - networkx_graph, list_of_labels=list_of_labels, list_of_bonds=list_of_bonds - ) - # The final level prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value=False, description="Show parameters") - interact( - show_bond_info, - w=checkbox, - topology=fixed(topology), - list_of_bonds=fixed(list_of_bonds), - ) - - return - -def show_bond_info(w, topology, list_of_bonds): - if w: - report_bond_parameters(topology, list_of_bonds) - else: - # TODO: Should be able to remove this blank print statement so that deselecting the - # checkbox removes the listed parameters. - print("") - - return - - -def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): - params_list = select_params_on_networkx(networkx_graph, [Atom1, Atom2, Atom3]) - if params_list: - edges_widget = widgets.Dropdown( - options=params_list, - layout=widgets.Layout(width="60%"), - style=dict(description_width="initial"), - description="Selected Edge", - ) - interact( - select_edges_on_networkx, - networkx_graph=fixed(networkx_graph), - top=fixed(top), - list_of_params=edges_widget, - ) - else: - plot_networkx_params(networkx_graph, list_of_edges=[]) - - return - - -def select_dihedrals_from_sites( - networkx_graph, top, Atom1=None, Atom2=None, Atom3=None, Atom4=None -): - - params_list = select_params_on_networkx( - networkx_graph, [Atom1, Atom2, Atom3, Atom4] - ) - if params_list: - edges_widget = widgets.Dropdown( - options=params_list, - layout=widgets.Layout(width="60%"), - style=dict(description_width="initial"), - description="Selected Edge", - ) - interact( - select_edges_on_networkx, - networkx_graph=fixed(networkx_graph), - top=fixed(top), - list_of_params=edges_widget, - ) - else: - plot_networkx_params(networkx_graph, list_of_edges=[]) - - return - - -def select_params_on_networkx(networkx_graph, atoms): - # Create a list of the edges that have been selected corresponding to the selected - # list of sites names. If all sites are None, then select edges that are missing parameter types. - # Works for selecting angles or dihedals based on the number of atoms sent in the `atoms` variable. - - list_of_params = [] - list_of_names = [] - mia_angle_flag = 0 - if len(atoms) == 3: - if all(atoms): - for node, angles in networkx_graph.nodes(data="angles"): - for angle in angles: - names = [member.name for member in angle.connection_members] - if atoms == [names[i] for i in [1, 0, 2]] or atoms == [ - names[i] for i in [1, 2, 0] - ]: - list_of_params.append(angle.connection_members) - list_of_names.append(_get_formatted_atom_types_names_for(angle)) - elif all(atoms[0:2]): - for node, angles in networkx_graph.nodes(data="angles"): - for angle in angles: - names = [member.name for member in angle.connection_members] - if atoms[0:2] == names[1:3] or atoms[0:2] == list( - reversed(names[0:2]) - ): - list_of_params.append(angle.connection_members) - list_of_names.append(_get_formatted_atom_types_names_for(angle)) - elif atoms[0]: - for node, angles in networkx_graph.nodes(data="angles"): - for angle in angles: - names = [member.name for member in angle.connection_members] - if atoms[0] == names[1]: - list_of_params.append(angle.connection_members) - list_of_names.append(_get_formatted_atom_types_names_for(angle)) - else: - for node, angles in networkx_graph.nodes(data="angles"): - if not angles: - print("No angle parameters have been applied to this topology") - return - for angle in angles: - if angle.angle_type is None: - list_of_params.append(angle.connection_members) - list_of_names.append(_get_formatted_atom_types_names_for(angle)) - mia_angle_flag = 1 - if not mia_angle_flag: - print( - "All angles are typed. Select a central atom to look at the different angle_types." - ) - else: - print("Since no sites are input, angles with missing types are shown.") - - elif len(atoms) == 4: - # select a list of dihedrals - if all(atoms): - for node, dihedrals in networkx_graph.nodes(data="dihedrals"): - for dihedral in dihedrals: - names = [member.name for member in dihedral.connection_members] - if ( - atoms == [names[i] for i in [2, 1, 0, 3]] - or atoms == [names[i] for i in [1, 2, 3, 0]] - or atoms == [names[i] for i in [1, 2, 0, 3]] - or atoms == [names[i] for i in [2, 1, 3, 0]] - ): - list_of_params.append(dihedral.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) - ) - elif all(atoms[0:3]): - for node, dihedrals in networkx_graph.nodes(data="dihedrals"): - for dihedral in dihedrals: - names = [member.name for member in dihedral.connection_members] - if ( - atoms[0:3] == [names[i] for i in [1, 2, 3]] - or atoms[0:3] == [names[i] for i in [2, 1, 3]] - or atoms[0:3] == [names[i] for i in [1, 2, 0]] - or atoms[0:3] == [names[i] for i in [2, 1, 0]] - ): - list_of_params.append(dihedral.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) - ) - elif all(atoms[0:2]): - for node, dihedrals in networkx_graph.nodes(data="dihedrals"): - for dihedral in dihedrals: - names = [member.name for member in dihedral.connection_members] - if atoms[0:2] == names[1:3] or atoms[0:2] == [names[2], names[1]]: - list_of_params.append(dihedral.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) - ) - else: - for node, dihedrals in networkx_graph.nodes(data="dihedrals"): - if not dihedrals: - print("No dihedral parameters have been applied to this topology") - return - for dihedral in dihedrals: - if dihedral.dihedral_type is None: - list_of_params.append(dihedral.connection_members) - list_of_names.append( - _get_formatted_atom_types_names_for(dihedral) - ) - mia_angle_flag = 1 - - if not mia_angle_flag: - print( - "All dihedrals are typed. Select two central atoms to see associated dihedrals." - ) - else: - print( - "Since no sites are input, dihedrals with missing types are shown." - ) - - else: - print("invalid atom selections") - - labeled_params = zip(list_of_names, list_of_params) - - # create a dict so each selected bond selects the proper edges on the networkx.graph object. - selectable_list = {} - for label, param in labeled_params: - if label in selectable_list.keys(): - selectable_list[label].append(param) - else: - selectable_list[label] = [] - selectable_list[label].append(param) - - # turn the dict selectable list into a list of tuples. - list_of_edges = [] - for key in selectable_list: - list_of_edges.append((key, selectable_list[key])) - - return list_of_edges - - -def _get_formatted_atom_types_names_for(connection): - names = [] - for member in connection.connection_members: - if not member.atom_type: - label = "" - else: - label = member.atom_type.name - names.append(label) - - return " --- ".join(names) - - -def select_edges_on_networkx(networkx_graph, top, list_of_params): - list_of_edges = get_networkx_edges(list_of_params) - plot_networkx_params(networkx_graph, list_of_edges=list_of_edges) - - # The interact prints the parameters of the selected bond using a checkbox. - checkbox = widgets.Checkbox(value=False, description="Show parameters") - interact( - show_parameter_values, - topology=fixed(top), - list_of_params=fixed(list_of_params), - checkbox=checkbox, - ) - - return - - -def get_networkx_edges(list_of_params): - # Return a list of edges within a given dihedral or angle - # Both orientations of every edge are saved, to guarentee the edge can be - # found on a networkx_graph.edge object. - list_of_edges = [] - if len(list_of_params[0]) == 4: - for param in list_of_params: - edge1 = (param[0], param[1]) - edge2 = (param[1], param[2]) - edge3 = (param[1], param[0]) - edge4 = (param[2], param[1]) - edge5 = (param[2], param[3]) - edge6 = (param[3], param[2]) - list_of_edges.append(edge1) - list_of_edges.append(edge2) - list_of_edges.append(edge3) - list_of_edges.append(edge4) - list_of_edges.append(edge5) - list_of_edges.append(edge6) - elif len(list_of_params[0]) == 3: - for param in list_of_params: - edge1 = (param[0], param[1]) - edge2 = (param[1], param[2]) - edge3 = (param[1], param[0]) - edge4 = (param[2], param[1]) - list_of_edges.append(edge1) - list_of_edges.append(edge2) - list_of_edges.append(edge3) - list_of_edges.append(edge4) - else: - raise ValueError( - "The parameters are not proper angles or dihedrals. Connection members are missing." - ) - - return list_of_edges - - -def plot_networkx_nodes( - networkx_graph, - ax, - atom_name=None, - edge_weights=None, - edge_colors=None, - node_sizes=None, - list_of_labels=["atom_type.name"], -): - # Place nodes at 2D positions related to position in the topology - layout = nx.drawing.layout.kamada_kawai_layout(networkx_graph) - - # Use this dictionary to color specific atoms - node_color_dict = { - "C": "grey", - "H": "silver", - "O": "red", - "N": "blue", - "Cl": "green", - } - node_colors = [] - for node in networkx_graph.nodes: - if node.name in list(node_color_dict.keys()): - node_colors.append(node_color_dict[node.name]) - else: - node_colors.append("black") - - # Node sizes determines if looking at just sites. Bonds, angles, dihedrals have edge_weights - # and edge_colors as identifiers - if node_sizes: - nx.draw( - networkx_graph, layout, ax, node_color=node_colors, node_size=node_sizes - ) - else: - nx.draw( - networkx_graph, - layout, - ax, - node_color=node_colors, - width=list(edge_weights.values()), - edge_color=list(edge_colors.values()), - ) - - # Offset positions to place labels - for atom, pos in layout.items(): - layout[atom] = pos + [0.09, 0] - - # Get a list of labels to plot - labels = identify_labels(networkx_graph, list_of_labels, atom_name) - # Plot labels on current figure - nx.draw_networkx_labels(networkx_graph, layout, labels, horizontalalignment="left") - ax.margins(0.3, 0.3) - - return ax - - -def identify_labels(networkx_graph, list_of_labels, atom_name=None): - # If atom_name specified, only show labels on that site. - # Otherwise, show labels for every atom from the label list. - if atom_name: - list_of_nodes = [] - for node in networkx_graph.nodes: - if node.name == atom_name: - list_of_nodes.append(node) - labels = return_labels_for_nodes(list_of_nodes, list_of_labels) - else: - labels = return_labels_for_nodes(list(networkx_graph.nodes), list_of_labels) - - return labels - - -def return_labels_for_nodes(list_of_nodes, list_of_labels): - # Get the label values for the sites specified. - # labels is a dict of each node and the labels to put there - labels = {} - for i, node in enumerate(list_of_nodes): - node.label = str(i) + ":" + str(node.name) - for label in list_of_labels: - if "." in label: - label1, label2 = label.split(".") - try: - node.label = ( - node.label + "\n" + str(getattr(getattr(node, label1), label2)) - ) - except AttributeError: - node.label = node.label + "\nNoneType" - elif label == "charge": - if isinstance(getattr(node, label), unyt.array.unyt_quantity): - node.label = ( - node.label - + "\n" - + str((getattr(node, label) / unyt.electron_charge).round(4)) - + " e" - ) - else: - node.label = node.label + "\nNone" - elif label == "position": - if isinstance(getattr(node, label)[0], unyt.array.unyt_quantity): - node.label = ( - node.label - + "\n" - + str( - getattr(node, label).to("angstrom").round(2) * unyt.angstrom - ) - ) - else: - node.label = node.label + "\nNone" - else: - try: - node.label = node.label + "\n" + str(getattr(node, label)) - except AttributeError: - node.label = node.label + "\nNoneType" - if len(node.label) > 12: - node.label = "".join([line + "\n" for line in node.label.split()]) - labels[node] = node.label - - return labels - - -def show_parameter_values(topology, list_of_params, checkbox): - if checkbox: - try: - report_parameter_expression(topology, list_of_params[0]) - except AttributeError: - print("There are no values for the parameter expression") - else: - # TODO: Should be able to remove this blank print statement so that deselecting the - # checkbox removes the listed parameters. - print("") - - return - - -def report_parameter_expression(topology, param): - # return nicely printed parameters for a given edge. - if len(param) == 4: - try: - for dihedral in list(topology.dihedrals): - if dihedral.connection_members == ( - param[0], - param[1], - param[2], - param[3], - ): - print(dihedral.dihedral_type.expression, "\n") - print("{:<12} {:<15}".format("Parameter", "Value")) - for k, v in dihedral.dihedral_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) - except AttributeError: - print("Dihedral not typed") - elif len(param) == 3: - try: - for angle in list(topology.angles): - if angle.connection_members == (param[0], param[1], param[2]): - print(angle.angle_type.expression, "\n") - print("{:<12} {:<15}".format("Parameter", "Value")) - for k, v in angle.angle_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) - except AttributeError: - print("Angle not typed") - else: - raise ValueError( - "Parameters are not proper angles or dihedrals. Connection members are missing" - ) - - return - - -def get_edges(networkx_graph, atom_name1, atom_name2): - - # Create a list of the edges that have been selected corresponding to the selected atom_name1 - # and atom_name2. If both are None, then select edges that are missing bond types. - selectable_dict = {} - mia_bond_flag = 0 - if atom_name1 and atom_name2: - for edge in list(networkx_graph.edges): - if ( - (edge[0].name == atom_name1 - and edge[1].name == atom_name2) - or (edge[0].name == atom_name2 - and edge[1].name == atom_name1) - ): - selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) - elif atom_name1: - for edge in list(networkx_graph.edges): - if edge[0].name == atom_name1 or edge[1].name == atom_name1: - selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) - else: - for nodes in list(networkx_graph.edges.items()): - if nodes[1]["connection"].bond_type is None: - selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) - mia_bond_flag = 1 - if not mia_bond_flag: - return print("All bonds are typed") - # turn the dic selectable dict into a list of tuples. - return list(selectable_dict.items()) - - -def create_dict_of_labels_for_edges(selectable_dict, edge): - label0 = "" - label1 = "" - try: - label0 = edge[0].atom_type.name - except AttributeError: - print("An atomtype for {} is missing".format(edge[0].label)) - try: - label1 = edge[1].atom_type.name - except AttributeError: - print("An atomtype for {} is missing".format(edge[1].label)) - label = label0 + " --- " + label1 - if label in selectable_dict.keys(): - selectable_dict[label].append(edge) - else: - selectable_dict[label] = [] - selectable_dict[label].append(edge) - return selectable_dict - - -def plot_networkx_bonds( - networkx_graph, - atom_name1=None, - atom_name2=None, - list_of_labels=["atom_type.name"], - list_of_bonds=[], -): - - fig, ax = plt.subplots(1, 1, figsize=(8, 8)) - - # Create dictionaries of edges that correspond red thick lines for the selected bonds - edge_weights = {} - edge_colors = {} - for bond in networkx_graph.edges: - if bond in list_of_bonds: - edge_weights[bond] = 5 - edge_colors[bond] = "red" - else: - edge_weights[bond] = 1 - edge_colors[bond] = "k" - - ax = plot_networkx_nodes( - networkx_graph, - ax, - edge_weights=edge_weights, - edge_colors=edge_colors, - list_of_labels=list_of_labels, - atom_name=atom_name1, - ) - return fig, ax - - -def report_bond_parameters(topology, edge): - # return nicely printed bond parameters for a given edge. - for bond in topology.bonds: - try: - if bond.connection_members == edge[0] or bond.connection_members == ( - edge[0][1], - edge[0][0], - ): - print(bond.bond_type.expression, "\n") - print("{:<12} {:<15}".format("Parameter", "Value")) - for k, v in bond.bond_type.parameters.items(): - print("{:<12} {:<15}".format(k, v)) - except AttributeError: - print("The bond between {} is missing parameters".format(edge)) - - return - - -def plot_networkx_atomtypes( - topology, atom_name=None, list_of_labels=["atom_type.name"] -): - """Get a networkx plot showing the atom types in a topology object. - - Parameters - ---------- - topology : A gmso.core.topology.Topology object - This should be a gmso topology object that you want to visualize the atom types - that have been parameterized. - atom_name : The atom name which will have larger node sizes. - When drawing the networkx graph, all atoms with this name will be 3X as large. - This input will be of type string. To see what atom names are available, use - for site in topology.sites: - print(site.name) - - Returns - ------- - matplotlib.pyplot.figure - The drawn networkx plot of the topology on a matplotlib editable figures - - matplotlib.pyplot.axis - The axis information that corresponds to that figure. This output can be - shown using - matplotlib.pyplot.show() - """ - - fig, ax = plt.subplots(1, 1, figsize=(8, 8)) - networkx_graph = to_networkx(topology) - - # larger nodes for the atom selected - node_sizes = [] - for node in networkx_graph.nodes: - if node.name == atom_name: - node_sizes.append(900) - else: - node_sizes.append(300) - - ax = plot_networkx_nodes( - networkx_graph, - ax, - edge_weights=None, - edge_colors=None, - node_sizes=node_sizes, - list_of_labels=list_of_labels, - atom_name=atom_name, - ) - - return (fig, ax) - - -def highlight_networkx_edges(networkx_graph, list_of_edges): - # return edge parameters for the specified networkx edge list. - edge_weights = {} - edge_colors = {} - for edge in networkx_graph.edges: - edge_weights[edge] = 1 - edge_colors[edge] = "k" - for edge in networkx_graph.edges: - if edge in list_of_edges: - edge_weights[edge] = 5 - edge_colors[edge] = "r" - - return edge_weights, edge_colors diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index dd0f00029..7e3d1196e 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -1,16 +1,15 @@ import pytest -from gmso.formats.networkx import (highlight_networkx_edges, plot_networkx_atomtypes, +from gmso.utils.nx_utils import (highlight_networkx_edges, plot_networkx_atomtypes, plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, -show_parameter_values, interactive_networkx_atomtypes, interactive_networkx_bonds, -interactive_networkx_angles, interactive_networkx_dihedrals, select_dihedrals_from_sites, +show_parameter_values, select_dihedrals_from_sites, plot_networkx_nodes, plot_networkx_params, select_edges_on_networkx, get_edges, report_parameter_expression, report_bond_parameters, return_labels_for_nodes, -select_angles_from_sites, call_interactive_sites, select_edges, show_bond_info) -from gmso.formats.networkx import _get_formatted_atom_types_names_for +select_angles_from_sites, call_interactive_sites, select_edges, show_bond_info, +_get_formatted_atom_types_names_for) from gmso.tests.base_test import BaseTest -from gmso.utils.io import import_, has_matplotlib, has_ipywidgets, run_from_ipython +from gmso.utils.io import import_, has_matplotlib, has_ipywidgets from gmso.external.convert_networkx import to_networkx if has_matplotlib: @@ -100,30 +99,6 @@ def test_show_parameter_values(self,typed_ethane): with pytest.raises(ValueError): show_parameter_values(typed_ethane, [parameters], True) - @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") - def test_interactive_networkx_atomtypes(self,typed_ethane,capsys): - interactive_networkx_atomtypes(typed_ethane) - captured, err = capsys.readouterr() - assert isinstance(err,str) - - @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") - def test_interactive_networkx_bonds(self,typed_ethane,capsys): - interactive_networkx_bonds(typed_ethane) - captured, err = capsys.readouterr() - assert isinstance(err,str) - - @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") - def test_interactive_networkx_angles(self,typed_ethane,capsys): - interactive_networkx_angles(typed_ethane) - captured, err = capsys.readouterr() - assert isinstance(err,str) - - @pytest.mark.skipif(not run_from_ipython(), reason="Test work in jupyter notebooks only") - def test_interactive_networkx_dihedrals(self,typed_ethane,capsys): - interactive_networkx_dihedrals(typed_ethane) - captured, err = capsys.readouterr() - assert isinstance(err,str) - def test_select_dihedrals_from_sites(self,typed_ethane,capsys): graph = to_networkx(typed_ethane) select_dihedrals_from_sites(graph,typed_ethane) diff --git a/gmso/tests/test_utils.py b/gmso/tests/test_utils.py index 4fb29e2b9..bb48cb324 100644 --- a/gmso/tests/test_utils.py +++ b/gmso/tests/test_utils.py @@ -1,7 +1,7 @@ import unyt as u from gmso.utils.misc import unyt_to_hashable - +from gmso.utils.io import run_from_ipython def test_unyt_to_hashable(): hash(unyt_to_hashable(None)) @@ -14,3 +14,7 @@ def test_unyt_to_hashable(): assert hash(unyt_to_hashable(1 * u.nm)) != hash(unyt_to_hashable(1.01 * u.nm)) assert hash(unyt_to_hashable(1 * u.nm)) != hash(unyt_to_hashable(1.01 * u.second)) assert hash(unyt_to_hashable(1 * u.nm)) != hash(unyt_to_hashable([1, 1] * u.nm)) + +def test_has_ipython(): + __IPYTHON__ = None + assert run_from_ipython() is False diff --git a/gmso/utils/nx_utils.py b/gmso/utils/nx_utils.py new file mode 100644 index 000000000..074b2723b --- /dev/null +++ b/gmso/utils/nx_utils.py @@ -0,0 +1,662 @@ +import networkx as nx +import unyt + +from gmso.utils.io import import_, has_ipywidgets +widgets = import_('ipywidgets') +plt = import_('matplotlib.pyplot') +if has_ipywidgets: + from ipywidgets import interact, fixed + +from gmso.external.convert_networkx import to_networkx + +def plot_networkx_params(networkx_graph, list_of_edges): + """Get a networkx plot showing the specified edges in a networkx graph object. + + Parameters + ---------- + networkx_graph : A networkx.Graph object + This is a networkx graph object that can be created from a topology using the to_networkx + function found in gmso.external.convert_networkx + list_of_edges : a list of edges that should be shown on the plot + Will be of the shape [(node1,node2), (node2,node3), etc...] + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to a networkx drawn figure. This output can be + shown using + matplotlib.pyplot.show() + """ + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + edge_weights, edge_colors = highlight_networkx_edges(networkx_graph, list_of_edges) + ax = plot_networkx_nodes( + networkx_graph, ax, edge_weights=edge_weights, edge_colors=edge_colors + ) + + return (fig, ax) + + +def call_interactive_sites(Atom1, Atom2, networkx_graph, topology, list_of_labels): + list_of_edges = get_edges(networkx_graph, Atom1, Atom2) + if list_of_edges: + # Call the second level, which takes the selected bonds and shows them on the figure + interact( + select_edges, + list_of_bonds=list_of_edges, + list_of_labels=fixed(list_of_labels), + networkx_graph=fixed(networkx_graph), + topology=fixed(topology), + ) + else: + plot_networkx_bonds(networkx_graph, list_of_labels=list_of_labels) + + return + + +def select_edges(networkx_graph, topology, list_of_bonds, list_of_labels): + plot_networkx_bonds( + networkx_graph, list_of_labels=list_of_labels, list_of_bonds=list_of_bonds + ) + # The final level prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value=False, description="Show parameters") + interact( + show_bond_info, + w=checkbox, + topology=fixed(topology), + list_of_bonds=fixed(list_of_bonds), + ) + + return + +def show_bond_info(w, topology, list_of_bonds): + if w: + report_bond_parameters(topology, list_of_bonds) + else: + # TODO: Should be able to remove this blank print statement so that deselecting the + # checkbox removes the listed parameters. + print("") + + return + + +def select_angles_from_sites(networkx_graph, top, Atom1, Atom2, Atom3): + params_list = select_params_on_networkx(networkx_graph, [Atom1, Atom2, Atom3]) + if params_list: + edges_widget = widgets.Dropdown( + options=params_list, + layout=widgets.Layout(width="60%"), + style=dict(description_width="initial"), + description="Selected Edge", + ) + interact( + select_edges_on_networkx, + networkx_graph=fixed(networkx_graph), + top=fixed(top), + list_of_params=edges_widget, + ) + else: + plot_networkx_params(networkx_graph, list_of_edges=[]) + + return + + +def select_dihedrals_from_sites( + networkx_graph, top, Atom1=None, Atom2=None, Atom3=None, Atom4=None +): + + params_list = select_params_on_networkx( + networkx_graph, [Atom1, Atom2, Atom3, Atom4] + ) + if params_list: + edges_widget = widgets.Dropdown( + options=params_list, + layout=widgets.Layout(width="60%"), + style=dict(description_width="initial"), + description="Selected Edge", + ) + interact( + select_edges_on_networkx, + networkx_graph=fixed(networkx_graph), + top=fixed(top), + list_of_params=edges_widget, + ) + else: + plot_networkx_params(networkx_graph, list_of_edges=[]) + + return + + +def select_params_on_networkx(networkx_graph, atoms): + # Create a list of the edges that have been selected corresponding to the selected + # list of sites names. If all sites are None, then select edges that are missing parameter types. + # Works for selecting angles or dihedals based on the number of atoms sent in the `atoms` variable. + + list_of_params = [] + list_of_names = [] + mia_angle_flag = 0 + if len(atoms) == 3: + if all(atoms): + for node, angles in networkx_graph.nodes(data="angles"): + for angle in angles: + names = [member.name for member in angle.connection_members] + if atoms == [names[i] for i in [1, 0, 2]] or atoms == [ + names[i] for i in [1, 2, 0] + ]: + list_of_params.append(angle.connection_members) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) + elif all(atoms[0:2]): + for node, angles in networkx_graph.nodes(data="angles"): + for angle in angles: + names = [member.name for member in angle.connection_members] + if atoms[0:2] == names[1:3] or atoms[0:2] == list( + reversed(names[0:2]) + ): + list_of_params.append(angle.connection_members) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) + elif atoms[0]: + for node, angles in networkx_graph.nodes(data="angles"): + for angle in angles: + names = [member.name for member in angle.connection_members] + if atoms[0] == names[1]: + list_of_params.append(angle.connection_members) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) + else: + for node, angles in networkx_graph.nodes(data="angles"): + if not angles: + print("No angle parameters have been applied to this topology") + return + for angle in angles: + if angle.angle_type is None: + list_of_params.append(angle.connection_members) + list_of_names.append(_get_formatted_atom_types_names_for(angle)) + mia_angle_flag = 1 + if not mia_angle_flag: + print( + "All angles are typed. Select a central atom to look at the different angle_types." + ) + else: + print("Since no sites are input, angles with missing types are shown.") + + elif len(atoms) == 4: + # select a list of dihedrals + if all(atoms): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if ( + atoms == [names[i] for i in [2, 1, 0, 3]] + or atoms == [names[i] for i in [1, 2, 3, 0]] + or atoms == [names[i] for i in [1, 2, 0, 3]] + or atoms == [names[i] for i in [2, 1, 3, 0]] + ): + list_of_params.append(dihedral.connection_members) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) + elif all(atoms[0:3]): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if ( + atoms[0:3] == [names[i] for i in [1, 2, 3]] + or atoms[0:3] == [names[i] for i in [2, 1, 3]] + or atoms[0:3] == [names[i] for i in [1, 2, 0]] + or atoms[0:3] == [names[i] for i in [2, 1, 0]] + ): + list_of_params.append(dihedral.connection_members) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) + elif all(atoms[0:2]): + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): + for dihedral in dihedrals: + names = [member.name for member in dihedral.connection_members] + if atoms[0:2] == names[1:3] or atoms[0:2] == [names[2], names[1]]: + list_of_params.append(dihedral.connection_members) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) + else: + for node, dihedrals in networkx_graph.nodes(data="dihedrals"): + if not dihedrals: + print("No dihedral parameters have been applied to this topology") + return + for dihedral in dihedrals: + if dihedral.dihedral_type is None: + list_of_params.append(dihedral.connection_members) + list_of_names.append( + _get_formatted_atom_types_names_for(dihedral) + ) + mia_angle_flag = 1 + + if not mia_angle_flag: + print( + "All dihedrals are typed. Select two central atoms to see associated dihedrals." + ) + else: + print( + "Since no sites are input, dihedrals with missing types are shown." + ) + + else: + print("invalid atom selections") + + labeled_params = zip(list_of_names, list_of_params) + + # create a dict so each selected bond selects the proper edges on the networkx.graph object. + selectable_list = {} + for label, param in labeled_params: + if label in selectable_list.keys(): + selectable_list[label].append(param) + else: + selectable_list[label] = [] + selectable_list[label].append(param) + + # turn the dict selectable list into a list of tuples. + list_of_edges = [] + for key in selectable_list: + list_of_edges.append((key, selectable_list[key])) + + return list_of_edges + + +def _get_formatted_atom_types_names_for(connection): + names = [] + for member in connection.connection_members: + if not member.atom_type: + label = "" + else: + label = member.atom_type.name + names.append(label) + + return " --- ".join(names) + + +def select_edges_on_networkx(networkx_graph, top, list_of_params): + list_of_edges = get_networkx_edges(list_of_params) + plot_networkx_params(networkx_graph, list_of_edges=list_of_edges) + + # The interact prints the parameters of the selected bond using a checkbox. + checkbox = widgets.Checkbox(value=False, description="Show parameters") + interact( + show_parameter_values, + topology=fixed(top), + list_of_params=fixed(list_of_params), + checkbox=checkbox, + ) + + return + + +def get_networkx_edges(list_of_params): + # Return a list of edges within a given dihedral or angle + # Both orientations of every edge are saved, to guarentee the edge can be + # found on a networkx_graph.edge object. + list_of_edges = [] + if len(list_of_params[0]) == 4: + for param in list_of_params: + edge1 = (param[0], param[1]) + edge2 = (param[1], param[2]) + edge3 = (param[1], param[0]) + edge4 = (param[2], param[1]) + edge5 = (param[2], param[3]) + edge6 = (param[3], param[2]) + list_of_edges.append(edge1) + list_of_edges.append(edge2) + list_of_edges.append(edge3) + list_of_edges.append(edge4) + list_of_edges.append(edge5) + list_of_edges.append(edge6) + elif len(list_of_params[0]) == 3: + for param in list_of_params: + edge1 = (param[0], param[1]) + edge2 = (param[1], param[2]) + edge3 = (param[1], param[0]) + edge4 = (param[2], param[1]) + list_of_edges.append(edge1) + list_of_edges.append(edge2) + list_of_edges.append(edge3) + list_of_edges.append(edge4) + else: + raise ValueError( + "The parameters are not proper angles or dihedrals. Connection members are missing." + ) + + return list_of_edges + + +def plot_networkx_nodes( + networkx_graph, + ax, + atom_name=None, + edge_weights=None, + edge_colors=None, + node_sizes=None, + list_of_labels=["atom_type.name"], +): + # Place nodes at 2D positions related to position in the topology + layout = nx.drawing.layout.kamada_kawai_layout(networkx_graph) + + # Use this dictionary to color specific atoms + node_color_dict = { + "C": "grey", + "H": "silver", + "O": "red", + "N": "blue", + "Cl": "green", + } + node_colors = [] + for node in networkx_graph.nodes: + if node.name in list(node_color_dict.keys()): + node_colors.append(node_color_dict[node.name]) + else: + node_colors.append("black") + + # Node sizes determines if looking at just sites. Bonds, angles, dihedrals have edge_weights + # and edge_colors as identifiers + if node_sizes: + nx.draw( + networkx_graph, layout, ax, node_color=node_colors, node_size=node_sizes + ) + else: + nx.draw( + networkx_graph, + layout, + ax, + node_color=node_colors, + width=list(edge_weights.values()), + edge_color=list(edge_colors.values()), + ) + + # Offset positions to place labels + for atom, pos in layout.items(): + layout[atom] = pos + [0.09, 0] + + # Get a list of labels to plot + labels = identify_labels(networkx_graph, list_of_labels, atom_name) + # Plot labels on current figure + nx.draw_networkx_labels(networkx_graph, layout, labels, horizontalalignment="left") + ax.margins(0.3, 0.3) + + return ax + + +def identify_labels(networkx_graph, list_of_labels, atom_name=None): + # If atom_name specified, only show labels on that site. + # Otherwise, show labels for every atom from the label list. + if atom_name: + list_of_nodes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + list_of_nodes.append(node) + labels = return_labels_for_nodes(list_of_nodes, list_of_labels) + else: + labels = return_labels_for_nodes(list(networkx_graph.nodes), list_of_labels) + + return labels + + +def return_labels_for_nodes(list_of_nodes, list_of_labels): + # Get the label values for the sites specified. + # labels is a dict of each node and the labels to put there + labels = {} + for i, node in enumerate(list_of_nodes): + node.label = str(i) + ":" + str(node.name) + for label in list_of_labels: + if "." in label: + label1, label2 = label.split(".") + try: + node.label = ( + node.label + "\n" + str(getattr(getattr(node, label1), label2)) + ) + except AttributeError: + node.label = node.label + "\nNoneType" + elif label == "charge": + if isinstance(getattr(node, label), unyt.array.unyt_quantity): + node.label = ( + node.label + + "\n" + + str((getattr(node, label) / unyt.electron_charge).round(4)) + + " e" + ) + else: + node.label = node.label + "\nNone" + elif label == "position": + if isinstance(getattr(node, label)[0], unyt.array.unyt_quantity): + node.label = ( + node.label + + "\n" + + str( + getattr(node, label).to("angstrom").round(2) * unyt.angstrom + ) + ) + else: + node.label = node.label + "\nNone" + else: + try: + node.label = node.label + "\n" + str(getattr(node, label)) + except AttributeError: + node.label = node.label + "\nNoneType" + if len(node.label) > 12: + node.label = "".join([line + "\n" for line in node.label.split()]) + labels[node] = node.label + + return labels + + +def show_parameter_values(topology, list_of_params, checkbox): + if checkbox: + try: + report_parameter_expression(topology, list_of_params[0]) + except AttributeError: + print("There are no values for the parameter expression") + else: + # TODO: Should be able to remove this blank print statement so that deselecting the + # checkbox removes the listed parameters. + print("") + + return + + +def report_parameter_expression(topology, param): + # return nicely printed parameters for a given edge. + if len(param) == 4: + try: + for dihedral in list(topology.dihedrals): + if dihedral.connection_members == ( + param[0], + param[1], + param[2], + param[3], + ): + print(dihedral.dihedral_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) + for k, v in dihedral.dihedral_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("Dihedral not typed") + elif len(param) == 3: + try: + for angle in list(topology.angles): + if angle.connection_members == (param[0], param[1], param[2]): + print(angle.angle_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) + for k, v in angle.angle_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("Angle not typed") + else: + raise ValueError( + "Parameters are not proper angles or dihedrals. Connection members are missing" + ) + + return + + +def get_edges(networkx_graph, atom_name1, atom_name2): + + # Create a list of the edges that have been selected corresponding to the selected atom_name1 + # and atom_name2. If both are None, then select edges that are missing bond types. + selectable_dict = {} + mia_bond_flag = 0 + if atom_name1 and atom_name2: + for edge in list(networkx_graph.edges): + if ( + (edge[0].name == atom_name1 + and edge[1].name == atom_name2) + or (edge[0].name == atom_name2 + and edge[1].name == atom_name1) + ): + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) + elif atom_name1: + for edge in list(networkx_graph.edges): + if edge[0].name == atom_name1 or edge[1].name == atom_name1: + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) + else: + for nodes in list(networkx_graph.edges.items()): + if nodes[1]["connection"].bond_type is None: + selectable_dict = create_dict_of_labels_for_edges(selectable_dict, edge) + mia_bond_flag = 1 + if not mia_bond_flag: + return print("All bonds are typed") + # turn the dic selectable dict into a list of tuples. + return list(selectable_dict.items()) + + +def create_dict_of_labels_for_edges(selectable_dict, edge): + label0 = "" + label1 = "" + try: + label0 = edge[0].atom_type.name + except AttributeError: + print("An atomtype for {} is missing".format(edge[0].label)) + try: + label1 = edge[1].atom_type.name + except AttributeError: + print("An atomtype for {} is missing".format(edge[1].label)) + label = label0 + " --- " + label1 + if label in selectable_dict.keys(): + selectable_dict[label].append(edge) + else: + selectable_dict[label] = [] + selectable_dict[label].append(edge) + return selectable_dict + + +def plot_networkx_bonds( + networkx_graph, + atom_name1=None, + atom_name2=None, + list_of_labels=["atom_type.name"], + list_of_bonds=[], +): + + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + + # Create dictionaries of edges that correspond red thick lines for the selected bonds + edge_weights = {} + edge_colors = {} + for bond in networkx_graph.edges: + if bond in list_of_bonds: + edge_weights[bond] = 5 + edge_colors[bond] = "red" + else: + edge_weights[bond] = 1 + edge_colors[bond] = "k" + + ax = plot_networkx_nodes( + networkx_graph, + ax, + edge_weights=edge_weights, + edge_colors=edge_colors, + list_of_labels=list_of_labels, + atom_name=atom_name1, + ) + return fig, ax + + +def report_bond_parameters(topology, edge): + # return nicely printed bond parameters for a given edge. + for bond in topology.bonds: + try: + if bond.connection_members == edge[0] or bond.connection_members == ( + edge[0][1], + edge[0][0], + ): + print(bond.bond_type.expression, "\n") + print("{:<12} {:<15}".format("Parameter", "Value")) + for k, v in bond.bond_type.parameters.items(): + print("{:<12} {:<15}".format(k, v)) + except AttributeError: + print("The bond between {} is missing parameters".format(edge)) + + return + + +def plot_networkx_atomtypes( + topology, atom_name=None, list_of_labels=["atom_type.name"] +): + """Get a networkx plot showing the atom types in a topology object. + + Parameters + ---------- + topology : A gmso.core.topology.Topology object + This should be a gmso topology object that you want to visualize the atom types + that have been parameterized. + atom_name : The atom name which will have larger node sizes. + When drawing the networkx graph, all atoms with this name will be 3X as large. + This input will be of type string. To see what atom names are available, use + for site in topology.sites: + print(site.name) + + Returns + ------- + matplotlib.pyplot.figure + The drawn networkx plot of the topology on a matplotlib editable figures + + matplotlib.pyplot.axis + The axis information that corresponds to that figure. This output can be + shown using + matplotlib.pyplot.show() + """ + + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + networkx_graph = to_networkx(topology) + + # larger nodes for the atom selected + node_sizes = [] + for node in networkx_graph.nodes: + if node.name == atom_name: + node_sizes.append(900) + else: + node_sizes.append(300) + + ax = plot_networkx_nodes( + networkx_graph, + ax, + edge_weights=None, + edge_colors=None, + node_sizes=node_sizes, + list_of_labels=list_of_labels, + atom_name=atom_name, + ) + + return (fig, ax) + + +def highlight_networkx_edges(networkx_graph, list_of_edges): + # return edge parameters for the specified networkx edge list. + edge_weights = {} + edge_colors = {} + for edge in networkx_graph.edges: + edge_weights[edge] = 1 + edge_colors[edge] = "k" + for edge in networkx_graph.edges: + if edge in list_of_edges: + edge_weights[edge] = 5 + edge_colors[edge] = "r" + + return edge_weights, edge_colors From 494553d4fc84e946034662e9184ac2fcadab2383 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Mon, 3 May 2021 15:14:05 -0500 Subject: [PATCH 60/62] add calls to io/nx_utils for functions in formats/networkx.py --- gmso/formats/__init__.py | 2 ++ gmso/formats/networkx.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 3ea0c9f9e..1cd51bcaa 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,3 +3,5 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata +from .networkx import interactive_networkx_atomtypes, interactive_networkx_bonds, + interactive_networkx_angles, interactive_networkx_dihedrals diff --git a/gmso/formats/networkx.py b/gmso/formats/networkx.py index 85b184198..18701957d 100644 --- a/gmso/formats/networkx.py +++ b/gmso/formats/networkx.py @@ -62,7 +62,7 @@ def interactive_networkx_atomtypes(topology, list_of_labels=None): @interact def get_interactive_sites(Label=list_of_labels, Atom_Name=names_tuple): # Plot atom types for the widget inputs - plot_networkx_atomtypes(topology, Atom_Name, [Label]) + nx_utils.plot_networkx_atomtypes(topology, Atom_Name, [Label]) return @@ -143,7 +143,7 @@ def interactive_networkx_bonds(topology, additional_labels=None): ) ) interact( - call_interactive_sites, + nx_utils.call_interactive_sites, Atom1=atom_selection[0], Atom2=atom_selection[1], list_of_labels=fixed(list_of_labels), @@ -222,7 +222,7 @@ def interactive_networkx_angles(topology): ) ) interact( - select_angles_from_sites, + nx_utils.select_angles_from_sites, networkx_graph=fixed(networkx_graph), top=fixed(topology), Atom1=atom_selection[0], @@ -307,7 +307,7 @@ def interactive_networkx_dihedrals(topology): ) ) interact( - select_dihedrals_from_sites, + nx_utils.select_dihedrals_from_sites, networkx_graph=fixed(networkx_graph), top=fixed(topology), Atom1=atom_selection[0], From 1e7cd862dec8aee137353ad693e8749e06e27543 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Thu, 6 May 2021 10:24:31 -0500 Subject: [PATCH 61/62] fix __init__.py for importing networkx functionality --- gmso/formats/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gmso/formats/__init__.py b/gmso/formats/__init__.py index 1cd51bcaa..7903eff28 100644 --- a/gmso/formats/__init__.py +++ b/gmso/formats/__init__.py @@ -3,5 +3,5 @@ from .gsd import write_gsd from .xyz import read_xyz, write_xyz from .lammpsdata import write_lammpsdata -from .networkx import interactive_networkx_atomtypes, interactive_networkx_bonds, - interactive_networkx_angles, interactive_networkx_dihedrals +from .networkx import (interactive_networkx_atomtypes, interactive_networkx_bonds, +interactive_networkx_angles, interactive_networkx_dihedrals) From 9f3ad3130cbe93ccf3c1be6137691883e01dfdf6 Mon Sep 17 00:00:00 2001 From: CalCraven Date: Tue, 11 May 2021 13:49:26 -0500 Subject: [PATCH 62/62] improve logic for unit tests --- gmso/tests/test_networkx.py | 51 ++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/gmso/tests/test_networkx.py b/gmso/tests/test_networkx.py index 7e3d1196e..fbce0537f 100644 --- a/gmso/tests/test_networkx.py +++ b/gmso/tests/test_networkx.py @@ -1,4 +1,5 @@ import pytest +import networkx as nx from gmso.utils.nx_utils import (highlight_networkx_edges, plot_networkx_atomtypes, plot_networkx_bonds, select_params_on_networkx, get_networkx_edges, identify_labels, @@ -23,7 +24,6 @@ class TestNetworkx(BaseTest): def test_highlight_networkx_edges(self, typed_ethane): list(typed_ethane.angles)[0].angle_type = None list(typed_ethane.dihedrals)[0].dihedral_type = None - graph = to_networkx(typed_ethane) list_edges = list(graph.edges)[0:3] test_edge_weights, test_edge_colors = highlight_networkx_edges(graph, list_edges[0:2]) @@ -33,12 +33,10 @@ def test_highlight_networkx_edges(self, typed_ethane): assert test_edge_colors[list_edges[0]] == 'r' assert test_edge_colors[list_edges[1]] == 'r' assert test_edge_colors[list_edges[2]] == 'k' - def test_plot_networkx_atomtypes(self,typed_ethane): fig, ax = plot_networkx_atomtypes(typed_ethane,atom_name=None) test_fig, test_ax = plt.subplots(1) - assert isinstance(fig, test_fig.__class__) assert isinstance(ax, test_ax.__class__) @@ -46,7 +44,6 @@ def test_plot_networkx_bonds(self,typed_ethane): graph = to_networkx(typed_ethane) fig, ax = plot_networkx_bonds(graph) test_fig, test_ax = plt.subplots(1) - assert isinstance(fig, test_fig.__class__) assert isinstance(ax, test_ax.__class__) @@ -60,24 +57,38 @@ def test_select_params_on_networkx(self,typed_ethane,capsys): assert len(select_params_on_networkx(graph,['C',None,None])) == 3 assert len(select_params_on_networkx(graph,['C','C','H', None])) == 1 assert len(select_params_on_networkx(graph,['C','C', None, None])) == 1 - for angle in typed_ethane.angles: - angle.angle_type = None - graph = to_networkx(typed_ethane) - select_params_on_networkx(graph,['C','C','H']) - for angle in typed_ethane.angles: - angle = None - graph = to_networkx(typed_ethane) - select_params_on_networkx(graph,['C','C','H']) - for dihedral in typed_ethane.dihedrals: - dihedral.dihedral_type = None - graph = to_networkx(typed_ethane) - select_params_on_networkx(graph,['C','C','H']) - for dihedral in typed_ethane.dihedrals: - dihedral = None + + def test_select_params_on_networkx_output(self,typed_ethane,capsys): graph = to_networkx(typed_ethane) - select_params_on_networkx(graph,['C','C','H']) + select_params_on_networkx(graph,[None, None, None]) captured, err = capsys.readouterr() - assert isinstance(err,str) + assert captured.startswith("All angles") + select_params_on_networkx(graph,[None, None, None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("All dihedrals") + for node, angles in graph.nodes(data="angles"): + if angles[0]: + angles[0].angle_type = None + result = select_params_on_networkx(graph,[None, None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("Since no sites are input, angles") + for node, dihedrals in graph.nodes(data="dihedrals"): + if dihedrals[0]: + dihedrals[0].dihedral_type = None + result = select_params_on_networkx(graph,[None, None, None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("Since no sites are input, dihedrals") + nx.set_node_attributes(graph,None,name="angles") + result = select_params_on_networkx(graph,[None, None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("No angle") + nx.set_node_attributes(graph,None,name="dihedrals") + result = select_params_on_networkx(graph,[None, None, None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("No dihedral") + result = select_params_on_networkx(graph,[None, None]) + captured, err = capsys.readouterr() + assert captured.startswith("invalid") def test__get_formatted_atom_types_names_for(self,typed_ethane): graph = to_networkx(typed_ethane)