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

Add Einstein-Helfand viscosity implementation #25

Merged
merged 24 commits into from Aug 15, 2023

Conversation

xhgchen
Copy link
Collaborator

@xhgchen xhgchen commented Jul 14, 2023

Fixes #17 and #9

Changes made in this Pull Request:

  • Use class VelocityAutocorr as template
  • Add items to __init__()
  • Write _prepare() and _single_frame()
  • Early work on _conclude()

Areas to work on:

  • Complete _conclude()
  • Units
  • Boltzmann constant usage in Python

For later:

@codecov
Copy link

codecov bot commented Jul 14, 2023

Codecov Report

Merging #25 (b862476) into main (1211874) will not change coverage.
The diff coverage is 100.00%.

Additional details and impacted files

@xhgchen xhgchen added the enhancement New feature or request label Jul 14, 2023

# args
self.dim_type = dim_type
self._parse_dim_type()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be a static method.

self.dim_type = dim_type
self._parse_dim_type()

is much less clear than

self.dim_type, self.dim_fac = self._parse_dim_type(dim_type)

The latter makes it clearer what _parse_dim_type is doing. In the former, you don't even know that a self.dim_fac variable has been created.

Comment on lines 116 to 138
def _parse_dim_type(self):
r"""Sets up the desired dimensionality of the calculation."""
keys = {
"x": [0],
"y": [1],
"z": [2],
"xy": [0, 1],
"xz": [0, 2],
"yz": [1, 2],
"xyz": [0, 1, 2],
}

self.dim_type = self.dim_type.lower()

try:
self._dim = keys[self.dim_type]
except KeyError:
raise ValueError(
"invalid dim_type: {} specified, please specify one of xyz, "
"xy, xz, yz, x, y, z".format(self.dim_type)
)

self.dim_fac = len(self._dim)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we make this a static method that returns dim_type and dim_fac?

Generally, I am of the opinion that it's always best to use static methods when possible. It makes it much easier to inspect the function call and understand what's happening.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So do you mean we would set the instance variables self._dim and self.dim_fac in __init__()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I see what you mean now. Missed the comment above, my bad!

Comment on lines 172 to 174
masses = self._mass_reshape.astype(np.float64)
velocities = self._velocity_array.astype(np.float64)
positions = self._position_array.astype(np.float64)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe that the default float in python is already 64 bits, what will this do?

Comment on lines +20 to +22
class ViscosityHelfand(AnalysisBase):
"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's our plan for benchmarking the viscosity analysis?

Copy link
Member

Choose a reason for hiding this comment

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

@xhgchen you can benmark as part of your reproduction.

@xhgchen
Copy link
Collaborator Author

xhgchen commented Jul 21, 2023

With the most recent commit, the calculation now actually has all the necessary elements! Still very early though and I definitely need to do some proper testing.

Comment on lines +181 to +188
diff = (
self._masses_rs
* self._velocities[:-lag, :, :]
* self._positions[:-lag, :, :]
- self._masses_rs
* self._velocities[lag:, :, :]
* self._positions[lag:, :, :]
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we break down this math to make it more understandable? something like

something = self._masses_rs * self._velocities[:-lag, :, :] * self._positions[:-lag, :, :]
something_else = self._masses_rs * self._velocities[:-lag, :, :] * self._positions[:-lag, :, :]
diff = something - something_else

Comment on lines 198 to 205
self.results.visc_by_particle = self.results.visc_by_particle / (
2
* constants["Boltzman_constant"]
* self._vol_avg
* self.temp_avg
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above here, I'd advocate for adding intermediate variables over putting a single term on each line. Ultimately a matter of taste though. I sense this will be a reoccuring thing so curious what @hmacdope thinks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wouldn't adding intermediate variables take more memory and make the calculation scale worse with larger datasets? If the difference doesn't really matter, I would be good with adding them. I'm also curious what @hmacdope thinks.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm actually not sure. I suspect the difference is negligable if it exists at all. This could be something to try out with %timeit in a jupyter notebook.

transport_analysis/viscosity.py Show resolved Hide resolved
* Use class `VelocityAutocorr` as template
* Add items to `__init__()`
* Write `_prepare()` and `_single_frame()`
* Early work on `_conclude()`
* Change `self.*_array` to simply `self.*`
* Fix `self._masses_rs`
* Set up `self._volumes` in `_prepare()` and `_single_frame()`
* Complete `_conclude()` with 2, Boltzman constant, average volume,
and average temp
* Check that AtomGroup is accepted
* Test some exceptions
* Functionality not yet tested here
* Move `visc_by_particle` division step outside of for loop
* Contains mass, velocities, positions, and volume
* Format `viscosity.py` with Black
* Simplify `characteristic_poly_helfand()`
* CI will not work otherwise
* use try except and update with mda 2.6.0 release
* Improve accuracy and agreement with `ViscosityHelfand` class
* Improve performance
@xhgchen
Copy link
Collaborator Author

xhgchen commented Aug 11, 2023

characteristic_poly_helfand() is now vectorized, so that is half of #26 taken care of.

@xhgchen xhgchen changed the title Add Einstein-Helfand viscosity class skeleton Add Einstein-Helfand viscosity implementation Aug 11, 2023
@xhgchen xhgchen marked this pull request as ready for review August 11, 2023 22:41
Copy link
Member

@hmacdope hmacdope left a comment

Choose a reason for hiding this comment

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

Quick review, looking good!



@pytest.fixture(scope="module")
def NSTEP():
Copy link
Member

Choose a reason for hiding this comment

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

No need to back this in as a fixtue, just use 5001 in place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, just wondering why 5001 in place is better? I thought setting NSTEP as a fixture would be more maintainable in case we ever decide to test it with another value (and your MSD module uses the fixture too)?

Copy link
Member

Choose a reason for hiding this comment

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

fair enough I agree.


@pytest.fixture(scope="module")
def visc_helfand(ag):
vh_t = VH(ag, fft=False)
Copy link
Member

Choose a reason for hiding this comment

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

Parametrize over fft=True and fft=False

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll remove FFT for now then and raise an issue.

Copy link
Member

Choose a reason for hiding this comment

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

👍

start=0,
step=1,
):
# update when mda 2.6.0 releases with typo fix
Copy link
Member

Choose a reason for hiding this comment

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

Link to the issue number.

step=1,
):
# update when mda 2.6.0 releases with typo fix
try:
Copy link
Member

Choose a reason for hiding this comment

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

Grab the actual value here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Was wondering also - why is grabbing the actual value better here?

Copy link
Member

Choose a reason for hiding this comment

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

Just to reduce code volume, you still have to index the dict later with this approach.



class TestViscosityHelfand:
# fixtures are helpful functions that set up a test
Copy link
Member

Choose a reason for hiding this comment

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

Can remove this comment now.

Comment on lines +20 to +22
class ViscosityHelfand(AnalysisBase):
"""
Copy link
Member

Choose a reason for hiding this comment

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

@xhgchen you can benmark as part of your reproduction.

self.temp_avg = temp_avg
self.dim_type = dim_type.lower()
self._dim, self.dim_fac = self._parse_dim_type(self.dim_type)
# self.fft = fft # consider whether fft is possible later
Copy link
Member

Choose a reason for hiding this comment

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

If its not possibe raise exception or remove.

2 * constants[self.boltzmann] * self._vol_avg * self.temp_avg
)
# average over # particles and update results array
self.results.timeseries = self.results.visc_by_particle.mean(axis=1)
Copy link
Member

Choose a reason for hiding this comment

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

Add plotting?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wanted to do that in a separate PR to avoid making this PR too bloated. Would it be better to add it here?

Copy link
Member

Choose a reason for hiding this comment

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

thats fine to do next PR

@xhgchen xhgchen requested a review from hmacdope August 13, 2023 20:10
Copy link
Member

@hmacdope hmacdope 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 going to approve so we can move forward here. If any issues come up when testing, lets address them in a new PR. I would however love an additional look over by @orionarcher :)

@xhgchen xhgchen merged commit 7c9b10d into MDAnalysis:main Aug 15, 2023
24 checks passed
@xhgchen xhgchen deleted the viscosity-helfand branch August 15, 2023 01:18
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.

Add a Helfand method viscosity Implementation
3 participants