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

Fix "unsupported operand" issue when accessing IOData.charge #196

Merged
merged 9 commits into from
Aug 28, 2020

Conversation

tovrstra
Copy link
Member

This is the fix for the following type of error message:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/maarten/anaconda3/envs/iodata/lib/python3.8/site-packages/iodata/iodata.py", line 274, in charge
    return self.atcorenums.sum() - self.nelec
TypeError: unsupported operand type(s) for -: 'float' and 'NoneType

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.)

@tovrstra tovrstra mentioned this pull request Aug 25, 2020
@tovrstra tovrstra marked this pull request as draft August 25, 2020 08:55
@tovrstra tovrstra added the bug label Aug 25, 2020
@tovrstra tovrstra marked this pull request as ready for review August 25, 2020 11:14
iodata/iodata.py Outdated
Comment on lines 269 to 275
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
Copy link
Member

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)?

Copy link
Contributor

@lmacaya lmacaya Aug 26, 2020

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.

Copy link
Member Author

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
Comment on lines 262 to 263
result = self.atnums.astype(float)
self._atcorenums = result
self.atcorenums = result
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

@FarnazH FarnazH left a 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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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'

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll check

Copy link
Member Author

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
Comment on lines 286 to 290
def charge(self, charge: float):
# The internal _charge is used only if atcorenums is None.
if self.atcorenums is None:
self._charge = charge
else:
Copy link
Contributor

@lmacaya lmacaya Aug 26, 2020

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

@tovrstra tovrstra mentioned this pull request Aug 26, 2020
Copy link
Contributor

@lmacaya lmacaya left a 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
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Contributor

@mcoolsce mcoolsce left a 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
Copy link
Contributor

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?

Copy link
Member Author

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.

@FarnazH FarnazH self-requested a review August 27, 2020 20:44
Copy link
Member

@FarnazH FarnazH left a 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.

@FarnazH FarnazH self-requested a review August 28, 2020 04:34
Copy link
Member

@FarnazH FarnazH left a 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.

@tovrstra
Copy link
Member Author

@FarnazH @lmacaya @mcoolsce Thanks for reviewing everyone! It really helped a lot!

@tovrstra tovrstra merged commit e247a0a into theochem:master Aug 28, 2020
@tovrstra tovrstra mentioned this pull request Aug 28, 2020
@tovrstra tovrstra deleted the fix_charge_issue branch June 5, 2024 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants