Skip to content
Open
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
63 changes: 32 additions & 31 deletions avaframe/ana4Stats/probAna.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from avaframe.out3Plot import statsPlots as sP
from avaframe.in1Data import getInput as gI


# create local logger
# change log level in calling module to DEBUG to see log messages
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -203,17 +202,17 @@ def updateCfgRange(cfg, cfgProb, varName, varDict):
if valVariation == "":
valVariation = "-"
parValue = (
variationType
+ "$"
+ valSteps
+ "$"
+ valVariation
+ "$"
+ cfgDist["GENERAL"]["minMaxInterval"]
+ "$"
+ cfgDist["GENERAL"]["buildType"]
+ "$"
+ cfgDist["GENERAL"]["support"]
variationType
+ "$"
+ valSteps
+ "$"
+ valVariation
+ "$"
+ cfgDist["GENERAL"]["minMaxInterval"]
+ "$"
+ cfgDist["GENERAL"]["buildType"]
+ "$"
+ cfgDist["GENERAL"]["support"]
)
# if variation using percent
elif variationType.lower() == "percent":
Expand All @@ -225,9 +224,9 @@ def updateCfgRange(cfg, cfgProb, varName, varDict):
parValue = valVariation + "$" + valSteps
if "ci" in valVariation:
message = (
"Variation Type: range - variationValue is %s not a valid option - only \
scalar value allowed or consider variationType rangefromci"
% valVariation
"Variation Type: range - variationValue is %s not a valid option - only \
scalar value allowed or consider variationType rangefromci"
% valVariation
)
log.error(message)
raise AssertionError(message)
Expand All @@ -236,9 +235,9 @@ def updateCfgRange(cfg, cfgProb, varName, varDict):
parValue = valVariation + "$" + valSteps
else:
message = (
"Variation Type: %s - not a valid option, options are: percent, range, \
normaldistribution, rangefromci"
% variationType
"Variation Type: %s - not a valid option, options are: percent, range, \
normaldistribution, rangefromci"
% variationType
)
log.error(message)
raise AssertionError(message)
Expand Down Expand Up @@ -272,9 +271,9 @@ def updateCfgRange(cfg, cfgProb, varName, varDict):
valValues = np.linspace(float(valStart), float(valStop), int(valSteps))
else:
message = (
"Variation Type: %s - not a valid option, options are: percent, range, \
normaldistribution, rangefromci"
% variationType
"Variation Type: %s - not a valid option, options are: percent, range, \
normaldistribution, rangefromci"
% variationType
)
log.error(message)
raise AssertionError(message)
Expand Down Expand Up @@ -616,8 +615,8 @@ def makeDictFromVars(cfg):

if (len(varParList) == len(varValues) == len(cfg[lengthsPar].split("|")) == len(varTypes)) is False:
message = (
"For every parameter in varParList a variationValue, %s and variationType needs to be provided"
% lengthsPar
"For every parameter in varParList a variationValue, %s and variationType needs to be provided"
% lengthsPar
)
log.error(message)
raise AssertionError(message)
Expand Down Expand Up @@ -801,13 +800,14 @@ def createSampleWithVariationStandardParameters(cfgProb, cfgStart, varParList, v
"values": sampleWBounds,
"typeList": cfgProb["PROBRUN"]["varParType"].split("|"),
"thFromIni": "",
"bounds": np.column_stack((lowerBounds, upperBounds)).tolist()
}

return paramValuesD


def createSampleWithVariationForThParameters(
avaDir, cfgProb, cfgStart, varParList, valVariationValue, varType, thReadFromShp
avaDir, cfgProb, cfgStart, varParList, valVariationValue, varType, thReadFromShp
):
"""Create a sample of parameters for a desired parameter variation,
and fetch thickness values from shp file and perform variation for each feature within
Expand Down Expand Up @@ -907,23 +907,23 @@ def createSampleWithVariationForThParameters(
# set lower and upper bounds depending on varType (percent, range, rangefromci)
lowerBounds[fullVarType == "percent"] = varValList[fullVarType == "percent"] - varValList[
fullVarType == "percent"
] * (fullValVar[fullVarType == "percent"] / 100.0)
] * (fullValVar[fullVarType == "percent"] / 100.0)
upperBounds[fullVarType == "percent"] = varValList[fullVarType == "percent"] + varValList[
fullVarType == "percent"
] * (fullValVar[fullVarType == "percent"] / 100.0)
] * (fullValVar[fullVarType == "percent"] / 100.0)

lowerBounds[fullVarType == "range"] = (
varValList[fullVarType == "range"] - fullValVar[fullVarType == "range"]
varValList[fullVarType == "range"] - fullValVar[fullVarType == "range"]
)
upperBounds[fullVarType == "range"] = (
varValList[fullVarType == "range"] + fullValVar[fullVarType == "range"]
varValList[fullVarType == "range"] + fullValVar[fullVarType == "range"]
)

lowerBounds[fullVarType == "rangefromci"] = (
varValList[fullVarType == "rangefromci"] - ciValues[fullVarType == "rangefromci"]
varValList[fullVarType == "rangefromci"] - ciValues[fullVarType == "rangefromci"]
)
upperBounds[fullVarType == "rangefromci"] = (
varValList[fullVarType == "rangefromci"] + ciValues[fullVarType == "rangefromci"]
varValList[fullVarType == "rangefromci"] + ciValues[fullVarType == "rangefromci"]
)

# create a sample of parameter values using scipy latin hypercube or morris sampling
Expand Down Expand Up @@ -1102,9 +1102,10 @@ def createCfgFiles(paramValuesDList, comMod, cfg, cfgPath=""):
cfgStart[section][par] = str(pVal[index])
else:
cfgStart["GENERAL"][par] = str(pVal[index])
if modName.lower() in ["com1dfa", "com5snowslide", "com6rockavalanche"]:
if modName.lower() in ["com1dfa", "com5snowslide", "com6rockavalanche", 'com8motpsa']:
cfgStart["VISUALISATION"]["scenario"] = str(count1)
cfgStart["INPUT"]["thFromIni"] = paramValuesD["thFromIni"]
cfgStart["VISUALISATION"]["sampleMethod"] = cfg['PROBRUN']['sampleMethod']
Copy link
Contributor

Choose a reason for hiding this comment

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

The new line cfgStart["VISUALISATION"]["sampleMethod"] = cfg['PROBRUN']['sampleMethod'] reads sampleMethod from cfg['PROBRUN'].

However, probAnaCfg.ini has sampleMethod under [PROBRUN] only when probAna is the caller. If createCfgFiles is called from a different path where cfg doesn't have PROBRUN.sampleMethod, this will raise KeyError. The code also assumes VISUALISATION section exists in cfgStart for com8MoTPSA — while the new com8MoTPSACfg.ini does add it, there's no sampleMethod default there, making the flow dependent on the caller always providing this key.

if "releaseScenario" in paramValuesD.keys():
cfgStart["INPUT"]["releaseScenario"] = paramValuesD["releaseScenario"]
cfgF = pathlib.Path(cfgPath, ("%d_%sCfg.ini" % (countS, modName)))
Expand Down
6 changes: 3 additions & 3 deletions avaframe/ana4Stats/probAnaCfg.ini
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ samplingStrategy = 1
# #++++++VARIATION INFO FOR DRAW SAMPLES FROM FULL SET OF VARIATIONS
# type of parameters that shall be varied -separated by | (options: float)
varParType = float|float
# factor used to create the number of samples, if morris number of samples depends on number of varied variables and number of trajectories, for now use nSample as number of trajectories
# factor used to create the number of samples, if morris: number of samples depends on number of varied variables and number of trajectories, for now use nSample as number of trajectories, n >=2 for morris
nSample = 40
# sample method used to create sample (options: latin, morris)
sampleMethod = latin
Expand Down Expand Up @@ -71,13 +71,13 @@ frictModel = samosAT


[com4FlowPy_com4FlowPy_override]
# use default com1DFA config as base configuration (True) and override following parameters
# use default com4FlowPy config as base configuration (True) and override following parameters
# if False and local is available use local
defaultConfig = True


[com8MoTPSA_com8MoTPSA_override]
# use default com1DFA config as base configuration (True) and override following parameters
# use default com8MoTPSA config as base configuration (True) and override following parameters
# if False and local is available use local
defaultConfig = True

Expand Down
140 changes: 140 additions & 0 deletions avaframe/ana6Optimisation/README_ana6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# ana6 – Sensitivity Analysis & Optimisation

The `ana6Optimisation` module provides tools for performing Morris sensitivity analysis and parameter optimisation within the AvaFrame workflow. It supports input parameter ranking, convergence analysis of sensitivity indices and surrogate-based optimisation strategies. The module can be used either sequentially (Morris analysis followed by optimisation) or independently for direct optimisation.

---

## Module Structure

The module contains the following files:

- `runMorrisSA.py` (configuration: `runMorrisSACfg.ini`)
- `runPlotMorrisConvergence.py` (uses `runMorrisSACfg.ini`)
- `runOptimisation.py` (configuration: `runOptimisationCfg.ini`)
- `optimisationUtils.py`

---

## Workflow
### Reference Data and Working Directory

All scripts must be executed within the directory: `avaframe/ana6Optimisation`

In `avaframeCfg.ini`, the avalanche reference directory (`avalancheDir`) must include the suffix `../`, for example: `../data/avaFleisskar`

This ensures correct relative path resolution within the AvaFrame project structure.

To compute goodness-of-fit metrics between reference and simulation results and to perform AIMEC analysis, the following reference data must be provided in: `avaframe/data/<avaName>/Inputs`

The required folder structure is:
Folder:
- **LINES**
Contains the AIMEC path.

- **POLYGONS**
Contains Cropshape and defines the maximal extent of runout area that is used for calculating areal indicators.

- **REFDATA**
Defines the runout area of the reference event.

- **REL**
Defines the release area of the avalanche event.

File:
- **Digital Elevation Model (DEM)**
Must be placed directly in the `Inputs` directory and must cover the entire affected area.

More Details here: https://docs.avaframe.org/en/latest/moduleCom1DFA.html

___

### Morris Sensitivity Analysis (MorrisSA)

The Morris sensitivity analysis provides a ranking of input parameters based on their influence on the model response.

Before running `runMorrisSA.py`, the following step is required prior:

- Execute `runAna4ProbAnaCom8MoTPSA`
- In `probAnaCfg.ini`:
- Set the sampling method to `'morris'`
- Define the number of Morris trajectories (`nSample`)
- Select the input parameters and define their variation bounds

This step generates the required simulations and stores the sampled parameters and their bounds in a pickle file.

**Afterwards:**

- Run `runMorrisSA.py`
- Configure settings via `runMorrisSACfg.ini`
- The `MORRIS_CONVERGENCE` setting can be ignored for standard sensitivity analysis

**Outputs:**

- Pickle file containing:
- Ranked input parameters
- Morris sensitivity indices
- Parameter bounds
- Visualisation plots of the sensitivity results

---

### Morris Convergence Analysis

The convergence analysis evaluates how the Morris sensitivity indices stabilise with increasing numbers of trajectories. Its purpose is to determine the minimum number of trajectories that yields robust results.

**Requirements:**

- Run `runAna4ProbAnaCom8MoTPSA` multiple times with different numbers of Morris trajectories
- Rename Output folders afterwards with the following naming convention: OutputsR<number>


where `<number>` corresponds to the number of trajectories

This process is computationally expensive, as it requires a large number of simulations.

**Execution:**

- Run `runPlotMorrisConvergence.py`

**Output:**

- Convergence plots of Morris sensitivity indices

---

### Optimisation

The optimisation process identifies the set of input parameters that yields the best agreement between simulation results and a defined reference. "Best" is defined by the objective function implemented in the optimisation routine.

Optimisation can be performed in two ways:

**With prior Morris analysis:**
- Parameter ranking is available
- Parameter bounds are already defined
- Execute `runOpmisiation.py` with scenario 1 in `runOptimisationCfg.ini`

**Without prior Morris analysis:**
- Execute `runAna4ProbAnaCom8MoTPSA.py` to generate some initial samples (for surrogate)
- In `probAnaCfg.ini`:
- Set the sampling method to `'latin'`
- Define the number of model runs (`nSample`)
- Select the input parameters and define their variation bounds
- Execute `runOpmisiation.py` with scenario 2 in `runOptimisationCfg.ini`

**Two optimisation strategies are implemented:**

- Surrogate-based non-sequential optimisation
- Surrogate-based Bayesian (sequential) optimisation

**Outputs:**

- Optimal parameter set
- Visualisation plots of the optimisations results and progress

---

## Notes

- Performing Morris sensitivity analysis before optimisation is recommended to reduce the parameter space.
- Convergence analysis significantly increases computational cost.
- All workflows are controlled via `.ini` configuration files.
Loading
Loading