[WIP] Multizone adjoints for turbomachinery#2446
[WIP] Multizone adjoints for turbomachinery#2446joshkellyjak wants to merge 66 commits intodevelopfrom
Conversation
| AD::EndPreacc(); | ||
|
|
||
| /*--- Max is not differentiable, so we not register them for preacc. ---*/ |
There was a problem hiding this comment.
What is the difference between ending the preaccumulation before or after?
| } | ||
| } | ||
| } else if (Multizone_Problem && DiscreteAdjoint) { | ||
| SU2_MPI::Error(string("OUTPUT_WRT_FREQ cannot be specified for this solver " |
There was a problem hiding this comment.
| SU2_MPI::Error(string("OUTPUT_WRT_FREQ cannot be specified for this solver " | |
| SU2_MPI::Error("OUTPUT_WRT_FREQ cannot be specified for this solver " |
| "(writing of restart and sensitivity files not possible for multizone discrete adjoint during runtime yet).\n" | ||
| "Please remove this option from the config file, output files will be written when solver finalizes.\n"), CURRENT_FUNCTION); |
There was a problem hiding this comment.
Hmmm I'm pretty sure it is possible
There was a problem hiding this comment.
Yes and no, writing the files is fine, but the continuation of the multi-zone discrete adjoint solver is erratic. My guess is that some indices aren't cleared properly before re-recording the tape. (Writing adjoint solution variables to file only, without re-recording at all and without sensitivities, might give us some hints.)
The debug mode could eventually pin down where exactly things go wrong.
| BPressure = config->GetPressureOut_BC(); | ||
| Temperature = config->GetTotalTemperatureIn_BC(); | ||
|
|
||
| if (!reset){ | ||
| AD::RegisterInput(BPressure); | ||
| AD::RegisterInput(Temperature); | ||
| } | ||
|
|
||
| BPressure = config->GetPressureOut_BC(); | ||
| Temperature = config->GetTotalTemperatureIn_BC(); | ||
|
|
There was a problem hiding this comment.
Why are these wrong but not the others that follow the same pattern?
There was a problem hiding this comment.
This threw a tagging error, and if I remember correctly it's because in the other sections the variables it accesses are directly used in the solver, whereas in the Giles implementation they are not, creating a mismatch in tags.
There was a problem hiding this comment.
Can we fix it in a way that keeps a clear pattern for doing this type of thing?
There was a problem hiding this comment.
I can give it a go
…ding of objective function calculation
… to run in quasi-MZ approach. (NEEDS CLEANING)
… MZ turbo testcase
| switch(donor_config->GetKind_MixingPlaneInterface()){ | ||
| case MATCHING: | ||
| targetSpan.donorSpan = iSpanTarget; | ||
| targetSpan.coefficient = 0.0; | ||
| break; | ||
|
|
||
| case NEAREST_SPAN: | ||
| // Find the nearest donor span | ||
| for (auto iSpanDonor = 0; iSpanDonor < nSpanDonor - 1; iSpanDonor++) { | ||
| const auto dist = abs(spanValuesTarget[iSpanTarget] - spanValuesDonor[iSpanDonor]); | ||
| if(dist < minDist){ | ||
| minDist = dist; | ||
| tSpan = iSpanDonor; | ||
| } | ||
| } | ||
| targetSpan.donorSpan = tSpan; | ||
| targetSpan.coefficient = 0.0; | ||
| break; | ||
|
|
||
| case LINEAR_INTERPOLATION: | ||
| { | ||
| bool printWarning = false; | ||
| // Check if target span is within donor bound | ||
| if (spanValuesTarget[iSpanTarget] <= spanValuesDonor[0]) { | ||
| // Below hub - use hub value | ||
| targetSpan.donorSpan = 0; | ||
| targetSpan.coefficient = 0.0; | ||
| printWarning = true; | ||
| } | ||
| else if (spanValuesTarget[iSpanTarget] >= spanValuesDonor[nSpanDonor - 1]) { | ||
| // Above shroud - use shroud value | ||
| targetSpan.donorSpan = nSpanDonor - 1; | ||
| targetSpan.coefficient = 0.0; | ||
| printWarning = true; | ||
| } | ||
| else { | ||
| // Find the donor span interval that brackets the target span | ||
| for (auto iSpanDonor = 0; iSpanDonor < nSpanDonor - 1; iSpanDonor++) { | ||
| const auto test = abs(spanValuesTarget[iSpanTarget] - spanValuesDonor[iSpanDonor]); | ||
| if(test < minDist && spanValuesTarget[iSpanTarget] > spanValuesDonor[iSpanDonor]){ | ||
| kSpan = iSpanDonor; | ||
| minDist = test; | ||
| } | ||
| } | ||
| // Calculate interpolation coefficient | ||
| coeff = (spanValuesTarget[iSpanTarget] - spanValuesDonor[kSpan]) / | ||
| (spanValuesDonor[kSpan + 1] - spanValuesDonor[kSpan]); | ||
| targetSpan.donorSpan = kSpan; | ||
| targetSpan.coefficient = coeff; | ||
| } | ||
| if (printWarning) { | ||
| if (rank == MASTER_NODE) { | ||
| cout << "Warning! Target spans exist outside the bounds of donor spans! Clamping interpolator..." << endl; | ||
| if (spanValuesTarget[iSpanTarget] <= spanValuesDonor[0]) cout << "This is an issue at the hub." << endl; | ||
| if (spanValuesTarget[iSpanTarget] >= spanValuesDonor[nSpanDonor - 1]) cout << "This is an issue at the shroud." << endl; | ||
| cout << "Setting coeff = 0.0 and transferring endwall value!" << endl; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| default: | ||
| SU2_MPI::Error("MixingPlane interface option not implemented yet", CURRENT_FUNCTION); | ||
| break; | ||
| } |
Check notice
Code scanning / CodeQL
Long switch case Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, to fix a “long switch case” issue, you extract the lengthy logic into a dedicated helper function and have the case body invoke that function with the required inputs, returning or mutating data as needed. This shortens the case body, makes the switch easier to read, and isolates the complex behavior into a function that can be tested or modified independently.
Here, the best approach is to move the logic inside case LINEAR_INTERPOLATION: into a new private helper method of CMixingPlane, e.g. ComputeLinearInterpolationSpan(...). This helper will:
- Take as parameters all the data it needs:
- the target span index
iSpanTarget, - the donor and target span values arrays
spanValuesDonorandspanValuesTarget, - donor span count
nSpanDonor, - the
targetSpanstruct reference (to writedonorSpanandcoefficient), - and
rank(for warning output).
- the target span index
- Implement exactly the same logic that is currently in the
LINEAR_INTERPOLATIONcase: bounds checks with clamping, interval search, coefficient computation, and conditional warning printing. - Be defined as a member function in
CMixingPlane.cpp(and declared in the corresponding headerCMixingPlane.hpp, but we are not allowed to edit it here, so we will implement it as astaticor anonymous‑namespace helper function in the.cppinstead, using only what we see). Since we cannot change the header, the safest is to define astaticfree helper function insideCMixingPlane.cppnearSetTransferCoeffand use it directly there.
However, we are constrained to only modify shown snippets and remain within Common/src/interface_interpolation/CMixingPlane.cpp. We can therefore:
-
Introduce a
statichelper function above theswitchwithin the same snippet, e.g.:static void ComputeLinearInterpolationSpan( int iSpanTarget, const su2double* spanValuesDonor, const su2double* spanValuesTarget, int nSpanDonor, int rank, decltype(targetSpans[0][0])& targetSpan);
(With the exact implementation copied from the current
LINEAR_INTERPOLATIONcase.) -
Replace the body of
case LINEAR_INTERPOLATION:with a single call to that helper, passing iniSpanTarget,spanValuesDonor,spanValuesTarget,nSpanDonor,rank, andtargetSpan.
All existing behavior (including the warning messages and value clamping) will remain identical, because we will not change the logic, only relocate it. No new imports or external dependencies are required.
| for (auto iSize = 0; iSize < size; iSize++){ | ||
| if (buffDonorMarker[static_cast<size_t>(iSize) * static_cast<unsigned long>(nSpanDonor + 1)] != -1) { | ||
| for (auto iSpan = 0; iSpan < nSpanDonor + 1; iSpan++){ | ||
| for (auto iVar = 0u; iVar < nMixingVars; iVar++) sendDonorVar[iSpan * nMixingVars + iVar] = buffDonorVar[static_cast<size_t>(iSize) * nSpanDonorVars + iSpan * nMixingVars + iVar]; |
Check failure
Code scanning / CodeQL
Multiplication result converted to larger type High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, to avoid overflow when a multiplication result is intended to be stored in or used as a larger integer type, at least one operand should be explicitly cast to that larger type before the multiplication. This promotes the entire multiplication to be performed in the larger type, ensuring that intermediate results cannot overflow the smaller type.
In this specific case, the problematic expression is in the nested loop where values are copied from buffDonorVar into sendDonorVar:
for (auto iSpan = 0; iSpan < nSpanDonor + 1; iSpan++){
for (auto iVar = 0u; iVar < nMixingVars; iVar++)
sendDonorVar[iSpan * nMixingVars + iVar] =
buffDonorVar[static_cast<size_t>(iSize) * nSpanDonorVars +
iSpan * nMixingVars + iVar];
}The product iSpan * nMixingVars is computed in the default integer type and then used as part of a size_t index expression on the right-hand side; a similar computation is used for the left-hand side index. The best fix is to promote iSpan (or nMixingVars) to a wide type such as size_t (or unsigned long, consistent with nearby code) before multiplication, ensuring that the multiplication happens in the wider type. Concretely, we should:
- Cast
iSpantosize_t(orunsigned long) in both index expressions whereiSpan * nMixingVarsappears. - Optionally, also cast
nMixingVarsto the same wide type for clarity and type consistency, but this is not strictly necessary; promoting just one operand is enough.
No new methods or external libraries are required; we only add explicit casts in the relevant expressions. All changes are confined to SU2_CFD/src/interfaces/cfd/CMixingPlaneInterface.cpp in the shown region.
| @@ -106,7 +106,10 @@ | ||
| for (auto iSize = 0; iSize < size; iSize++){ | ||
| if (buffDonorMarker[static_cast<size_t>(iSize) * static_cast<unsigned long>(nSpanDonor + 1)] != -1) { | ||
| for (auto iSpan = 0; iSpan < nSpanDonor + 1; iSpan++){ | ||
| for (auto iVar = 0u; iVar < nMixingVars; iVar++) sendDonorVar[iSpan * nMixingVars + iVar] = buffDonorVar[static_cast<size_t>(iSize) * nSpanDonorVars + iSpan * nMixingVars + iVar]; | ||
| for (auto iVar = 0u; iVar < nMixingVars; iVar++) | ||
| sendDonorVar[static_cast<size_t>(iSpan) * nMixingVars + iVar] = | ||
| buffDonorVar[static_cast<size_t>(iSize) * nSpanDonorVars + | ||
| static_cast<size_t>(iSpan) * nMixingVars + iVar]; | ||
| } | ||
| markDonor = buffDonorMarker[static_cast<unsigned long>(iSize) * static_cast<unsigned long>(nSpanDonor + 1)]; | ||
| break; // Avoid overwriting |
…n recording wrt mesh coordinates. The tape debug mode code is currently also reworked in #2721. The changes in this commit could already be useful for the current turbo discrete adjoint development.
Proposed Changes
This is a cleaned up PR of the fixes needed for multizone adjoints for turbomachinery from the previous PR of @oleburghardt and I's work.
This hopefully is useable, so if you would like to test and report please feel free to contact me on Slack.
TODO:
Related Work
Now closed PR #2317
PR Checklist
Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.
pre-commit run --allto format old commits.