-
Notifications
You must be signed in to change notification settings - Fork 23
Surface Charge Script #67
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7cffd87
Initial Commit of Surface Charge Script SCI-1315
Alex-AMC c6f0335
Added Readme for surface charge script SCI-1396
Alex-AMC e2519e5
Updated scripts folder readme SCI-1396
Alex-AMC 6bbf52d
Fixed typo SCI-1315
Alex-AMC 1d51751
Apply suggestions from reviewdog SCI-1396
Alex-AMC 9054c55
SCI-1396 reviewed and modified .md file
maloney-ccdc dda2b9e
Removed underscore from file name as no longer required SCI-1315
Alex-AMC 51b4344
Added description of Surface charge SCI-1315
Alex-AMC File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Surface Charge Calculator | ||
|
||
## Summary | ||
|
||
This tool returns the surface charge for a given structure and list of supplied hkl and offsets. | ||
The script provides a GUI that can be used from Mercury or from the command line. | ||
|
||
The output is a HTML file with a table for all the all selected surfaces and their associated charge, projected surface areas and normalised surface charge. | ||
|
||
Charges are currently calculated using Gasteiger charges. Further development could be made to use user derived charges. Please let me know if that is something you'd like [[email protected]]([email protected]). | ||
|
||
Example Output: | ||
 | ||
|
||
> **Note** - When comparing charges for structures out of the CSD and from mol2 files the values might be different as the bonding might not be the same. When importing a mol2 the bonding and charges have to be calculated on the fly. Whereas the CSD structures the bonding is pre-assigned. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whereas for CSD structures? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes thank you! I got sidetracked whilst writing this note and it could probably do with re-writing anyway |
||
|
||
## Requirements | ||
|
||
- Requires a minimum of CSD 2022.2 | ||
|
||
## Licensing Requirements | ||
|
||
- CSD-Particle Licence | ||
|
||
## Instructions on Running | ||
|
||
- To Run from command line: | ||
|
||
```commandline | ||
# With an activated environment | ||
> python surface_charge.py | ||
``` | ||
|
||
- To run from mercury: | ||
Add the folder containing the script to your Python API menu. Mercury -> CSD Python API-> Options -> Add Location. Then just select the `surface_charge.py` script from the drop down menu | ||
 | ||
 | ||
|
||
Running either from the command line or Mercury you will get the same interface allowing you to select a refcode from the CSD or input a mol2 file directly. | ||
|
||
Example input : | ||
 | ||
|
||
## Author | ||
|
||
Alex Moldovan (2024) | ||
|
||
> For feedback or to report any issues please contact [[email protected]](mailto:[email protected]) | ||
Alex-AMC marked this conversation as resolved.
Show resolved
Hide resolved
Alex-AMC marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# | ||
# This script can be used for any purpose without limitation subject to the | ||
# conditions at http://www.ccdc.cam.ac.uk/Community/Pages/Licences/v2.aspx | ||
# | ||
# This permission notice and the following statement of attribution must be | ||
# included in all copies or substantial portions of this script. | ||
# | ||
# The following line states a licence feature that is required to show this script in Mercury and Hermes script menus. | ||
# Created 18/08/2024 by Alex Moldovan | ||
# ccdc-licence-features not-applicable | ||
|
||
import os | ||
import warnings | ||
from typing import List, Tuple | ||
|
||
import numpy as np | ||
from ccdc.io import CrystalReader | ||
from ccdc.particle import Surface | ||
from ccdc.utilities import HTMLReport | ||
|
||
|
||
class SurfaceCharge: | ||
def __init__(self, crystal, use_existing_charges: bool = False): | ||
if use_existing_charges: | ||
# if crystal.molecule.assign_partial_charges() is False: | ||
# warnings.warn(f"Gasteiger charges could not be assigned to molecule: {crystal.identifier}", | ||
# RuntimeWarning) | ||
raise NotImplementedError("Use existing charges instead. Current implementation only supports Gasteiger.") | ||
Alex-AMC marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.crystal = crystal | ||
self.surface = None | ||
self._surface_charge = None | ||
|
||
def calculate_surface_charge(self, hkl: Tuple[int, int, int], offset: float): | ||
self.surface = Surface(self.crystal, miller_indices=hkl, offset=offset) | ||
if self.surface.slab.assign_partial_charges(): | ||
self._surface_charge = np.round(np.sum([atom.partial_charge for atom in self.surface.surface_atoms]), 3) | ||
return | ||
warnings.warn(f"Gasteiger charges could not be assigned to molecule: {self.crystal.identifier}", | ||
RuntimeWarning) | ||
self._surface_charge = np.nan | ||
|
||
@property | ||
def surface_charge(self): | ||
if self._surface_charge is None: | ||
raise ValueError("Surface charge calculation not yet calculated, run calculate_surface_charge()") | ||
return self._surface_charge | ||
|
||
@property | ||
def surface_charge_per_area(self): | ||
return self.surface_charge / self.surface.descriptors.projected_area | ||
|
||
|
||
class SurfaceChargeController: | ||
def __init__(self, structure: str, hkl_and_offsets: List[Tuple[int, int, int, float]], | ||
output_directory: str = None, use_existing_charges: bool = False): | ||
self.surface_charges_per_area = [] | ||
self.surface_charges = [] | ||
self.projected_area = [] | ||
self.crystal = None | ||
if output_directory is None: | ||
output_directory = os.getcwd() | ||
self.output_directory = output_directory | ||
self.structure = structure | ||
self._surfaces = None | ||
self.get_structure() | ||
self.identifier = self.crystal.identifier | ||
self._surfaces = hkl_and_offsets | ||
self.use_existing_charges = use_existing_charges | ||
|
||
def get_structure(self): | ||
if "." not in self.structure: | ||
self.crystal = CrystalReader('CSD').crystal(self.structure) | ||
elif ".mol2" in self.structure: | ||
self.crystal = CrystalReader(self.structure)[0] | ||
else: | ||
raise IOError(" \n ERROR : Please supply refcode or mol2") | ||
|
||
@property | ||
def surfaces(self): | ||
if self._surfaces: | ||
return self._surfaces | ||
|
||
def calculate_surface_charge(self): | ||
for surface in self.surfaces: | ||
charges = SurfaceCharge(crystal=self.crystal, use_existing_charges=self.use_existing_charges) | ||
charges.calculate_surface_charge(hkl=surface[:3], offset=surface[3]) | ||
self.surface_charges.append(charges.surface_charge) | ||
self.projected_area.append(charges.surface.descriptors.projected_area) | ||
self.surface_charges_per_area.append(charges.surface_charge_per_area) | ||
|
||
def make_report(self): | ||
html_content = self.generate_html_table() | ||
output_file = os.path.join(self.output_directory, self.identifier + "_surface_charge.html") | ||
with HTMLReport(file_name=output_file, | ||
ccdc_header=True, append=False, embed_images=False, | ||
report_title=f'Surface Charge Calculations - {self.identifier}') as self.report: | ||
self.report.write(html_content) | ||
|
||
print(f"Results saved to {output_file}") | ||
|
||
def generate_html_table(self): | ||
# HTML Table Header | ||
html = """ | ||
<html> | ||
<head> | ||
<title>Calculation Results</title> | ||
<style> | ||
table { width: 100%; border-collapse: collapse; } | ||
th, td { border: 1px solid black; padding: 8px; text-align: center; } | ||
th { background-color: #f2f2f2; } | ||
</style> | ||
</head> | ||
<body> | ||
<h2>Calculation Results</h2> | ||
<table> | ||
<tr> | ||
<th>hkl</th> | ||
<th>Offset</th> | ||
<th>Projected Area</th> | ||
<th>Surface Charge</th> | ||
<th>Surface Charge per Projected Area</th> | ||
</tr> | ||
""" | ||
|
||
# HTML Table Rows | ||
for i, (h, k, l, offset) in enumerate(self.surfaces): | ||
hkl = "{" + f"{h}, {k}, {l}" + "}" | ||
html += f""" | ||
<tr> | ||
<td>{hkl}</td> | ||
<td>{offset:.2f}</td> | ||
<td>{self.projected_area[i]:.3f}</td> | ||
<td>{self.surface_charges[i]:.3f}</td> | ||
<td>{self.surface_charges_per_area[i]:.4f}</td> | ||
</tr> | ||
""" | ||
|
||
# HTML Table Footer | ||
html += """ | ||
</table> | ||
</body> | ||
</html> | ||
""" | ||
return html |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.