Skip to content

Commit

Permalink
Merge branch 'features/container_gui'
Browse files Browse the repository at this point in the history
  • Loading branch information
nl78 committed Feb 10, 2025
2 parents 6c6c2b9 + 50349a4 commit 51c1f0e
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 141 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/fmu_manipulation_toolbox-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ jobs:
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Install needed packets
run: >
run: |
sudo apt-get update
sudo apt-get install gcc-multilib
- name: Configure CMake
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ This package was formerly known as `fmutool`.
* ADDED: `fmucontainer` log more information when embedded FMU cannot be loaded
* ADDED: `fmucontainer` startTime and stopTime are deduced from 1st embedded FMU
* ADDED: `fmucontainer` support new option `-auto-parameter` (parameters are not exposed by default)
* FIXED: `fmutool` support "apply on" filter correctly
* [ ] ADDED: preliminary version of GUI for `fmucontainer`


## Version 1.8.1
* FIXED: `fmucontainer` read links from `.json` input files
* CHANGED: switch to PyQT6 and add minor GUI improvements
Expand Down
47 changes: 27 additions & 20 deletions container/container.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,12 @@ static int read_conf_fmu(container_t *container, const char *dirname, config_fil

logger(fmi2OK, "Loading '%s.dll' from directory '%s'", identifier, directory);

if (fmu_load_from_directory(container, i, directory, identifier, guid)) {
logger(fmi2Error, "Cannot load from directory '%s'", directory);
int status = fmu_load_from_directory(container, i, directory, identifier, guid);
if (status) {
logger(fmi2Error, "Cannot load from directory '%s' (status=%d)", directory, status);
free(identifier);
free(container->fmu);
container->fmu = NULL; /* to allow freeInstance on container */
return -4;
}

Expand Down Expand Up @@ -920,9 +923,9 @@ static fmi2Status do_internal_step_parallel_mt(container_t* container, fmi2Real
fmi2Boolean noSetFMUStatePriorToCurrentPoint) {
fmi2Status status = fmi2OK;

container->currentCommunicationPoint = currentCommunicationPoint;
container->step_size = step_size;
container->noSetFMUStatePriorToCurrentPoint = noSetFMUStatePriorToCurrentPoint;
container->fmi_currentCommunicationPoint = currentCommunicationPoint;
container->fmi_step_size = step_size;
container->fmi_noSetFMUStatePriorToCurrentPoint = noSetFMUStatePriorToCurrentPoint;

/* Launch computation for all threads*/
for (int i = 0; i < container->nb_fmu; i += 1) {
Expand Down Expand Up @@ -961,9 +964,9 @@ static fmi2Status do_internal_step_parallel(container_t* container, fmi2Real cur
return status;
}

container->currentCommunicationPoint = currentCommunicationPoint;
container->step_size = step_size;
container->noSetFMUStatePriorToCurrentPoint = noSetFMUStatePriorToCurrentPoint;
container->fmi_currentCommunicationPoint = currentCommunicationPoint;
container->fmi_step_size = step_size;
container->fmi_noSetFMUStatePriorToCurrentPoint = noSetFMUStatePriorToCurrentPoint;

for (int i = 0; i < container->nb_fmu; i += 1) {
const fmu_t* fmu = &container->fmu[i];
Expand Down Expand Up @@ -991,30 +994,34 @@ fmi2Status fmi2DoStep(fmi2Component c,
fmi2Real communicationStepSize,
fmi2Boolean noSetFMUStatePriorToCurrentPoint) {
container_t *container = (container_t*)c;
const fmi2Real end_time = currentCommunicationPoint + communicationStepSize + container->tolerance;
const fmi2Real end_time = currentCommunicationPoint + communicationStepSize;
fmi2Real current_time;
fmi2Status status = fmi2OK;


const int nb_step = (int)((end_time - container->time + container->tolerance) / container->time_step);
/*
* Early return if requested end_time is lower than next container time step.
*/
if (end_time < container->time + container->time_step) {
if (nb_step == 0)
return fmi2OK;
}

for(current_time = container->time;
current_time + container->time_step < end_time;
current_time += container->time_step) {
fmi2Real start_time = container->time;
for(int i = 0; i < nb_step; i += 1) {
if (container->mt)
status = do_internal_step_parallel_mt(container, current_time, container->time_step, noSetFMUStatePriorToCurrentPoint);
status = do_internal_step_parallel_mt(container, container->time, container->time_step, noSetFMUStatePriorToCurrentPoint);
else
status = do_internal_step_parallel(container, current_time, container->time_step, noSetFMUStatePriorToCurrentPoint);
status = do_internal_step_parallel(container, container->time, container->time_step, noSetFMUStatePriorToCurrentPoint);
if (status != fmi2OK) {
logger(fmi2Error, "Container cannot do_internal_step. Status=%d", status);
break;
}
container->time = start_time + (i+1) * container->time_step;
}
container->time = current_time;

if (fabs(currentCommunicationPoint + communicationStepSize - current_time) > container->tolerance) {
logger(fmi2Warning, "CommunicationStepSize should be divisible by %e", container->time_step);
double remaining_time = currentCommunicationPoint + communicationStepSize - container->time;
if (fabs(remaining_time) > container->tolerance) {
logger(fmi2Warning, "Container CommunicationStepSize should be divisible by %e. (remaining=%e, nb_step=%d)",
container->time_step, remaining_time, nb_step);
return fmi2Warning;
}

Expand Down
6 changes: 3 additions & 3 deletions container/container.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ typedef struct container_s {

fmu_t *fmu;

fmi2Real currentCommunicationPoint;
fmi2Real step_size;
fmi2Boolean noSetFMUStatePriorToCurrentPoint;
fmi2Real fmi_currentCommunicationPoint;
fmi2Real fmi_step_size;
fmi2Boolean fmi_noSetFMUStatePriorToCurrentPoint;

} container_t;

Expand Down
36 changes: 27 additions & 9 deletions container/fmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ static int fmu_do_step_thread(fmu_t* fmu) {
}

fmu->status = fmuDoStep(fmu,
container->currentCommunicationPoint,
container->step_size,
container->noSetFMUStatePriorToCurrentPoint);
container->fmi_currentCommunicationPoint,
container->fmi_step_size,
container->fmi_noSetFMUStatePriorToCurrentPoint);

thread_mutex_unlock(&fmu->mutex_fmu);
}
Expand Down Expand Up @@ -211,32 +211,50 @@ void fmu_unload(fmu_t *fmu) {


fmi2Status fmuGetReal(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, fmi2Real value[]) {
return fmu->fmi_functions.fmi2GetReal(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2GetReal(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuGetReal status=%d", fmu->identifier, status);
return status;
}


fmi2Status fmuGetInteger(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, fmi2Integer value[]) {
return fmu->fmi_functions.fmi2GetInteger(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2GetInteger(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuGetInteger status=%d", fmu->identifier, status);
return status;
}


fmi2Status fmuGetBoolean(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, fmi2Boolean value[]) {
return fmu->fmi_functions.fmi2GetBoolean(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2GetBoolean(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuGetBoolean status=%d", fmu->identifier, status);
return status;
}


fmi2Status fmuSetReal(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, const fmi2Real value[]) {
return fmu->fmi_functions.fmi2SetReal(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2SetReal(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuSetReal status=%d", fmu->identifier, status);
return status;
}


fmi2Status fmuSetInteger(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, const fmi2Integer value[]) {
return fmu->fmi_functions.fmi2SetInteger(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2SetInteger(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuSetInteger status=%d", fmu->identifier, status);
return status;
}


fmi2Status fmuSetBoolean(const fmu_t *fmu, const fmi2ValueReference vr[], size_t nvr, const fmi2Boolean value[]) {
return fmu->fmi_functions.fmi2SetBoolean(fmu->component, vr, nvr, value);
fmi2Status status = fmu->fmi_functions.fmi2SetBoolean(fmu->component, vr, nvr, value);
if (status != fmi2OK)
logger(status, "%s: fmuSetBoolean status=%d", fmu->identifier, status);
return status;
}


Expand Down
32 changes: 22 additions & 10 deletions doc/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ A FMU Container is classical FMU which embeds other's FMU's.
FMU Manipulation Toolbox ships `fmucontainer` command which makes easy to embed FMU's into FMU.


# FMU Container Description
# How to create an FMU Container ?

A FMU Container is described by multiple parameters:
The `fmucontainer` command creates container from a description file. This file contains multiple parameters:

- time step: a FMU Container acts as a fixed step time "solver".
- optional feature
- time step: a FMU Container acts as a fixed step time "solver" for its embedded FMU's.
- optional features
- multi-threading:
- profiling
- routing table
Expand All @@ -25,19 +25,31 @@ A FMU Container is described by multiple parameters:

Some of these parameters can be defined by Command Line Interface or by input files.

Several formats are supported for input files:
- a `CSV` format: define only the routing table. Other options are defined witch CLI.
- a `JSON` file: all parameters can be defined in the file. CLI can defined default values if option is not present in
`JSON` file.
- a `SSP` file. See [ssp-standard.org](https://ssp-standard.org). define only the routing table. Other options are
defined witch CLI.
Several formats are supported as description files:
- a `CSV` format: define only the routing table. Other options are defined as command line options.
- a `JSON` file: all parameters can be defined in the file. Command line can define default values if the parameter
is not present in `JSON` file.
- a `SSP` file. See [ssp-standard.org](https://ssp-standard.org). define only the routing table. Other options are defined with command line.

![Routing](../doc/routing.png "Routing table")

## CSV Input file

Example :
```csv
rule;from_fmu;from_port;to_fmu;to_port
FMU;bb_position.fmu;;;
FMU;bb_velocity.fmu;;;
OUTPUT;bb_position.fmu;position1;;position
LINK;bb_position.fmu;is_ground;bb_velocity.fmu;reset
LINK;bb_velocity.fmu;velocity;bb_position.fmu;velocity
OUTPUT;bb_velocity.fmu;velocity;;
```

## Json Input file



## SSP Input file


Expand Down
10 changes: 6 additions & 4 deletions fmu_manipulation_toolbox/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(self, name: Optional[str], step_size: float = None, mt=False, profi

self.parent: Optional[AssemblyNode] = None
self.children: Dict[str, AssemblyNode] = {} # sub-containers
self.fmu_names_list: Set[str] = set() # FMUs contained at this level
self.fmu_names_list: List[str] = [] # FMUs contained at this level (ordered list)
self.input_ports: Dict[Port, str] = {} # value is input port name, key is the source
self.output_ports: Dict[Port, str] = {} # value is output port name, key is the origin
self.start_values: Dict[Port, str] = {}
Expand All @@ -75,11 +75,13 @@ def add_sub_node(self, sub_node):
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")

sub_node.parent = self
self.fmu_names_list.add(sub_node.name)
if sub_node.name not in self.fmu_names_list:
self.fmu_names_list.append(sub_node.name)
self.children[sub_node.name] = sub_node

def add_fmu(self, fmu_name: str):
self.fmu_names_list.add(fmu_name)
if fmu_name not in self.fmu_names_list:
self.fmu_names_list.append(fmu_name)

def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
Expand All @@ -104,7 +106,7 @@ def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
identifier = str(Path(self.name).stem)
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)

for fmu_name in sorted(self.fmu_names_list):
for fmu_name in self.fmu_names_list:
container.get_fmu(fmu_name)

for port, source in self.input_ports.items():
Expand Down
24 changes: 19 additions & 5 deletions fmu_manipulation_toolbox/fmu_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ def experiment_attrs(self, attrs: Dict[str, str]):

def scalar_type(self, type_name, attrs):
if self.current_port:
if type_name == "Enumeration":
type_name = "Integer"
self.current_port.set_port_type(type_name, attrs)
self.current_port = None

Expand Down Expand Up @@ -424,11 +426,15 @@ def make_fmu_xml(self, xml_file, step_size: float, profiling: bool):

if self.start_time is None:
self.start_time = self.execution_order[0].start_time
logger.info(f"start_time={self.start_time} (deduced from the _first_ embedded FMU)")
logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
else:
logger.info(f"start_time={self.start_time}")

if self.stop_time is None:
self.stop_time = self.execution_order[0].stop_time
logger.info(f"stop_time={self.stop_time} (deduced from the _first_ embedded FMU)")
logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
else:
logger.info(f"stop_time={self.stop_time}")

xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
<fmiModelDescription
Expand Down Expand Up @@ -615,6 +621,14 @@ def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool):
for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
print(f"{vr} {cport.port.vr}", file=txt_file)

@staticmethod
def long_path(path: Union[str, Path]) -> str:
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
if os.name == 'nt':
return "\\\\?\\" + os.path.abspath(str(path))
else:
return path

def make_fmu_skeleton(self, base_directory: Path) -> Path:
logger.debug(f"Initialize directory '{base_directory}'")

Expand All @@ -641,16 +655,16 @@ def make_fmu_skeleton(self, base_directory: Path) -> Path:
shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")

for fmu in self.involved_fmu.values():
shutil.copytree(fmu.fmu.tmp_directory, resources_directory / fmu.name, dirs_exist_ok=True)

shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
return resources_directory

def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
logger.debug(f"Zipping directory '{base_directory}' => '{fmu_filename}'")
with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
for root, dirs, files in os.walk(base_directory):
for file in files:
zip_file.write(os.path.join(root, file),
zip_file.write(self.long_path(os.path.join(root, file)),
os.path.relpath(os.path.join(root, file), base_directory))
logger.info(f"'{fmu_filename}' is available.")

Expand Down
5 changes: 2 additions & 3 deletions fmu_manipulation_toolbox/fmu_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ def start_element(self, name, attrs):
self.remove_port(attrs['name'])
else:
self.keep_port(attrs['name'])
else:
else: # Keep ScalarVariable as it is.
self.keep_port(attrs['name'])
self.skip_until = name # do not read inner tags
elif name == 'CoSimulation':
self.operation.cosimulation_attrs(attrs)
elif name == 'DefaultExperiment':
Expand All @@ -100,7 +99,7 @@ def start_element(self, name, attrs):
self.operation.fmi_attrs(attrs)
elif name == 'Unknown':
self.unknown_attrs(attrs)
elif name in ('Real', 'Integer', 'String', 'Boolean'):
elif name in ('Real', 'Integer', 'String', 'Boolean', 'Enumeration'):
self.operation.scalar_type(name, attrs)

except ManipulationSkipTag:
Expand Down
Loading

0 comments on commit 51c1f0e

Please sign in to comment.