import logging
from eptransition.exceptions import UnimplementedMethodException
from eptransition.idf.objects import IDFObject
module_logger = logging.getLogger("eptransition.rules.base_rule")
[docs]class ObjectTypeAndName:
"""
This is a simple class for defining an object type/name combination
:param str object_type: The object type
:param str object_name: The name of the object (usually field[0]
"""
def __init__(self, object_type, object_name):
self.type = object_type
self.name = object_name
[docs]class TransitionReturn:
"""
This is a simple class for capturing the response from a transition call
:param [IDFObject] objects_to_write: The list of IDFObject instances to be written as a result of this transition
:param [ObjectTypeAndName] objects_to_delete: The list of idf object type/name combinations to be deleted as a
result of this transition
"""
def __init__(self, objects_to_write, objects_to_delete=None):
self.to_write = objects_to_write
if not objects_to_delete:
objects_to_delete = []
self.to_delete = objects_to_delete
[docs]class TransitionRule:
"""
This class is a must-override base class for defining transition rules for idf objects
"""
def __init__(self):
pass
[docs] def get_name_of_object_to_transition(self):
"""
This method should be overridden in derived classes and return a single name of an object that this rule handles
the transition for.
:return: A string name of an object to transition
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("TransitionRule", "get_name_of_object_to_transition")
[docs] def get_names_of_dependent_objects(self):
"""
This method should be overridden in derived classes and return a list of object names that the derived
transition implementation is dependent upon.
:return: A list of string object names
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("TransitionRule", "get_names_of_dependent_objects")
[docs] def transition(self, core_object, dependent_objects):
"""
This method is the core transition operation for this object.
:param core_object: The original idf object to be transitioned
:param dependent_objects: A dictionary of {object_name: [idf_object, ...]} containing the idf object data in the
original idf that have object names defined in this derived classes
`get_names_of_dependent_objects` method. Each key in this argument is a string
object name, and each value is a list of all the idf objects in the file of that type.
:return: A list of new IDFObject instances, typically just one though
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("TransitionRule", "transition")
[docs]class OutputVariableTransitionRule:
"""
This class is a must-override base class for defining transition rules for output variable objects
These objects are treated somewhat specially by the tool because a small change can affect so
many objects, and it would be unwise to expect each version to include so much repeated code.
The structure of the output objects here is based on 8.5/8.6. In the future, if the objects didn't change much,
it would make most sense to just keep using this class and making small tweaks as needed. If more major
changes occur, it would be best to create a new base class to move forward.
The fields for each object are described next
- OV: Output:Variable
0. Key Value
1. Variable Name * * * *
2. Reporting Frequency
3. Schedule Name
- OM: Output:Meter, OMM: Output:Meter:MeterFileOnly
0. Name * * * *
1. Reporting Frequency
- OMC: Output:Meter:Cumulative, OMCM: Output:Meter:Cumulative:MeterFileOnly
0. Name * * * *
1. Reporting Frequency
- OTT: Output:Table:TimeBins
0. Key Value
1. Variable Name * * * *
2. Interval Start
3. Interval Size
4. Interval Count
5. Schedule Name
6. Variable Type
- FMUI: ExternalInterface:FunctionalMockupUnitImport:From:Variable
0. EnergyPlus Key Value
1. EnergyPlus Variable Name * * * *
2. FMU File Name
3. FMU Instance Name
4. FMU Variable Name
- FMUE: ExternalInterface:FunctionalMockupUnitExport:From:Variable
0. EnergyPlus Key Value
1. EnergyPlus Variable Name * * * *
2. FMU Variable Name
- EMS: EnergyManagementSystem:Sensor
0. Name
1. Output:Variable or Output:Meter Key Name
2. Output:Variable or Output:Meter Name * * * *
- OTM: Output:Table:Monthly
0. Name
1. Digits after Decimal
2. Variable or Meter X Name * * * *
3. Variable or Meter X Aggregation Type
... repeating with variable names for each 2, 4, 6, 8, ...
- OTA: Output:Table:Annual
0. Name
1. Filter
2. Schedule Name
3. Variable or Meter X Name * * * *
4. Variable or Meter X Aggregation Type
... repeating with variable names for each 3, 5, 7, 9, ...
- MC: Meter:Custom
0. Name
1. Fuel Type
2. Key Name X
3. Output Variable or Meter Name X * * * *
... repeating with variable names for each 3, 5, 7, 9, ...
- MCD: Meter:CustomDecrement
0. Name
1. Fuel Type
2. Source Meter Name ????
3. Key Name X
4. Output Variable or Meter Name X
... repeating with variable names for each 4, 6, 8, 10, ...
"""
# convenience constants
OV = "OUTPUT:VARIABLE"
OM = "OUTPUT:METER"
OMM = "OUTPUT:METER:METERFILEONLY"
OMC = "OUTPUT:METER:CUMULATIVE"
OMCM = "OUTPUT:METER:CUMULATIVE:METERFILEONLY"
OTT = "OUTPUT:TABLE:TIMEBINS"
FMUI = "EXTERNALINTERFACE:FUNCTIONALMOCKUPUNITIMPORT:FROM:VARIABLE"
FMUE = "EXTERNALINTERFACE:FUNCTIONALMOCKUPUNITEXPORT:FROM:VARIABLE"
EMS = "ENERGYMANAGEMENTSYSTEM:SENSOR"
OTM = "OUTPUT:TABLE:MONTHLY"
OTA = "OUTPUT:TABLE:ANNUAL"
MC = "METER:CUSTOM"
MCD = "METER:CUSTOMDECREMENT"
def __init__(self):
pass
[docs] def original_full_variable_type_list(self):
# set strings to be used by derived classes for convenience
return [self.OV, self.OM, self.OMM, self.OMC, self.OMCM, self.OTT,
self.FMUI, self.FMUE, self.EMS, self.OTM, self.MC, self.MCD]
[docs] def original_standard_indexes_from_object(self, object_name):
"""
This method returns the list of indexes where variable names are found. These are zero based indexes. This
method returns a base version that can be used by a derived class directly, modified, or used as a template
for future derived classes.
:param object_name: The upper case name of the object currently being transitioned.
:return: A list of zero-based indexes
"""
if object_name in [self.OM, self.OMM, self.OMC, self.OMCM]:
return [0]
elif object_name in [self.OV, self.OTT, self.FMUE, self.FMUI]:
return [1]
elif object_name in [self.EMS]: # pragma no cover -- will add back in once we test an idf that has EMS
return [2]
elif object_name in [self.OTM]: # pragma no cover -- will add back in once we test an idf that has OTM
return range(2, 100, 2)
elif object_name in [self.OTA, self.MC]: # pragma no cover -- will add back in once we test in idf that has OTA
return range(3, 100, 2)
elif object_name in [self.MCD]: # pragma no cover -- will add back in once we test in idf that has MCD
return range(4, 100, 2)
[docs] def get_dependent_object_names(self):
"""
This method can be overridden in derived classes if any of the output variable name changes depend on other
objects in the idf. Simply return a list of object names
:return: A list of object names that output variable name changes are dependent upon
"""
return []
[docs] def get_output_objects(self):
"""
This method should be overridden in derived classes and return a list of all output-related object types
in this version of EnergyPlus. A base version is available in the base class that can be used as a starter
and if an object name changes, the derived class can change that name as needed in the return array.
:return: A list of strings, each representing an output object type name
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("OutputVariableTransitionRule", "get_output_objects")
[docs] def get_standard_indexes_from_object(self, object_name):
"""
This method should be overridden in derived classes and return a list of the zero-based field indexes that
include a variable name in the given object type. A base version is available in the base class that can be
used as a starter and if the structure of any object types changes, the derived class can change that one as
needed in the return list
:param object_name: The name of the object being inspected
:return: A list of zero-based indexes, each representing a field containing an output variable name
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("OutputVariableTransitionRule", "get_standard_indexes_from_object")
[docs] def get_complex_operation_types(self):
"""
This method should be overridden in the derived classes and return a list of object names that require more
complex transition operations than a simple variable name swap
:return: A list of strings, each representing an object name that requires complex transition operations
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("OutputVariableTransitionRule", "get_complex_operation_types")
[docs] def get_simple_swaps(self):
"""
This method should be overridden in derived classes and return a dictionary where each key is the name of
an output variable, and the value of each key is the new variable name. This map is used when doing the
simple variable name swaps.
:return: A dictionary of <old_variable_name, new_variable_name>
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("OutputVariableTransitionRule", "get_simple_swaps")
[docs] def simple_name_swap(self, variable_name):
"""
This method is a simple method that queries the *must-override* `get_simple_swaps` method in the derived class
and either returns a new variable name to swap in place of the original name, or returns None as a signal that
this original variable name does not need replacement
:param variable_name: The original variable name to potentially be replaced
:return: A new variable name, if a swap is to be performed, or None if not
"""
swaps = self.get_simple_swaps()
if variable_name in swaps: # pragma no cover -- add in once we have a transition rule that does a swap
return swaps[variable_name]
else:
return None
[docs] def complex_output_operation(self, full_object, dependent_objects):
"""
This method should be overridden in derived classes and should perform the complex operations to transition
the argument object passed in. The function should return a list because some complex operations may split the
initial object into multiple objects. The object passed in will have any simple name swaps already performed.
:param full_object: The original object to be replaced.
:param dependent_objects: A dictionary of dependent objects
:return: A list of new IDFObject instances, typically just one though
:raises UnimplementedMethodException: Raised if this method is called on the base class itself
"""
raise UnimplementedMethodException("OutputVariableTransitionRule", "complex_output_operation")
[docs] def transition(self, core_object, dependent_objects):
"""
This method can be implemented by derived classes if necessary, but should capture the entire transition
functionality just using the other required <must-override> methods in this class. This function first scans
all the variable names in the current locations, and renames as needed. Then this function checks if
this object type needs a complex transition, and if so, calls the appropriate derived method. This method then
returns a full IDFObject instance.
:param core_object: The original object to be replaced
:param dependent_objects: A dictionary of dependent objects
:return: A list of new IDFObject instances, typically just one though
"""
original_idf_fields = core_object.fields
obj_name_upper = core_object.object_name.upper()
# first just copy all fields into a new list
new_idf_fields = original_idf_fields
# then go through and do simple renames of the variables in the expected locations
indexes = self.get_standard_indexes_from_object(obj_name_upper)
if indexes is None:
module_logger.warn("no indexes for output object: {}".format(core_object.object_name)) # pragma no cover
else:
for i in indexes:
try:
original_idf_fields[i]
except IndexError: # pragma no cover this could be covered if the idf tests were larger
break
maybe_new_name = self.simple_name_swap(original_idf_fields[i].upper())
if maybe_new_name: # pragma no cover -- add in once we have a transition rule that does a swap
new_idf_fields[i] = maybe_new_name
# and create a temporary object
new_variable_object = IDFObject([core_object.object_name] + new_idf_fields)
# then do complex operations if needed
if obj_name_upper in self.get_complex_operation_types():
new_variable_objects = self.complex_output_operation(new_variable_object, dependent_objects)
else:
new_variable_objects = [new_variable_object]
# and finally return whatever we ended up with
return TransitionReturn(new_variable_objects)