diff --git a/.gitignore b/.gitignore index cda7e3a37b6607b3b9360a049121d38774cecebe..d18ea1ce67c8cd151b3cab775b7d515c32ac9fb2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # ignore symbolic links *.egg-info +*.eggs # ignore compiled python files *.pyc diff --git a/Assert_Results.py b/Assert_Results.py new file mode 100644 index 0000000000000000000000000000000000000000..3da257ba3502c70bbcdd85a3120188dcba62e266 --- /dev/null +++ b/Assert_Results.py @@ -0,0 +1,18 @@ +import re +import os +import sys + +from dataprocessing.Validationtools import * +from dataprocessing.readtools import * +print("Test Start") +# We need to extract all the result files from git now +for files in os.listdir( + os.path.abspath("reference-results/Neplan/BasicGrids")): + # Assert the result, model result path read from cmd line + assert_modelia_neplan_results(os.path.splitext(files)[0], + os.path.abspath("reference-results/Modelica/BasicGrids/" + + os.path.splitext(files)[0] + ".mat"), + os.path.abspath("reference-results/Neplan/BasicGrids/" + + os.path.splitext(files)[0] + ".rlf")) + +print("Test End") diff --git a/dataprocessing/readtools.py b/dataprocessing/readtools.py index eeaee0a82cea109115bc14b8a2a350cf899997d1..8504447450ba8b8c6ef14a7c13a2330c903189da 100644 --- a/dataprocessing/readtools.py +++ b/dataprocessing/readtools.py @@ -2,7 +2,7 @@ import numpy as np import pandas as pd from .timeseries import * import re - +import cmath def read_timeseries_Modelica(filename, timeseries_names=None, is_regex=False): from modelicares import SimRes @@ -12,6 +12,7 @@ def read_timeseries_Modelica(filename, timeseries_names=None, is_regex=False): timeseries = [] for name in sim.names(): timeseries.append(TimeSeries(name, sim(name).times(), sim(name).values())) + timeseries_names = sim.names() elif is_regex is True: # Read in variables which match with regex timeseries = [] @@ -29,8 +30,8 @@ def read_timeseries_Modelica(filename, timeseries_names=None, is_regex=False): for name in timeseries_names: timeseries.append(TimeSeries(name, sim(name).times(), sim(name).values())) - print('Modelica results column names: ' + str(timeseries_names)) - print('Modelica results number: ' + str(len(timeseries_list))) + #print('Modelica results column names: ' + str(timeseries_names)) + #print('Modelica results number: ' + str(len(timeseries_names))) return timeseries @@ -178,3 +179,95 @@ def read_timeseries_dpsim_cmpl_separate(filename, timeseries_names=None): for result in timeseries_list: print(result.name) return timeseries_list + + +def read_timeseries_NEPLAN_loadflow(file_name, timeseries_names=None, is_regex=False): + """ + Read in NEPLAN loadflow result from result file, the result is in angle notation, amplitude and angle are stored + separately + To keep consistent with the names of voltage in most cases, the name of voltage variables are changed into '.V*' + instead of '.U*' as in the result file + + :param file_name: name of the mat file for the loadflow result from neplan + :param timeseries_names: column name to be read + :param is_regex: flag for using regular expression + :return: list of Timeseries objects + """ + str_tmp = open(file_name, "r") # Read in files + low = 0 # flag for the start of a new data in str_cmp + high = 0 # flag for the end of this new data in str_cmp + flag = True # To judge if this is the first line of the file, which will be the names for the data type + + # Read in data from result file of neplan + seq = [] # list for data type names + value = [] # list for data + + + namelist = ['U', 'ANGLEU', 'P', 'Q','I', 'ANGLEI'] + timeseries = [] + line_del = [] # a list for the value to be deleted + isfloat = re.compile(r'^[-+]?[0-9]+\.[0-9]+$') # regular expression to find float values + + # the ',' in the floats in result file to '.' + for line in str_tmp.readlines(): + line = line.replace(",", ".") + high -= high + low -= low + del value[:] + # read in different data and start processing + for letter in line: + if letter == " " or letter == "\n": # different data(separated by ' ') or end(/n) + if low is not high: # if low is equal to high, no data read in + if flag: # first line of the file, list for data-type name + seq.append(line[low:high]) + else: # not first line of the file,list for data + if isfloat.match(line[low:high]): + value.append(float(line[low:high])) + else: + value.append(line[low:high]) + else: # no data for this datatype + value.append(r'#') # No value, set as # + low = high + 1 # refresh low flag + high += 1 + + """ + A typical line current in neplan has two parts from both end, but we doesn't have to calculate them + with the assumption that the topology of the gird should be correct with which we can validate the + current by comparing the voltage of the nodes connected to the ends of the line + """ + if flag is not True: + if value[3] is not '#': + for m in range(6): + timeseries.append(TimeSeries(value[3] + '.' + namelist[m], + np.array([0., 1.]), np.array([value[m + 6], value[m + 6]]))) + else: + for m in range(2): + timeseries.append(TimeSeries(value[1] + '.' + namelist[m], + np.array([0., 1.]), np.array([value[m + 6], value[m + 6]]))) + flag = False + str_tmp.close() + + # Read in variables which match with regex + if is_regex is True: + p = re.compile(timeseries_names) + length = len(timeseries) + for rule_check in range(length): + if p.search(timeseries[rule_check].name): + pass + else: + line_del.append(rule_check) + + # Read in specified time series + elif timeseries_names is not None: + length = len(timeseries) + for rule_check in range(length): + if timeseries_names == timeseries[rule_check].name: + pass + else: + line_del.append(rule_check) + # delete those values that are not needed. + line_del = set(line_del) + line_del = sorted(line_del) + for num_to_del in range(len(line_del)): + del timeseries[line_del[len(line_del) - num_to_del - 1]] + return timeseries diff --git a/dataprocessing/validationtools.py b/dataprocessing/validationtools.py new file mode 100644 index 0000000000000000000000000000000000000000..b1cdc7f311454cb9ee889b83232491a3aa507180 --- /dev/null +++ b/dataprocessing/validationtools.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import os +from dataprocessing.readtools import * + +def convert_neplan_to_modelica_timeseries(neplan_timeseries): + """ + Mapping the variable names between modelica and neplan + - Voltage: change *.U and *.ANGLEU to *.V and *.Vangle + - Current: remove unnecessary current variables + :param neplan_timeseries: result of neplan in timeseries + :return: a mapped neplan_timeseries + """ + line_del = [] + # remove all the line current + + # Find current of the same component, which means the current needn't to be validated + for check in range(len(neplan_timeseries)): + if neplan_timeseries[check].values[0] == '#': + line_del.append(check) + if '.P' in neplan_timeseries[check].name: + line_del.append(check) + if '.Q' in neplan_timeseries[check].name: + line_del.append(check) + for i in range(check + 1, len(neplan_timeseries)): + if neplan_timeseries[check].name == neplan_timeseries[i].name: + line_del.append(check) # delete list of the unnecessary data + line_del.append(i) + line_del = sorted(set(line_del)) + for num_to_del in range(len(line_del)): + del neplan_timeseries[line_del[len(line_del) - num_to_del - 1]] + + # Change the unit of variables to keep consistent with those in modelica + for i in range(len(neplan_timeseries)): + if 'ANGLE' in neplan_timeseries[i].name: + neplan_timeseries[i].values = neplan_timeseries[i].values / 180 * cmath.pi # unification of the unit + elif '.U' in neplan_timeseries[i].name or '.I' in neplan_timeseries[i].name: + neplan_timeseries[i].values = neplan_timeseries[i].values * 1000 + + # Change the name of variables to keep consistent with those in modelica + for i in range(len(neplan_timeseries)): + neplan_timeseries[i].name = neplan_timeseries[i].name.replace(' ', '') + neplan_timeseries[i].name = neplan_timeseries[i].name.replace('.ANGLEU', '.Vangle') + neplan_timeseries[i].name = neplan_timeseries[i].name.replace('.U', '.Vpp') + neplan_timeseries[i].name = neplan_timeseries[i].name.replace('.ANGLEI', '.Iangle') + + return neplan_timeseries + + +def compare_modelica_neplan(modelica_res, neplan_res): # compare the result file from NEPLAN and Modelica + """ + Compare Results from modelic and neplan, the name of the components should be kept consistent. + :param modelica_res: the path of the modelica result file, whose suffix should be .mat + :param neplan_res: the path of the neplan result file, whose suffix should be .rlf + :return: + """ + # Read in original neplan result file + file_Neplan = os.path.abspath(neplan_res) + # Read in original Modelica result file + file_Modelica = os.path.abspath(modelica_res) + result_neplan = convert_neplan_to_modelica_timeseries(read_timeseries_NEPLAN_loadflow(file_Neplan)) + result_modelica = read_timeseries_Modelica(file_Modelica) + + # Transfer the angle unit to degree + for i in range(len(result_neplan)): + result_neplan[i].name = result_neplan[i].name.upper() + if 'ANGLE' in result_neplan[i].name: + result_neplan[i].values = result_neplan[i].values / cmath.pi * 180 + for i in range(len(result_modelica)): + result_modelica[i].name = result_modelica[i].name.upper() + if 'ANGLE' in result_modelica[i].name: + result_modelica[i].values = result_modelica[i].values / cmath.pi * 180 + + timeseries_names = [] # list for names of components + timeseries_error = [] # list for error + len_limit = len(result_modelica) + + # Match the components in result files, and compare them + for i in range(len(result_neplan)): + flag_not_found = False + for j in range(len_limit): + if result_neplan[i].name == result_modelica[j].name: # Find the same variable + timeseries_names.append(result_neplan[i].name) + timeseries_error.append(TimeSeries.rmse(result_modelica[j], result_neplan[i])) + flag_not_found = True + if flag_not_found is False: + # No such variable in Modelica model, set the error to -1 + timeseries_error.append(-1) + return dict(zip(timeseries_names, timeseries_error)) + +def assert_modelia_neplan_results(net_name, modelica_res, neplan_res): # Assert the model using the function above + """ + Assert the result in Modelica according to the results from neplan + :param net_name: The name of the net should be clarified manually + :param modelica_res: the path of the modelica result file, whose suffix should be .mat + :param neplan_res: the path of the neplan result file, whose suffix should be .rlf + :return: + """ + fail_list = [] # List for all the failed test + error = compare_modelica_neplan(modelica_res, neplan_res) + # the limitations are set to 0.5 + for name in error.keys(): + if abs(error[name]) > 0.5: + fail_list.append(name) + else: + print("Test on %s Passed" % name) + + # fail_list is 0, which means all the tests are passed + if len(fail_list) is 0: + print("\033[1;36;40mModel %s Passed\033[0m" % net_name) + else: + for name in fail_list: + print("\033[1;31;40mTest on %s of %s Failed\033[0m" % (name, net_name)) + raise ValueError('Test on %s is not passed!' % net_name) + + + + + + diff --git a/examples/NEPLAN/read_NEPLAN_example.py b/examples/NEPLAN/read_NEPLAN_example.py new file mode 100644 index 0000000000000000000000000000000000000000..dd265a0856bf57e3f68a54ee39688cb3ff092db6 --- /dev/null +++ b/examples/NEPLAN/read_NEPLAN_example.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import re +from dataprocessing.readtools import * + + +file = r"C:\Users\admin\Desktop\Load_read\Load_flow_WCSS.rlf" + + +# Example 1: Read in all variable +print('************************ Test for read in all variable start ****************') +result = read_timeseries_NEPLAN_loadflow(file) +print(result[24].values) +for i in range(len(result)): + print('%s is %s' % (result[i].name, result[i].values)) # result as list of TimeSeries +print('************************ Test for read in all variable end ****************') + +print('\n') + + + +# Example 2: Read in specific variable +print('************************ Test for read in specific variable start ****************') +print('************************ Read in specific Voltage ****************') +result2 = read_timeseries_NEPLAN_loadflow(file, 'FOUR.U') +for i in range(len(result2)): + print('%s is %s' % (result2[i].name, result2[i].values)) + +print('************************ Read in specific Current ****************') +result3 = read_timeseries_NEPLAN_loadflow(file, 'LINE89.I') +for i in range(len(result3)): + print('%s is %s' % (result3[i].name, result3[i].values)) +print('************************ Test for read in specific variable end ****************') +print('\n') + + + +# Example 3: Read in using regular expression +print('************************ Test for read in using Regular Expression start ****************') +result4 = read_timeseries_NEPLAN_loadflow(file, '^.*\.I$', True) +for i in range(len(result4)): + print('%s is %s' % (result4[i].name, result4[i].values)) +print('************************ Test for read in using Regular Expression end ****************') +print('\n') \ No newline at end of file