Closed anderslogg closed 7 months ago
@dwastberg Should this be in dtcc-builder or dtcc-wrangler?
@anderslogg , @vbassn , I've made a Python implementation for the above quality metrics. The main issue was that while there is a concensus on the meaning of each indicator there are different definitions for their computation.
I describe the methods I used for each indicator to get some feedback before starting the C++ implementation.
In meshes with triangular faces is computed by measuring the ratio of the longest edge length to the shortest edge length within a triangle. A lower aspect ratio indicates a more regular and well-shaped triangle.
Aspect Ratio = L_max / L_min
I measured a face's orthogonality as the minimum deviation of its angles from a right angle. The closer this indicator is to 0, the closer the face resembles a right triangle.
oi_0 = abs(1 - (angle_between(AC, AB) / (np.pi / 2)))
oi_1 = abs(1 - (angle_between(AB, BC) / (np.pi / 2)))
oi_2 = abs(1 - (angle_between(AC, BC) / (np.pi / 2)))
orthogonality[i] = min((oi_0, oi_1, oi_2))
As a measure for skewness I used equiangular skew.
`Equiangle Skew = max(Θmax- Θc)/(180-θc),(Θc-Θmin)/θc)`
where:
- Θmax : is the largest angle in a face or cell
- Θmin : is the smallest angle in a face or cell
- θc : is the angle for equi-angular face or cell i.e. 60 for a triangle and 90 for a square.
Smoothness measures the change in size between neighbour faces of the mesh. In the case of a surface mesh I measured smoothness as the standard deviation in area of all faces adjacent to a Vertex.
I used three different metrics for AR in Volume Meshes:
As in surface meshes, checking the ratio of the longest edge length to the shortest edge length. Edge length check provides a lazy aspect ratio indicator for tetrahedron cells
Measuring AR as the Ratio of the longest edge length to the length of the inradius of the tetrahedron cell.
Measuring AR as the Ratio of the Circumradius of the tetrahedron cell to the inradius of the tetrahedron cell.
As a metric for orthogonality I computed the normals of the cells faces and measure orthogonality as the RMS of the dot products between the cell's normals. We could potentially also use the dot products of opposite edges of the tetrahedron.
As a measure of skewness for tetrahedron cells I used the ratio:
Skewness = (V_ideal - V) / V_ideal
Where:
Smoothness measures the change in size between neighbour cells of the mesh. In the case of a volume mesh I measured smoothness as the standard deviation in volume of all cells adjacent to a Vertex.
The functions above return arrays with metrics for each cell or vertex for further statistical analysis. Additionally, we can define specific thresholds for each indicator, helping us determine what constitutes a 'good' mesh.
@Georspai Great job and thanks for the complete and very informative overview. I think the next step should be to use the Python implementation on a couple of test cases (2-3 meshes) and compare the 4 indicators to what Mariya gets with Fluent. If possible, try choose the same indicators as in Fluent to get the exact same results. Then we have a working Python reference implementation that we know is correct. Then it's a matter of porting that to C++ and veryfying again that it gives the same as in Python.
@anderslogg actually the only missing thing is how to enable them in options. @Georspai has come up with a binary solution, but I am not really convinced.
@anderslogg @vbassn Since all parameters are mostly booleans
, ints
and some strings
for paths, I wanted to follow the same mentality and use ints
for the parameters that pick which metrics are going to be measured. I used the same logic as linux file permissions.
_parameters = {}
_parameters["model_name"] = "DTCC"
_parameters["auto_domain"] = True
_parameters["debug"] = False
_parameters["build_mesh"] = True
_parameters["build_volume_mesh"] = True
_parameters["save_protobuf"] = True
_parameters["save_json"] = False
_parameters["save_shp"] = False
_parameters["save_vtk"] = False
_parameters["save_stl"] = False
_parameters["save_obj"] = False
_parameters["ground_smoothing"] = 3
_parameters["metrics_report_level"] = 45
_parameters["metrics_report_level_volume"] = 927
def default():
return _parameters.copy()
Essentially, we have: Surface Meshes: 6 metrics, so the level ranges from 0 to 63. Volume Meshes: 4 Cell-Based Metrics and 6 Face-Based Metrics, so the report level ranges from 0 to 1023.
In this case:
The metrics_report_level
45 is 101101, meaning all metrics except the second and the second last.
The metrics_report_level_volume
927 is 1110011111, meaning all metrics except the sixth and the seventh.
When the level is zero, the "quick" check runs, which includes the two most significant metrics.
But upon some discussion with Vasilis, we realize that this design is concise but not user friendly at all. So I would like to discuss some other solution for how the parameter indicating the metrics should be.
@Georspai thanks! @anderslogg how do you feel about the options?
@Georspai @vbassn I don't think the binary system works. It's too cryptic. Is the problem that we need to pass too many parameters down the chain from the Python layer to C++?
It makes more sense to be explicit about the parameters. That also fills an additional purpose which is to expose which metrics are available, something like this:
_parameters["report_mesh_metric_aspect_ratio"] = True
_parameters["report_mesh_metric_orthogonality"] = False
...
_parameters["report_volume_mesh_metric_aspect_ratio"] = True
_parameters["report_volume_mesh_metric_orthogonality = False
...
We need to compute metrics including the following: