So finally I managed to migrate the function based views in the Django code to class based views. This has resulted in significant decrease in code repetition and the code is now much more readable and maintainable.
To start, I needed to create a class hierarchy. The base class in SimulationData which extracts the simulation model instance, reads the circuit files and processes them. The next layer is ListControlVariables that inherits SimulationData and extracts control files and their variables.
The class SimulationData is:
The methods of the class perform functions like retrieving the simulation model instance, getting the circuit schematic model instances, checking if there are errors in reading the circuit files, processing the simulation circuit files and updating the database.
A few are obvious like getting the simulation model instance or circuit files model instances. Some not so obvious. When the circuit schematics are processed, they are checked for errors. These errors are both errors like component not found, unmatching jump labels etc. Also, connectivity errors are found - broken branches, jumps next to nodes etc. This checking is done almost every time because the simulator uses the "component_objects" dictionary of Python objects to contain information about every component found in the circuit. This is essential because different types of components have different classes - example resistors have Resistor class while voltage sources have VoltageSource class. Every time a class is found, it is instantiated and added to component_objects. So component_objects is the key to processing circuit components. By having the process_circuit_schematics() method in the SimulationData class, it is available to all other classes that inherit it.
Another non-obvious class method in update_db(). The database contains every circuit component in its records. And the details include the cell position of the circuit component and the polarity if any. These can always change as a user can move the components around in the schematic spreadsheet. The update_db() method updates the information in the schematic with the database.
The SimulationData class is the base class as the above methods are needed for every simulation. The next layer in the class hierarchy is the control class which is called ListControlVariables class. This is because there may be some simulations that don't have control. However, to figure out how a control function works, the circuit files will need to be processed and this means control needs SimulationData. ListControlVariables is as below:
This class provides method to perform repeated tasks with respect to control files. Extracting the control file model instance. Providing a list of all special variables in the control file as context variables.
With these two classes implemented the amount of times the process circuit schematic code was written to get back the dictionary of component_objects or that of components_found has decreased. Moreover, with component_objects and component_founds class attributes rather than regular variables, the function call is much less cumbersome as compared to before.
The next blog post will describe how class based views for listing circuit items will be created.
To start, I needed to create a class hierarchy. The base class in SimulationData which extracts the simulation model instance, reads the circuit files and processes them. The next layer is ListControlVariables that inherits SimulationData and extracts control files and their variables.
The class SimulationData is:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SimulationData: | |
""" | |
This class is a parent class to most other class views. | |
It extracts a simulation model, reads the circuit files, | |
processes circuits and checks for errors. | |
""" | |
def get_sim_model(self): | |
""" | |
Gets the simulation model instance from the | |
GET request parameters. | |
""" | |
if 'id' in self.kwargs: | |
self.sim_id = int(self.kwargs['id']) | |
self.sim_para_model = SimulationCase.objects.get(id=self.sim_id) | |
else: | |
self.sim_para_model = None | |
return | |
def get_circuit_files(self): | |
""" | |
Get all the circuit schematics in a simulation model. | |
""" | |
try: | |
self.sim_para_model | |
except: | |
self.get_sim_model() | |
if self.sim_para_model: | |
self.ckt_file_list = self.sim_para_model.circuitschematics_set.all() | |
else: | |
self.ckt_file_list = None | |
return | |
def get_circuit_read_errors(self): | |
""" | |
Check if any of the circuit schematics cannot be read. | |
""" | |
self.ckt_read_errors = [] | |
self.ckt_errors = -1 | |
if self.ckt_file_list: | |
for ckt_file_item in self.ckt_file_list: | |
ckt_full_path = os.path.join(os.sep, \ | |
self.sim_para_model.sim_working_directory, \ | |
ckt_file_item.ckt_file_name) | |
# Try to read the file. | |
try: | |
check_ckt_file = open(ckt_full_path, "r") | |
# If can't be read, it means file doesn't exist in the working directory. | |
except: | |
self.ckt_read_errors.append('Circuit spreadsheet could not be read. \ | |
Make sure it is in same directory as working directory above') | |
self.ckt_errors = 1 | |
else: | |
self.ckt_read_errors.append('') | |
return | |
def process_circuit_schematics(self): | |
""" | |
This function also reads circuit schematics and | |
generates component objects. It checks for network errors. | |
""" | |
try: | |
self.sim_para_model | |
except: | |
self.get_sim_model() | |
self.get_circuit_files() | |
self.nw_input = [] | |
self.conn_ckt_mat = [] | |
if self.ckt_file_list: | |
for ckt_file_item in self.ckt_file_list: | |
self.nw_input.append(ckt_file_item.ckt_file_name.split(".csv")[0]) | |
full_file_path = os.path.join(os.sep, \ | |
self.sim_para_model.sim_working_directory, \ | |
ckt_file_item.ckt_file_name) | |
ckt_file_object = open(full_file_path, "r") | |
# Read the circuit into conn_ckt_mat | |
# Also performs a scrubbing of circuit spreadsheet | |
self.conn_ckt_mat.append(NwRdr.csv_reader(ckt_file_object)) | |
# Making a list of the type of components in the | |
# circuit. | |
self.components_found, self.component_objects, self.ckt_error_list = \ | |
NwRdr.determine_circuit_components(self.conn_ckt_mat, self.nw_input) | |
if not self.ckt_error_list: | |
# Make lists of nodes and branches in the circuit. | |
self.node_list, self.branch_map, self.node_branch_errors = \ | |
NwRdr.determine_nodes_branches(self.conn_ckt_mat, self.nw_input) | |
if self.node_branch_errors: | |
self.ckt_error_list.extend(self.node_branch_errors) | |
return | |
def update_db_parameters(self): | |
""" | |
Checks if the components in the circuit schematics | |
exist in the database. If they do, their positions | |
are updated. If they are new, database entries are | |
created. | |
""" | |
try: | |
self.sim_para_model | |
except: | |
self.get_sim_model() | |
try: | |
self.components_found | |
except: | |
self.process_circuit_schematics() | |
all_components = self.sim_para_model.circuitcomponents_set.all() | |
for comp_types in self.components_found.keys(): | |
# Take every type of component found | |
# item -> resistor, inductor etc | |
for c1 in range(len(self.components_found[comp_types])): | |
# Each component type will be occurring | |
# multiple times. Iterate through every find. | |
# The list corresponding to each component is | |
# the unique cell position in the spreadsheet | |
check_comp_exists = all_components.filter(comp_type=comp_types).\ | |
filter(comp_tag=self.components_found[comp_types][c1][1]) | |
if check_comp_exists and len(check_comp_exists)==1: | |
old_comp_object = check_comp_exists[0] | |
old_comp_object.comp_number = c1 + 1 | |
old_comp_object.comp_pos_3D = self.components_found[comp_types][c1][0] | |
old_comp_object.comp_pos = NwRdr.csv_element_2D(\ | |
NwRdr.csv_tuple(self.components_found[comp_types][c1][0])[1:]) | |
sheet_number = NwRdr.csv_tuple(self.components_found[comp_types][c1][0])[0] | |
old_comp_object.comp_sheet = sheet_number | |
old_comp_object.sheet_name = self.nw_input[sheet_number] + ".csv" | |
old_comp_object.sim_case = self.sim_para_model | |
old_comp_object.save() | |
self.sim_para_model.save() | |
else: | |
new_comp_object = CircuitComponents() | |
new_comp_object.comp_type = comp_types | |
new_comp_object.comp_number = c1 + 1 | |
new_comp_object.comp_pos_3D = self.components_found[comp_types][c1][0] | |
new_comp_object.comp_pos = NwRdr.csv_element_2D(\ | |
NwRdr.csv_tuple(self.components_found[comp_types][c1][0])[1:]) | |
sheet_number = NwRdr.csv_tuple(self.components_found[comp_types][c1][0])[0] | |
new_comp_object.comp_sheet = sheet_number | |
new_comp_object.sheet_name = self.nw_input[sheet_number] + ".csv" | |
new_comp_object.comp_tag = self.components_found[comp_types][c1][1] | |
new_comp_object.sim_case = self.sim_para_model | |
new_comp_object.save() | |
self.sim_para_model.save() | |
for comp_items in self.component_objects.keys(): | |
self.component_objects[comp_items].create_form_values( | |
self.sim_para_model, | |
self.ckt_file_list, | |
self.branch_map | |
) | |
# Generate a table of meters that can be used when designing | |
# control interfaces. | |
try: | |
self.meter_list = self.sim_para_model.metercomponents_set.all() | |
except: | |
self.meter_list = [] | |
for comp_items in self.component_objects.keys(): | |
if self.component_objects[comp_items].is_meter=="yes": | |
meter_found = False | |
if self.meter_list: | |
check_meter = self.meter_list.\ | |
filter(comp_type=self.component_objects[comp_items].type).\ | |
filter(comp_tag=self.component_objects[comp_items].tag) | |
if check_meter and len(check_meter)==1: | |
old_meter_item = check_meter[0] | |
old_meter_item.ckt_file_name = \ | |
self.component_objects[comp_items].sheet_name | |
old_meter_item.comp_pos_3D = \ | |
self.component_objects[comp_items].pos_3D | |
old_meter_item.save() | |
self.sim_para_model.save() | |
meter_found = True | |
else: | |
meter_found = False | |
if not meter_found: | |
new_meter_item = models.MeterComponents() | |
new_meter_item.comp_type = self.component_objects[comp_items].type | |
new_meter_item.comp_tag = self.component_objects[comp_items].tag | |
new_meter_item.comp_pos_3D = self.component_objects[comp_items].pos_3D | |
new_meter_item.ckt_file_name = self.component_objects[comp_items].sheet_name | |
new_meter_item.comp_name = self.component_objects[comp_items].type + \ | |
"_" + self.component_objects[comp_items].tag | |
new_meter_item.sim_case = self.sim_para_model | |
new_meter_item.save() | |
self.sim_para_model.save() | |
# Remove meters that have been deleted from the circuit | |
for meter_item in self.sim_para_model.metercomponents_set.all(): | |
if meter_item.comp_pos_3D not in self.component_objects.keys(): | |
meter_item.delete() | |
self.sim_para_model.save() | |
# Generate a table of control components from circuit components | |
# that can be used for designing control interfaces. | |
try: | |
self.control_comp_list = self.sim_para_model.controllablecomponents_set.all() | |
except: | |
self.control_comp_list = [] | |
for comp_items in self.component_objects.keys(): | |
if self.component_objects[comp_items].has_control=="yes": | |
controllable_comp_found = False | |
if self.control_comp_list: | |
check_control_comp = self.control_comp_list.\ | |
filter(comp_type=self.component_objects[comp_items].type).\ | |
filter(comp_tag=self.component_objects[comp_items].tag) | |
if check_control_comp and len(check_control_comp)==1: | |
old_control_item = check_control_comp[0] | |
old_control_item.ckt_file_name = \ | |
self.component_objects[comp_items].sheet_name | |
old_control_item.comp_pos_3D = \ | |
self.component_objects[comp_items].pos_3D | |
old_control_item.save() | |
self.sim_para_model.save() | |
controllable_comp_found = True | |
else: | |
controllable_comp_found = False | |
if not controllable_comp_found: | |
new_control_item = models.ControllableComponents() | |
new_control_item.comp_type = self.component_objects[comp_items].type | |
new_control_item.comp_tag = self.component_objects[comp_items].tag | |
new_control_item.comp_pos_3D = self.component_objects[comp_items].pos_3D | |
new_control_item.ckt_file_name = self.component_objects[comp_items].sheet_name | |
new_control_item.comp_name = self.component_objects[comp_items].type + \ | |
"_" + self.component_objects[comp_items].tag | |
new_control_item.control_tag = self.component_objects[comp_items].control_tag | |
new_control_item.sim_case = self.sim_para_model | |
new_control_item.save() | |
self.sim_para_model.save() | |
# Delete any database entries that are no longer in the circuit. | |
for c2 in range(len(all_components)-1, -1, -1): | |
comp_exist = all_components[c2] | |
comp_found = False | |
c1 = 0 | |
if comp_exist.comp_type in self.components_found.keys(): | |
while c1<len(self.components_found[comp_exist.comp_type]) and comp_found==False: | |
if self.components_found[comp_exist.comp_type][c1][1]==comp_exist.comp_tag: | |
comp_found = True | |
c1 += 1 | |
if comp_found==False: | |
comp_exist.delete() | |
self.sim_para_model.save() | |
return | |
def get_plot_data(self): | |
""" | |
Generate the list of plots. | |
""" | |
return [ | |
self.sim_para_model.circuitplot_set.all(), | |
self.sim_para_model.plotlines_set.all() | |
] |
The methods of the class perform functions like retrieving the simulation model instance, getting the circuit schematic model instances, checking if there are errors in reading the circuit files, processing the simulation circuit files and updating the database.
A few are obvious like getting the simulation model instance or circuit files model instances. Some not so obvious. When the circuit schematics are processed, they are checked for errors. These errors are both errors like component not found, unmatching jump labels etc. Also, connectivity errors are found - broken branches, jumps next to nodes etc. This checking is done almost every time because the simulator uses the "component_objects" dictionary of Python objects to contain information about every component found in the circuit. This is essential because different types of components have different classes - example resistors have Resistor class while voltage sources have VoltageSource class. Every time a class is found, it is instantiated and added to component_objects. So component_objects is the key to processing circuit components. By having the process_circuit_schematics() method in the SimulationData class, it is available to all other classes that inherit it.
Another non-obvious class method in update_db(). The database contains every circuit component in its records. And the details include the cell position of the circuit component and the polarity if any. These can always change as a user can move the components around in the schematic spreadsheet. The update_db() method updates the information in the schematic with the database.
The SimulationData class is the base class as the above methods are needed for every simulation. The next layer in the class hierarchy is the control class which is called ListControlVariables class. This is because there may be some simulations that don't have control. However, to figure out how a control function works, the circuit files will need to be processed and this means control needs SimulationData. ListControlVariables is as below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ListControlVariables(SimulationData): | |
""" | |
This super class extracts all the control files and | |
the special variables and their parameters. | |
""" | |
def get_control_file(self): | |
if 'control_id' in self.kwargs: | |
self.control_id = int(self.kwargs['control_id']) | |
self.config_control_file = models.ControlFile.objects.get(\ | |
id=self.control_id) | |
return | |
def get_control_variables(self, *args, **kwargs): | |
self.get_sim_model() | |
self.get_control_file() | |
try: | |
control_input_list = \ | |
self.config_control_file.controlinputs_set.all() | |
except: | |
control_input_list = [] | |
if control_input_list: | |
input_component_list = [input_item for input_item in control_input_list] | |
else: | |
input_component_list = [] | |
try: | |
control_output_list = \ | |
self.config_control_file.controloutputs_set.all() | |
except: | |
control_output_list = [] | |
if control_output_list: | |
output_component_list = [output_item for output_item in control_output_list] | |
else: | |
output_component_list = [] | |
try: | |
control_staticvar_list = \ | |
self.config_control_file.controlstaticvariable_set.all() | |
except: | |
control_staticvar_list = [] | |
if control_staticvar_list: | |
staticvar_component_list = [staticvar_item for staticvar_item in control_staticvar_list] | |
else: | |
staticvar_component_list = [] | |
try: | |
control_timeevent_list = \ | |
self.config_control_file.controltimeevent_set.all() | |
except: | |
control_timeevent_list = [] | |
if control_timeevent_list: | |
timeevent_component_list = [timeevent_item for timeevent_item in control_timeevent_list] | |
else: | |
timeevent_component_list = [] | |
try: | |
control_varstore_list = \ | |
self.sim_para_model.controlvariablestorage_set.all().\ | |
filter(control_file_name=self.config_control_file.control_file_name) | |
except: | |
control_varstore_list = [] | |
if control_varstore_list: | |
varstore_component_list = [varstore_item for varstore_item in control_varstore_list] | |
else: | |
varstore_component_list = [] | |
return [ | |
input_component_list, | |
output_component_list, | |
staticvar_component_list, | |
timeevent_component_list, | |
varstore_component_list, | |
] | |
def get_control_context(self, *args, **kwargs): | |
control_component_list = self.get_control_variables(*args, **kwargs) | |
control_context = {} | |
control_context['sim_id'] = self.sim_id | |
control_context['control_id'] = self.control_id | |
control_context['control_component_list'] = control_component_list | |
return control_context |
This class provides method to perform repeated tasks with respect to control files. Extracting the control file model instance. Providing a list of all special variables in the control file as context variables.
With these two classes implemented the amount of times the process circuit schematic code was written to get back the dictionary of component_objects or that of components_found has decreased. Moreover, with component_objects and component_founds class attributes rather than regular variables, the function call is much less cumbersome as compared to before.
The next blog post will describe how class based views for listing circuit items will be created.