diff --git a/adflow/__init__.py b/adflow/__init__.py index 2e01ac45b..6089b728d 100644 --- a/adflow/__init__.py +++ b/adflow/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.7.3" +__version__ = "2.7.4" from mpi4py import MPI diff --git a/adflow/pyADflow.py b/adflow/pyADflow.py index 42d407e6e..47c74ea38 100644 --- a/adflow/pyADflow.py +++ b/adflow/pyADflow.py @@ -106,6 +106,7 @@ def __init__(self, comm=None, options=None, debug=False, dtype="d"): self.optionMap, self.moduleMap = self._getOptionMap() self.pythonOptions, deprecatedOptions, self.specialOptions = self._getSpecialOptionLists() immutableOptions = self._getImmutableOptions() + self.rootChangedOptions = {} self.possibleAeroDVs, self.possibleBCDvs, self.basicCostFunctions = self._getObjectivesAndDVs() @@ -981,7 +982,9 @@ def __call__(self, aeroProblem, **kwargs): is used in a multidisciplinary environment when the outer solver can suppress all I/O during intermediate solves. """ - + self.rootChangedOptions = self.comm.bcast(self.rootChangedOptions, root=0) + for key, val in self.rootChangedOptions.items(): + self.setOption(key, val) startCallTime = time.time() # Get option about adjoint memory @@ -4656,6 +4659,59 @@ def getResidual(self, aeroProblem, res=None, releaseAdjointMemory=True): return res + def solveErrorEstimate(self, aeroProblem, funcError, evalFuncs=None): + r""" + Evaluate the desired function errors given in iterable object + 'evalFuncs', and add them to the dictionary 'funcError'. The keys + in the funcError dictionary will be have an ``_`` prepended to them. + + Parameters + ---------- + aeroProblem : pyAero_problem class + The aerodynamic problem to get the error for + + funcError : dict + Dictionary into which the function errors are saved. + We define error to be :math:`\epsilon = f^\ast - f`, where + :math:`f^\ast` is the converged solution and :math:`f` is the unconverged solution. + + evalFuncs : iterable object containing strings + If not None, use these functions to evaluate. + + Examples + -------- + >>> CFDsolver(ap) + >>> funcsSens = {} + >>> CFDSolver.evalFunctionsSens(ap, funcsSens) + >>> funcError = {} + >>> CFDsolver.solveErrorEstimate(ap, funcError) + >>> # Result will look like (if aeroProblem, ap, has name of 'wing'): + >>> print(funcError) + >>> # {'wing_cl':0.00085, 'wing_cd':0.000021} + """ + self.setAeroProblem(aeroProblem) + res = self.getResidual(aeroProblem) + if evalFuncs is None: + evalFuncs = sorted(self.curAP.evalFuncs) + + # Make sure we have a list that has only lower-cased entries + tmp = [] + for f in evalFuncs: + tmp.append(f.lower()) + evalFuncs = tmp + + # Do the functions one at a time: + for f in evalFuncs: + + key = f"{self.curAP.name}_{f}" + + # Set dict structure for this derivative + psi = self.getAdjoint(f) + error = numpy.dot(psi, res) + errorTot = self.comm.allreduce(error) + errorTot = -1 * errorTot # multiply by -1 so that estimate is added to current output + funcError[key] = errorTot + def getFreeStreamResidual(self, aeroProblem): self.setAeroProblem(aeroProblem) rhoRes, totalRRes = self.adflow.nksolver.getfreestreamresidual() @@ -4675,6 +4731,28 @@ def _getSurfaceSize(self, groupName, includeZipper=True): [nPts, nCells] = self.adflow.surfaceutils.getsurfacesize(self.families[groupName], includeZipper) return nPts, nCells + def rootSetOption(self, name, value, reset=False): + """ + Set ADflow options from the root proc. + The option will be broadcast to all procs when __call__ is invoked. + + Parameters + ---------- + name : str + The name of the option + value : Any + The value of the option + reset : Bool + If True, we reset all previously-set rootChangedOptions. + + See Also + -------- + :func: setOption + """ + if reset: + self.rootChangedOptions = {} + self.rootChangedOptions[name] = value + def setOption(self, name, value): """ Set Solver Option Value diff --git a/tests/reg_tests/refs/error_euler_wing_full.json b/tests/reg_tests/refs/error_euler_wing_full.json new file mode 100644 index 000000000..5e817751a --- /dev/null +++ b/tests/reg_tests/refs/error_euler_wing_full.json @@ -0,0 +1,235 @@ +{ + "errors": { + "mdo_tutorial_cd": 4.461715234198106e-16, + "mdo_tutorial_cl": -9.6153107436299e-15, + "mdo_tutorial_cmz": -1.0473464896718247e-14, + "mdo_tutorial_drag": 1.8189520666778846e-10, + "mdo_tutorial_fx": 3.0493468198221515e-10, + "mdo_tutorial_lift": -3.919969883963042e-09, + "mdo_tutorial_mz": -1.3876922049555845e-08 + }, + "metadata": { + "ADPC": false, + "AGMGLevels": 1, + "AGMGNSmooth": 3, + "ANKADPC": false, + "ANKASMOverlap": 1, + "ANKCFL0": 5.0, + "ANKCFLCutback": 0.5, + "ANKCFLExponent": 0.5, + "ANKCFLFactor": 10.0, + "ANKCFLLimit": 100000.0, + "ANKCFLMin": 1.0, + "ANKConstCFLStep": 0.4, + "ANKCoupledSwitchTol": 1e-16, + "ANKInnerPreconIts": 1, + "ANKJacobianLag": 10, + "ANKLinResMax": 0.1, + "ANKLinearSolveTol": 0.05, + "ANKMaxIter": 40, + "ANKNSubiterTurb": 1, + "ANKOuterPreconIts": 1, + "ANKPCILUFill": 2, + "ANKPCUpdateTol": 0.5, + "ANKPhysicalLSTol": 0.2, + "ANKPhysicalLSTolTurb": 0.99, + "ANKSecondOrdSwitchTol": 1e-16, + "ANKStepFactor": 1.0, + "ANKStepMin": 0.01, + "ANKSubspaceSize": -1, + "ANKSwitchTol": 1000.0, + "ANKTurbCFLScale": 1.0, + "ANKTurbKSPDebug": false, + "ANKUnsteadyLSTol": 1.0, + "ANKUseFullVisc": true, + "ANKUseMatrixFree": true, + "ANKUseTurbDADI": true, + "ASMOverlap": 1, + "CFL": 1.7, + "CFLCoarse": 1.0, + "CFLLimit": 1.5, + "GMRESOrthogonalizationType": "modified Gram-Schmidt", + "ILUFill": 2, + "L2Convergence": 1e-14, + "L2ConvergenceCoarse": 0.01, + "L2ConvergenceRel": 1e-16, + "MGCycle": "sg", + "MGStartLevel": -1, + "NKADPC": false, + "NKASMOverlap": 1, + "NKFixedStep": 0.25, + "NKInnerPreconIts": 1, + "NKJacobianLag": 20, + "NKLS": "cubic", + "NKLinearSolveTol": 0.3, + "NKOuterPreconIts": 1, + "NKPCILUFill": 2, + "NKSubspaceSize": 60, + "NKSwitchTol": 1e-05, + "NKUseEW": true, + "NKViscPC": false, + "RKReset": false, + "TSStability": false, + "adjointDivTol": 100000.0, + "adjointL2Convergence": 1e-14, + "adjointL2ConvergenceAbs": 1e-16, + "adjointL2ConvergenceRel": 1e-16, + "adjointMaxIter": 500, + "adjointMaxL2DeviationFactor": 1.0, + "adjointMonitorStep": 10, + "adjointSolver": "GMRES", + "adjointSubspaceSize": 100, + "alphaFollowing": true, + "alphaMode": false, + "altitudeMode": false, + "applyAdjointPCSubspaceSize": 20, + "applyPCSubspaceSize": 10, + "approxPC": true, + "backgroundVolScale": 1.0, + "betaMode": false, + "blockSplitting": true, + "cavitationNumber": 1.4, + "closedSurfaceFamilies": null, + "coarseDiscretization": "central plus scalar dissipation", + "computeCavitation": false, + "coupledSolution": false, + "cutCallback": null, + "debugZipper": false, + "deltaT": 0.01, + "designSurfaceFamily": null, + "discretization": "central plus scalar dissipation", + "dissipationLumpingParameter": 6.0, + "dissipationScalingExponent": 0.67, + "eddyVisInfRatio": 0.009, + "equationMode": "steady", + "equationType": "Euler", + "eulerWallTreatment": "linear pressure extrapolation", + "firstRun": true, + "flowType": "external", + "forcesAsTractions": true, + "frozenTurbulence": false, + "globalPreconditioner": "additive Schwarz", + "gridFile": "input_files/mdo_tutorial_euler_scalar_jst.cgns", + "gridPrecision": "double", + "gridPrecisionSurface": "single", + "infChangeCorrection": true, + "innerPreconIts": 1, + "isoVariables": [], + "isosurface": {}, + "liftIndex": 2, + "limiter": "van Albada", + "loadBalanceIter": 10, + "loadImbalance": 0.1, + "localPreconditioner": "ILU", + "lowSpeedPreconditioner": false, + "machMode": false, + "matrixOrdering": "RCM", + "maxL2DeviationFactor": 1.0, + "meshSurfaceFamily": null, + "monitorVariables": [ + "cpu", + "resrho", + "resturb", + "cl", + "cd" + ], + "nCycles": 2000, + "nCyclesCoarse": 500, + "nFloodIter": -1, + "nRKReset": 5, + "nRefine": 10, + "nSaveSurface": 1, + "nSaveVolume": 1, + "nSubiter": 1, + "nSubiterTurb": 3, + "nTimeStepsCoarse": 48, + "nTimeStepsFine": 400, + "nearWallDist": 0.1, + "numberSolutions": true, + "outerPreconIts": 3, + "outputDirectory": "tests/output_files", + "outputSurfaceFamily": "allSurfaces", + "overlapFactor": 0.9, + "oversetLoadBalance": true, + "oversetPriority": {}, + "oversetProjTol": 1e-12, + "oversetUpdateMode": "frozen", + "pMode": false, + "partitionLikeNProc": -1, + "partitionOnly": false, + "preconditionerSide": "right", + "printAllOptions": true, + "printIntro": true, + "printIterations": true, + "printTiming": true, + "printWarnings": true, + "qMode": false, + "rMode": false, + "resAveraging": "alternate", + "restartAdjoint": true, + "restartFile": "input_files/mdo_tutorial_euler_scalar_jst.cgns", + "restrictionRelaxation": 0.8, + "selfZipCutoff": 120.0, + "sepSensorOffset": 0.0, + "sepSensorSharpness": 10.0, + "setMonitor": true, + "skipAfterFailedAdjoint": true, + "smoothParameter": 1.5, + "smoother": "Runge-Kutta", + "solutionPrecision": "single", + "solutionPrecisionSurface": "single", + "storeConvHist": true, + "storeRindLayer": true, + "surfaceVariables": [ + "cp", + "vx", + "vy", + "vz", + "mach" + ], + "timeAccuracy": 2, + "timeIntegrationScheme": "BDF", + "timeIntervals": 1, + "timeLimit": -1.0, + "turbResScale": 10000.0, + "turbulenceModel": "SA", + "turbulenceOrder": "first order", + "turbulenceProduction": "strain", + "useALE": true, + "useANKSolver": true, + "useApproxWallDistance": true, + "useBlockettes": true, + "useDiagTSPC": true, + "useExternalDynamicMesh": false, + "useGridMotion": false, + "useLinResMonitor": false, + "useMatrixFreedrdw": true, + "useNKSolver": true, + "useOversetWallScaling": false, + "useQCR": false, + "useRotationSA": false, + "useTSInterpolatedGridVelocity": false, + "useWallFunctions": false, + "useZipperMesh": true, + "useft2SA": true, + "verifyExtra": true, + "verifySpatial": true, + "verifyState": true, + "vis2": 0.25, + "vis2Coarse": 0.5, + "vis4": 0.0156, + "viscPC": false, + "viscWallTreatment": "constant pressure extrapolation", + "viscousSurfaceVelocities": true, + "volumeVariables": [ + "resrho" + ], + "wallDistCutoff": 1e+20, + "windAxis": false, + "writeSolutionEachIter": false, + "writeSurfaceSolution": true, + "writeTecplotSurfaceSolution": false, + "writeVolumeSolution": true, + "zipperSurfaceFamily": null + } +} \ No newline at end of file diff --git a/tests/reg_tests/refs/error_euler_wing_partial.json b/tests/reg_tests/refs/error_euler_wing_partial.json new file mode 100644 index 000000000..de9f2eb19 --- /dev/null +++ b/tests/reg_tests/refs/error_euler_wing_partial.json @@ -0,0 +1,235 @@ +{ + "errors": { + "mdo_tutorial_cd": -1.7010382891037493e-07, + "mdo_tutorial_cl": -1.1546962987193241e-06, + "mdo_tutorial_cmz": -2.2509984428968154e-06, + "mdo_tutorial_drag": -0.06934792897018172, + "mdo_tutorial_fx": -0.05452720232042099, + "mdo_tutorial_lift": -0.4707465870618951, + "mdo_tutorial_mz": -2.9824828969005615 + }, + "metadata": { + "ADPC": false, + "AGMGLevels": 1, + "AGMGNSmooth": 3, + "ANKADPC": false, + "ANKASMOverlap": 1, + "ANKCFL0": 5.0, + "ANKCFLCutback": 0.5, + "ANKCFLExponent": 0.5, + "ANKCFLFactor": 10.0, + "ANKCFLLimit": 100000.0, + "ANKCFLMin": 1.0, + "ANKConstCFLStep": 0.4, + "ANKCoupledSwitchTol": 1e-16, + "ANKInnerPreconIts": 1, + "ANKJacobianLag": 10, + "ANKLinResMax": 0.1, + "ANKLinearSolveTol": 0.05, + "ANKMaxIter": 40, + "ANKNSubiterTurb": 1, + "ANKOuterPreconIts": 1, + "ANKPCILUFill": 2, + "ANKPCUpdateTol": 0.5, + "ANKPhysicalLSTol": 0.2, + "ANKPhysicalLSTolTurb": 0.99, + "ANKSecondOrdSwitchTol": 1e-16, + "ANKStepFactor": 1.0, + "ANKStepMin": 0.01, + "ANKSubspaceSize": -1, + "ANKSwitchTol": 1000.0, + "ANKTurbCFLScale": 1.0, + "ANKTurbKSPDebug": false, + "ANKUnsteadyLSTol": 1.0, + "ANKUseFullVisc": true, + "ANKUseMatrixFree": true, + "ANKUseTurbDADI": true, + "ASMOverlap": 1, + "CFL": 1.7, + "CFLCoarse": 1.0, + "CFLLimit": 1.5, + "GMRESOrthogonalizationType": "modified Gram-Schmidt", + "ILUFill": 2, + "L2Convergence": 1e-06, + "L2ConvergenceCoarse": 0.01, + "L2ConvergenceRel": 1e-16, + "MGCycle": "sg", + "MGStartLevel": -1, + "NKADPC": false, + "NKASMOverlap": 1, + "NKFixedStep": 0.25, + "NKInnerPreconIts": 1, + "NKJacobianLag": 20, + "NKLS": "cubic", + "NKLinearSolveTol": 0.3, + "NKOuterPreconIts": 1, + "NKPCILUFill": 2, + "NKSubspaceSize": 60, + "NKSwitchTol": 1e-05, + "NKUseEW": true, + "NKViscPC": false, + "RKReset": false, + "TSStability": false, + "adjointDivTol": 100000.0, + "adjointL2Convergence": 1e-14, + "adjointL2ConvergenceAbs": 1e-16, + "adjointL2ConvergenceRel": 1e-16, + "adjointMaxIter": 500, + "adjointMaxL2DeviationFactor": 1.0, + "adjointMonitorStep": 10, + "adjointSolver": "GMRES", + "adjointSubspaceSize": 100, + "alphaFollowing": true, + "alphaMode": false, + "altitudeMode": false, + "applyAdjointPCSubspaceSize": 20, + "applyPCSubspaceSize": 10, + "approxPC": true, + "backgroundVolScale": 1.0, + "betaMode": false, + "blockSplitting": true, + "cavitationNumber": 1.4, + "closedSurfaceFamilies": null, + "coarseDiscretization": "central plus scalar dissipation", + "computeCavitation": false, + "coupledSolution": false, + "cutCallback": null, + "debugZipper": false, + "deltaT": 0.01, + "designSurfaceFamily": null, + "discretization": "central plus scalar dissipation", + "dissipationLumpingParameter": 6.0, + "dissipationScalingExponent": 0.67, + "eddyVisInfRatio": 0.009, + "equationMode": "steady", + "equationType": "Euler", + "eulerWallTreatment": "linear pressure extrapolation", + "firstRun": true, + "flowType": "external", + "forcesAsTractions": true, + "frozenTurbulence": false, + "globalPreconditioner": "additive Schwarz", + "gridFile": "input_files/mdo_tutorial_euler_scalar_jst.cgns", + "gridPrecision": "double", + "gridPrecisionSurface": "single", + "infChangeCorrection": true, + "innerPreconIts": 1, + "isoVariables": [], + "isosurface": {}, + "liftIndex": 2, + "limiter": "van Albada", + "loadBalanceIter": 10, + "loadImbalance": 0.1, + "localPreconditioner": "ILU", + "lowSpeedPreconditioner": false, + "machMode": false, + "matrixOrdering": "RCM", + "maxL2DeviationFactor": 1.0, + "meshSurfaceFamily": null, + "monitorVariables": [ + "cpu", + "resrho", + "resturb", + "cl", + "cd" + ], + "nCycles": 5000, + "nCyclesCoarse": 500, + "nFloodIter": -1, + "nRKReset": 5, + "nRefine": 10, + "nSaveSurface": 1, + "nSaveVolume": 1, + "nSubiter": 1, + "nSubiterTurb": 3, + "nTimeStepsCoarse": 48, + "nTimeStepsFine": 400, + "nearWallDist": 0.1, + "numberSolutions": true, + "outerPreconIts": 3, + "outputDirectory": "tests/output_files", + "outputSurfaceFamily": "allSurfaces", + "overlapFactor": 0.9, + "oversetLoadBalance": true, + "oversetPriority": {}, + "oversetProjTol": 1e-12, + "oversetUpdateMode": "frozen", + "pMode": false, + "partitionLikeNProc": -1, + "partitionOnly": false, + "preconditionerSide": "right", + "printAllOptions": true, + "printIntro": true, + "printIterations": true, + "printTiming": true, + "printWarnings": true, + "qMode": false, + "rMode": false, + "resAveraging": "alternate", + "restartAdjoint": true, + "restartFile": null, + "restrictionRelaxation": 0.8, + "selfZipCutoff": 120.0, + "sepSensorOffset": 0.0, + "sepSensorSharpness": 10.0, + "setMonitor": true, + "skipAfterFailedAdjoint": true, + "smoothParameter": 1.5, + "smoother": "Runge-Kutta", + "solutionPrecision": "single", + "solutionPrecisionSurface": "single", + "storeConvHist": true, + "storeRindLayer": true, + "surfaceVariables": [ + "cp", + "vx", + "vy", + "vz", + "mach" + ], + "timeAccuracy": 2, + "timeIntegrationScheme": "BDF", + "timeIntervals": 1, + "timeLimit": -1.0, + "turbResScale": 10000.0, + "turbulenceModel": "SA", + "turbulenceOrder": "first order", + "turbulenceProduction": "strain", + "useALE": true, + "useANKSolver": true, + "useApproxWallDistance": true, + "useBlockettes": true, + "useDiagTSPC": true, + "useExternalDynamicMesh": false, + "useGridMotion": false, + "useLinResMonitor": false, + "useMatrixFreedrdw": true, + "useNKSolver": true, + "useOversetWallScaling": false, + "useQCR": false, + "useRotationSA": false, + "useTSInterpolatedGridVelocity": false, + "useWallFunctions": false, + "useZipperMesh": true, + "useft2SA": true, + "verifyExtra": true, + "verifySpatial": true, + "verifyState": true, + "vis2": 0.25, + "vis2Coarse": 0.5, + "vis4": 0.0156, + "viscPC": false, + "viscWallTreatment": "constant pressure extrapolation", + "viscousSurfaceVelocities": true, + "volumeVariables": [ + "resrho" + ], + "wallDistCutoff": 1e+20, + "windAxis": false, + "writeSolutionEachIter": false, + "writeSurfaceSolution": true, + "writeTecplotSurfaceSolution": false, + "writeVolumeSolution": true, + "zipperSurfaceFamily": null + } +} \ No newline at end of file diff --git a/tests/reg_tests/test_error_estimation.py b/tests/reg_tests/test_error_estimation.py new file mode 100644 index 000000000..47f81cdd0 --- /dev/null +++ b/tests/reg_tests/test_error_estimation.py @@ -0,0 +1,106 @@ +# built-ins +import unittest +import os +import copy +from parameterized import parameterized_class +from numpy.testing import assert_allclose + +# MACH classes +from adflow import ADFLOW + +# import the testing utilities that live a few directories up +from reg_default_options import adflowDefOpts +from reg_aeroproblems import ap_tutorial_wing +import reg_test_classes + + +baseDir = os.path.dirname(os.path.abspath(__file__)) +evalFuncs = ["fx", "mz", "cl", "cd", "cmz", "lift", "drag"] + + +@parameterized_class( + [ + # fully converged + { + "name": "euler_wing_full", + "options": { + "gridfile": os.path.join(baseDir, "../../input_files/mdo_tutorial_euler_scalar_jst.cgns"), + "restartfile": os.path.join(baseDir, "../../input_files/mdo_tutorial_euler_scalar_jst.cgns"), + "L2Convergence": 1e-14, + "adjointL2Convergence": 1e-14, + "mgcycle": "sg", + "useNKSolver": True, + "NKSwitchtol": 1e-5, + }, + "ref_file": "error_euler_wing_full.json", + "aero_prob": ap_tutorial_wing, + "evalFuncs": evalFuncs, + }, + # partially converged + { + "name": "euler_wing_partial", + "options": { + "gridfile": os.path.join(baseDir, "../../input_files/mdo_tutorial_euler_scalar_jst.cgns"), + "L2Convergence": 1e-6, + "adjointL2Convergence": 1e-14, + "mgcycle": "sg", + "ncycles": 5000, + "useNKSolver": True, + "NKSwitchtol": 1e-5, + }, + "ref_file": "error_euler_wing_partial.json", + "aero_prob": ap_tutorial_wing, + "evalFuncs": evalFuncs, + }, + ] +) +class TestError(reg_test_classes.RegTest): + """ + Tests that ADflow can converge the given test problems to the given tolerance. + """ + + N_PROCS = 2 + + def setUp(self): + if not hasattr(self, "name"): + # return immediately when the setup method is being called on the based class and NOT the + # classes created using parametrized + # this will happen when training, but will hopefully be fixed down the line + return + + super().setUp() + + options = copy.copy(adflowDefOpts) + options["outputdirectory"] = os.path.join(baseDir, options["outputdirectory"]) + options.update(self.options) + + self.ap = copy.deepcopy(self.aero_prob) + self.ap.evalFuncs = self.evalFuncs + + # Create the solver + self.CFDSolver = ADFLOW(options=options, debug=False) + + def test_error(self): + # do the solve + self.CFDSolver(self.ap) + self.assert_solution_failure() + + # solve the adjoint + funcsSens = {} + self.CFDSolver.evalFunctionsSens(self.ap, funcsSens) + self.assert_adjoint_failure() + + # compute the error + funcsError = {} + self.CFDSolver.solveErrorEstimate(self.ap, funcsError) + + # if fully converged, check that the error is very small + if "full" in self.name: + for value in funcsError.values(): + assert_allclose(value, 0, atol=1e-7) + + self.handler.root_add_dict("errors", funcsError, tol=1e-5) + + +if __name__ == "__main__": + unittest.main()