Skip to content

Commit cd2acc1

Browse files
authored
Merge pull request #35 from cindytsai/libyt-dev
Support dim 2/1 and Fix Stuff
2 parents d3347af + 996c394 commit cd2acc1

File tree

10 files changed

+453
-33
lines changed

10 files changed

+453
-33
lines changed

.github/workflows/build-test.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- version: "3.12"
2929
toxenv: "py312"
3030
include:
31-
- os: ubuntu-20.04
31+
- os: ubuntu-22.04
3232
python: { version: "3.7", toxenv: "py37" }
3333

3434
steps:
@@ -40,6 +40,15 @@ jobs:
4040
run: |
4141
python -m pip install --upgrade pip
4242
python -m pip install tox
43+
- name: Get test dataset
44+
uses: actions/cache@v4
45+
id: cache_dataset
46+
with:
47+
path: ${{ github.workspace }}/tests/data
48+
key: ${{ hashFiles('tests/data/**') }}
49+
- if: steps.cache_dataset.outputs.cache-hit != 'true'
50+
working-directory: ${{ github.workspace }}/tests/data
51+
run: sh get_test_data.sh
4352
- name: Run pytest
4453
run: tox -e ${{ matrix.python.toxenv }} -- --html=report.html --self-contained-html
4554
- name: Upload pytest report

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,6 @@ ENV/
106106
.idea/
107107

108108
report.html
109+
110+
tests/data/gamer/*
111+
tests/data/enzo/*

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# libyt frontend for yt
22

33
[![PyPI version](https://badge.fury.io/py/yt-libyt.svg)](https://badge.fury.io/py/yt-libyt)
4+
[![Run Tests](https://github.com/data-exp-lab/yt_libyt/actions/workflows/build-test.yml/badge.svg)](https://github.com/data-exp-lab/yt_libyt/actions/workflows/build-test.yml)
5+
[![Style Check](https://github.com/data-exp-lab/yt_libyt/actions/workflows/style-check.yml/badge.svg)](https://github.com/data-exp-lab/yt_libyt/actions/workflows/style-check.yml)
46

57
This is a [`yt`](https://yt-project.org/) frontend for [`libyt`](https://github.com/yt-project/libyt).
68

setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ ignore =
6666
E741,
6767
# Line break occurred before a binary operator (black compatibility)
6868
W503,
69+
# Except handlers should only be names of exception classes
70+
B030,
6971

7072
################### tox env ###################
7173
# Test -- pytest (Default)
@@ -85,6 +87,8 @@ deps =
8587
pytest-html
8688
pytest-cov
8789
pytest-randomly
90+
h5py
91+
libconf
8892
commands =
8993
pytest {posargs}
9094

tests/data/get_test_data.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
# This script downloads test data for yt_libyt
3+
# Run this script inside the tests/data directory: sh get-test-data.sh
4+
5+
# gamer
6+
mkdir -p gamer
7+
curl -o "gamer/Plummer.tar.gz" "https://yt-project.org/data/Plummer.tar.gz"
8+
tar -xvzf "gamer/Plummer.tar.gz" -C gamer
9+
10+
# enzo
11+
mkdir -p enzo
12+
curl -o "enzo/IsolatedGalaxy.tar.gz" "https://yt-project.org/data/IsolatedGalaxy.tar.gz"
13+
tar -xvzf "enzo/IsolatedGalaxy.tar.gz" -C enzo
14+
curl -o "enzo/EnzoKelvinHelmholtz.tar.gz" "https://yt-project.org/data/EnzoKelvinHelmholtz.tar.gz"
15+
tar -xvzf "enzo/EnzoKelvinHelmholtz.tar.gz" -C enzo

tests/stubs/libyt_v_0_2_stub.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import types
2+
3+
import numpy as np
4+
import yt
5+
6+
LIBYT_VERSION = (0, 2, 0)
7+
8+
9+
def create_libyt_stub(
10+
simulation: str,
11+
fig_base_name: str,
12+
test_data: str,
13+
get_code_params: dict,
14+
field_list: dict,
15+
particle_list: dict,
16+
simulation_field_to_yt_field,
17+
) -> types.ModuleType:
18+
"""
19+
Returns a stub module that mimics libyt with a specific simulation.
20+
21+
:note: yt store the data structure in 3d, which is if the simulation is 2d, an additional dimension is added.
22+
23+
hierarchy is stored in 3d as well in a continuous array with 0 as the first grid, if dimensionality is 2,
24+
the third dimension is set to 1.
25+
grid_data stores the grid id starting from the index offset.
26+
27+
When extracting the data using yt, the data is always in 3d, so if the simulation is 2d, we need to extract it.
28+
29+
:param simulation: simulation name, e.g., "gamer", "enzo", etc.
30+
:param fig_base_name: figure base name
31+
:param test_data: the absolute path to the test data.
32+
:param get_code_params: the code parameters defined in the simulation frontend, and how to get it.
33+
{
34+
"code_params": [(param_name, default_value), ...],
35+
"method": (function to get the parameter)
36+
"expected_error": Exception type that is expected to be raised if the parameter is not found
37+
}
38+
:param field_list: libyt-v0.2 defined field list
39+
:param particle_list: libyt-v0.2 defined particle list
40+
:param simulation_field_to_yt_field: mapping field_list and particle_list name to yt field name
41+
"""
42+
# Mock libyt module based on libyt version 0.x.0
43+
stub = types.ModuleType("libyt")
44+
stub.libyt_info = {
45+
"version": LIBYT_VERSION,
46+
"SERIAL_MODE": True,
47+
"INTERACTIVE_MODE": False,
48+
"JUPYTER_KERNEL": False,
49+
"SUPPORT_TIMER": False,
50+
}
51+
stub.param_yt = {
52+
"frontend": simulation,
53+
"fig_basename": fig_base_name,
54+
"current_time": None,
55+
"current_redshift": None,
56+
"omega_lambda": None,
57+
"omega_matter": None,
58+
"hubble_constant": None,
59+
"length_unit": None,
60+
"mass_unit": None,
61+
"time_unit": None,
62+
"magnetic_unit": None,
63+
"cosmological_simulation": None,
64+
"dimensionality": None,
65+
"refine_by": None,
66+
"velocity_unit": None,
67+
"domain_left_edge": None,
68+
"domain_right_edge": None,
69+
"periodicity": None,
70+
"domain_dimensions": None,
71+
"num_grids": None,
72+
"index_offset": None,
73+
"field_list": None,
74+
"particle_list": None,
75+
}
76+
stub.param_user = {}
77+
stub.hierarchy = {
78+
"grid_left_edge": None,
79+
"grid_right_edge": None,
80+
"grid_dimensions": None,
81+
"grid_parent_id": None,
82+
"grid_levels": None,
83+
"proc_num": None,
84+
# "par_count_list": None, # This is optional, which is a bad design choice.
85+
}
86+
stub.grid_data = {}
87+
stub.particle_data = {}
88+
89+
ds = yt.load(test_data)
90+
# Fill in param_user
91+
for param in get_code_params["code_params"]:
92+
try:
93+
stub.param_user[param[0]] = get_code_params["method"](ds, param[0])
94+
except get_code_params["expected_error"]:
95+
stub.param_user[param[0]] = param[1]
96+
97+
# Fill in param_yt
98+
for param in ds.__dict__.keys():
99+
if param in stub.param_yt and stub.param_yt[param] is None:
100+
stub.param_yt[param] = getattr(ds, param)
101+
if stub.param_yt["velocity_unit"] is None:
102+
stub.param_yt["velocity_unit"] = stub.param_yt["length_unit"] / stub.param_yt["time_unit"]
103+
stub.param_yt["domain_left_edge"] = ds.domain_left_edge
104+
stub.param_yt["domain_right_edge"] = ds.domain_right_edge
105+
stub.param_yt["periodicity"] = ds.periodicity
106+
stub.param_yt["domain_dimensions"] = ds.domain_dimensions
107+
stub.param_yt["num_grids"] = ds.index.num_grids
108+
stub.param_yt["index_offset"] = ds._index_class.grid._id_offset
109+
stub.param_yt["field_list"] = field_list
110+
stub.param_yt["particle_list"] = particle_list
111+
112+
# Fill in hierarchy
113+
stub.hierarchy["grid_left_edge"] = ds.index.grid_left_edge
114+
stub.hierarchy["grid_right_edge"] = ds.index.grid_right_edge
115+
stub.hierarchy["grid_dimensions"] = ds.index.grid_dimensions
116+
stub.hierarchy["grid_levels"] = ds.index.grid_levels
117+
stub.hierarchy["proc_num"] = np.zeros((stub.param_yt["num_grids"], 1), dtype=np.int32)
118+
stub.hierarchy["grid_parent_id"] = np.ones((stub.param_yt["num_grids"], 1), dtype=np.int32) * -1
119+
for g in ds.index.grids:
120+
if g.Parent is not None:
121+
stub.hierarchy["grid_parent_id"][g.id - stub.param_yt["index_offset"]] = g.Parent.id
122+
123+
# Fill in grid_data
124+
for gid in range(stub.param_yt["num_grids"]):
125+
if gid + stub.param_yt["index_offset"] not in stub.grid_data:
126+
stub.grid_data[gid + stub.param_yt["index_offset"]] = {}
127+
for field in field_list.keys():
128+
# TODO: assume cell-centered fields
129+
ghost_cells = field_list[field]["ghost_cell"]
130+
allocate_dim = stub.hierarchy["grid_dimensions"][gid][
131+
: stub.param_yt["dimensionality"]
132+
].copy()
133+
if field_list[field]["contiguous_in_x"]:
134+
allocate_dim = np.flip(allocate_dim)
135+
for d in range(2 * stub.param_yt["dimensionality"]):
136+
allocate_dim[int(d / 2)] += ghost_cells[d]
137+
stub.grid_data[gid + stub.param_yt["index_offset"]][field] = (
138+
np.ones(allocate_dim, dtype=np.float64) * np.nan
139+
)
140+
141+
if field_list[field]["contiguous_in_x"]:
142+
if stub.param_yt["dimensionality"] == 3:
143+
stub.grid_data[gid + stub.param_yt["index_offset"]][field][
144+
ghost_cells[0] : allocate_dim[0] - ghost_cells[1],
145+
ghost_cells[2] : allocate_dim[1] - ghost_cells[3],
146+
ghost_cells[4] : allocate_dim[2] - ghost_cells[5],
147+
] = (
148+
ds.index.grids[gid][simulation_field_to_yt_field[field]]
149+
.swapaxes(0, 2)
150+
.in_base("code")
151+
)
152+
elif stub.param_yt["dimensionality"] == 2:
153+
stub.grid_data[gid + stub.param_yt["index_offset"]][field][
154+
ghost_cells[0] : allocate_dim[0] - ghost_cells[1],
155+
ghost_cells[2] : allocate_dim[1] - ghost_cells[3],
156+
] = np.squeeze(
157+
ds.index.grids[gid][simulation_field_to_yt_field[field]].in_base("code")
158+
).swapaxes(
159+
0, 1
160+
)
161+
else:
162+
stub.grid_data[gid + stub.param_yt["index_offset"]][field][
163+
ghost_cells[0] : allocate_dim[0] - ghost_cells[1]
164+
] = ds.index.grids[gid][simulation_field_to_yt_field[field]].in_base("code")
165+
166+
return stub

tests/test_enzo.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import os
2+
import sys
3+
4+
import yt
5+
from stubs.libyt_v_0_2_stub import create_libyt_stub
6+
7+
import yt_libyt
8+
9+
10+
def test_enzo_isolated_galaxy():
11+
# Create libyt stub and make it importable
12+
simulation = "enzo"
13+
problem = "IsolatedGalaxy"
14+
fig_base_name = f"{simulation}-{problem}"
15+
test_data_path = os.path.join(
16+
os.path.dirname(__file__), "data", simulation, f"{problem}/galaxy0030/galaxy0030"
17+
)
18+
code_param_list = {
19+
"code_params": [
20+
("HydroMethod", None),
21+
("MultiSpecies", None),
22+
("DualEnergyFormalism", None),
23+
],
24+
"method": (lambda ds, code_param: ds.parameters[code_param]),
25+
"expected_error": KeyError,
26+
}
27+
field_list = {
28+
"Density": {
29+
"attribute": ["", [], None],
30+
"field_type": "cell-centered",
31+
"contiguous_in_x": True,
32+
"ghost_cell": [3, 3, 3, 3, 3, 3],
33+
}
34+
}
35+
particle_list = {}
36+
simulation_field_to_yt_field = {"Density": ("gas", "density")}
37+
38+
libyt_stub = create_libyt_stub(
39+
simulation,
40+
fig_base_name,
41+
test_data_path,
42+
code_param_list,
43+
field_list,
44+
particle_list,
45+
simulation_field_to_yt_field,
46+
)
47+
sys.modules["libyt"] = libyt_stub
48+
49+
# Run yt_libyt code
50+
ds = yt_libyt.libytDataset()
51+
slc = yt.SlicePlot(ds, "z", ("enzo", "Density"))
52+
slc.save(f"{fig_base_name}-libyt.png")
53+
54+
# Compare to post-processing results
55+
ds_post = yt.load(test_data_path)
56+
slc_post = yt.SlicePlot(ds_post, "z", ("enzo", "Density"))
57+
slc_post.save(f"{fig_base_name}-post.png")
58+
fig_size = slc_post.data_source["enzo", "Density"].size
59+
60+
assert (
61+
sum(slc.data_source["enzo", "Density"] - slc_post.data_source["enzo", "Density"]) / fig_size
62+
< 1e-2
63+
)
64+
65+
66+
def test_enzo_kelvin_helmholtz():
67+
# Create libyt stub and make it importable
68+
simulation = "enzo"
69+
problem = "EnzoKelvinHelmholtz"
70+
fig_base_name = f"{simulation}-{problem}"
71+
test_data_path = os.path.join(
72+
os.path.dirname(__file__), "data", simulation, f"{problem}/DD0000/DD0000"
73+
)
74+
code_param_list = {
75+
"code_params": [
76+
("HydroMethod", None),
77+
("MultiSpecies", None),
78+
("DualEnergyFormalism", None),
79+
],
80+
"method": (lambda ds, code_param: ds.parameters[code_param]),
81+
"expected_error": KeyError,
82+
}
83+
field_list = {
84+
"Density": {
85+
"attribute": ["", [], None],
86+
"field_type": "cell-centered",
87+
"contiguous_in_x": True,
88+
"ghost_cell": [3, 3, 3, 3, 0, 0],
89+
}
90+
}
91+
particle_list = {}
92+
simulation_field_to_yt_field = {"Density": ("gas", "density")}
93+
94+
libyt_stub = create_libyt_stub(
95+
simulation,
96+
fig_base_name,
97+
test_data_path,
98+
code_param_list,
99+
field_list,
100+
particle_list,
101+
simulation_field_to_yt_field,
102+
)
103+
sys.modules["libyt"] = libyt_stub
104+
105+
# Run yt_libyt code
106+
ds = yt_libyt.libytDataset()
107+
slc = yt.SlicePlot(ds, "z", ("enzo", "Density"))
108+
slc.save(f"{fig_base_name}-libyt.png")
109+
110+
# Compare to post-processing results
111+
ds_post = yt.load(test_data_path)
112+
slc_post = yt.SlicePlot(ds_post, "z", ("enzo", "Density"))
113+
slc_post.save(f"{fig_base_name}-post.png")
114+
fig_size = slc_post.data_source["enzo", "Density"].size
115+
116+
assert (
117+
sum(slc.data_source["enzo", "Density"] - slc_post.data_source["enzo", "Density"]) / fig_size
118+
< 1e-2
119+
)

0 commit comments

Comments
 (0)