Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draw bonds computed by ASE #535

Merged
merged 36 commits into from
Nov 17, 2023
Merged

Draw bonds computed by ASE #535

merged 36 commits into from
Nov 17, 2023

Conversation

yakutovicha
Copy link
Member

fixes #48
supercedes #377

In nglview if a system is too big bonds will not be computed.
I bypass the "ball+stick" representation defining it as "spacefill" plus shapes that are computed via ase connectivity matrix.

I would not draw also the atom spheres through shapes otherwise it would be a problem to pick atoms with mouse selection

Despite I tested it also with ~1000 atoms it is meant to work on smaller system sizes.

The image below is outdated, just the "ball+stick" representation is needed to produce the image

image (15)

To Do:

  • Consider using Enum for the dropdown widgets.

@yakutovicha yakutovicha added the enhancement New feature or request label Nov 6, 2023
Copy link

codecov bot commented Nov 7, 2023

Codecov Report

Attention: 1 lines in your changes are missing coverage. Please review.

Comparison is base (f403330) 85.88% compared to head (0cd8708) 87.12%.

Files Patch % Lines
aiidalab_widgets_base/viewers.py 98.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #535      +/-   ##
==========================================
+ Coverage   85.88%   87.12%   +1.23%     
==========================================
  Files          27       27              
  Lines        4618     4643      +25     
==========================================
+ Hits         3966     4045      +79     
+ Misses        652      598      -54     
Flag Coverage Δ
python-3.10 87.12% <98.57%> (+1.23%) ⬆️
python-3.8 87.15% <98.57%> (+1.23%) ⬆️
python-3.9 87.15% <98.57%> (+1.23%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@yakutovicha yakutovicha marked this pull request as draft November 7, 2023 23:37
@yakutovicha yakutovicha marked this pull request as ready for review November 9, 2023 08:56
@yakutovicha
Copy link
Member Author

[ ] Consider using Enum for the dropdown widgets.

This item is not yet implemented, but I think we can discuss it first during today's meeting and then decide what to do.

The rest is ready, please review.

@yakutovicha yakutovicha added this to the v2.1.0 milestone Nov 9, 2023
Copy link
Member

@superstar54 superstar54 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yakutovicha Thanks for the work. It's important for the large structure. I made a few change requests.

Also two comments:

  • better to add a test for the _compute_bonds function.
  • There is a variable called uuid, which is the Unique identifier for the representation.. At first glance, I mixed it with the AiiDA node uuid. I suggest using another name, e.g. style_id.

aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved

radius = radius / 5

cutoff = ase.neighborlist.natural_cutoffs(structure, mult=1.09)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a comment on why you choose 1.09.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, while testing on a number of systems in some cases I had bonds where I did not 'want' them in in other cases I did not have bonds and I wanted them. This 1.09 was a good compromise. Feel free to change to a value that produces even better results.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 6e547fc

aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
Comment on lines 604 to 606
half_bond_point = v1 + bond_length * Radius[atom1.symbol] / (
Radius[atom1.symbol] + Radius[atom2.symbol]
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The math here is wrong. You need both the vector and the length to get the middle point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming here is confusing, I guess. This is to place the point where the bond changes colour. We find this point using the proportion of the atomic radius of the atoms.

I renamed the variable to intermediate_point in d4f0a06

Copy link
Member

@superstar54 superstar54 Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We find this point using the proportion of the atomic radius of the atoms.

No. The correct math is:

# get the bond_vector from the `neighbor_list` too.
normal = bond_vector/bond_length
r1 = Radius[atom1.symbol]
r2 = Radius[atom2.symbol]
intermediate_point = v1 + r1*normal + (bond_length - r1 - r2)/2*normal
# you can use the short term:  intermediate_point = v1 + (bond_length + r1 - r2)/2*normal

Copy link
Member Author

@yakutovicha yakutovicha Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is wrong with our approach.

pos1 - location of the first atom
pos2 - location of the second atom
bond_vector is a vector connecting the first atom and the second atom.
r1 - is the vdW radius of the first atom
r2 - is the vdW radius of the first atom

The intermediate point is a percentage of the vector r1 relative its contribution to the sum of two vdW radii.

bond_vector = pos2 - pos1

atom1_contribution = r1 / (r1 + r2)
atom2_contribution = r2 / (r1 + r2)

intermediate_point = pos1 + bond_vector * atom1_contribution

or

intermediate_point = pos2 - bond_vector * atom2_contribution

atom1_contribution + atom2_contribution = r1 / (r1 + r2) + r2 / (r1 + r2) = (r1 + r2) / (r1 + r2) = 1

I'd appreciate it if you could take a moment to double-check our work before mentioning any concerns about the math. Thank you!

Copy link
Member Author

@yakutovicha yakutovicha Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, we discussed with @superstar54 and @cpignedoli, where we agreed to cut the bond at the middle point between two atoms

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9ee0f99

self._viewer.set_representations(nglview_params, component=0)
self._viewer.add_unitcell()
self._viewer._add_shape(set(bonds), name="bonds")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it is not obvious in the GUI, but you add new shapes for the bonds, and haven't removed the default bond from the nglview itself, thus the two bonds overlap.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a hard nut to crash, but I think I managed in 066aa4a. What do you think @cpignedoli?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After setting different radii for different atoms in this commit, the wrong intermediate_point becomes obvious. Before, all atoms have the same radius in the ball+stick model, thus intermediate_point is (only) correct in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we do not remove default bonds: we never use the ngl representation "ball+stik" we always use spacefill (no bonds) and add bond shapes in the case we use the "fake ball+stik"

Copy link
Member

@cpignedoli cpignedoli Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v1 + bond_vector * Radius[atom1.symbol] / ( Radius[atom1.symbol] + Radius[atom2.symbol]
bond_vector goes from atom1 to atom2.
we will add to atom1 a vector bond_v*r1/(r1+r2) and to atom2 a vector, in opposite direction (or from intermediate point to atom2), bond_v*r2/(r1+r2) in total: bond_v*(r1+r2)/(r1+r2) = bond_v and the two portions of bonds will have length proportional to the size of the spheres

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @yakutovicha. I only have some tiny requests in terms of code style, @superstar54 did a more thorough test.

.github/workflows/ci.yml Show resolved Hide resolved
aiidalab_widgets_base/dicts.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Show resolved Hide resolved
Copy link
Contributor

@danielhollas danielhollas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already got two great reviews so I am leaving this one alone. :-)

yakutovicha and others added 2 commits November 15, 2023 22:10
Co-authored-by: Jusong Yu <jusong.yeu@gmail.com>
@yakutovicha
Copy link
Member Author

@cpignedoli please comment on where all the numbers are coming from.

@superstar54
Copy link
Member

Hi @yakutovicha, Thanks for the update! I am still worried about the test on the bond.

This function is executed during text, you can check the codecov report, for instance.

It's executed, but the result isn't verified. I would suggest running this function and checking the result bonds using a simple structure as input, e.g., H2O. See my comment on the math to calculate the intermediate_point.

@yakutovicha
Copy link
Member Author

yakutovicha commented Nov 16, 2023

I would suggest running this function and checking the result bonds using a simple structure as input, e.g., H2O.

Done in 9986465

unkcpz
unkcpz previously approved these changes Nov 16, 2023
Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My requests are all addressed, you have my approval. Thanks for the nice work! @yakutovicha

Copy link
Member

@superstar54 superstar54 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yakutovicha, Thanks for the update. Since bond is an important component in the viewer, and this PR is supposed to handle large systems with > 1000 bonds. So I added a comment on the performance.

aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
@superstar54
Copy link
Member

superstar54 commented Nov 17, 2023

@unkcpz @yakutovicha I moved the if outside the for loop, which makes the code ~1 time faster. Please review.

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small requests should be easy to address. @superstar54

Comment on lines 563 to 565
bonds = []
if len(structure) <= 1:
return bonds
Copy link
Member

@unkcpz unkcpz Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bonds = []
if len(structure) <= 1:
return bonds
if len(structure) <= 1:
return []

I think this is safe? No other place uses bonds, and all conditions return list.

@@ -554,9 +555,74 @@ def _observe_all_representations(self, change):
if change["new"]:
self._all_representations[-1].viewer_class = self

def _compute_bonds(self, structure, radius=1.0, color="element", povray=False):
"""Create an array of bonds for the structure."""
Copy link
Member

@unkcpz unkcpz Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Create an array"? But the return value is a list.

v2 = v1 + bond_vectors * 0.5

# Choose the correct way for computing the cylinder.
def povray_cylinder(v1, v2, radius, color):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree using function makes it more clean for me. Can you move it out as staticmethod instead of nesting it here? If it is only used in the class, change the name to _povray_cylinder. Same for cylinder -> _cylinder and move it out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I changed the inner function into a private helper function.

aiidalab_widgets_base/viewers.py Outdated Show resolved Hide resolved
@superstar54
Copy link
Member

Hi @unkcpz, thanks for the review. I made the changes as you suggested. Please have another look.

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks all good. The pr is waiting for your approve @superstar54 😄

@superstar54 superstar54 self-requested a review November 17, 2023 20:58
@unkcpz unkcpz merged commit ca8322d into master Nov 17, 2023
15 checks passed
@unkcpz unkcpz deleted the feature/draw-atomic-bonds branch November 17, 2023 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

nglview: figure out why bonds are not always shown by deffault
5 participants