-
Notifications
You must be signed in to change notification settings - Fork 48
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
Fix "unsupported operand" issue when accessing IOData.charge #196
Conversation
Related issues with nelec and spinpol are also fixed.
378ba48
to
d85e9a9
Compare
iodata/iodata.py
Outdated
if self._charge is not None: | ||
# _charge is treated as the dependent one, while atcorenums and | ||
# nelec are treated as independent. | ||
if self.nelec is None: | ||
# Switch to storing _nelec. | ||
self._nelec = atcorenums.sum() - self._charge | ||
self._charge = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a naive questions: why in the last self._charge
is set to None
? Is this because self._charge
is only assigned when charge
is assigned, and the derived approaches to get charge wouldn't set any of the private attributes (e.g. self._charge
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understood the code correctly, by assigning the variables _atcorenums
and _nelec
, it is possible to get the net charge value and even update it. If the charge was assigned before setting _atcorenums, it remains in _charges. By using self._charge = None
, we get rid of the initial charge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I intended it the way Luis explained. It is just to clarify (thus not strictly necessary) that the charge
is going to be derived from nelec
and atcorenums
.
iodata/iodata.py
Outdated
result = self.atnums.astype(float) | ||
self._atcorenums = result | ||
self.atcorenums = result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Picky Comment: Can we get rid of result
variable? The same at the beginning of if
statement, I couldn't add a comment there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Comments are only possible on lines that have changed, which is a bit unfortunate, because sometimes new changes imply things about old code.)
Agreed, result
can be eliminated, making things shorter. I'm not sure why it was this way, probably related to some old code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still going through this to fully understand it, but what about this case:
from iodata import IOData
d = IOData()
d.charge = 0
d.atcorenums = None
This gives the error below:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/Farnaz/packages/iodata/iodata/iodata.py", line 274, in atcorenums
self._nelec = atcorenums.sum() - self._charge
AttributeError: 'NoneType' object has no attribute 'sum'
iodata/iodata.py
Outdated
if self._charge is not None: | ||
# _charge is treated as the dependent one, while atcorenums and | ||
# nelec are treated as independent. | ||
if self.nelec is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if self.nelec is None: | |
if self.nelec is None and atcorenums is not None: |
Maybe this fix AttributeError: 'NoneType' object has no attribute 'sum'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get intuitive behavior, the code had to be extended, which is now the case in the latest commit. I've added several variations of unit tests to make sure it works (and checked with coverage that they reached all code paths.)
iodata/iodata.py
Outdated
def charge(self, charge: float): | ||
# The internal _charge is used only if atcorenums is None. | ||
if self.atcorenums is None: | ||
self._charge = charge | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If data.charge is set to None, after setting data.atcorenums and data.charge, raise an error (Line 291):
import numpy as np
from iodata import IOData
d = IOData()
d.atnums = np.array([6, 1, 1, 1, 1])
d.charge = 1
d.charge = None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/lmacaya/Desktop/horton3/fix_charge_issue/iodata/iodata/iodata.py", line 291, in charge
self.nelec = self.atcorenums.sum() - charge
TypeError: unsupported operand type(s) for -: 'float' and 'NoneType'
I don't know if this is an issue, but I point it out just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be fixed. I guess this is comparable to one of @FarnazH 's comments. Thanks for bringing this up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also fixed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I only noticed one problem in the atcorenum
setter, but I think the main issues are covered.
if self.nelec is not None: | ||
# Set _charge because charge can no longer be derived from | ||
# atcorenums and nelec. | ||
self._charge = self._atcorenums.sum() - self.nelec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line will raise an error if nelec
was set before atcorenums
and the latter is set to None afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll add a unit test for this scenario, and make sure it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You were right! It should be fixed now. I also found another minor issue with the dtype of atcorenums, which is fixed too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything looks good to me, except one possible inconsistency I could find.
if self.mo is None: | ||
return self._nelec | ||
return self.mo.nelec | ||
# When the molecular orbitals are present, they determine the number |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a IOData object is created with only charge
and atnums
present
mol = IOData(charge=-1, atnums=[8, 1, 1])
print(mol.nelec)
printing out nelec
results in None
while, after accessing atcorenums
first, it prints out the correct value of 11.0
.
mol = IOData(charge=-1, atnums=[8, 1, 1])
print(mol.atnumcores)
print(mol.nelec)
Is this the correct behaviour, or should nelec
also be derived from atnums
and the charge
only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, and there was a way to fix it. pfew.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, there was a mistake on my side in the code snippet I shared... I am testing the latest implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried a few cases, and the results were as expected. Thanks for all the fixes.
This is the fix for the following type of error message:
This issue is hampering two PRs (#188 and #186), so I'm putting in a separate PR, which can be merged easily.
(Related issues with nelec and spinpol are also fixed.)