Source code for idr_iisim.utils.models_dict
"""Industry"""
import json
import math
from typing import Any, Optional
import yaml
from idr_iisim.models.meta import Meta
from idr_iisim.models.process import Process
from idr_iisim.templates import load_template
[docs]
class Industry:
"""Industry class"""
def __init__(self, meta: Optional[Meta] = None):
# initializes an object of the class with an empty models dictionary.
self.models: dict[str, Process] = {}
self.dependencies: dict[str, set[str]] = {}
self.processed_models: dict[str, bool] = {}
self.meta: Optional[Meta] = meta
[docs]
def add_process(self, key: str, process: Process) -> None:
"""add model to the industry"""
self.models[key] = process
self.processed_models[key] = False
# add dependencies
# model_id -> list[dependent_models_ids]
model_dependencies = list(
filter(lambda x: x.input_from is not None, process.config.inputs)
)
# filter from values
from_list = set(map(lambda x: x.input_from, model_dependencies))
if len(from_list) > 0:
self.dependencies[key] = from_list
[docs]
def check_types(self) -> None:
"""check types among processes
This method checks if the types of the different inputs and demands
are consistent between the different processes.
"""
assert self.meta is not None
inputs_checks = [(self.meta.config.name, self.meta.config.inputs)]
inputs_checks += [
(name, model.config.inputs) for name, model in self.models.items()
]
for name, inputs in inputs_checks:
for input_field in inputs:
# Check if units are the same in both processess
if input_field.input_from is not None:
model_from = self.models[input_field.input_from]
if input_field.name not in model_from.outputs:
raise ValueError(
f"'{input_field.name}' does not exist in "
+ f"'{model_from.config.name}'"
)
units_from = model_from.outputs[input_field.name].units
if units_from != input_field.units:
raise ValueError(
f"Unit for '{input_field.name}' differs in "
+ f"'{name}' ({input_field.units}) and "
+ f"'{model_from.config.name}' ({units_from})"
)
for demand in self.meta.demands.values():
model_from = self.models[demand.used]
if demand.name not in model_from.inputs:
raise ValueError(
f"'{demand.name}' does not exist in "
+ f"'{model_from.config.name}'"
)
units_from = model_from.inputs[demand.name].units
if units_from != demand.units:
raise ValueError(
f"Unit for '{demand.name}' differs in "
+ f"'Meta' ({demand.units}) and "
+ f"'{model_from.config.name}' ({units_from})"
)
[docs]
def generate_execution_queue(self) -> list[str]:
"""Generate the correct execution queue of the processes
This method generate the execution queue in the correct order
taking into account the correct flow to based on the dependencies
between the different processes of the industry.
"""
queue: list[str] = []
# Include processes without dependencies
for process in self.models:
if process not in self.dependencies:
queue.append(process)
# Add the rest of the processes once their dependencies are fullfilled
while len(queue) != len(self.models):
for process, dependencies in self.dependencies.items():
if process not in queue:
should_include_process = True
for dependency in dependencies:
if dependency not in queue:
should_include_process = False
break
if should_include_process:
queue.append(process)
return queue
[docs]
def script_generator(self) -> str:
"""Generator of the script
This method generates the model (the Python class) of the industry
"""
assert self.meta is not None
# Load the template content
template_path = "templates/template_generated_industrial_class.txt"
method_template = load_template(template_path)
args = "self"
constructor = ""
constants = []
process_methods = []
min_units = -math.inf
max_units = math.inf
units = self.meta.get_units()
if self.meta.config.outcome.range:
min_units = self.meta.config.outcome.range[0]
if len(self.meta.config.outcome.range) > 1:
max_units = self.meta.config.outcome.range[1]
constants.append(self.meta.constants_generator())
for model_name in self.generate_execution_queue():
model = self.models[model_name]
process_methods.append(model.process_methods_generator())
constants.append(model.constants_generator())
constructor += self.meta.constructor_pre_generator(model.config.id)
constructor += model.process_call_method_generator()
constructor += "\n "
constructor += self.meta.constructor_post_generator()
outcome_name = self.meta.config.outcome.name
args += ", " + outcome_name
return method_template.substitute(
name=self.meta.config.short_name,
fullname=f'"{self.meta.config.name}"',
description=self.meta.config.description,
outcome_name=outcome_name,
constructor_method=constructor,
constants="\n".join(constants),
args=args,
process_methods="\n".join(process_methods),
get_methods=self.meta.getters_generator(),
units=json.dumps(units, indent=4),
min_units=min_units,
max_units=max_units,
)
[docs]
def load_yaml(path: str) -> dict[str, Any]:
"""load industry's yaml file
:param path: path of the YAML file to be loaded.
"""
try:
with open(path, encoding="utf-8") as file:
data: dict[str, Any] = yaml.safe_load(file)
return data
except Exception as e:
raise e