-
Notifications
You must be signed in to change notification settings - Fork 52
Added NaNReporter and HighMaReporter to LettuceCFD #248
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
MaxBille
wants to merge
20
commits into
lettucecfd:master
Choose a base branch
from
MaxBille:174-reporter-to-interrupt-simulation
base: master
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 all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8757a09
Added NaNReporter and HighMaReporter to LettuceCFD
MaxBille f1c047d
added example to test NaNReporter and HighMaReporter
PhiSpel b2e7048
pep8
PhiSpel 4ec6378
Merge pull request #1 from PhiSpel/174-reporter-ps
MaxBille 4ff09c0
added tests for highMa and NaN reporter
PhiSpel f48f76f
tried reducing cognitive complexity for codeclimate
PhiSpel b0d4106
slightly updated nan-reporter examples
PhiSpel d92f9f6
pep8 cosmetics
PhiSpel 7821519
Merge pull request #2 from PhiSpel/174-reporter-ps
MaxBille b708a14
Update test_nan_reporter.py
PhiSpel 111ff99
Update test_nan_reporter.py
PhiSpel 3ccc698
Merge branch 'lettucecfd:master' into 174-reporter-to-interrupt-simul…
MaxBille 806cd26
minor comment
MaxBille de3a78b
moved class "Breakable Simulation" to _simulation.py from nan_repoter…
MaxBille 11d56ca
renaming and native option:
MaxBille f0c7fa2
rewrite FailureReporter (NaN reporter and HighMa reporter) with unifi…
MaxBille 0a74e9c
Merge branch 'lettucecfd:master' into 174-reporter-to-interrupt-simul…
MaxBille c2da43d
corrected pytests for nan- and highma-reporter
MaxBille f06f0f8
corrected pytests for nan- and highma-reporter (2)
MaxBille 0c906c3
FailureReporter is now ABC class;
MaxBille 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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| """ | ||
| This file showcases: | ||
| a) interrupting a TGV simulation using a reporter that detects NaN in f | ||
| b) interrupting an obstacle simulation using a reporter that detects Ma > 0.3 | ||
| """ | ||
|
|
||
| import torch | ||
| import lettuce as lt | ||
| import os | ||
|
|
||
| if not os.path.exists("./data"): | ||
| os.mkdir("./data") | ||
|
|
||
| ### SIMULATION 1: TGV with NaN Reporter### | ||
| # a) unstable TGV, that causes NaN values in f, which are detected by the NaN | ||
| # ... reporter, who interrupts the simulation. | ||
| flow = lt.TaylorGreenVortex( | ||
| lt.Context(dtype=torch.float64), | ||
| resolution=32, | ||
| reynolds_number=30000, | ||
| mach_number=0.3, | ||
| stencil=lt.D2Q9 | ||
| ) | ||
| nan_reporter = lt.NaNReporter(100, outdir="./data/nan_reporter", | ||
| vtk_out=True) | ||
| simulation = lt.BreakableSimulation( | ||
| flow=flow, | ||
| collision=lt.BGKCollision(tau=flow.units.relaxation_parameter_lu), | ||
| reporter=[nan_reporter]) | ||
| simulation(10000) | ||
| print(f"Failed after {nan_reporter.failed_iteration} iterations") | ||
|
|
||
| ### SIMULATION 2: obstacle with High Ma Reporter ### | ||
| # b) unstable obstacle flow, that causes high Ma values (Ma > 0.3), which are | ||
| # ... detected by the HighMa reporter, who interrupts the simulation. | ||
| flow = lt.Obstacle( | ||
| lt.Context(dtype=torch.float64,use_native=False), | ||
| resolution=[32, 32], | ||
| reynolds_number=100, | ||
| mach_number=0.01, | ||
| stencil=lt.D2Q9(), | ||
| domain_length_x=32 | ||
| ) | ||
| flow.mask = ((2 < flow.grid[0]) & (flow.grid[0] < 10) | ||
| & (2 < flow.grid[1]) & (flow.grid[1] < 10)) | ||
| high_ma_reporter = lt.HighMaReporter(100, outdir="./data/highma_reporter", | ||
| vtk_out=True) | ||
| simulation = lt.BreakableSimulation( | ||
| flow=flow, | ||
| collision=lt.BGKCollision(tau=flow.units.relaxation_parameter_lu), | ||
| reporter=[high_ma_reporter]) | ||
| simulation(10000) | ||
| print(f"Failed after {high_ma_reporter.failed_iteration} iterations") | ||
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
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,181 @@ | ||
| import torch | ||
|
|
||
| import os | ||
| from typing import List, Tuple | ||
| from abc import ABC, abstractmethod | ||
|
|
||
| from ... import Reporter, BreakableSimulation | ||
| from .vtk_reporter import write_vtk | ||
|
|
||
| __all__ = ["FailureReporterBase", "NaNReporter", "HighMaReporter"] | ||
|
|
||
| class FailureReporterBase(Reporter, ABC): | ||
| """ | ||
| abstract base class for reporters that detect a failing simulation, due | ||
| to conditions like NaN, high Mach number etc. | ||
| - relies on BreakableSimulation class (!) | ||
| """ | ||
| #TODO (optional): make STOPPING the simulation optional | ||
| # -> for example the HighMa-Reporter only reports high Mach numbers and | ||
| # their location, but the simulation just continues normally. | ||
| # This could be useful and "ok" in some cases: EXAMPLE, in the event | ||
| # of a settling period (transient high velocities) at the beginning | ||
| # of the run. | ||
|
|
||
| def __init__(self, interval, k=100, outdir=None, vtk_out=False): | ||
| super().__init__(interval) | ||
| self.k = k | ||
| self.outdir = outdir | ||
| self.name = "FailureReporter" | ||
| self.vtk_out = vtk_out | ||
| self.failed_iteration = None | ||
|
|
||
| def __call__(self, simulation: 'BreakableSimulation'): | ||
| if simulation.flow.i % self.interval == 0: | ||
| if self.is_failed(simulation): | ||
| results = self.get_results(simulation) | ||
| self.failed_iteration = simulation.flow.i | ||
|
|
||
| if self.outdir is not None: | ||
| self._write_log(simulation, results) | ||
| self.save_vtk(simulation) | ||
|
|
||
| print( | ||
| f'(!) ABORT MESSAGE: {self.name}Reporter detected ' | ||
| f'{self.name} (reporter-interval = {self.interval}) ' | ||
| f'at iteration {simulation.flow.i}. ' | ||
| f'See log in {self.outdir} for details!') | ||
| # telling simulation to abort simulation by setting i too high | ||
| simulation.flow.i = int(simulation.flow.i + 1e10) | ||
| # TODO: make this more robust with a failed-flag in simulation | ||
| # and not rely on flow.i to be high "enough" to be higher than | ||
| # the target steps number given to simulation(steps)? | ||
| # the 1e10 is "a lot", but in a very unlikely case of a very long simulation, | ||
| # where num_steps >=1e10, this would not work... | ||
|
|
||
| def _get_top_failures(self, mask, values) -> List[Tuple]: | ||
| """extract coordinates and values at nodes (mask); | ||
| returns list of (pos, val) tuples""" | ||
| failed_values = values[mask] | ||
| all_coords = torch.nonzero(mask) | ||
| num_to_extract = min(self.k, failed_values.numel()) | ||
|
|
||
| if torch.isnan(failed_values).any(): | ||
| top_indices = torch.arange(num_to_extract, device=values.device) | ||
| else: | ||
| _, top_indices = torch.topk(failed_values, k=num_to_extract, | ||
| largest=True, sorted=True) | ||
|
|
||
| top_coords = all_coords[top_indices].cpu().numpy() | ||
| top_values = failed_values[top_indices].cpu().numpy() | ||
|
|
||
| return [ | ||
| (list(c.astype(int)), float(v)) | ||
| for c, v in zip(top_coords, top_values) | ||
| ] | ||
|
|
||
| def _write_log(self, simulation: 'BreakableSimulation', results): | ||
| """writes results to file and logs flow.i""" | ||
|
|
||
| if not os.path.exists(self.outdir): | ||
| os.mkdir(self.outdir) | ||
|
|
||
| filepath = os.path.join(self.outdir, f"{self.name}_reporter.log") | ||
| with open(filepath, "w") as file: | ||
| file.write(f"(!) {self.name} detected in step {simulation.flow.i} " | ||
| f"at following locations (top {self.k} listed):\n") | ||
| file.write(" ") | ||
| for location in self.locations_string(simulation): | ||
| file.write(f"{location:6} ") | ||
| file.write("\n") | ||
| for pos, val in results: | ||
| line="" | ||
| for pos_i in pos: | ||
| line = line + f"{int(pos_i):6} " | ||
| file.write(f"{line:<20} | {val:<15.6f}\n") | ||
| file.write("\n") | ||
|
|
||
| def save_vtk(self, simulation: 'BreakableSimulation'): | ||
| """saves vtk file to outdir""" | ||
| point_dict = dict() | ||
| u = simulation.flow.u_pu | ||
| p = simulation.flow.p_pu | ||
| if simulation.flow.stencil.d == 2: | ||
| point_dict["p"] = simulation.flow.context.convert_to_ndarray(p[0, ..., None]) | ||
| for d in range(simulation.flow.stencil.d): | ||
| point_dict[f"u{'xyz'[d]}"] = simulation.flow.context.convert_to_ndarray( | ||
| u[d, ..., None]) | ||
| else: | ||
| point_dict["p"] = simulation.flow.context.convert_to_ndarray(p[0, ...]) | ||
| for d in range(simulation.flow.stencil.d): | ||
| point_dict[f"u{'xyz'[d]}"] = simulation.flow.context.convert_to_ndarray(u[d, ...]) | ||
| write_vtk(point_dict, simulation.flow.i, self.outdir + f"/{self.name}_frame") | ||
|
|
||
| @abstractmethod | ||
| def locations_string(self, simulation: 'BreakableSimulation') -> List[str]: | ||
| ... | ||
|
|
||
| @abstractmethod | ||
| def is_failed(self, simulation: 'BreakableSimulation'): | ||
| """checks if simulation meets criterion""" | ||
| ... | ||
|
|
||
| @abstractmethod | ||
| def get_results(self, simulation: 'BreakableSimulation'): | ||
| """calls specific method to create list of locations at which | ||
| simulation failed""" | ||
| ... | ||
|
|
||
|
|
||
| class NaNReporter(FailureReporterBase): | ||
|
|
||
| def __init__(self, interval, k=100, outdir=None, vtk_out=False): | ||
| super().__init__(interval, k, outdir, vtk_out) | ||
| self.name = "NaN" | ||
|
|
||
| def is_failed(self, simulation: 'BreakableSimulation'): | ||
| return torch.isnan(simulation.flow.f).any() | ||
|
|
||
| def get_results(self, simulation: 'BreakableSimulation'): | ||
| nan_mask = torch.isnan(simulation.flow.f) | ||
| return self._get_top_failures(nan_mask, simulation.flow.f) | ||
|
|
||
| def locations_string(self, simulation: 'BreakableSimulation') -> List[str]: | ||
| """create locations string as a header for the table of locations and | ||
| values in the output""" | ||
| locations = ["q", "x"] | ||
| if simulation.flow.stencil.d > 1: | ||
| locations.append("y") | ||
| if simulation.flow.stencil.d > 2: | ||
| locations.append("z") | ||
| return locations | ||
|
|
||
|
|
||
| class HighMaReporter(FailureReporterBase): | ||
| def __init__(self, interval, threshold=0.3, k=100, outdir=None, vtk_out=False): | ||
| super().__init__(interval, k, outdir, vtk_out) | ||
| self.threshold = threshold | ||
| self.name = "HighMa" | ||
|
|
||
|
|
||
| def is_failed(self, simulation: 'BreakableSimulation'): | ||
| u = simulation.flow.u() | ||
| ma = torch.norm(u, dim=0) / simulation.flow.stencil.cs | ||
| return (ma > self.threshold).any() | ||
|
|
||
| def get_results(self, simulation: 'BreakableSimulation'): | ||
| u = simulation.flow.u() | ||
| ma = torch.norm(u, dim=0) / simulation.flow.stencil.cs | ||
| mask = ma > self.threshold | ||
| return self._get_top_failures(mask, ma) | ||
|
|
||
| def locations_string(self, simulation: 'BreakableSimulation') -> List[str]: | ||
| """create locations string as a header for the table of locations and | ||
| values in the output""" | ||
| locations = ["x"] | ||
| if simulation.flow.stencil.d > 1: | ||
| locations.append("y") | ||
| if simulation.flow.stencil.d > 2: | ||
| locations.append("z") | ||
| locations.append("Ma") | ||
| return locations |
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,17 @@ | ||
| import os | ||
| from tests.conftest import * | ||
|
|
||
|
|
||
| def test_high_ma_reporter(tmpdir): | ||
| flow = Obstacle(context=Context(), resolution=[16, 16], | ||
| reynolds_number=10, mach_number=0.2, domain_length_x=16) | ||
| flow.mask = ((2 < flow.grid[0]) & (flow.grid[0] < 10) | ||
| & (2 < flow.grid[1]) & (flow.grid[1] < 10)) | ||
| collision = BGKCollision(tau=flow.units.relaxation_parameter_lu) | ||
| reporter = HighMaReporter(1, outdir=tmpdir, vtk_out=True) | ||
| simulation = BreakableSimulation(flow, collision, [reporter]) | ||
| simulation(100) | ||
| assert os.path.isfile(tmpdir / "HighMa_reporter.log") | ||
| assert os.path.isfile(tmpdir / "HighMa_frame_00000013.vtr") | ||
| assert flow.i > 100 | ||
| assert reporter.failed_iteration == 13 |
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,16 @@ | ||
| import os | ||
| from tests.conftest import * | ||
|
|
||
|
|
||
| def test_nan_reporter(tmpdir): | ||
| flow = TaylorGreenVortex(context=Context(), resolution=[16, 16], | ||
| reynolds_number=1e12, mach_number=1) | ||
| collision = BGKCollision(tau=flow.units.relaxation_parameter_lu) | ||
| reporter = NaNReporter(10, outdir=tmpdir, vtk_out=True) | ||
| simulation = BreakableSimulation(flow, collision, [reporter]) | ||
| simulation(100) | ||
| assert os.path.isfile(tmpdir / "NaN_reporter.log") | ||
| print(os.listdir(tmpdir)) | ||
| assert os.path.isfile(tmpdir / "NaN_frame_00000070.vtr") or os.path.isfile(tmpdir / "NaN_frame_00000080.vtr") | ||
| assert flow.i > 100 | ||
| assert reporter.failed_iteration in [70, 80] |
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.
Do the reporters only work with this specific
Simulationclass (i.e.BreakableSimulation)?If not, I prefer to initialize this class within the example script (i.e. header).
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.
Hi @MaxBille
Is there an update on this?
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.
@McBs yes, it should be BreakableSimulation since we manipulate
simulation.flow.i. Ifsimulationthis is notBreakableSimulation, it will continue running becausecall()contains a blind for-loop (lettuce/lettuce/_simulation.py
Line 243 in d04e156