-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from dnv-opensource/docs_add_code_example_for_a…
…bo_akademi added farn_example.md to docs
- Loading branch information
Showing
3 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ API Documentation | |
:maxdepth: 4 | ||
|
||
farn | ||
farn_example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ API Documentation | |
farn | ||
ospx | ||
dictIO | ||
farn_example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
# Example of farn API usage | ||
|
||
The following gives an example of how the farn API can be used in conjunction with OSP cosim and the ospx package. | ||
|
||
## Case | ||
|
||
Case is a simple dataclass designed to hold case-specific information as instance attributes.<br> | ||
Below example uses the most typical / important ones: | ||
|
||
~~~py | ||
from pathlib import Path | ||
from typing import Dict, List | ||
from farn.core import Case, Parameter | ||
|
||
case_name: str = "test_001" | ||
|
||
case_folder: Path = Path("path/to/your/experiment/cases") / case_name | ||
|
||
case_parameters: List[Parameter] = [ | ||
Parameter("x1", 1.0), | ||
Parameter("x2", 2.0), | ||
Parameter("x3", 3.0), | ||
] | ||
|
||
command_sets: Dict[str, List[str]] = { | ||
"prepare": [ | ||
"copy path/to/template/dir/caseDict .", # copy a (case-agnostic) ospx config file from a template directory into the case folder | ||
"dictParser caseDict", # parse the ospx config file. This will make it case-specific | ||
"ospCaseBuilder parsed.caseDict", # build the (case-specific) OspSystemStructure.xml | ||
], | ||
"run": [ | ||
"cosim run OspSystemStructure.xml -b 0 -d 10", # run OSP cosim | ||
], | ||
"post": [ | ||
"watchCosim -d watchDict", # optional post-processing. watchCosim creates a sub-folder 'results' in the case folder | ||
], | ||
} | ||
|
||
case: Case = Case( | ||
case=case_name, | ||
path=case_folder, | ||
is_leaf=True, | ||
parameters=case_parameters, | ||
command_sets=command_sets, | ||
) | ||
~~~ | ||
|
||
|
||
## Cases | ||
|
||
Many farn functions expect not a single Case object, but a list of Case objects passed in.<br> | ||
So, even if you create only a single Case object, you will likely need to wrap it in a Cases list for processing. | ||
|
||
~~~py | ||
from farn.core import Cases | ||
|
||
cases: Cases = Cases() | ||
cases.append(case) | ||
~~~ | ||
|
||
## Create case folder(s) | ||
|
||
~~~py | ||
from farn.farn import create_case_folders | ||
|
||
_ = create_case_folders(cases) | ||
~~~ | ||
|
||
## Create paramDict file in case folder(s) | ||
|
||
It is a convention used by both farn and ospx that a 'paramDict' file is expected to exist in the case folder | ||
which contains the case-specific values of all varied parameters.<br> | ||
create_param_dict_files() will create this paramDict file in the case folder(s): | ||
|
||
~~~py | ||
from farn.farn import create_param_dict_files | ||
|
||
_ = create_param_dict_files(cases) | ||
~~~ | ||
|
||
|
||
## Prepare OSP simulation case(s) | ||
|
||
Execute the command_set that performs preparatory tasks in your case folder(s). | ||
|
||
~~~py | ||
from farn.farn import execute_command_set | ||
|
||
_ = execute_command_set( | ||
cases=cases, | ||
command_set="prepare", | ||
) | ||
~~~ | ||
|
||
|
||
## Run OSP simulation case(s) | ||
|
||
Execute the command_set that actually runs your simulation case(s). | ||
|
||
~~~py | ||
_ = execute_command_set( | ||
cases=cases, | ||
command_set="run", | ||
) | ||
~~~ | ||
|
||
## Execute post-processing (optional) | ||
|
||
Execute the command_set that performs post-processing tasks in your case folder(s) (if any). | ||
|
||
~~~py | ||
_ = execute_command_set( | ||
cases=cases, | ||
command_set="post", | ||
) | ||
~~~ | ||
|
||
## Read results | ||
|
||
Reading results after your simulation cases finished can be rather individual and there will surely be multiple ways to accomplish this. | ||
Two exemplary ways being | ||
|
||
a) You can read the *.csv files that cosim.exe generates, i.e. by reading them into a Pandas DataFrame. | ||
|
||
b) In case you executed watchCosim as a post-processing step, a sub-folder 'results' will exist in each case folder that contains the results additionally as a resultDict file. | ||
|
||
Variant a) is less complex and can be done with Pandas (no example given here). <br> | ||
Variant b) offers some additional possibilities but requires also additional code. <br> | ||
Below hence an example for variant b), i.e. how to read a resultDict file generated by watchCosim | ||
|
||
~~~py | ||
from typing import Any, Dict, List | ||
from pandas import DataFrame, Series | ||
from dictIO import DictReader | ||
from dictIO.utils.path import relative_path | ||
|
||
# @NOTE: Adjust 'component_name' and 'variable_name' to what is defined in your (FMU) model | ||
# @NOTE: 'y' is just an example. Add mappings for more column names / variables as you like and need. | ||
mapping: Dict[str, Dict[str, Any]] = { | ||
"y": { # column name you want to map a variable to | ||
"key": "component_name|variable_name:latestValue", # variable in FMU | ||
"unit": 1, # usually 1, unless you want to apply a scaling factor | ||
} | ||
} | ||
|
||
# column names | ||
names: List[str] = [name for name in mapping if not re.search("(^_|COMMENT)", name)] | ||
|
||
series: Dict[str, Series] = { | ||
"path": Series(data=None, dtype=dtype(str), name="path"), | ||
} | ||
|
||
for index, case in cases: | ||
|
||
case_folder: Path = case.path | ||
result_folder: Path = case_folder / "results" | ||
result_dict_file: Path = result_folder / "watchDict-test_project-resultDict" # adapt to output of watchCosim. | ||
|
||
series["path"].loc[index] = str(relative_path(Path.cwd(), case_folder)) | ||
|
||
result_dict = DictReader.read(result_dict_file, includes=False, comments=False) | ||
|
||
for name in names: | ||
value: Any = None | ||
value_eval_string = "result_dict['" + "']['".join(mapping[name]["key"].split(":")) + "']" | ||
try: | ||
value = eval(value_eval_string) | ||
except Exception: | ||
logger.warning(f'"{value_eval_string}" not in {result_dict_file}') | ||
continue | ||
|
||
if name not in series: | ||
series[name] = Series(data=None, dtype=dtype(type(value)), name=name) | ||
|
||
if value is not None: | ||
series[name].loc[index] = value | ||
|
||
df: DataFrame = DataFrame(data=series) | ||
|
||
df.set_index("path", inplace=True) # optional. Makes the 'path' column the DataFrame's index | ||
~~~ |