Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,507 changes: 878 additions & 629 deletions avaframe/ana3AIMEC/aimecTools.py

Large diffs are not rendered by default.

31 changes: 23 additions & 8 deletions avaframe/ana3AIMEC/ana3AIMEC.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def fullAimecAnalysis(avalancheDir, cfg, inputDir='', demFileName=''):
'demFileName': demFileName}
pathDict = aimecTools.readAIMECinputs(avalancheDir, pathDict, cfgSetup.getboolean('defineRunoutArea'),
dirName=anaMod)
pathDict = aimecTools.checkAIMECinputs(cfgSetup, pathDict)
pathDict = aimecTools.checkAIMECinputs(cfgSetup, pathDict, inputsDF)
log.info("Running ana3AIMEC model on test case DEM \n %s \n with profile \n %s ",
pathDict['demSource'], pathDict['profileLayer'])
# Run AIMEC postprocessing
Expand Down Expand Up @@ -117,7 +117,10 @@ def mainAIMEC(pathDict, inputsDF, cfg):
# get hash of the reference
refSimRowHash = pathDict['refSimRowHash']
# read reference file and raster and config
refResultSource = inputsDF.loc[refSimRowHash, cfgSetup['runoutResType']]
runoutResType = pathDict['runoutResType']
runoutLayer = pathDict.get('runoutLayer', '')
refResTypeCol = aimecTools.resolveResTypeColumn(inputsDF.loc[refSimRowHash], runoutResType, runoutLayer)
refResultSource = inputsDF.loc[refSimRowHash, refResTypeCol]
refRaster = IOf.readRaster(refResultSource)
refRasterData = refRaster['rasterData']
refHeader = refRaster['header']
Expand Down Expand Up @@ -209,7 +212,14 @@ def mainAIMEC(pathDict, inputsDF, cfg):
compResTypes1 = cfgPlots['compResType1'].split('|')
compResTypes2 = cfgPlots['compResType2'].split('|')
for indRes, compResType in enumerate(compResTypes1):
outAimec.plotMaxValuesComp(pathDict, resAnalysisDF, compResType, compResTypes2[indRes],
compResType2 = compResTypes2[indRes]
missingCols = [c for c in [compResType, compResType2] if c not in resAnalysisDF.columns]
if missingCols:
log.warning("Skipping comparison plot %s vs %s: columns %s not available in results "
"(result type not in resTypeList for all simulations)",
compResType, compResType2, missingCols)
continue
outAimec.plotMaxValuesComp(pathDict, resAnalysisDF, compResType, compResType2,
hue=cfgPlots['scenarioName'])

return rasterTransfo, resAnalysisDF, plotDict, newRasters
Expand Down Expand Up @@ -331,13 +341,15 @@ def postProcessAIMEC(cfg, rasterTransfo, pathDict, resAnalysisDF, newRasters, ti
flagMass = cfgFlags.getboolean('flagMass')
refSimRowHash = pathDict['refSimRowHash']
resTypeList = pathDict['resTypeList']
runoutLayer = pathDict.get('runoutLayer', '')

# apply domain transformation
log.info('Analyzing data in path coordinate system')

for resType in resTypeList:
log.debug("Assigning %s data to deskewed raster" % resType)
inputFiles = resAnalysisDF.loc[simRowHash, resType]
resTypeCol = aimecTools.resolveResTypeColumn(resAnalysisDF.loc[simRowHash], resType, runoutLayer)
inputFiles = resAnalysisDF.loc[simRowHash, resTypeCol]
if isinstance(inputFiles, pathlib.PurePath):
rasterData = IOf.readRaster(inputFiles)
newRaster = aimecTools.transform(rasterData, inputFiles, rasterTransfo, interpMethod)
Expand Down Expand Up @@ -380,16 +392,19 @@ def postProcessAIMEC(cfg, rasterTransfo, pathDict, resAnalysisDF, newRasters, ti
simRowHash, rasterTransfo, newRaster, resType, resAnalysisDF
)

# compute runout based on runoutResType
resAnalysisDF = aimecTools.computeRunOut(cfgSetup, rasterTransfo, resAnalysisDF, newRasters, simRowHash)
runoutLine = aimecTools.computeRunoutLine(cfgSetup, rasterTransfo, newRasters, simRowHash, 'simulation', name='')
# compute runout based on base runoutResType (resolver handles per-row column access)
runoutResType = pathDict['runoutResType']
resAnalysisDF = aimecTools.computeRunOut(cfgSetup, rasterTransfo, resAnalysisDF, newRasters, simRowHash,
runoutResType=runoutResType)
runoutLine = aimecTools.computeRunoutLine(cfgSetup, rasterTransfo, newRasters, simRowHash, 'simulation', name='',
runoutResType=runoutResType)

if 'refPoint' in refDataTransformed:
# compute differences between runout points
resAnalysisDF = aimecTools.computeRunoutPointDiff(resAnalysisDF, refDataTransformed['refPoint'], simRowHash)

# plot comparison between runout lines
outAimec.compareRunoutLines(cfgSetup, refDataTransformed, newRasters['newRaster'+cfgSetup['runoutResType'].upper()],
outAimec.compareRunoutLines(cfgSetup, refDataTransformed, newRasters['newRaster'+runoutResType.upper()],
runoutLine, rasterTransfo, resAnalysisDF.loc[simRowHash], pathDict)

# analyze distribution of diffs between runout lines
Expand Down
4 changes: 4 additions & 0 deletions avaframe/ana3AIMEC/ana3AIMECCfg.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ resTypes = ppr|pft|pfv
# data result type for runout analysis (ppr, pft, pfv)
runoutResType = ppr

# layer to use for analysis (e.g. L1, L2). Leave empty for single-layer modules.
# Required when analyzing multi-layer results (e.g. com8MoTPSA).
runoutLayer =

# show cross profile max or mean values on slComparisonstat plot (options: Max, Mean)
runoutCrossType = Max

Expand Down
9 changes: 7 additions & 2 deletions avaframe/ana3AIMEC/dfa2Aimec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# local modules
from avaframe.in3Utils import fileHandlerUtils as fU
from avaframe.in3Utils import cfgUtils
from avaframe.ana3AIMEC import aimecTools

# create local logger
# change log level in calling module to DEBUG to see log messages
Expand Down Expand Up @@ -147,8 +148,6 @@ def dfaBench2Aimec(avaDir, cfg, simNameRef='', simNameComp=''):
# Load all infos on comparison module simulations
compData, resTypeCompList = fU.makeSimFromResDF(avaDir, None, inputDir=inputDirComp, simName=simNameComp)

resTypeList = list(set(resTypeRefList).intersection(resTypeCompList))
pathDict['resTypeList'] = resTypeList
pathDict['colorParameter'] = colorParameter
pathDict['refSimRowHash'] = refSimRowHash
pathDict['refSimName'] = refSimName
Expand Down Expand Up @@ -182,6 +181,12 @@ def dfaBench2Aimec(avaDir, cfg, simNameRef='', simNameComp=''):
message = ('Multiple rows of the dataFrame have the same simulation name.')
log.warning(message)

# compute base-name resTypeList and validate via checkAIMECinputs
# use raw column-name lists as initial resTypeList (will be overwritten by checkAIMECinputs)
resTypeList = list(set(resTypeRefList).intersection(resTypeCompList))
pathDict['resTypeList'] = resTypeList
pathDict = aimecTools.checkAIMECinputs(cfgSetup, pathDict, inputsDF)

return inputsDF, pathDict


Expand Down
136 changes: 91 additions & 45 deletions avaframe/com8MoTPSA/com8MoTPSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@


def com8MoTPSAMain(cfgMain, cfgInfo=None):
"""Run the full MoT-PSA workflow: generate configs, run simulations in parallel, postprocess.

Parameters
----------
cfgMain : configparser.ConfigParser
main AvaFrame configuration (avalancheDir, nCPU, plot flags)
cfgInfo : dict or None, optional
override configuration info passed to MoTGenerateConfigs
"""
# Get all necessary information from the configuration files
currentModule = sys.modules[__name__]
simDict, inputSimFiles = mT.MoTGenerateConfigs(cfgMain, cfgInfo, currentModule)
Expand Down Expand Up @@ -62,7 +71,56 @@ def com8MoTPSAMain(cfgMain, cfgInfo=None):
com8MoTPSAPostprocess(simDict, cfgMain, inputSimFiles)


def copyRawToLayerPeakFiles(workDir, outputDirPeakFile):
"""Rename and copy raw MoT-PSA output files to peakFiles with L1/L2 layer naming.

MoT-PSA produces raw suffixes (p1/p2_max, h1/h2_max, s1/s2_max) that map
to AvaFrame result types (ppr, pfd, pfv). The digit in the raw suffix encodes
the layer: 1 -> L1 (dense flow), 2 -> L2 (powder snow).

Example: simKey_null_psa_p1_max.asc -> simKey_null_psa_L1_ppr.asc

Parameters
----------
workDir : pathlib.Path
simulation work directory containing raw MoT-PSA output files
outputDirPeakFile : pathlib.Path
target directory for renamed peak files
"""
# Each entry: (glob pattern, raw L1 suffix, raw L2 suffix, AvaFrame resType)
layerRenameMap = [
("*p?_max*", "p1_max", "p2_max", "ppr"),
("*h?_max*", "h1_max", "h2_max", "pfd"),
("*s?_max*", "s1_max", "s2_max", "pfv"),
]
for globPattern, rawL1, rawL2, resType in layerRenameMap:
rawFiles = list(workDir.glob(globPattern))
# Replace raw suffixes with layer naming (e.g. p1_max -> L1_ppr, p2_max -> L2_ppr)
targetFiles = [
pathlib.Path(str(f.name).replace(rawL1, "L1_%s" % resType).replace(rawL2, "L2_%s" % resType))
for f in rawFiles
]
# Prepend output directory and copy
targetFiles = [outputDirPeakFile / f for f in targetFiles]
for source, target in zip(rawFiles, targetFiles):
shutil.copy2(source, target)


def com8MoTPSAPostprocess(simDict, cfgMain, inputSimFiles):
"""Postprocess MoT-PSA results: rename outputs to L1/L2 peak files, generate plots and reports.

For each simulation, copies DataTime.txt and renames raw MoT-PSA output files
(p1/p2_max, h1/h2_max, s1/s2_max) to AvaFrame layer naming (L1/L2 + ppr/pfd/pfv).

Parameters
----------
simDict : dict
simulation dictionary
cfgMain : configparser.ConfigParser
main AvaFrame configuration (avalancheDir, plot flags)
inputSimFiles : dict
input file paths, must contain "demFile"
"""
avalancheDir = cfgMain["MAIN"]["avalancheDir"]
# Copy max files to output directory

Expand All @@ -73,55 +131,11 @@ def com8MoTPSAPostprocess(simDict, cfgMain, inputSimFiles):
for key in simDict:
workDir = pathlib.Path(avalancheDir) / "Work" / "com8MoTPSA" / str(key)

# identify simType
simType = simDict[key]["simType"]

# Copy DataTime.txt
dataTimeFile = workDir / "DataTime.txt"
shutil.copy2(dataTimeFile, outputDir / (str(key) + "_DataTime.txt"))

# TODO: functionize it
# Copy ppr files
pprFiles = list(workDir.glob("*p?_max*"))
targetFiles = [
pathlib.Path(str(f.name).replace("%s_psa_p1_max" % simType, "%s_dfa_ppr" % simType))
for f in pprFiles
]
targetFiles = [
pathlib.Path(str(f).replace("%s_psa_p2_max" % simType, "%s_psa_ppr" % simType))
for f in targetFiles
]
targetFiles = [outputDirPeakFile / f for f in targetFiles]
for source, target in zip(pprFiles, targetFiles):
shutil.copy2(source, target)

# Copy pfd files
pfdFiles = list(workDir.glob("*h?_max*"))
targetFiles = [
pathlib.Path(str(f.name).replace("%s_psa_h1_max" % simType, "%s_dfa_pfd" % simType))
for f in pfdFiles
]
targetFiles = [
pathlib.Path(str(f).replace("%s_psa_h2_max" % simType, "%s_psa_pfd" % simType))
for f in targetFiles
]
targetFiles = [outputDirPeakFile / f for f in targetFiles]
for source, target in zip(pfdFiles, targetFiles):
shutil.copy2(source, target)

# Copy pfv files
pfvFiles = list(workDir.glob("*s?_max*"))
targetFiles = [
pathlib.Path(str(f.name).replace("%s_psa_s1_max" % simType, "%s_dfa_pfv" % simType))
for f in pfvFiles
]
targetFiles = [
pathlib.Path(str(f).replace("%s_psa_s2_max" % simType, "%s_psa_pfv" % simType))
for f in targetFiles
]
targetFiles = [outputDirPeakFile / f for f in targetFiles]
for source, target in zip(pfvFiles, targetFiles):
shutil.copy2(source, target)
copyRawToLayerPeakFiles(workDir, outputDirPeakFile)

# create plots and report
modName = __name__.split(".")[-1]
Expand All @@ -134,6 +148,18 @@ def com8MoTPSAPostprocess(simDict, cfgMain, inputSimFiles):


def com8MoTPSATask(rcfFile):
"""Run a single MoT-PSA simulation by invoking the MoT-PSA executable with an rcf file.

Parameters
----------
rcfFile : str or pathlib.Path
path to the .rcf configuration file for this simulation

Returns
-------
list
the command that was executed (["./MoT-PSA", rcfFile])
"""
# TODO: Obvious...
os.chdir(os.path.dirname(os.path.abspath(__file__)))
command = ["./MoT-PSA", rcfFile]
Expand All @@ -144,6 +170,26 @@ def com8MoTPSATask(rcfFile):


def com8MoTPSAPreprocess(simDict, inputSimFiles, cfgMain):
"""Prepare all MoT-PSA simulations: derive input rasters, set config paths, write rcf files.

For each simulation in simDict, processes release/entrainment/bed shear/deposition
input data, configures MoT-PSA file paths and parameters, and writes the .rcf
configuration file needed by the MoT-PSA executable.

Parameters
----------
simDict : dict
simulation dictionary keyed by simKey, each value contains "cfgSim" and "simType"
inputSimFiles : dict
input file paths (DEM, release scenarios, etc.)
cfgMain : configparser.ConfigParser
main AvaFrame configuration (avalancheDir)

Returns
-------
list of pathlib.Path
paths to the generated .rcf files, one per simulation
"""
# Load avalanche directory from general configuration file
avalancheDir = cfgMain["MAIN"]["avalancheDir"]
# set inputsDir where original input data and remeshed rasters are stored
Expand Down
9 changes: 9 additions & 0 deletions avaframe/com8MoTPSA/com8MoTPSACfg.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
# model type - only for file naming (psa - powder snow avalanche)
modelType = psa

# available layers: L1 (dense flow), L2 (powder snow)
# if empty, all available layers are output
layers = L1|L2

# list of simulations that shall be performed (null, ent, res, entres, available (use all available input data))
simTypeList = null

Expand Down Expand Up @@ -73,6 +77,11 @@ secondaryRelThDistVariation =
# secondary area release thickness (only considered if secondaryRelThFromFile=False) [m]
secondaryRelTh =

#+++++++++++++general start conditions: time dependent release
# if timeDependentRelease is True, provide the the timesteps, thickness and velocity
# for a releases in a csv-file in the REL folder
timeDependentRelease = False

#+++++++++++++Volume classes [m³]
volClassSmall = 25000.
volClassMedium = 60000.
Expand Down
27 changes: 20 additions & 7 deletions avaframe/in3Utils/cfgUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,10 @@ def parseSimName(name):
"""Parse simulation name handling both old and new formats.

Auto-detects:
- Old format: relName_simHash_defID_[frictIndi]_simType_modelType[_resType][_timeStep]
- New format: relName_simHash_modName_defID_[frictIndi]_simType_modelType[_resType][_timeStep]
- Old format: relName_simHash_defID_[frictIndi]_simType_modelType[_layer][_resType][_timeStep]
- New format: relName_simHash_modName_defID_[frictIndi]_simType_modelType[_layer][_resType][_timeStep]
[ ] denotes optional items
Layer component matches pattern L followed by digits (e.g., L1, L2, L12)

Parameters
----------
Expand All @@ -550,6 +551,7 @@ def parseSimName(name):
- frictIndi: str | None (optional, values: "S", "M", "L")
- simType: str (required)
- modelType: str (required)
- layer: str | None (optional, e.g., "L1", "L2")
- resType: str | None (optional, only in filenames)
- timeStep: str | None (optional, only in filenames)

Expand Down Expand Up @@ -619,15 +621,25 @@ def parseSimName(name):
simType = remainingParts[offset]
modelType = remainingParts[offset + 1]

# Step 6: Extract optional file components (resType, timeStep)
# Step 6: Extract optional file components (layer, resType, timeStep)
layer = None
resType = None
timeStep = None

if len(remainingParts) > offset + 2:
resType = remainingParts[offset + 2]

if len(remainingParts) > offset + 3:
timeStep = remainingParts[offset + 3]
candidate = remainingParts[offset + 2]
if re.match(r"^L\d+$", candidate):
# Layer component detected (e.g., L1, L2, L12)
layer = candidate
if len(remainingParts) > offset + 3:
resType = remainingParts[offset + 3]
if len(remainingParts) > offset + 4:
timeStep = remainingParts[offset + 4]
else:
# No layer
resType = candidate
if len(remainingParts) > offset + 3:
timeStep = remainingParts[offset + 3]

# Step 7: Return structured dictionary
return {
Expand All @@ -638,6 +650,7 @@ def parseSimName(name):
"frictIndi": frictIndi,
"simType": simType,
"modelType": modelType,
"layer": layer,
"resType": resType,
"timeStep": timeStep,
}
Expand Down
Loading
Loading