diff --git a/content/documentation/visualising_output/BEAM_Derived_Number_Density_Electron.gif b/content/documentation/visualising_output/BEAM_Derived_Number_Density_Electron.gif new file mode 100644 index 0000000..63c7d6b Binary files /dev/null and b/content/documentation/visualising_output/BEAM_Derived_Number_Density_Electron.gif differ diff --git a/content/documentation/visualising_output/BEAM_Derived_Number_Density_Electrons.png b/content/documentation/visualising_output/BEAM_Derived_Number_Density_Electron.png similarity index 100% rename from content/documentation/visualising_output/BEAM_Derived_Number_Density_Electrons.png rename to content/documentation/visualising_output/BEAM_Derived_Number_Density_Electron.png diff --git a/content/documentation/visualising_output/Python.md b/content/documentation/visualising_output/Python.md deleted file mode 100644 index 53a7467..0000000 --- a/content/documentation/visualising_output/Python.md +++ /dev/null @@ -1,306 +0,0 @@ ---- -draft: false -toc: true -type: docs - -title: Python -linktitle: Python -weight: 330 -menu: - documentation: - parent: Visualising output - weight: 30 ---- - -# Installing the python sdf readers {#installing_the_python_sdf_readers} - -To install the python sdf readers you need to have an installation of -python (2 or 3) with the numpy library. The automated plotting library -requires the matplotlib library. Both numpy and matplotlib are available -through most system package managers or are installable through -[pip](https://pip.pypa.io/en/stable/). - -Once you have a working python install, just go into one of the epoch -directories (epoch1d, epoch2d or epoch3d) and type - -`make sdfutils` - -This will build the SDF python library and install the sdf_helper -wrapper and utility layer. - -# Using the sdf_helper wrapper layer {#using_the_sdf_helper_wrapper_layer} - -The low level python SDF library is not user friendly, so a wrapper -layer called sdf_helper has been written. This wrapper layer simplifies -loading SDF files and provides simple plotting routines using -matplotlib. - -### Importing sdf_helper {#importing_sdf_helper} - -Importing sdf_helper is as simple as - -```python -import sdf_helper -``` - -In these examples, the numpy and matplotlib libraries are usually loaded -too, and an alias is created for sdf_helper, so the boilerplate code -looks like - -```python -import sdf_helper as sh -import numpy as np -import matplotlib.pyplot as plt -``` - -### Loading an sdf file using sdf_helper {#loading_an_sdf_file_using_sdf_helper} - -To load a file, use the `getdata` function. This function takes either a -string which it loads as a filename, so to load the file `Data/0010.df` -you would run - -```python -import sdf_helper as sh -data=sh.getdata('Data/0010.sdf') -``` - -or it takes a number which is the dump number, and optionally a second -parameter which is the directory name as a string, so you would run - -```python -import sdf_helper as sh -data=sh.getdata(10, 'Data') -``` - -Because memory is only allocated when needed in the SDF python reader -there is no way of specifying which variables to load using getdata. All -variables are available when the file is first loaded, and memory is -allocated when the variable is first used. - -### Listing the available variables in an sdf file {#listing_the_available_variables_in_an_sdf_file} - -To see what variables are available use the list_variables method - -```python -import sdf_helper as sh -data=sh.getdata('Data/0010.sdf') -sh.list_variables(data) -``` - -This produces an output that looks something like - -```text -CPUs_Current_rank [0] -CPUs_Original_rank [2] -Current_Jx [400] -Derived_Charge_Density [400] -Derived_Number_Density [400] -Derived_Number_Density_Left [400] -Derived_Number_Density_Right [400] -Electric_Field_Ex [400] -Grid_CPUs_Original_rank [3] -Grid_CPUs_Original_rank_mid [2] -Grid_Grid [401] -Grid_Grid_mid [400] -Grid_x_px_Left [400, 200] -Grid_x_px_Left_mid [399, 199] -Grid_x_px_Right [400, 200] -Grid_x_px_Right_mid [399, 199] -Wall_time [1] -dist_fn_x_px_Left [400, 200] -dist_fn_x_px_Right [400, 200] -``` - -These are the names of the variables in the data structure. This example -is taken from the supplied `two_stream.deck` example in 1D. - -### Working with the data in an SDF file {#working_with_the_data_in_an_sdf_file} - -You can access the underlying data using the names obtained from -`list_variables` - -```python -variable = data.Electric_Field_Ex -``` - -This returns an instance of either `sdf.BlockPlainVariable` or -`sdf.BlockPointVariable` depending on whether you have requested a grid -variable (such as Ex, Ey or a distribution function) or a particle -variable (such as particle momentum or weight). The raw contents of the -variable is a numpy array. It is then available using the `data` element -of these objects. - -```python -import numpy as np -variable = data.Electric_Field_Ex -raw = variable.data -print(type(raw)) -print(np.mean(raw)) -``` - -produces the output - -```text - --1.27980874427008e-06 -``` - -### Plotting using sdf_helper {#plotting_using_sdf_helper} - -The sdf_helper wrapper script comes with some plotting routines. They -are incomplete currently, but aim to provide as close as possible to -press ready figures in a single command. You need the `matplotlib` -library to use these routines, and they are only available for 1D and 2D -data at present. To plot data, simply provide an -`sdf.BlockPlainVariable` object to the routine `plot_auto`. An example -of plotting a 1D variable, using the `two_stream.deck` example deck to -generate the figures would be - -```python -import sdf_helper as sh -import matplotlib.pyplot as plt - -plt.ion() -data=sh.getdata('Data/0010.sdf') -sh.plot_auto(data.Current_Jx) -``` - -This will produce a window similar to the image shown here, with slight -difference depending on your version of matplotlib and your operating -system. The code `plt.ion()` sets matplotlib to interactive mode, so -control will be returned to you as soon as the plot has finished -drawing. - -[Example 1D plot generated by sdf_helper.plot_auto](Matplotlib1D_screenshot.png) - -Plotting a 2D function is the same basic idea, and the code - -```python -import sdf_helper as sh -import matplotlib.pyplot as plt - -plt.ion() -data=sh.getdata('Data/0010.sdf') -sh.plot_auto(data.dist_fn_x_px_Right, iso=0) -``` - -![ thumb \| 200px \| Example 2D plot generated by -sdf_helper.plot_auto](Matplotlib2D.png) - -will produce the figure on the right. The procedure for variables from -EPOCH2D data is exactly the same. - -### Changing colour tables {#changing_colour_tables} - -The easiest solution to changing colour tables is to set the global -colour table. This is done by - -```python -import matplotlib.pyplot as plt -plt.set_cmap(tablename) -``` - -where `tablename` is a string describing the colour table to be used. -The available strings are given -[here](http://matplotlib.org/users/colormaps.html) - -### Some bugs in matplotlib {#some_bugs_in_matplotlib} - -There are some bugs in matplotlib which can mean that sometimes the 2D -images don't render properly. If you get incorrect rendering, please -try updating matplotlib to the latest version for your platform. If that -doesn't work then pass the parameter `compatibility=True` to the -`plot_auto` routine. This may make the plot slightly less pretty, but -tends to work on more platforms. - -# Core Python library {#core_python_library} - -The SDF python reader allows you to read any SDF file and access any -information within the file. It has very few user friendly features to -assist working with the files. Some of the methods listed in the section -on sdf_helper (notably list_variables) are not available when using -the core library. Loading an sdf file with the core library has the -following syntax - -```python -import sdf -data=sdf.read(filename) -``` - -where filename is a string containing the name of the file to be loaded. -This returns an sdf.BlockList object - -### The sdf.BlockList object {#the_sdf.blocklist_object} - -The `list_variables` routine is added by the sdf_helper wrapper, but -you can check what elements are in the file by simply typing - -```python -data.__dict__ -``` - -Which will produce an output like the following example from EPOCH2D - -`{'Header': {'filename': '/Users/phsiav/dev/epoch/epoch2d/Data/0005.sdf', 'file_version': 1, 'file_revision': 4, 'code_name': 'Epoch2d', 'step': 53, 'time': 2.5293132385759517e-14, 'jobid1': 1552896563, 'jobid2': 376, 'code_io_version': 1, 'restart_flag': False, 'other_domains': False, 'station_file': False}, 'Wall_time': , 'Electric_Field_Ex': , 'Electric_Field_Ey': , 'Electric_Field_Ez': , 'Magnetic_Field_Bx': , 'Magnetic_Field_By': , 'Magnetic_Field_Bz': , 'Grid_Grid': , 'Grid_Grid_mid': , 'Grid_CPUs_Original_rank': , 'Grid_CPUs_Original_rank_mid': , 'CPUs_Original_rank': , 'CPUs_Current_rank': }` - -### The sdf.BlockPlainVariable object {#the_sdf.blockplainvariable_object} - -These objects represent the variables in the SDF file. It does not fully -implement the __dict__ property, so to inspect it's contents you -must use - -```python -dir(data.Electric_Field_Ey) -``` - -which produces an output like - -`['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'blocklist', 'data', 'data_length', 'datatype', 'dims', 'grid', 'grid_id', 'grid_mid', 'id', 'mult', 'name', 'stagger', 'units']` - -The key elements are `data` which contains the raw data for the variable -stored as a numpy array, `dims` which is an array containing the number -of elements in each dimension of the array and `grid` and `grid_mid` -which refer to sdf.BlockPlainMesh objects that represent the -grid axes that the variable is to be plotted against. Grid and grid_mid -do similar but different things. Grid is an array of points -corresponding to the edges of the computational cells, grid_mid to the -midpoints. This means that all of the arrays in `grid` are one element -longer than the arrays in `grid_mid`. To identify whether to use `grid` -or `grid_mid` you must compare the sizes of the variable `dims` array to -the sizes of the `grid` and `grid_mid` sizes and _for each axis_ use -the element of `grid` or `grid_mid` that has the same number of -elements. - -Important note! - 2D SDF data is loaded into Python rotated by 90 -degrees compared to the original Fortran code that generated it. - -### The sdf.BlockPlainMesh object {#the_sdf.blockplainmesh_object} - -Once again you have to use the `dir` command to output the information -about an sdf.BlockPlainMesh object, for example in EPOCH - -```python -dir(data.Grid_Grid) -``` - -Which produces output like - -`['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'blocklist', 'data', 'data_length', 'datatype', 'dims', 'extents', 'geometry', 'id', 'labels', 'mult', 'name', 'units']` - -The important element of this block is `data` which is a tuple of 1D -numpy arrays corresponding to each coordinate axis of the grid. - -### Plotting a variable using raw SDF and raw matplotlib {#plotting_a_variable_using_raw_sdf_and_raw_matplotlib} - -- Warning - This is not our recommended suggestion for plotting. We - recommend using our helper routines in sdf_helper\* - -```python -import matplotlib.pyplot as plt -import sdf - -data=sdf.read('Data/0005.sdf') -ey = data.Electric_Field_Ey -plt.pcolormesh(ey.grid_mid.data[0], ey.grid_mid.data[1], ey.data.T) -plt.show() -``` diff --git a/content/documentation/visualising_output/Python_BEAM.md b/content/documentation/visualising_output/Python_BEAM.md index 76dc69a..d1cbfc3 100644 --- a/content/documentation/visualising_output/Python_BEAM.md +++ b/content/documentation/visualising_output/Python_BEAM.md @@ -12,13 +12,12 @@ menu: weight: 30 --- -# BEAM {#BEAM} +![BEAM logo](BEAM.png) +*Original logo produced by Oscar Adams CC BY-SA 4.0* -![BEAM logo](BEAM.png) +**BEAM** (Broad EPOCH Analysis Modules) is a collection of independent yet complementary open-source tools for analysing EPOCH simulations in Python, designed to be modular, allowing researchers to adopt only the components they require without being constrained by a rigid framework. In line with the **FAIR principles — Findable**, **Accessible**, **Interoperable**, and **Reusable** — each package is openly published with clear documentation and versioning (Findable), distributed via public repositories (Accessible), designed to follow common standards for data structures and interfaces (Interoperable), and includes licensing and metadata to support long-term use and adaptation (Reusable). -**BEAM** (Broad EPOCH Analysis Modules) is a collection of independent yet complementary open-source tools for analysing EPOCH simulations in Python, designed to be modular so researchers can adopt only the components they require without being constrained by a rigid framework. In line with the **FAIR principles — Findable**, **Accessible**, **Interoperable**, and **Reusable** — each package is openly published with clear documentation and versioning (Findable), distributed via public repositories (Accessible), designed to follow common standards for data structures and interfaces (Interoperable), and includes licensing and metadata to support long-term use and adaptation (Reusable). The packages are as follows: - -- [sdf-xarray](https://github.com/epochpic/sdf-xarray): Reading and processing SDF files and converting them to [xarray](https://docs.xarray.dev/en/stable/). +- [sdf-xarray](https://github.com/epochpic/sdf-xarray): Processing and plotting of SDF files and converting them to [xarray](https://docs.xarray.dev/en/stable/). - [epydeck](https://github.com/epochpic/epydeck): Input deck reader and writer. - [epyscan](https://github.com/epochpic/epyscan): Create campaigns over a given parameter space using various sampling methods. @@ -34,11 +33,15 @@ All of the packages are available on PyPI and can be installed using pip: pip install sdf-xarray epydeck epyscan ``` -Each package can be used independently or together depending on your needs. +Each package can be used independently or combined, depending on your needs. + +## Citation {#citation} -## Citing {#citing} +If any of the BEAM packages contribute to a project that leads to publication, please acknowledge this by citing the module in question. This can be done by clicking the "Cite this repository" button located near the top right of their respective GitHub pages. -If any of the BEAM contribute to a project that leads to publication, please acknowledge this by citing the module in question. This can be done by clicking the "cite this repository" button located near the top right of their respective github pages. +## Contribution {#contribution} + +We welcome contributions to the BEAM ecosystem! Whether it's reporting issues, suggesting features, or submitting pull requests, your input helps improve these tools for the community. Please follow the contribution guidelines in each repository and feel free to reach out via GitHub discussions or issues. ## sdf-xarray: analysing EPOCH output {#sdf-xarray} @@ -91,15 +94,15 @@ print(ds) ``` `SDFPreprocess` checks that all the files are from the same simulation, as -ensures there's a `time` dimension so the files are correctly concatenated. +ensures that there's a `time` dimension so the files are correctly concatenated. If your simulation has multiple `output` blocks so that not all variables are output at every time step, then those variables will have `NaN` values at the corresponding time points. -After having loaded in a series of datasets we can select a simulation file by calling the `.isel()` function where we pass in the parameter of `time=0` where `0` can be a number between `0` and the total number of simulation files. +After loading a series of datasets, we can select a simulation file by calling the .isel() function and passing the parameter time=0, where 0 can be any number between 0 and the total number of simulation files. -We can also use the `.sel()` function if we know the exact simulation time we want to select. There must be a corresponding dataset with this time for it work correctly. +We can also use the `.sel()` function if we know the exact simulation time we want to select. There must be a corresponding dataset with this time for it to work correctly. ```python print(f"There are a total of {ds["time"].size} time steps. (This is the same as the number of SDF files in the folder)") @@ -119,22 +122,39 @@ ds["Electric_Field_Ex"].isel(time=20) ### Plotting {#plotting} -Since this package converts SDF files to xarray we can leverage the plotting features that are included in xarray. For example we can load in a SDF file and plot the number density of the electrons: +Since this package converts SDF files to xarray, we can leverage the plotting features that are included in xarray. For example we can load in a SDF file and plot the number density of the electrons: ```python import xarray as xr ds = xr.open_dataset("0010.sdf") -# NOTE: EPOCH saves the x and y axes into SDF files in the inverse order to that expected by xarray so we have to specify which axes is which otherwise our plot comes out inverted +# NOTE: EPOCH saves the x and y axes in SDF files in the inverse order of what is expected by xarray, so we must specify which axis is which; otherwise, our plot will be inverted. ds["Derived_Number_Density_Electron"].plot(x="X_Grid_mid", y="Y_Grid_mid") ``` -![Derived Number Density Electron Plot](BEAM_Derived_Number_Density_Electrons.png) +![Derived Number Density Electron Plot](BEAM_Derived_Number_Density_Electron.png) + +### Animating {#animating} + +This package also contains custom functionality to generate time-resolved animations by loading in all the SDF files in a given simulation run: + +```python +import xarray as xr +from sdf_xarray import SDFPreprocess + +ds = xr.open_mfdataset("*.sdf", preprocess=SDFPreprocess()) + +ani = ds["Derived_Number_Density_Electron"].epoch.animate() + +ani.save("Derived_Number_Density_Electron.gif", fps=5) +``` + +![Derived Number Density Electron Animation](BEAM_Derived_Number_Density_Electron.gif) ## epydeck: Writing input deck files with Python {#epydeck} -Writing large numbers of EPOCH input files by hand can be tedious and error-prone. `epydeck` (short for *EPOCH Python deck*) allows you to create and manipulate input decks in Python: +Writing a large number of EPOCH input files by hand can be tedious and error-prone. `epydeck` (short for *EPOCH Python deck*) allows you to create and manipulate input decks in Python: - Build decks using standard Python data structures - Load, modify, and save EPOCH-style `.deck` files @@ -149,7 +169,7 @@ The interface follows the standard Python - `epydeck.dumps` to write to a string -**The key features for this module are highlighted below, for in-depth documentation please visit, for in-depth documentation please visit ** +**The key features for this module are highlighted below, for in-depth documentation please visit ** ### Example {#epydeck_example} @@ -201,7 +221,7 @@ be dicts with the following keys: - `"log"`: (optional) `bool`, if `True` then grid is done in log space for this parameter -**The key features for this module are highlighted below, for in-depth documentation please visit, for in-depth documentation please visit ** +**The key features for this module are highlighted below, for in-depth documentation please visit ** ### Example {#epyscan_example} @@ -228,7 +248,7 @@ with open("template_deck_filename") as f: grid_scan = epyscan.GridScan(parameters, n_samples=4) # Define the root directory where the simulation folders will be saved. -# This directory will be created if it doesn't exist +# This directory will be created if it does not already exist run_root = pathlib.Path("example_campaign") # Initialize a campaign object with the template deck and the root directory. @@ -243,7 +263,7 @@ paths = [campaign.setup_case(sample) for sample in grid_scan] with open("paths.txt", "w") as f: [f.write(f"{path}\n") for path in paths] -# Opening paths.txt +# Example content of paths.txt # example_campaign/run_0_1000000/run_0_10000/run_0_100/run_0 # example_campaign/run_0_1000000/run_0_10000/run_0_100/run_1 # example_campaign/run_0_1000000/run_0_10000/run_0_100/run_2