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

Function to build heteroribbons #421

Merged
merged 8 commits into from
Jul 17, 2023
Merged

Conversation

pfebrer
Copy link
Contributor

@pfebrer pfebrer commented Jan 12, 2022

Closes #420

I still need confirmation from @tfrederiksen that there's nothing super wrong with the way it can be used :)

Is the code structure/arguments fine?

@pfebrer pfebrer changed the title Function to build heteroribbonds Function to build heteroribbons Jan 12, 2022
@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 12, 2022

Also if you prefer other names for the functions I don't mind, Thomas used composite, I don't know which one is better.

@codecov
Copy link

codecov bot commented Jan 12, 2022

Codecov Report

Merging #421 (eefa952) into main (fc1da2c) will decrease coverage by 0.01%.
The diff coverage is 94.72%.

@@            Coverage Diff             @@
##             main     #421      +/-   ##
==========================================
- Coverage   87.34%   87.34%   -0.01%     
==========================================
  Files         373      374       +1     
  Lines       47705    47959     +254     
==========================================
+ Hits        41669    41889     +220     
- Misses       6036     6070      +34     
Impacted Files Coverage Δ
src/sisl/__init__.py 97.56% <ø> (ø)
src/sisl/_environ.py 86.84% <ø> (ø)
src/sisl/_help.py 69.82% <0.00%> (ø)
src/sisl/_typing.py 0.00% <0.00%> (ø)
src/sisl/geom/category/base.py 92.85% <ø> (ø)
src/sisl/io/_help.py 82.14% <ø> (ø)
src/sisl/io/bigdft/sile.py 100.00% <ø> (ø)
src/sisl/io/gulp/sile.py 100.00% <ø> (ø)
src/sisl/io/orca/sile.py 100.00% <ø> (ø)
src/sisl/io/scaleup/sile.py 100.00% <ø> (ø)
... and 252 more

... and 38 files with indirect coverage changes

Copy link
Owner

@zerothi zerothi left a comment

Choose a reason for hiding this comment

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

Great job! Minor changes here and there. @tfrederiksen the name of the function, what do you think?

@tfrederiksen
Copy link
Contributor

Great work @pfebrer ! Overall it looks great to me. However, I am not fully convinced about the use of imaginary numbers to specify "open" structures. I think it follows naturally that each consequtive "column" in the structure needs to be of alternating type. Hence, for a given input of widths and repetitions are only two possible structures that can be created. This could be one flag to the function, say invert=True/False. What do you think?

@tfrederiksen
Copy link
Contributor

Great job! Minor changes here and there. @tfrederiksen the name of the function, what do you think?

I think it is good (and relatively short) as it is. In the literature I see mostly the formulation "graphene nanoribbon heterojunctions" for these structures. Other possibilities could therefore be the pair nanoribbon_heterojunction and gnr_heterojunction? (Or composite_nanoribbon and composite_gnr?)

Could the method eventually be extended to 2D nanomeshes?

@zerothi
Copy link
Owner

zerothi commented Jan 13, 2022

I think it is good (and relatively short) as it is. In the literature I see mostly the formulation "graphene nanoribbon heterojunctions" for these structures. Other possibilities could therefore be the pair nanoribbon_heterojunction and gnr_heterojunction? (Or composite_nanoribbon and composite_gnr?)

I like nanoribbon_hetero, kind of feel the "junction" is just too long. ;)

Hmm.. On the other hand explicitness is good... hmmm. Since hetero is already in the litterature, I like that name.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

I think it follows naturally that each consequtive "column" in the structure needs to be of alternating type. Hence, for a given input of widths and repetitions are only two possible structures that can be created. This could be one flag to the function, say invert=True/False.

It follows naturally in most cases, but there are two specific cases where it does not follow naturally:

  • At the beginning of the unit cell: The configuration of the initial string forces the creation of completely different ribbons. One can't be related to the other just by a simple invert operation. As you noted, you probably can always find a string of atoms where you can start with the "sisl provided" configuration and then shift, but I don't believe that this is a good solution :(

  • When an even section comes in and the previous section was odd: This case is fully defined if you specify the initial atom/final atom as in your approach of course, but not in this case where the ribbons are automatically aligned and 0 means "aligned". There are two valid ways to "perfectly align" an odd ribbon with an even ribbon:

    • If the bottom edge of the even ribbon is open ("closed" configuration), they are aligned at the bottom:

      graphene_heteroribbon([(7, 1), (8, 1)])

      newplot - 2022-01-13T115922 013
      In the case where the previous section is closed, a shift is then allowed, but only upwards, as a downward shift would leave the last atom of the even section dangling:

      graphene_heteroribbon([(7, 2), (8, 1, 1)])

      newplot - 2022-01-13T120710 062

    • If the top edge of the even ribbon is open ("open" configuration), they are aligned at the top:

      graphene_heteroribbon([(7, 1), (8, 1j)])

      newplot - 2022-01-13T121351 198
      And then in the case when the previous section is closed, only a downwards shift is allowed.

    If there is only one such choice, the obtained final structures are related by a mirror symmetry, certainly, as you note. But if there is more than one such choice, then it isn't the case anymore. Therefore, you need to have the ability to choose.

That's why I only allow imaginary numbers in these two cases. We could provide an extra field to specify this change instead of using this notation, but as I told Nick this has the pitfall that you need to specify all the parameters for the section, because sections are passed as tuples. Whatever you prefer.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

I like nanoribbon_hetero, kind of feel the "junction" is just too long. ;)

But then the graphene version of it would be graphene_nanoribbon_hetero ? 😅

@zerothi
Copy link
Owner

zerothi commented Jan 13, 2022

That's why I only allow imaginary numbers in these two cases. We could provide an extra field to specify this change instead of using this notation, but as I told Nick this has the pitfall that you need to specify all the parameters for the section, because sections are passed as tuples. Whatever you prefer.

But that is the same as the other arguments in the Section tuple, no?

I like nanoribbon_hetero, kind of feel the "junction" is just too long. ;)

But then the graphene version of it would be graphene_nanoribbon_hetero ? sweat_smile

Agreed... Hmm....
I also like your original one.. I don't know ;) !

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

But that is the same as the other arguments in the Section tuple, no?

Yes, it's just that the more parameters you add, the worse it gets :) Specially if parameters are not easily ordered by importance. E.g. we can all agree that length will be very used so it makes sense that it is the first optional argument.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

Also, for the name: It is not really a heterojunction but potentially several heterojunctions, so probably calling it heterojunction would not picture what the function does exactly.

@zerothi
Copy link
Owner

zerothi commented Jan 13, 2022

But that is the same as the other arguments in the Section tuple, no?

Yes, it's just that the more parameters you add, the worse it gets :) Specially if parameters are not easily ordered by importance. E.g. we can all agree that length will be very used so it makes sense that it is the first optional argument.

True... Perhaps you should add the possibility of accepting dict as sections.

     sections = [Heteroribbon_section(*section) for section in sections]
# to
def conv(s):
    if isinstance(s):
        return Heteroribbon_section(**s)
    return Heteroribbon_section(*s)
sections = [conv(section) for section in sections]

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

Well in that case you might as well build the section object yourself instead of passing a dict :)

@zerothi
Copy link
Owner

zerothi commented Jan 13, 2022

Well in that case you might as well build the section yourself instead of passing a dict :)

In any case I think it might be useful for some.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

However would you then introduce the open parameter? That forces the user to replace a list with a dict just to change the configuration.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

The imaginary number seems natural to me. 2j is just 2 shifted by a phase :)

@zerothi
Copy link
Owner

zerothi commented Jan 13, 2022

I don't know about this, @tfrederiksen what do you say?

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

Another option could be using negative numbers (-2 instead of 2j)

@tfrederiksen
Copy link
Contributor

tfrederiksen commented Jan 13, 2022

  • At the beginning of the unit cell: The configuration of the initial string forces the creation of completely different ribbons. One can't be related to the other just by a simple invert operation.

I think I didn't express my idea correctly. It is not to apply an invert operation after creating the structure, but to let the user select the "phase" of the first column (open/closed). I fully agree that this choice will create different structures. But the consecutive columns must respect phase alternation, no?. Indeed the meaning of shift would have to be redefined (no longer just multiples of the lattice vector in y).

My point is just that from the user perspective it may be simpler if one does not have to keep track of the phase, but just specify opening type and then widths/reps/shifts.

Edit: Additionally, in my opinion it is not very clear from the user perspective if a given "column" should be denoted "closed" (real) or "open" (imaginary). It seems ambiguous to me.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

Oooh ok I understand now, so

graphene_nanoribbon([(7, 2), (11, 2)], open_start=True) # or invert=True

would be the old

graphene_nanoribbon([(7, 2j), (11, 2)])

?

I like it :) But then the second case still remains.

@tfrederiksen
Copy link
Contributor

Oooh ok I understand now, so

graphene_nanoribbon([(7, 2), (11, 2)], open_start=True) # or invert=True

would be the old

graphene_nanoribbon([(7, 2j), (11, 2)])

?

Exactly!

I like it :) But then the second case still remains.

What do you mean by second case? The meaning of shift would then have to refer to half the lattice vector in y.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

Additionally, in my opinion it is not very clear from the user perspective if a given "column" should be denoted "closed" (real) or "open" (imaginary). It seems ambiguous to me.

For odd columns I think it's very well defined. Why not? You have to consider if you are looking at it from the left or the right, but that seems fine to me. I mean I do not call the column "open" or close, the same column can be an open or closed border. For even columns it is arbitrary I agree, but once you establish which is which it isn't ambiguous either.

@tfrederiksen
Copy link
Contributor

Additionally, in my opinion it is not very clear from the user perspective if a given "column" should be denoted "closed" (real) or "open" (imaginary). It seems ambiguous to me.

For odd columns I think it's very well defined. Why not? You have to consider if you are looking at it from the left or the right, but that seems fine to me. I mean I do not call the column "open" or close, the same column can be an open or closed border. For even columns it is arbitrary I agree, but once you establish which is which it isn't ambiguous either.

My point is just that understanding all of this would not be required by the user. One could just choose one of two possible "openings" and then build anything from there.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

What do you mean by second case? The meaning of shift would then have to refer to half the lattice vector in y.

Hmm ok, then you'd have to, for example always align at the bottom and then shift. I get your point. But I don't think that makes it more intuitive necessarily. That might make it harder to build valid ribbons because you have to know the allowed shift values. The main point of building this function for me was that I didn't have to think about which shifts are valid and which are not. I like the idea of just going by:

  • 0: aligned
  • -1: first possible downward shift (if available)
  • 1: first possible upwards shift (if available)

If the user needs to specify exactly where to place the ribbon (your proposal) I believe they have to think even more about phases, since otherwise they won't generate valid ribbons.

@tfrederiksen
Copy link
Contributor

What do you mean by second case? The meaning of shift would then have to refer to half the lattice vector in y.

Hmm ok, then you'd have to, for example always align at the bottom and then shift. I get your point. But I don't think that makes it more intuitive necessarily. That might make it harder to build valid ribbons because you have to know the allowed shift values. The main point of building this function for me was that I didn't have to think about which shifts are valid and which are not. I like the idea of just going by:

* `0`: aligned

* `-1`: first possible downward shift (if available)

* `1`: first possible upwards shift (if available)

If the user needs to specify exactly where to place the ribbon (your proposal) I believe they have to think even more about phases, since otherwise they won't generate valid ribbons.

All integer shifts should be valid (in units of half the lattice vector). But clearly the function needs to pick the only meaningful phase for that column not to mess up the bonding pattern.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 13, 2022

No, they are not, because some shifts leave dangling bonds at the edges of the ribbon

@zerothi
Copy link
Owner

zerothi commented Jan 28, 2022

After discussing with @pfebrer we have come up with some other ideas that @pfebrer will start exploring. :)

I will commence the 0.12 release and then this may enter in a minor release at a later point.

@zerothi
Copy link
Owner

zerothi commented Jan 9, 2023

@pfebrer we had a meeting and you were doing some explorations, but now I can't recall those explorations? What is the status here?

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 9, 2023

We thought about having some way to merge geometries in a smart way by adding two classes: Section and Junction,
where a junction would know and control how to merge the two geometries, doing something like this:

full_geom = Section() + Junction() + Section() + ...

Then we thought that we shouldn't limit it to Junctions and in general this could be Actions, being Section also something more general like GeometrySpec or whatever. I think we thought about this in particular to create defects.

full_geom = MetalSurface("Au", "111") + DefectAction() + Junction(adapt_to_lattice=0) + MetalSurface("Cu", "111")

for example would create an Au surface, then create a defect on it, and then join it with a Cu surface that would use the same lattice as gold.

This was a nice idea but I couldn't find a way to make it general and at the same time not much more difficult to use than the current approach for graphene nanoribbons which is just based on passing a list of tuples.

Now this is stalled, and for now I did not have plans to work on it 😅

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 9, 2023

The advantage of this approach was that there would be a general API for creating composite structures, instead of each method implementing one.

@zerothi
Copy link
Owner

zerothi commented Jan 9, 2023

Great, thanks for the reminder.

Ok, so what about I run it through again, and then we just merge this? Then we can always think about the next thing.

After having read your recap of our conversation, perhaps the most ideal thing would be to fix Geometry.add/append to accept a class that fixes the merging instead of relying on axis fixtures etc.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jan 9, 2023

Ok, so what about I run it through again, and then we just merge this? Then we can always think about the next thing.

What do you mean by "run it through"?

After having read your recap of our conversation, perhaps the most ideal thing would be to fix Geometry.add/append to accept a class that fixes the merging instead of relying on axis fixtures etc.

Yes but that would require the Geometry itself to have some knowledge of which type of geometry it is. Otherwise this merging class will have no idea what to do with it.

@zerothi
Copy link
Owner

zerothi commented Jan 9, 2023

Ok, so what about I run it through again, and then we just merge this? Then we can always think about the next thing.

What do you mean by "run it through"?

I'll check this PR again.

After having read your recap of our conversation, perhaps the most ideal thing would be to fix Geometry.add/append to accept a class that fixes the merging instead of relying on axis fixtures etc.

Yes but that would require the Geometry itself to have some knowledge of which type of geometry it is. Otherwise this merging class will have no idea what to do with it.

True, hmm...

@zerothi
Copy link
Owner

zerothi commented Jul 10, 2023

I am thinking this could be helpful. So I think we should merge it. :)

@zerothi
Copy link
Owner

zerothi commented Jul 10, 2023

Any comments @tfrederiksen @pfebrer



def test_graphene_heteroribbon():
a = graphene_heteroribbon([(7, 2), (9, 2)])

Check notice

Code scanning / CodeQL

Unused local variable

Variable a is not used.
aligned_match = last_bot_edge_open == this_bot_edge_open

# Shift the incoming section if the vertical shift makes them not match.
if aligned_match == (shift % 2 == 1):

Check failure

Code scanning / CodeQL

Potentially uninitialized local variable

Local variable 'aligned_match' may be used before it is initialized.
@pfebrer
Copy link
Contributor Author

pfebrer commented Jul 10, 2023

The only pitfall I see is that this introduces some kind of "framework" for composite structures, which might be a pain in the future if you want to go in another direction. But if you think it's fine, then OK 👍

@zerothi
Copy link
Owner

zerothi commented Jul 10, 2023

Agreed, but if you think this functionality is valuable, I think we should merge it. Then we can always move things to a deprecated folder and introduce the new method over time.... I think it has a long time perspective to get a better overall working scheme.

@pfebrer
Copy link
Contributor Author

pfebrer commented Jul 10, 2023

Ok, is it properly documented (can't explore it now I'm on the metro)? If it is then for me it is ok to merge it. Otherwise I can try to document it this afternoon or tomorrow.

@zerothi
Copy link
Owner

zerothi commented Jul 10, 2023

I'll have a go and ask for another go by you. I think the section classes could be hosted in the function as a property

def heteroribbon(...):


heteroribbon.section = _heteroribbon_section

or similar.

@@ -1,10 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import itertools
import math as m

Check notice

Code scanning / CodeQL

Unused import

Import of 'm' is not used.

@abstractmethod
def build_section(self, geometry):
...

Check notice

Code scanning / CodeQL

Statement has no effect

This statement has no effect.

@abstractmethod
def add_section(self, geometry, geometry_addition):
...

Check notice

Code scanning / CodeQL

Statement has no effect

This statement has no effect.
@zerothi
Copy link
Owner

zerothi commented Jul 16, 2023

@pfebrer feel free to have a look now :)

@pfebrer
Copy link
Contributor Author

pfebrer commented Jul 17, 2023

Yeah I think it's fine! Thank you for the polishing :)

@zerothi zerothi merged commit 2754537 into zerothi:main Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Function to produce armchair heteroribbons.
3 participants