Linearize Example🔗
Below is the code for the Custom Function linearize.py.
The actual file is a part of the Linearize Example introduced in Project Specific Custom Functions and which parts are referenced Creating Custom Functions.
The actual file can also be found in the installation folder: modelon_impact_installation_folder/custom_function_examples.
``` import os import numbers import tempfile
import pandas import numpy as np import scipy
def signature(): """ The signature function specifying how the Custom Function is presented to the user in Modelon Impact. :return: A dictionary on the format: "version" - The version number of the Custom Function "name" - The name of the Custom Function to appear in Modelon Impact "description" - A description of what the Custom Function does. "parameters" - A list of parameters to be set by the user via the Modelon Impact simulation browser (optional). Each parameter is specified by a dictionary on the format (all optional): "name" - the name of the parameter to appear in Modelon Impact "type" - The type of the parameter: String, Number, Boolean or Enumeration "description" - A description of the parameter "defaultValue" - The default value of the parameter """ return { "version": "0.0.1", "name": "Linearize", "description": "Linearize the model and compute its state space representation" "(matrices A, B, C and D).", "parameters": [ { "name": "t_linearize", "type": "Number", "description": "Time (in seconds) at which to perform linearization." "To linearize at initialization," "set t=0.", "defaultValue": 1, }, { "name": "print_to_log", "type": "Boolean", "description": "Linearized model statistics are printed in the log, " "if this option is set to True", "defaultValue": True, }, ], }
def run(get_fmu, environment, parametrization, upload_custom_artifact, t_linearize, print_to_log): """ The run function, defining the operation or computation of the Custom Function. :param get_fmu: A function returning an FMU object for the model the custom function is applied on, with applied non-structural parameters as set in Modelon Impact. :param environment: A dictionary specifying environment variables: "result_folder_path" - The path to the folder where the result is to be saved "result_file_name" - The name of the file where the results are to be saved "workspace_id" - The workspace ID "case_id" - The case ID "experiment_id" - The experiment ID "log_file_name" - The name of the log file :param upload_custom_artifact: Function for uploading a custom artifact to the storage. Takes arguments: "artifact_id": The artifact ID. "local_file_path": Path to the local custom artifact to upload Returns the route for downloading the artifact. :param parametrization: The parametrization of the model as set in Modelon Impact experiment mode. :param t_linearize: Time (in seconds) at which to perform linearization. :param print_to_log: Toggle weather the linearized model statistics should be shown in the simulation log. """
# In this case, the linearization is packaged into a separate function. This
# enables to use it outside of Modelon Impact and thereby also makes
# it convenient to test.
model = get_fmu()
for key, value in parametrization.items():
model.set(key, value)
return linearize(model, environment, upload_custom_artifact, t_linearize, print_to_log)
def linearize(model, environment, upload_custom_artifact, t_linearize, print_to_log): """ Compute the ABCD state space representation for a model and write the result to a .csv-file. Also save the result as a .mat-file and upload it as a custom artifact. The .mat-file can be used to load the result into MATLAB. :param model: An FMU object for the model to linearize. :param environment: A dictionary specifying environment variables: "result_folder_path" - The path to the folder where the result is to be saved "result_file_name" - The name of the file where the results are to be saved "workspace_id" - The workspace ID "case_id" - The case ID "experiment_id" - The experiment ID "log_file_name" - The name of the log file :param upload_custom_artifact: Function for uploading a custom artifact to the storage. Takes arguments: "artifact_id": The artifact ID. "local_file_path": Path to the local custom artifact to upload Returns the route for downloading the artifact. :param t_linearize: The time to simulate the model before linearizing. :param print_to_log: Toggle weather the linearized model statistics should be shown in the simulation log """
# Start by type checking the parameter, in case an invalid entry is given by
# the user
if not isinstance(t_linearize, numbers.Number) or t_linearize < 0:
raise ValueError("The parameter t_linearize needs to be a non-negative number.")
if t_linearize == 0:
model.initialize()
else:
model.simulate(final_time=t_linearize)
# Retrieve the state space representation of the linearized model
result = model.get_state_space_representation(use_structure_info=False)
ss = {matrix_name: result[i] for i, matrix_name in enumerate(["A", "B", "C", "D"])}
# Pretty print the matrices to the simulation log
if print_to_log:
for matrix_name, result in ss.items():
print('\n' + matrix_name + '= [')
matrix_shape = result.shape
if not (matrix_shape[0] == 0 or matrix_shape[1] == 0):
max_len = max(len(str(e)) for row in result for e in row)
for row in result:
print(
"\t".join(
['{:<{max_len}}'.format(e, max_len=max_len) for e in row]
)
)
print(']')
# Scalarize the state space matrices
scalarized_ss = {
"{}[{},{}]".format(matrix_name, index[0], index[1]): [x]
for matrix_name, matrix in ss.items()
for index, x in np.ndenumerate(matrix)
}
# Write the matrices to a csv file in the prescribed path
csv_file_path = os.path.join(
environment["result_folder_path"], environment["result_file_name"]
)
df = pandas.DataFrame(data=scalarized_ss)
df.to_csv(csv_file_path, index=False)
# Add variable names
state_names = list(model.get_states_list().keys())
input_names = list(model.get_input_list().keys())
output_names = list(model.get_output_list().keys())
ss['state_names'] = state_names
ss['input_names'] = input_names
ss['output_names'] = output_names
# Add operating point
operating_point_time = t_linearize
operating_point_states = [x[0] for x in model.get(state_names)]
operating_point_derivatives = list(model.get_derivatives())
operating_point_inputs = [x[0] for x in model.get(input_names)]
operating_point_outputs = [x[0] for x in model.get(output_names)]
ss['operating_point_time'] = operating_point_time
ss['operating_point_states'] = np.array(operating_point_states)
ss['operating_point_derivatives'] = np.array(operating_point_derivatives)
ss['operating_point_inputs'] = np.array(operating_point_inputs)
ss['operating_point_outputs'] = np.array(operating_point_outputs)
# Pretty print the linearization statistics to the simulation log
if print_to_log:
if state_names:
print('\n' + "# At operating point {}s :".format(str(t_linearize)) + '\n')
print(f'state_names = {state_names}\n')
print(f'operating_point_states = {operating_point_states}\n')
print(f'operating_point_derivatives = {operating_point_derivatives}\n')
if input_names:
print(f'input_names = {input_names}\n')
print(f'operating_point_inputs = {operating_point_inputs}\n')
if output_names:
print(f'output_names = {output_names}\n')
print(f'operating_point_outputs = {operating_point_outputs}\n')
# Write result to a .mat file that can be imported in MATLAB
# First write the result to a temporary directory
temp_dir = tempfile.mkdtemp()
temp_mat_file = os.path.join(temp_dir, "result.mat")
scipy.io.savemat(temp_mat_file, ss)
# Now upload the result to the server as a custom artifact
artifact_id = "ABCD"
artifact_route = upload_custom_artifact(artifact_id, temp_mat_file)
# Finally print the route where the artifact can be accessed
print('Stored artifact with ID: {}'.format(artifact_id))
print('')
print('Artifact can be downloaded from @artifact[here]({})'.format(artifact_route))
```