Starter Example¶
Introduction¶
Purpose¶
This example introduces the StudPy core objects and demonstrates a complete workflow.
Use Case¶
In this example, we consider a binary simulation tool that:
- takes a
main.jsonfile as input; - produces a
results.txtfile as output, containing a single numeric value.
The input and output files are assumed to have the following structures:
main.json
{
"static_attributes": {
"static_attribute_1": 1000
},
"thermodynamic": {
"temperature": 273
},
"seed": 3305
}
results.txt
Our goal is to generate a dataset of inputs/outputs by varying the temperature and seed parameters.
Design workflow¶
- Define the Files Builder which will create the case folder structure and case input file for the simulation
- Define the Case Builder and its parameters (1 linear and 1 random) which will create the cases and associated values
- Define the Study which associates Files Builder and Case Builder
- Define and execute the Batch which will execute and aggregate case results
Script¶
import shutil
from pathlib import Path
from studpy.inputs import FilesBuilder, FileProcessor
from studpy.cases import CaseBuilder
from studpy.study import Study
from studpy.batch import BatchExecCmdIter
# ##############################################################################
# 1. Creation of a FilesBuilder
files_builder = FilesBuilder(
folder=Path(__file__).parent / "cases",
processors=[
FileProcessor(relative_path="main.json", source='{"static_attributes":{"static_attribute_1": 1000}}')
],
)
# ##############################################################################
# 2. Creation of a CaseBuilder
case_builder = CaseBuilder.load({
"parameters": [
{
"input_path": "thermodynamic.temperature",
"name": "Temperature",
"config": {"type": "linear", "size": 3, "low": 273, "high": 700}
},
{
"input_path": "seed",
"name": "Seed",
"config": {"type": "random_int", "size": 2, "low": 0, "high": 6000}
},
]
})
# ##############################################################################
# 3. Creation of the Study
study = Study(case_builder, files_builder)
# ##############################################################################
# 4. Creation of the Batch
# ASSUMPTION: jq is installed
# Implement abstract Class
class ExampleBatch(BatchExecCmdIter):
def __init__(self, study: Study):
self.study = study
self.total = 0
# abstract implementation
def get_nb_runs(self) -> int:
return self.study.cases_count()
# abstract implementation
def start_callable(self) -> None:
self.study.prepare()
self.study.files_builder.prepare()
# abstract implementation
def pre_callable(self, index_run: int) -> None:
self.study.case_build_inputs(index_run)
# abstract implementation
def cmd_and_cwd_callable(self, index_run: int) -> tuple[str, str|None]:
case_folder = files_builder.get_case_folder(index_run)
inputs_folder = self.study.files_builder.get_case_inputs_folder(index_run)
# equivalent
# return f"jq -r '.thermodynamic.temperature * .seed' {inputs_folder / 'main.json'} > {case_folder / 'results.txt'}", None
return f"jq -r '.thermodynamic.temperature * .seed' {inputs_folder / 'main.json'} > results.txt", case_folder
# abstract implementation
def post_callable(self, index_run: int, returncode: int | None, stdout: str, stderr: str) -> str | None:
if returncode == 0:
case_folder = self.study.files_builder.get_case_folder(index_run)
res = (case_folder / 'results.txt').read_text()
self.total += float(res)
# Execute
batch = ExampleBatch(study)
batch.exec()
print(batch.total)
Explainations¶
1. Define the File Builder¶
# ##############################################################################
# 1. Creation of a FilesBuilder
files_builder = FilesBuilder(
folder=Path(__file__).parent / "cases",
processors=[
FileProcessor(relative_path="main.json", source='{"static_attributes":{"static_attribute_1": 1000}}')
],
)
Purposes:
- Define the main target folder that will contain all input and output files.
- Specify how the input files are generated.
See API FilesBuilder
2. Define the Case Builder and its parameters¶
# ##############################################################################
# 2. Creation of a CaseBuilder
case_builder = CaseBuilder.load({
"parameters": [
{
"input_path": "thermodynamic.temperature",
"name": "Temperature",
"config": {"type": "linear", "size": 3, "low": 273, "high": 700}
},
{
"input_path": "seed",
"name": "Seed",
"config": {"type": "random_int", "size": 2, "low": 0, "high": 6000}
},
]
})
Please note that there is no need to specify the main.json file in input_path, as there is only one file.
You can build cases with:
Results:
Show the datasets of parameters values like :
3. Define the Study¶
You can manipulate the creation of cases
# FOR DEBUG : creates all cases folders and inputs files
files_builder.prepare()
study.case_build_inputs_all()
Results:
- Create the cases folders and inputs files like :
cases/
├── 0/
│ └── main.json
├── 1/
│ └── main.json
├── 2/
│ └── main.json
├── 3/
│ └── main.json
├── 4/
│ └── main.json
└── 5/
└── main.json
with the cases values applied like in cases/0/main.json:
{
"static_attributes": {
"static_attribute_1": 1000
},
"thermodynamic": {
"temperature": 273.0
},
"seed": 5368.0
}
4. Define the Batch and Execute (Final script)¶
# ##############################################################################
# 4. Creation of the Batch
# ASSUMPTION: jq is installed
# Implement abstract Class
class ExampleBatch(BatchExecCmdIter):
def __init__(self, study: Study):
self.study = study
self.total = 0
# abstract implementation
def get_nb_runs(self) -> int:
return self.study.cases_count()
# abstract implementation
def start_callable(self) -> None:
self.study.files_builder.prepare()
# abstract implementation
def pre_callable(self, index_run: int) -> None:
self.study.case_build_inputs(index_run)
# abstract implementation
def cmd_callable(self, index_run: int) -> str:
case_folder = self.study.files_builder.get_case_inputs_folder(index_run)
return f"jq -r '.thermodynamic.temperature * .seed' {case_folder / 'main.json'} > {case_folder / 'results.txt'}"
# abstract implementation
def post_callable(self, index_run: int, returncode: int | None, stdout: str, stderr: str) -> str | None:
if returncode == 0:
case_folder = self.study.files_builder.get_case_inputs_folder(index_run)
res = (case_folder / 'results.txt').read_text()
self.total += float(res)
# Execute
batch = ExampleBatch(study)
batch.exec()
print(batch.total)
Results:
- Create the cases folders, inputs and results files like :
cases/
├── 0/
│ ├── main.json
│ └── results.txt
├── 1/
│ ├── main.json
│ └── results.txt
├── 2/
│ ├── main.json
│ └── results.txt
├── 3/
│ ├── main.json
│ └── results.txt
├── 4/
│ ├── main.json
│ └── results.txt
└── 5/
│ ├── main.json
│ └── results.txt
- Displays output like
[INFO] [ExampleBatch] START — 6 runs
[INFO] [ExampleBatch] RUN 1/6
[INFO] [ExampleBatch] RUN 2/6
[INFO] [ExampleBatch] RUN 3/6
[INFO] [ExampleBatch] RUN 4/6
[INFO] [ExampleBatch] RUN 5/6
[INFO] [ExampleBatch] RUN 6/6
[INFO] [ExampleBatch] END — 0 errors / 6 runs
8352718.5