Skip to content

Basic simulation capability [WIP] - #33

Closed
SolarDrew wants to merge 54 commits into
PlasmaPy:masterfrom
SolarDrew:simulation
Closed

SolarDrew wants to merge 54 commits into
PlasmaPy:masterfrom
SolarDrew:simulation

Conversation

@SolarDrew

@SolarDrew SolarDrew commented May 16, 2017 •

Copy link
Copy Markdown
Contributor

Closes #7 and provides a simple-to-use interface for setting up and running simulations. What's here at the moment is based largely on a similar previous project, which can be found here, but I'm happy to deviate from it based on input from others.

Below are some snapshots of density from a demo 2D simulation with a plasma blob initally in the middle and diffusing outwards. It's technically solving the MHD equations but with B = 0. As you can see, I'm getting some instabilities even without magnetic field, and the less said about the boundary conditions the better.
Also speed is obviously an issue at the moment.

Density, i=0
Density, i=1000
Density, i=2000
Density, i=3000
Density, i=4000

Pynite being my previous attempt at a python plasma simulation code.
@pep8speaks

pep8speaks commented May 16, 2017 •

Copy link
Copy Markdown

Hello @SolarDrew! Thanks for updating the PR.

Line 22:80: E501 line too long (102 > 79 characters)

Line 132:80: E501 line too long (80 > 79 characters)

Comment last updated on August 13, 2017 at 13:34 Hours UTC

Comment thread plasmapy/classes/plasma.py Outdated
def density(self, density: u.kg/u.m**3):
"""Sets the simulation's density profile to the specified array.
Other arrays which depend on the density values, such as the kinetic pressure,
are then redefined automatically.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's clever, I like it!


assert density.shape == self.grid_size, \
'Specified density array shape does not match simulation grid.'
self._density = density

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doesn't the previous necessitate self._pressure = self.pressure() after this line here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it doesn't, but because the comment is wrong rather than the implementation.

@StanczakDominik StanczakDominik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Had a brief look to stay up-to-date on what you're doing here. It looks pretty awesome!

import astropy.units as u

mu0 = np.pi * 4.0e-7 * (u.newton / (u.amp**2))
from astropy.utils.console import ProgressBar

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right, because of course astropy would have a built-in progress bar for all things astronomical.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, astropy is amazing :D

Comment thread plasmapy/classes/plasma.py Outdated
self._magnetic_field = np.zeros((3, *self.domain_shape)) * u.T

# Connect a simulation object for simulating
self.simulation_physics = MHDSimulation()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love that you're keeping this generic!

raise ValueError("Either max_time or max_its must be set.")

if np.isinf(max_time):
pb = ProgressBar(max_its)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps we could raise a warning here if both max_time and max_its are set?

@SolarDrew SolarDrew Jun 1, 2017 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. But the question then becomes how to procede in a predictable way if there are two possible end 'times'. Stop at whichever one is sooner I suppose, but I feel like that's going to lead to unexpected behaviour if users aren't careful, so maybe raising an exception would be better. I'm happy to be convinced otherwise though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sure, an exception does make more sense than a warning.

Comment thread plasmapy/classes/simulation.py Outdated

# Collect equations into a nice easy-to-use list
# self.equations = [self._ddt_density, self._ddt_momentum,
# self._ddt_energy, self._ddt_magfield]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wow, I love this idea.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks :) I found in the earlier version of this code that it's an easy and generalisable way to get at the functions you need.

Comment thread plasmapy/classes/simulation.py Outdated


class MHDSimulation():
"""Physics class for magnetohydrodynamics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

r"""

Comment thread plasmapy/classes/plasma.py Outdated

mu0 = np.pi * 4.0e-7 * (u.newton / (u.amp**2))

class Plasma():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You don't need the () in Python 3.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But it looks so strange without them....

@SolarDrew

Copy link
Copy Markdown
Contributor Author

Update: so before NAM I managed to get some of the hyperviscosity implemented and running. With this in place, I've managed to run the Riemann shock tube test and the output looks like this:
riemann_shock_final
For those who aren't familiar with it, it should look something like this:
image
(image from here).

So there are still definitely some issues that need fixing, mostly with stability, but it's getting there.

@namurphy

Copy link
Copy Markdown
Member

I haven't looked into this much, but sympy.utilities.codegen is probably useful (for some definition of useful). There was a tutorial on automatic code generation with SymPy at SciPy 2017. (These are what I mentioned in our last telecon.)

In the intermediate future we may also wish to have some finite element modeling capabilities in here too, such as with packages like FEniCS. This could be really helpful with non-trivial geometry like tokamaks with non-circular cross sections.

SolarDrew added 10 commits July 30, 2017 11:57
Used for allowing the viscous tensor to interact with things
To more easily see if/when calculations are going wrong
Turns out this was breaking the tests because it should have been using
just the dx value for the appropriate axis. Didn't show up in 1D
examples.
Was previously default 5/3, now set to match the example
I'm trying to replicate
dot = np.array([sum([vec[i] * tensor[i, 0, ...] for i in dims]),
sum([vec[i] * tensor[i, 1, ...] for i in dims]),
sum([vec[i] * tensor[i, 2, ...] for i in dims])]) \
* vec.unit * tensor.unit

@StanczakDominik StanczakDominik Aug 13, 2017 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps, if speed is an issue, this part could be optimized somehow. Two possibilities that come to mind are some crazy numpy broadcasting tricks (I'm not saying I see how to do this just yet, though!) and JITting this part via Numba.

Numba doesn't play nice with Coveragepy, though. 😞

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I feel like this could be speeded up by doing it the 'proper' numpy way. At the moment most of the viscous tensor stuff is still a bit fluid (boom boom) because I don't really fully understand and I haven't figured out how to do it with broadcasting. Premature optimisation, right...?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Of course! Still, let's keep it in mind for later :)

I think I might have done something like this with numpy.einsum or whatever. I'll try to figure out how to do this via broadcasting a bit later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Definitely :) Cool, thanks :D

t0 = datetime.datetime.now()
for maxt in np.arange(0, 0.21, 0.02):
print('- Running Simulation... ', end='')
riemann.simulate(max_time=maxt*u.s)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As I see it, we're running all pictures from 0 to t where t increases in the arange, right? Perhaps it would be easier on the computation to rewrite simulation to avoid that - go from 0 to t_1, take a snapshot.

In all likelihood this is something you've already thought of, but I just thought I'd raise it out in the open if anyone wants to hack away at implementing that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Huh, this is outdated now. Okay, git trees are magic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, they are :P You're right, in the long term I'd like to have the Simulation object itself be able to save a snapshot so you don't need this sort of thing, but I haven't got around to it yet so I'm still using this hack.

riemann.simulate(max_time=maxt*u.s)
print("Simulation complete")

fig, ax = plt.subplots(2, 2)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I always liked doing it like this:

fig, ((density_ax, velocity_ax), (energy_ax, pressure_ax)) = plt.subplots...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this is a bit nicer. Good call, thanks.

@StanczakDominik

Copy link
Copy Markdown
Member

Sorry @SolarDrew, I think it's time to mark this one as stale. 😉

@SolarDrew

Copy link
Copy Markdown
Contributor Author

😭 Fair enough. Some day I'll come back to this and make it work!

@namurphy namurphy added plasmapy.simulation Related to the plasmapy.simulation subpackage and removed status: in progress labels May 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plasmapy.simulation Related to the plasmapy.simulation subpackage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a basic 2D MHD code

5 participants