Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 70 additions & 38 deletions src/flowvcutils/inigenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import configparser
from .utils import get_project_root
import os

import math

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -106,16 +106,22 @@ def streach_bounds(self, pt_min, pt_max, cell_size):

"""
current_point = pt_min
n_pts = 0
if current_point >= pt_max:
raise ValueError("min must be less than max")
if cell_size <= 0:
raise ValueError("Cell Size must be >0")
while current_point < pt_max:
current_point = current_point + cell_size
n_pts = n_pts + 1
new_max = current_point
return (new_max, n_pts)

domain = pt_max - pt_min
# ceil the domain / cell_size to find how many cells needed to cover the domain:
n_cells = math.ceil(domain / cell_size)

# compute the new max
new_max = pt_min + n_cells * cell_size

# Rounding to 8 decimal places to reduce floating-point artifacts
new_max = round(new_max, 8)

return (new_max, n_cells)

def set_data_range_manual(self, min_xyz, max_xyz, streach=False, cell_size=0):
# Manually set
Expand Down Expand Up @@ -205,39 +211,62 @@ def __init__(self, results_processor):
self.load_config()
self.__update_dict = {}

def set_data_range_defaults(self, cell_size, streach=True, manual_bounds=None):
if manual_bounds:
def set_data_range_defaults(
self, auto_range, cell_size, manual_bounds=None, streach=False
):
"""
1. If auto_range is True, compute Data_MeshBounds from .vtu.
Otherwise, leave Data_MeshBounds unchanged.
2. If manual_bounds is provided, update FTLE_MeshBounds from it.
Otherwise, if auto_range is True, copy from Data_MeshBounds.
Otherwise, do not overwrite FTLE_MeshBounds.
"""

(x_range, y_range, z_range) = self.results_processor.set_data_range_manual(
manual_bounds[0], # (min_x, min_y, min_z)
manual_bounds[1], # (max_x, max_y, max_z)
streach,
cell_size,
def _update_bounds(prefix, x_range, y_range, z_range):
"""Update __update_dict with mesh bounds for prefix')."""
self.__update_dict.update(
{
f"{prefix}.xmin": str(x_range[0]),
f"{prefix}.xmax": str(x_range[1]),
f"{prefix}.ymin": str(y_range[0]),
f"{prefix}.ymax": str(y_range[1]),
f"{prefix}.zmin": str(z_range[0]),
f"{prefix}.zmax": str(z_range[1]),
}
)
else:

def _update_res(prefix):
"""Update __update_dict with xres, yres, zres for prefix)."""
self.__update_dict.update(
{
f"{prefix}.xres": str(self.results_processor.x_points),
f"{prefix}.yres": str(self.results_processor.y_points),
f"{prefix}.zres": str(self.results_processor.z_points),
}
)

# 1) If auto_range => pull Data_MeshBounds from .vtu
if auto_range:
x_range, y_range, z_range = self.results_processor.find_data_range(
streach=streach, cell_size=cell_size
)
_update_bounds("Data_MeshBounds", x_range, y_range, z_range)

self.__update_dict.update(
{
"Data_MeshBounds.xmin": str(x_range[0]),
"Data_MeshBounds.xmax": str(x_range[1]),
"Data_MeshBounds.ymin": str(y_range[0]),
"Data_MeshBounds.ymax": str(y_range[1]),
"Data_MeshBounds.zmin": str(z_range[0]),
"Data_MeshBounds.zmax": str(z_range[1]),
"FTLE_MeshBounds.xmin": str(x_range[0]),
"FTLE_MeshBounds.xmax": str(x_range[1]),
"FTLE_MeshBounds.ymin": str(y_range[0]),
"FTLE_MeshBounds.ymax": str(y_range[1]),
"FTLE_MeshBounds.zmin": str(z_range[0]),
"FTLE_MeshBounds.zmax": str(z_range[1]),
"FTLE_MeshBounds.xres": str(self.results_processor.x_points),
"FTLE_MeshBounds.yres": str(self.results_processor.y_points),
"FTLE_MeshBounds.zres": str(self.results_processor.z_points),
}
)
# 2) If manual_bounds => use it for FTLE_MeshBounds
if manual_bounds:
(ftle_xr, ftle_yr, ftle_zr) = self.results_processor.set_data_range_manual(
manual_bounds[0],
manual_bounds[1],
streach=streach,
cell_size=cell_size,
)
_update_bounds("FTLE_MeshBounds", ftle_xr, ftle_yr, ftle_zr)
_update_res("FTLE_MeshBounds")

# Otherwise, if auto_range is on but no manual bounds => copy from Data to FTLE
elif auto_range:
_update_bounds("FTLE_MeshBounds", x_range, y_range, z_range)
_update_res("FTLE_MeshBounds")

def set_path_defaults(self):
self.__update_dict.update(
Expand Down Expand Up @@ -313,10 +342,13 @@ def write_config_file(self, file_path=None, file_name=None):

def process_directory(self, auto_range, cell_size, direction, manual_bounds=None):
self.set_path_defaults()
if auto_range:
self.set_data_range_defaults(
cell_size=cell_size, manual_bounds=manual_bounds
)
# if auto_range:
self.set_data_range_defaults(
auto_range=auto_range,
cell_size=cell_size,
streach=True,
manual_bounds=manual_bounds,
)
if direction == "backward":
self.set_backwards_defaults()
elif direction == "forward":
Expand Down
2 changes: 1 addition & 1 deletion src/flowvcutils/jsonlogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def settup_logging(config_file_path=None):
config = json.load(f_in)

# for handler_name, handler in config["handlers"].items():
for handler_name, handler in config["handlers"].items():
for _handler_name, handler in config["handlers"].items():
if "filename" in handler:
handler["filename"] = str(logs_dir / pathlib.Path(handler["filename"]).name)

Expand Down
154 changes: 92 additions & 62 deletions tests/test_inigenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,71 +396,101 @@ def test_integration_full_config(create_sample_vtu_file):
assert not (Output_TEnd < Data_TMin)


def test_integration_manual_bounds():
def test_integration_manual_bounds(create_sample_vtu_file):
"""
Test that manual bounds are set correctly and the .in file picks them up,
rather than reading from a .vtu file.
Test that if auto_range is True, the Data_MeshBounds come from the .vtu file,
but the FTLE_MeshBounds and mesh resolution are driven by manual_bounds.
"""
# Arbitrary example bounds: x in [0.0,1.0], y in [2.0,3.0], z in [4.0,4.2]
# Our sample_vtu has points at (1,2,3), (4,5,6), and (-1,-2,-3),
# so we expect Data_MeshBounds to be:
# X in [-1.0, 4.0]
# Y in [-2.0, 5.0]
# Z in [-3.0, 6.0]
# Meanwhile, the FTLE_MeshBounds should match the manual bounds.
vtu_file = create_sample_vtu_file
directory = Path(vtu_file).parent.parent
(directory / "input_bin").mkdir()

# Manual bounds for FTLE: X in [0..1], Y in [2..3], Z in [4..4.2]
# with cell_size=0.1 => xres=10, yres=10, zres=2
manual_bounds = ((0.0, 2.0, 4.0), (0.95, 3.0, 4.16))
cell_size = 0.1

with TemporaryDirectory() as temp_dir:
os.mkdir(os.path.join(temp_dir, "input_bin"))
# Call the generator, passing our manual bounds
inigenerator_main(
directory=temp_dir,
auto_range=True, # "auto" sizing, but overridden by manual_bounds
cell_size=cell_size,
direction="backward",
batch=False,
manual_bounds=manual_bounds,
)

# The generator should produce an .in file named after the directory
dir_name = os.path.basename(temp_dir)
in_file_name = (
f"{dir_name}.in" if not dir_name.endswith("_") else f"{dir_name[:-1]}.in"
)
in_file = Path(temp_dir) / "input_bin" / in_file_name

# Ensure file was created
assert in_file.exists(), f"{in_file} was not created."

# Read certain fields from the .in file
var_list = [
"data_meshbounds.xmin",
"data_meshbounds.xmax",
"data_meshbounds.ymin",
"data_meshbounds.ymax",
"data_meshbounds.zmin",
"data_meshbounds.zmax",
"ftle_meshbounds.xres",
"ftle_meshbounds.yres",
"ftle_meshbounds.zres",
]
config_values = load_config(str(in_file), var_list)

# Convert to float/int as appropriate
xmin = float(config_values["data_meshbounds.xmin"])
xmax = float(config_values["data_meshbounds.xmax"])
ymin = float(config_values["data_meshbounds.ymin"])
ymax = float(config_values["data_meshbounds.ymax"])
zmin = float(config_values["data_meshbounds.zmin"])
zmax = float(config_values["data_meshbounds.zmax"])
xres = int(config_values["ftle_meshbounds.xres"])
yres = int(config_values["ftle_meshbounds.yres"])
zres = int(config_values["ftle_meshbounds.zres"])

# Check bounding box: cell_size=0.1 => expected domain [0..1], [2..3], [4..4.2]
# => xres=10, yres=10, zres=2
assert math.isclose(xmin, 0.0), f"Expected 0.0, got {xmin}"
assert math.isclose(xmax, 1.0), f"Expected 1.0, got {xmax}"
assert math.isclose(ymin, 2.0), f"Expected 2.0, got {ymin}"
assert math.isclose(ymax, 3.0), f"Expected 3.0, got {ymax}"
assert math.isclose(zmin, 4.0), f"Expected 4.0, got {zmin}"
assert math.isclose(zmax, 4.2), f"Expected 4.2, got {zmax}"
assert xres == 10, f"Expected xres=10, got {xres}"
assert yres == 10, f"Expected yres=10, got {yres}"
assert zres == 2, f"Expected zres=2, got {zres}"
# Call inigenerator with auto_range=True and the manual bounds
inigenerator_main(
directory=str(directory),
auto_range=True,
cell_size=cell_size,
direction="backward",
batch=False,
manual_bounds=manual_bounds,
)

# The generator should produce an .in file named after the directory
dir_name = directory.name
in_file_name = dir_name[:-1] + ".in" if dir_name.endswith("_") else f"{dir_name}.in"
in_file = directory / "input_bin" / in_file_name

# Ensure file was created
assert in_file.exists(), f"{in_file} was not created."

# Read certain fields from the .in file
var_list = [
"data_meshbounds.xmin",
"data_meshbounds.xmax",
"data_meshbounds.ymin",
"data_meshbounds.ymax",
"data_meshbounds.zmin",
"data_meshbounds.zmax",
"ftle_meshbounds.xmin",
"ftle_meshbounds.xmax",
"ftle_meshbounds.ymin",
"ftle_meshbounds.ymax",
"ftle_meshbounds.zmin",
"ftle_meshbounds.zmax",
"ftle_meshbounds.xres",
"ftle_meshbounds.yres",
"ftle_meshbounds.zres",
]
config_values = load_config(str(in_file), var_list)

# Convert to float/int as appropriate
data_xmin = float(config_values["data_meshbounds.xmin"])
data_xmax = float(config_values["data_meshbounds.xmax"])
data_ymin = float(config_values["data_meshbounds.ymin"])
data_ymax = float(config_values["data_meshbounds.ymax"])
data_zmin = float(config_values["data_meshbounds.zmin"])
data_zmax = float(config_values["data_meshbounds.zmax"])

ftle_xmin = float(config_values["ftle_meshbounds.xmin"])
ftle_xmax = float(config_values["ftle_meshbounds.xmax"])
ftle_ymin = float(config_values["ftle_meshbounds.ymin"])
ftle_ymax = float(config_values["ftle_meshbounds.ymax"])
ftle_zmin = float(config_values["ftle_meshbounds.zmin"])
ftle_zmax = float(config_values["ftle_meshbounds.zmax"])

ftle_xres = int(config_values["ftle_meshbounds.xres"])
ftle_yres = int(config_values["ftle_meshbounds.yres"])
ftle_zres = int(config_values["ftle_meshbounds.zres"])

# Confirm Data_MeshBounds came from the sample_vtu_file
assert math.isclose(data_xmin, -1.0), f"Expected Data X-min = -1.0, got {data_xmin}"
assert math.isclose(data_xmax, 4.0), f"Expected Data X-max = 4.0, got {data_xmax}"
assert math.isclose(data_ymin, -2.0), f"Expected Data Y-min = -2.0, got {data_ymin}"
assert math.isclose(data_ymax, 5.0), f"Expected Data Y-max = 5.0, got {data_ymax}"
assert math.isclose(data_zmin, -3.0), f"Expected Data Z-min = -3.0, got {data_zmin}"
assert math.isclose(data_zmax, 6.0), f"Expected Data Z-max = 6.0, got {data_zmax}"

# Confirm FTLE_MeshBounds came from manual_bounds
# We specified (0.0..~1.0), (2.0..3.0), (4.0..4.2)
# with cell_size=0.1 => xres=10, yres=10, zres=2
assert math.isclose(ftle_xmin, 0.0), f"Expected FTLE X-min = 0.0, got {ftle_xmin}"
assert math.isclose(ftle_xmax, 1.0), f"Expected FTLE X-max ~ 1.0, got {ftle_xmax}"
assert math.isclose(ftle_ymin, 2.0), f"Expected FTLE Y-min = 2.0, got {ftle_ymin}"
assert math.isclose(ftle_ymax, 3.0), f"Expected FTLE Y-max = 3.0, got {ftle_ymax}"
assert math.isclose(ftle_zmin, 4.0), f"Expected FTLE Z-min = 4.0, got {ftle_zmin}"
assert math.isclose(ftle_zmax, 4.2), f"Expected FTLE Z-max = 4.2, got {ftle_zmax}"

assert ftle_xres == 10, f"Expected FTLE xres=10, got {ftle_xres}"
assert ftle_yres == 10, f"Expected FTLE yres=10, got {ftle_yres}"
assert ftle_zres == 2, f"Expected FTLE zres=2, got {ftle_zres}"