-
Notifications
You must be signed in to change notification settings - Fork 7
Add a tool for plotting distribution functions in GK #183
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
Open
Maxwell-Rosen
wants to merge
31
commits into
main
Choose a base branch
from
plot-distf-gk
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
69fbb2b
Add gk_distf command for building real distribution function from Jf …
Maxwell-Rosen 11acdc3
Add support for mc2nu. This has been something I've wanted for a long…
Maxwell-Rosen d61df54
Add a source boolean to plot sources
Maxwell-Rosen 3a2caeb
Update tool to have a version which is callable from python. Here is …
Maxwell-Rosen c35a7e2
Add grid type detection to GData context based on grid shape. This wa…
Maxwell-Rosen 74b1570
Enhance gk_distf to support frame ranges and improve frame resolution…
Maxwell-Rosen 1bf42c6
rename build_gk_distf to load_gk_distf, which is more in line with ho…
Maxwell-Rosen 3144a14
Add multiblock implementation
Maxwell-Rosen 81610a7
Update kwargs for some shortcuts
Maxwell-Rosen 5c6e110
Refactor the file for organization. Match a few more conventions of t…
Maxwell-Rosen 0311199
Enhance documentation and variable naming in gk_distf.py for clarity …
Maxwell-Rosen b52f285
Refactor file handling in gk_distf.py: streamline file resolution and…
Maxwell-Rosen e1d1ef3
Refactor gk_distf.py: remove redundant file parsing functions, stream…
Maxwell-Rosen 6f825af
Refactor gk_distf.py: improve documentation by clarifying grid deform…
Maxwell-Rosen 24200f5
Refactor gk_distf.py: remove unnecessary CLI command comment separato…
Maxwell-Rosen ecb53b0
Fix load_gk_distf function: reorder mc2nu_file assignment for consist…
Maxwell-Rosen d1520e6
Simplify non-uniform map
Maxwell-Rosen 4a54724
Simplify the non-uniform mapping part of this workflow
Maxwell-Rosen 42c0eb8
re-order the subtraction
Maxwell-Rosen e9d9630
Improve clarity in the cell centered to nodal operation. Remove tag i…
Maxwell-Rosen 0763f71
Fix typo in comment for index initialization in _extract_values_along…
Maxwell-Rosen eda9e40
Add clarification comment for GData object in load_gk_distf function
Maxwell-Rosen efc49bc
Add two clarifying comments
Maxwell-Rosen 96b97a8
Set a default for the non-uniform map. I didn't realize that this did…
Maxwell-Rosen ca5bc18
Add a comma separated list of frames to load
Maxwell-Rosen dbf77d8
Fix typo
Maxwell-Rosen 7e950c3
Add comments to describe the LLM involvement in this PR
Maxwell-Rosen 329a75d
Remove changes to gdata. Add a generalized suffix to the species so y…
Maxwell-Rosen 5725479
Enhance load_gk_distf function with optional filename overrides and u…
Maxwell-Rosen e29c3f3
Add support for optional mapc2p file in load_gk_distf function
Maxwell-Rosen 951ee6c
Remove unused import and clean up load_gk_distf function parameters
Maxwell-Rosen 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
Some comments aren't visible on the classic Files Changed page.
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,223 @@ | ||
| import os | ||
|
|
||
| import click | ||
| import numpy as np | ||
|
|
||
| from postgkyl.data import GData | ||
| from postgkyl.data import GInterpModal | ||
| from postgkyl.utils import verb_print | ||
|
|
||
|
|
||
| def _resolve_path(path: str, file_name: str) -> str: | ||
| if os.path.isabs(file_name): | ||
| return file_name | ||
| return os.path.join(path, file_name) | ||
|
|
||
|
|
||
| def _resolve_files(name: str, species: str, frame: int, path: str, source: bool = False): | ||
| if source: | ||
| frame_file = _resolve_path(path, f"{name}-{species}_source_{frame}.gkyl") | ||
| else: | ||
| frame_file = _resolve_path(path, f"{name}-{species}_{frame}.gkyl") | ||
| mapc2p_vel_file = _resolve_path(path, f"{name}-{species}_mapc2p_vel.gkyl") | ||
| mc2nu_file = _resolve_path(path, f"{name}-mc2nu_pos_deflated.gkyl") | ||
| jacobvel_file = _resolve_path(path, f"{name}-{species}_jacobvel.gkyl") | ||
| jacobtot_inv_file = _resolve_path(path, f"{name}-jacobtot_inv.gkyl") | ||
| return frame_file, mapc2p_vel_file, mc2nu_file, jacobvel_file, jacobtot_inv_file | ||
|
|
||
|
|
||
| def _resolve_mapping_kwargs(use_c2p_vel: bool, mapc2p_vel_file: str) -> dict: | ||
| if use_c2p_vel: | ||
| return {"mapc2p_vel_name": mapc2p_vel_file} | ||
| return {"mapc2p_vel_name": ""} | ||
|
|
||
|
|
||
| def _cell_centers_to_nodes(cell_centers: np.ndarray) -> np.ndarray: | ||
| nodes = np.zeros(cell_centers.size + 1, dtype=cell_centers.dtype) | ||
| nodes[1:-1] = 0.5 * (cell_centers[:-1] + cell_centers[1:]) | ||
| nodes[0] = cell_centers[0] - (nodes[1] - cell_centers[0]) | ||
| nodes[-1] = cell_centers[-1] + (cell_centers[-1] - nodes[-2]) | ||
| return nodes | ||
|
|
||
|
|
||
| def _get_dim_split(out_grid: list, mc2nu_data: GData) -> tuple[int, int]: | ||
| total_dims = len(out_grid) | ||
| cdim = mc2nu_data.get_num_dims() | ||
| if cdim < 1 or cdim > total_dims: | ||
| raise ValueError(f"Invalid cdim={cdim} for total_dims={total_dims}") | ||
| vdim = total_dims - cdim | ||
| return cdim, vdim | ||
|
|
||
|
|
||
| def _extract_mapped_axis(mapped_values: np.ndarray, axis: int, cdim: int) -> np.ndarray: | ||
| if cdim == 1: | ||
| return np.asarray(mapped_values[..., axis]).reshape(-1) | ||
|
|
||
| # Extract one mapped coordinate direction at a reference point in the | ||
| # remaining configuration directions to construct a 1D axis grid. | ||
| idx = [0] * (cdim + 1) | ||
| idx[axis] = slice(None) | ||
| idx[-1] = axis | ||
| return np.asarray(mapped_values[tuple(idx)]).reshape(-1) | ||
|
|
||
|
|
||
| def _apply_mc2nu_grid(out_grid: list, mc2nu_file: str, debug: bool) -> tuple[list, int, int]: | ||
| mc2nu_data = GData(mc2nu_file) | ||
| cdim, vdim = _get_dim_split(out_grid, mc2nu_data) | ||
|
|
||
| mc2nu_interp = GInterpModal(mc2nu_data, 1, "ms") | ||
| _, mc2nu_values = mc2nu_interp.interpolate(tuple(range(cdim))) | ||
| mapped_values = np.asarray(mc2nu_values) | ||
|
|
||
| deformed_grid = list(out_grid) | ||
| for d in range(cdim): | ||
| mapped_cfg = _extract_mapped_axis(mapped_values, d, cdim) | ||
|
|
||
| old_cfg = np.asarray(out_grid[d]) | ||
| if mapped_cfg.size == old_cfg.size: | ||
| new_cfg = mapped_cfg | ||
| elif mapped_cfg.size + 1 == old_cfg.size: | ||
| new_cfg = _cell_centers_to_nodes(mapped_cfg) | ||
| else: | ||
| raise ValueError( | ||
| "mc2nu mapping size is incompatible with configuration grid on axis " | ||
| f"{d}: {mapped_cfg.size} vs {old_cfg.size}" | ||
| ) | ||
|
|
||
| deformed_grid[d] = new_cfg | ||
|
|
||
| if debug: | ||
| click.echo(f"gk_distf: cdim={cdim}, vdim={vdim}") | ||
| click.echo(f"gk_distf: mc2nu mapped {cdim} configuration axis/axes") | ||
|
|
||
| return deformed_grid, cdim, vdim | ||
|
|
||
|
|
||
| def _assert_files_exist(files: dict): | ||
| for kind, file_name in files.items(): | ||
| if not os.path.exists(file_name): | ||
| raise FileNotFoundError(f"Missing {kind} file: {file_name}") | ||
|
|
||
|
|
||
| def _compute_fjx(jf_values: np.ndarray, jac_values: np.ndarray) -> np.ndarray: | ||
| if jf_values.shape == jac_values.shape: | ||
| return jf_values / jac_values | ||
|
|
||
| if jf_values.shape[:-1] != jac_values.shape[:-1]: | ||
| raise ValueError( | ||
| "Jf and jacobvel spatial shapes do not match: " | ||
| f"{jf_values.shape} vs {jac_values.shape}" | ||
| ) | ||
|
|
||
| if jac_values.shape[-1] != 1: | ||
| raise ValueError( | ||
| "jacobvel component axis must be 1 or match Jf: " | ||
| f"{jf_values.shape} vs {jac_values.shape}" | ||
| ) | ||
|
|
||
| return jf_values / jac_values | ||
|
|
||
|
|
||
| def _broadcast_multiply(distf: np.ndarray, jacobtot_inv: np.ndarray) -> np.ndarray: | ||
| if jacobtot_inv.ndim > distf.ndim: | ||
| raise ValueError( | ||
| "jacobtot_inv has more dimensions than distribution function: " | ||
| f"{jacobtot_inv.ndim} > {distf.ndim}" | ||
| ) | ||
|
|
||
| expected_prefix = distf.shape[:jacobtot_inv.ndim] | ||
| if expected_prefix != jacobtot_inv.shape: | ||
| raise ValueError( | ||
| "Cannot broadcast jacobtot_inv over distribution function. " | ||
| f"Expected prefix {expected_prefix}, got {jacobtot_inv.shape}" | ||
| ) | ||
|
|
||
| jacob_shape = jacobtot_inv.shape + (1,) * (distf.ndim - jacobtot_inv.ndim) | ||
| return distf * jacobtot_inv.reshape(jacob_shape) | ||
|
|
||
|
|
||
| def _interpolate_fjx_and_jacob(fjx_data: GData, jacobtot_inv_data: GData): | ||
| fjx_interp = GInterpModal(fjx_data, 1, "gkhyb") | ||
| jacob_interp = GInterpModal(jacobtot_inv_data, 1, "ms") | ||
|
|
||
| grid, fjx_values = fjx_interp.interpolate() | ||
| _, jacob_values = jacob_interp.interpolate() | ||
|
|
||
| fjx_values = np.squeeze(fjx_values) | ||
| jacob_values = np.squeeze(jacob_values) | ||
| return grid, fjx_values, jacob_values | ||
|
|
||
|
|
||
| @click.command() | ||
| @click.option("--name", "-n", required=True, type=click.STRING, | ||
| help="Simulation name prefix (e.g. gk_lorentzian_mirror).") | ||
| @click.option("--species", "-s", required=True, type=click.STRING, | ||
| help="Species name (e.g. ion or elc).") | ||
| @click.option("--frame", "-f", required=True, type=click.INT, | ||
| help="Frame number.") | ||
| @click.option("--source", is_flag=True, | ||
| help="Use <name>-<species>_source_<frame>.gkyl as the input distribution.") | ||
| @click.option("--path", "-p", default="./", type=click.STRING, | ||
| help="Path to simulation data.") | ||
| @click.option("--tag", "-t", default="df", type=click.STRING, | ||
| help="Tag for output dataset.") | ||
| @click.option("--c2p-vel/--no-c2p-vel", default=False, | ||
| help="Use <name>-<species>_mapc2p_vel.gkyl when loading Jf.") | ||
| @click.option("--mc2nu", is_flag=True, | ||
| help="Use <name>-mc2nu_pos_deflated.gkyl to deform configuration-space grid.") | ||
| @click.option("--debug", is_flag=True, | ||
| help="Print resolved file names and shape diagnostics.") | ||
| @click.pass_context | ||
| def gk_distf(ctx, **kwargs): | ||
| """Gyrokinetics: build real distribution function from saved Jf data.""" | ||
| verb_print(ctx, "Starting gk_distf") | ||
| data = ctx.obj["data"] | ||
|
|
||
| files = {} | ||
| jf_file, mapc2p_vel_file, mc2nu_file, jacobvel_file, jacobtot_inv_file = _resolve_files( | ||
| kwargs["name"], kwargs["species"], kwargs["frame"], kwargs["path"], kwargs["source"]) | ||
| files["Jf"] = jf_file | ||
| files["jacobvel"] = jacobvel_file | ||
| files["jacobtot_inv"] = jacobtot_inv_file | ||
|
|
||
| if kwargs["c2p_vel"]: | ||
| files["mapc2p_vel"] = mapc2p_vel_file | ||
| if kwargs["mc2nu"]: | ||
| files["mc2nu"] = mc2nu_file | ||
|
|
||
| _assert_files_exist(files) | ||
|
|
||
| map_kwargs = _resolve_mapping_kwargs(kwargs["c2p_vel"], mapc2p_vel_file) | ||
|
|
||
| if kwargs["debug"]: | ||
| click.echo(f"gk_distf: Jf={jf_file}") | ||
| click.echo(f"gk_distf: jacobvel={jacobvel_file}") | ||
| click.echo(f"gk_distf: jacobtot_inv={jacobtot_inv_file}") | ||
| if kwargs["c2p_vel"]: | ||
| click.echo(f"gk_distf: mapc2p_vel={mapc2p_vel_file}") | ||
| if kwargs["mc2nu"]: | ||
| click.echo(f"gk_distf: mc2nu={mc2nu_file}") | ||
|
|
||
| jf_data = GData(jf_file, mapc2p_vel_name=map_kwargs["mapc2p_vel_name"]) | ||
| jacobvel_data = GData(jacobvel_file) | ||
| jacobtot_inv_data = GData(jacobtot_inv_file) | ||
|
|
||
| fjx_data = GData(tag=kwargs["tag"], ctx=jf_data.ctx) | ||
| fjx_values = _compute_fjx(jf_data.get_values(), jacobvel_data.get_values()) | ||
| fjx_data.push(jf_data.get_grid(), fjx_values) | ||
|
|
||
| out_grid, fjx_interp, jacob_interp = _interpolate_fjx_and_jacob(fjx_data, jacobtot_inv_data) | ||
| distf_values = _broadcast_multiply(fjx_interp, jacob_interp) | ||
|
|
||
| if kwargs["mc2nu"]: | ||
| out_grid, cdim, vdim = _apply_mc2nu_grid(out_grid, mc2nu_file, kwargs["debug"]) | ||
| jf_data.ctx["grid_type"] = "mc2nu" | ||
|
|
||
| if kwargs["debug"]: | ||
| click.echo(f"gk_distf: output shape={distf_values.shape}") | ||
|
|
||
| out = GData(tag=kwargs["tag"], ctx=jf_data.ctx) | ||
| out.push(out_grid, np.asarray(distf_values)[..., np.newaxis]) | ||
| data.add(out) | ||
|
|
||
| verb_print(ctx, "Finishing gk_distf") | ||
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
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.
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 don't like this. It adds an argument for a very specific file from one of many other modules in Gkeyll. What about a Maxwellian from a BGK collision op? what about the heating term from our heating module? etc
In general this command should work with any phase-space quantity, with option to divide by J_x, J_v and/or B, or not.
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.
We could specify something like
--suffix sourceinstead of--source,and that would append the suffix of the file that is loaded. We can do this if it were more general.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.
No extra flag is needed. I think it should be possible to do
pgkyl gk-distf <sim_name>-<species>_<frame>.gkylor
pgkyl gk-distf <sim_name>-<species>_source_<frame>.gkylwithout passing the optional arguments
-nor-s. Likewise,--c2p,--c2p-vel,--mc2nu(and possibly--jacobx,--jacobvand--jacobgyro(the latter for B)) should be optional and possibly take the file name corresponding to these quantities. If the file is given, it uses that. If the flag is given without a file, it uses the hardcoded names. If the flag is not given, it doesn't do that transformation/scaling.