Skip to content
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

Generalized Tecplot ASCII and Binary Readers/Writers #95

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

lamkina
Copy link
Contributor

@lamkina lamkina commented Jun 29, 2024

Purpose

This PR implements generalized Tecplot readers and writers for ASCII and Binary files. I created two new data structures to hold Ordered and Finite Element Tecplot zone types. The data structures contain a dictionary for the variable data arrays, the strand ID, solution time, and for FE zones the connectivity.

There is a single read function that determines the ASCII or Binary functionality automatically based on the file extension. The same pattern is used for the writer. The structure of the data is determined automatically by the dimensions of the numpy arrays for ordered zones. For example, an array of shape (10, 10, 10) will be treated as an I=10, J=10, K=10 Tecplot ordered zone. For FE zones, the nodes are a flattened array and the connectivity should match the FE zone type (LineSeg, Quadrilateral, Triangle, etc.). The writer uses the shape of the connectivity to determine the FE zone type to write.

I added tests that confirm the reader and writer work properly, as well as tests using ADflow ASCII slice .dat files and ADflow binary surface .plt files.

Expected time until merged

A few weeks

Type of change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (non-backwards-compatible fix or feature)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Documentation update
  • Maintenance update
  • Other (please describe)

Testing

Run the tests added for the Tecplot IO module.

Checklist

  • I have run flake8 and black to make sure the Python code adheres to PEP-8 and is consistently formatted
  • I have formatted the Fortran code with fprettify or C/C++ code with clang-format as applicable
  • I have run unit and regression tests which pass locally with my changes
  • I have added new tests that prove my fix is effective or that my feature works
  • I have added necessary documentation

@lamkina lamkina requested review from A-CGray and eytanadler June 29, 2024 05:18
@lamkina lamkina requested a review from a team as a code owner June 29, 2024 05:18
@lamkina lamkina requested a review from gawng June 29, 2024 05:18
Copy link

codecov bot commented Jun 29, 2024

Codecov Report

Attention: Patch coverage is 90.86687% with 59 lines in your changes missing coverage. Please review.

Project coverage is 51.87%. Comparing base (30aebba) to head (99e04c7).

Files with missing lines Patch % Lines
baseclasses/problems/pyWeight_problem.py 12.12% 29 Missing ⚠️
baseclasses/utils/tecplotIO.py 96.47% 21 Missing ⚠️
baseclasses/solvers/pyAero_solver.py 18.18% 9 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main      #95       +/-   ##
===========================================
+ Coverage   41.25%   51.87%   +10.61%     
===========================================
  Files          26       27        +1     
  Lines        2596     3181      +585     
===========================================
+ Hits         1071     1650      +579     
- Misses       1525     1531        +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lamkina
Copy link
Contributor Author

lamkina commented Jun 29, 2024

Still need to update the docs, but I wanted to get the review started. We might want to consider a minor version bump as well?

@lamkina
Copy link
Contributor Author

lamkina commented Jun 29, 2024

This is ready for review. I updated the docs and bumped the minor version.

@lamkina lamkina removed the request for review from eytanadler June 29, 2024 19:05
gawng
gawng previously approved these changes Jul 1, 2024
Copy link
Contributor

@gawng gawng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this looks good. I agree with the minor version bump. Thanks for alphabetizing the imports too

Copy link
Contributor

@eirikurj eirikurj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking very good! I was planning on doing a more thorough review, but to avoid having this sitting for long, I think there are few things there to get immediate feedback on. Please check my comments based in this first pass.

baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
tests/test_tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
baseclasses/utils/tecplotIO.py Outdated Show resolved Hide resolved
@lamkina
Copy link
Contributor Author

lamkina commented Jul 26, 2024

@eirikurj @gawng @A-CGray

I realized we are writing Tecplot files in AeroSolver here so I will go through and check the rest of the files to make sure I replace instances of this with the new reader/writer module.

I'll re-request reviews after I finish these changes.

@A-CGray A-CGray marked this pull request as draft July 29, 2024 15:58
@lamkina lamkina marked this pull request as ready for review September 13, 2024 18:50
@lamkina
Copy link
Contributor Author

lamkina commented Sep 13, 2024

@A-CGray @gawng @eirikurj this should be ready for another round of review

@lamkina
Copy link
Contributor Author

lamkina commented Sep 18, 2024

@gawng @A-CGray @eirikurj bump

@A-CGray
Copy link
Member

A-CGray commented Sep 23, 2024

@A-CGray the insane grid structure you were seeing in Tecplot was actually correct. I was using random coordinates to generate the nodal data. I am updating to using linearly increasing nodal data so the output in Tecplot will resemble a line, square, or cube.

See here in the tests where this was happening.

Oh yeah this is fine, I didn't mean to suggest anything was wrong by posting that image, was just showing evidence that I'd tested opening the binary files written by the tests in Tecplot.

@lamkina lamkina requested a review from A-CGray September 25, 2024 14:59
A-CGray
A-CGray previously approved these changes Sep 25, 2024
Copy link
Member

@A-CGray A-CGray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @lamkina !

gawng
gawng previously approved these changes Oct 8, 2024
Copy link
Contributor

@gawng gawng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am satisfied with this. WIll be taking advantage of this code in DCFoil. Thanks!

@A-CGray
Copy link
Member

A-CGray commented Oct 9, 2024

I guess we can't merge until @eirikurj regains access and marks his requested changes as completed. Or @anilyil can override

@lamkina
Copy link
Contributor Author

lamkina commented Oct 9, 2024

@eirikurj asked to wait until he regains access to do a final review and then he can merge.

@eytanadler
Copy link

I've now used this and can absolutely vouch for its usefulness! @lamkina and I just chatted about plotting in matplotlib. One problem is that by default matplotlib's tricontourf function includes any regions outside the CFD domain in the triangulation, so they end up getting colored by the contour. To prevent this, the tecplot file's connectivity can be passed to the tricontourf function. However, the function only accepts triangular elements, not quadrilateral. Thus, we think it'd be best to add the following method to the TecplotFEZone class to provide triangular connectivity even for quad zones.

@property
def triConnectivity(self) -> npt.NDArray:
    if self.zoneType == ZoneType.FETRIANGLE:
        return self.connectivity
    elif self.zoneType == ZoneType.FEQUADRILATERAL:
        return np.row_stack((self.connectivity[:, [0, 1, 2]], self.connectivity[:, [2, 3, 0]]))
    else:
        raise TypeError(f"triConnectivity not supported for {self.zoneType.name} zone type")

@lamkina lamkina dismissed stale reviews from gawng and A-CGray via c2baf7c November 8, 2024 22:24
A-CGray
A-CGray previously approved these changes Nov 8, 2024
Copy link
Member

@A-CGray A-CGray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lamkina
Copy link
Contributor Author

lamkina commented Nov 8, 2024

@eytanadler and I discovered that it's possible for FE Zones to have the full surface mesh nodal data and then a conn that indexes that global array. This makes things a bit clunky to access just the nodal data for a single zone. For example, you would need to do zone.data["CoordinateX"][zone.connectivity], however, this would duplicate nodes several times.

I implemented a property in TecplotFEZone called uniqueNodalData that returns only the data at unique nodes. However, this contains a dictionary with unordered nodal data variables.

Another solution might be to update the nodal data and connectivity when we read the zone to make sure we only store the minimum amount of information necessary for a given zone.

@eirikurj , @gawng , @eirikurj , and @eytanadler what are your preferences here?

@A-CGray
Copy link
Member

A-CGray commented Nov 9, 2024

I would vote for removing the redundant data

@eytanadler
Copy link

After a bit more discussion and @A-CGray's comment, @lamkina and I think defaulting to the data/connectivity unique to each zone should be the default. The original data and connectivity from the file should be kept just in case in dataRaw and connectivityRaw attributes.

@@ -1154,8 +1154,8 @@ def _readZoneHeader(self, lines: List[str], iCurrent: int) -> Tuple[Dict[str, An
headerString = ", ".join(header)

# Use regex to parse the header information
zoneNameMatch = re.search(r'zone t\s*=\s*"([^"]*)"', headerString, re.IGNORECASE)
zoneName = zoneNameMatch.group(1) if zoneNameMatch else None
zoneNameMatch = re.search(r'(zone t)\s*=\s*[\'""]?([^\'""\n,]+)[\'""]?(?=[,\n]|$)', headerString, re.IGNORECASE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future people's sake, can you add a comment here explaining what this regex matches?

Copy link
Contributor Author

@lamkina lamkina Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I need to review and add more tests for the regex patterns. To my knowledge, there's no established rules published by Tecplot that describe the formatting of the ASCII header data. I'm considering simpler approach that removes the regex and just assumes all data follows an = sign and terminates with either a comma or newline unless it's surrounded by single or double quotes.

Copy link
Contributor

@eirikurj eirikurj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lamkina this is looking great! I tested this locally, but overall I wanted to do a few other tests and review the unit tests more. However, I dont want to delay this so see my very minor current comments now, and I'll do another pass for the diff later.

@@ -490,48 +499,42 @@ def writeMassesTecplot(self, filename):

filename: str
filename for writing the masses. This string will have the
.dat suffix appended to it.
# .dat suffix appended to it if it does not already have it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# intentional?

for i in range(len(points)):
f.write(f"{points[i][0]:f} {points[i][1]:f} {points[i][2]:f}\n")
# Build the FETriangle data array
dataArrays = np.zeros((len(self.p0) * 3, 3), dtype=float)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking here, but why use this large array? Makes it more readable to just use x etc. If there is no need to keep the data together, I would favor readability. This appears in other classes as well.

@@ -0,0 +1,1840 @@
import re
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is for the documentation (no code issues here). For clarity, we should separate out the documentation of the tecplotIO module into a subsection in the docs.

# ==============================================================================
# ASCII WRITERS
# ==============================================================================
def writeArrayToFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This routine does not have any unit tests, testing it directly. Should we add tests here, since its not a member function and could be used outside the writer?

with self.assertRaises(TypeError, msg=msg):
TecplotOrderedZone(123, {"X": X, "Y": Y, "Z": Z}, solutionTime=0.0, strandID=-1)

msg = "Solution time must be a float"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This msg (and others below) are not used

def writeArrayToFile(
arr: npt.NDArray[np.float64],
handle: TextIO,
maxLineWidth: int = 4000,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4000 used to be the limit years ago. The data format guide indicates 32000 characters for the ASCII format (see here)
Please update here and elsewhere.


Parameters
----------
line : str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description missing compared to inputs

strandIDMatch = re.search(r"strandid\s*=\s*(\d+)", headerString, re.IGNORECASE)
strandID = int(strandIDMatch.group(1)) if strandIDMatch else -1

headerDict = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought (not an action item), but there are other fields available that we are not implementing for reading or writing, so maybe we should put a note somewhere that this is a limited reader/writer?
One potentially useful field is the AUXDATA.
Another one is the datatype DT (list of datatypes for the variables). However, for most of our cases, this is not that important, as we tend to write mostly floats as opposed to int.
Other variables that might be useful are the variable sharing and zone connectivity sharing (both we dont use).

with open(filename, "r") as handle:
lines = handle.readlines()

pattern = r"[\s,\t\n\r]+" # Separator pattern
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For compatibility and maintenance, it would be better to just join/combine the Enum values here instead of the string (and then adding +).

def _validateVariables(self) -> None:
"""Check that all zones have the same variables."""
if not all(set(self.zones[0].variables) == set(zone.variables) for zone in self.zones):
raise ValueError("All zones must have the same variables.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to add tests to catch these exceptions (and elsewhere)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants