def to_FLDs(*args, **kw): """Load or recieve a template workbook of CrossSections and convert them all to FLD files args: can either be a path string to a target template workbook or an existing SectionBook object kw: path - output destination for FLD files""" if (type(args[0]) == str): #load the template sb = fields_funks.load_template(args[0]) if ('path' not in kw): kw['path'] = os.path.dirname(args[0]) elif (isinstance(args[0], fields_class.SectionBook)): sb = args[0] else: raise (fields_class.EMFError( """Input argument to to_FLDs() must be a filepath or a SectionBook.""" )) #check for duplicate sheets sheets = [] for xs in sb: if (xs.sheet in sheets): raise (fields_class.EMFError( """Can't create FLD files because of duplicate CrossSection names. Name "%s" is used at least twice.""" % xs.sheet)) else: sheets.append(xs.sheet) #generate FLD files for xs in sb: to_FLD(xs, **kw)
def to_FLD(xs, **kw): """Create an FLD input file for FIELDS from a CrossSection object args: xs - CrossSection object kw: path - output file destination""" #check input if (not isinstance(xs, fields_class.CrossSection)): raise (fields_class.EMFError( """Input argument to to_FLD() must be a CrossSection object, not an input of type: %s Use to_FLDs() for a SectionBook object.""" % str(type(xs)))) #get a filename fn = fields_funks._path_manage(xs.sheet, 'FLD', **kw) #write the .FLD file ofile = open(fn, 'w') #miscellaneous stuff first _write_FLD_entries(ofile, xs.sheet, xs.title, 60, xs.soil_resistivity, xs.max_dist, xs.step, xs.sample_height, xs.lROW, xs.rROW) #number of conductors and ground wires Lconds = len(xs.hot) _write_FLD_entries(ofile, Lconds) Lgrounds = len(xs.gnd) _write_FLD_entries(ofile, Lgrounds) #write the hot and gnd conductor data in the same format for c in xs.hot + xs.gnd: _write_FLD_entries(ofile, c.tag, c.x, c.y, c.subconds, c.d_cond, c.d_bund, 'ED!(I)', c.I, c.V, c.phase) #write the ground wire data a second time, in a different format for c in xs.gnd: _write_FLD_entries(ofile, c.tag, c.x, c.y, c.d_cond, 0, 0) #close/save ofile.close() print('FLD file generated: %s' % fn)
def _bisect(xs, conds, x_sample, funk, target, hlow, hhigh, max_iter, rel_err): #get sample x and y arrays with a single element in each x_sample = np.array([x_sample], dtype=float) y_sample = xs.sample_height*np.ones((2,), dtype=float) #evaluate at the bracketing values flow = funk(hlow, target, xs, conds, x_sample, y_sample) fhigh = funk(hhigh, target, xs, conds, x_sample, y_sample) #check that the root is bracketed if(flow*fhigh > 0.): raise(fields_class.EMFError(""" The root is not bracketed with an upper height adjustment limit of %g. Rootfinding with bisection can't be performed. f(h_0 = %g) = %g f(h_1 = %g) = %g""" % (hhigh, hlow, flow, hhigh, fhigh))) #evaluate at a midpoint hmid = (hhigh + hlow)/2.0 fmid = funk(hmid, target, xs, conds, x_sample, y_sample) count = 1 #iterate while((abs(fmid/target) > rel_err) and (count < max_iter)): #test and throw half out if(fmid*flow > 0.): hlow = hmid elif(fmid*fhigh > 0.): hhigh = hmid elif(fmid == 0.): return(hmid) #evaluate at middle hmid = (hhigh + hlow)/2.0 fmid = funk(hmid, target, xs, conds, x_sample, y_sample) #increment count += 1 #check if the iteration limit was hit if(count == max_iter): raise(fields_class.EMFError(""" Failure in _bisection method. The iteration limit of %d was exseeded with a relative error threshold of %g. The final estimate was %g""" % (max_iter, rel_err, fmid))) return(hmid)
def drop_template(*args, **kw): """Copy the emf.fields template in the current directory or a directory specified by an input string args: drop_path - string, path of copied template file""" #check inputs if(len(args) > 1): raise(fields_class.EMFError("""drop_template only accepts zero or one input argument. A string can be passed to specify the directory in which the template file is copied. With no arguments, the template file is copied into the current directory.""")) elif(len(args) == 1): kw = {'path': args[0]} #get template file path template_path = os.path.dirname(os.path.dirname(__file__)) template_path = os.path.join(template_path, 'templates') template_path = os.path.join(template_path, 'fields-template.xlsx') #get drop path drop_path = _path_manage('fields-template', 'xlsx', **kw) #copy and notify shutil.copyfile(template_path, drop_path) print('emf.fields template written to: %s' % drop_path)
def load_template(file_path, **kw): """Import conductor data from an excel template, loading each conductor into a Conductor object, each Conductor into a CrossSection object, and each CrossSection object into a SectionBook object. The SectionBook object is returned. args: template_path - string, path to cross section template excel workbook kw: sheets - list of strings, a list of sheet names to load, default is all sheets""" #import the cross sections as a dictionary of pandas DataFrames, also #getting a list of the ordered sheets file_path = _check_extension(file_path, 'xlsx', """Templates must be excel workbooks. The input target path "%s" is not recognized as an excel file""" % file_path) xl = pd.ExcelFile(file_path) sheets = xl.sheet_names frames = xl.parse(sheetname = None, skiprows = [0,1,2,3], parse_cols = 16, header = None) #remove necessary sheets if the 'sheets' keyword is passed in if('sheets' in kw): include = kw['sheets'] sheets = [sh for sh in sheets if sh in include] #create a SectionBook object to store the CrossSection objects basename = os.path.basename(file_path) if('.' in basename): name = basename[:basename.index('.')] else: name = basename sb = fields_class.SectionBook(name) #convert the dataframes into a list of CrossSection objects titles = [] for k in sheets: #load miscellaneous information applicable to the whole CrossSection df = frames[k] xs = fields_class.CrossSection(k) misc = df[1].values xs.tag = misc[0] xs.title = str(misc[1]) #check for duplicate title inputs if(xs.title in titles): raise(fields_class.EMFError("""Cross-sections should have unique title entries. title: "%s" in sheet: "%s" is used by at least one other sheet.""" % (xs.title, k))) else: titles.append(xs.title) xs.soil_resistivity = misc[3] xs.max_dist = misc[4] xs.step = misc[5] xs.sample_height = misc[6] xs.lROW = misc[7] xs.rROW = misc[8] #load hot conductors tags, x, y = [], [], [] for i in range(df[3].dropna().shape[0]): #initialize a Conductor cond = fields_class.Conductor(df[2].iat[i]) #check for conductors with identical tags (names/labels) if(cond.tag in tags): raise(fields_class.EMFError("""Conductors in a Cross Section must have unique tags. The conductor tag "%s" in sheet: "%s" is used at least twice.""" % (cond.tag, k))) else: tags.append(cond.tag) #cond.freq = misc[2] cond.x = df[3].iat[i] cond.y = df[4].iat[i] #check for conductors with identical x,y coordinates if(cond.x in x): idx = x.index(cond.x) if(cond.y == y[idx]): raise(fields_class.EMFError("""Conductors cannot have identical x,y coordinates. Conductor "%s" is in the exact same place as conductor "%s".""" % (cond.tag, tags[idx]))) else: x.append(cond.x) y.append(cond.y) cond.subconds = df.iat[i,5] cond.d_cond = df.iat[i,6] cond.d_bund = df.iat[i,7] cond.V = df.iat[i,8] cond.I = df.iat[i,9] cond.phase = df.iat[i,10] xs.add_conductor(cond) #load grounded conductors tags, x, y = [], [], [] for i in range(df[12].dropna().shape[0]): #initialize a Conductor cond = fields_class.Conductor(df.iat[i,11]) #check for conductors with identical tags (names/labels) if(cond.tag in tags): raise(fields_class.EMFError("""Conductors in a Cross Section must have unique tags. The conductor tag "%s" in sheet: "%s" is used at least twice.""" % (cond.tag, k))) else: tags.append(cond.tag) #cond.freq = misc[2] cond.x = df.iat[i,12] cond.y = df.iat[i,13] #check for conductors with identical x,y coordinates if(cond.x in x): idx = x.index(cond.x) if(cond.y == y[idx]): raise(fields_class.EMFError("""Conductors cannot have identical x,y coordinates. Conductor "%s" is in the exact same place as conductor "%s".""" % (cond.tag, tags[idx]))) else: x.append(cond.x) y.append(cond.y) cond.subconds = 1. cond.d_cond = df.iat[i,14] cond.d_bund = df.iat[i,14] cond.V = 0. cond.I = df.iat[i,15] cond.phase = df.iat[i,16] xs.add_conductor(cond) #add the CrossSection object to the SectionBook #fields automatically updated upon addition to SectionBook sb.add_section(xs) #return the SectionBook object return(sb)
def optimize_phasing(xs, circuits, **kw): """Permute the phasing of non-grounded conductors and find the arrangement that results in the lowest fields at the left and right edge of the ROW. The number of hot conductors must be a multiple of three. The phases of consecutive groups of three conductors are swapped around, assuming that those groups represent a single three-phase transfer circuit. args: xs - target CrossSection object circuits - list of lists of Conductor tags, or 'all'. If a list of lists, each sublist contains the Conductor tags of the Conductors comprising a single circuit. If 'all', circuits are assumed to be consecutive groups of three conductors. (consecutive according to the order in which hot conductors were added to the CrossSection) kw: save - bool, toggle saving of the results DataFrame to an excel book path - string, location/filename for saved results workbook, forces saving even if no 'save' keyword is used. returns: res - pandas DataFrame listing conductor phasings that optimize electric and magnetic fields at both ROW edges. opt - new SectionBook object containing the permuted phasings that optimize the E and B fields at the left and right ROW edges.""" if(circuits == 'all'): #number of hot wires hot = xs.hot N = len(xs.hot) #check the number of hot lines if(N % 3 != 0): raise(fields_class.EMFError("""The number of hot (not grounded) conductors must be a multiple of three for phase optimization with 'all' circuits. Circuits are assumed to be three-phase and conductors comprising each circuit are assumed to be consecutive groups of three, in the order that they appear in the template. The number of hot conductors is not a multiple of three in the CrossSection named: %s""" % xs.sheet)) #number of circuits, groups of 3 hot conductors G = int(N/3) #circuits, consecutive groups of three conductors circuits = [[] for i in range(G)] for i in range(G): for j in range(3): circuits[i].append(hot[i*3 + j].tag) else: #check that all conductor tags are present and refer to hot conds gnd = xs.gnd for circ in circuits: for tag in circ: if(xs[tag] is None): raise(fields_class.EMFError("""Unrecognized conductor tag: %s All conductor tags must refer to Conductor objects in the target CrossSecton object.""" % repr(tag))) if(xs[tag] in gnd): raise(fields_class.EMFError("""Only phasing of non-grounded Conductors can be permuted. Tag "%s" refers to a grounded Conductor""" % repr(tag))) #convert the conductor tags to integer indices in xs.conds for i in range(len(circuits)): for j in range(len(circuits[i])): circuits[i][j] = xs._tag2idx[circuits[i][j]] #all permutations of the phases of each circuit perm = [] for c in circuits: perm.append(list(itertools.permutations(c))) #all possible arrangements of line phasings, 6 permutations for each circuit #so 6^(N/3) total line arrangements. Leave P as a generator to avoid storing #a huge, factorial sized array of indices P = itertools.product(*perm) #variables to find the minima with respect to each field and ROW edge B_left_min, B_left_arr, B_right_min, B_right_arr = np.inf, [], np.inf, [] E_left_min, E_left_arr, E_right_min, E_right_arr = np.inf, [], np.inf, [] #get coordinates of the ROW edges x_ROW = np.array([xs.lROW, xs.rROW], dtype=float) y_ROW = xs.sample_height*np.ones((2,), dtype=float) #pull conductor data x, y, I, V, phase = xs.x, xs.y, xs.I, xs.V, xs.phase subconds, d_cond, d_bund = xs.subconds, xs.d_cond, xs.d_bund #phasing array to swap the elements around in phase_swap = phase.copy() #store a flattened version of the conductor indices for swapping conds = np.array([i for j in circuits for i in j], dtype=int) #loop through all possible arrangements in P for arr in P: #flatten the new arrangement new_arr = _flatten(arr) #swap phases according to the new phasing arrangement phase_swap[conds] = phase[new_arr] #calculate fields with index swapped phases Ex, Ey = fields_calcs.E_field(x, y, subconds, d_cond, d_bund, V, phase_swap, x_ROW, y_ROW) Ex, Ey, Eprod, Emax = fields_calcs.phasors_to_magnitudes(Ex, Ey) Bx, By = fields_calcs.B_field(x, y, I, phase_swap, x_ROW, y_ROW) Bx, By, Bprod, Bmax = fields_calcs.phasors_to_magnitudes(Bx, By) #test for minima if(Bmax[0] < B_left_min): B_left_min, B_left_arr = Bmax[0], new_arr if(Bmax[1] < B_right_min): B_right_min, B_right_arr = Bmax[1], new_arr if(Emax[0] < E_left_min): E_left_min, E_left_arr = Emax[0], new_arr if(Emax[1] < E_right_min): E_right_min, E_right_arr = Emax[1], new_arr #return results in a DataFrame results = pd.DataFrame(data={ 'Optimal Phasing - Bmax Left ROW Edge': phase[B_left_arr], 'Optimal Phasing - Bmax Right ROW Edge': phase[B_right_arr], 'Optimal Phasing - Emax Left ROW Edge': phase[E_left_arr], 'Optimal Phasing - Emax Right ROW Edge': phase[E_right_arr]}, index=[xs.conds[i].tag for i in conds]) #compile a new sectionbook with the optimal phasings fn = _path_str_condition(xs.sheet).replace(' ', '-') xs = xs.copy() opt = fields_class.SectionBook(xs.sheet + '-optimal_phasing') xs.sheet += ' (original)' xs.tag = 'Phase Optimized' opt.add_section(xs) names = ['Optimized for Bmax left','Optimized for Bmax right', 'Optimized for Emax left','Optimized for Emax right'] for n, ti in zip(names, results.columns): #copy the input xs new_xs = xs.copy() #change the identification fields new_xs.sheet, new_xs.title, new_xs.tag = n, ti, 'Phase Optimized' #swap the conductor phasings for c in new_xs.hot: t = c.tag if(t in results.index): c.phase = results.at[t, ti] #store new_xs in the SectionBook opt.add_section(new_xs) #deal with saving if('path' in kw): kw['save'] = True if('save' in kw): if(kw['save']): fn = _path_manage(fn + '_phase_optimization', 'xlsx', **kw) xl = pd.ExcelWriter(fn, engine='xlsxwriter') results.to_excel(xl, index_label='Conductor Tag', sheet_name='phase_assignments') opt.ROW_edge_export(xl=xl) df, c, h = _xs_sb_diff(xs, opt) df.to_excel(xl, sheet_name='ROW_edge_diff', index=False, columns=c, header=h) for xs in opt: xs.fields.to_excel(xl, sheet_name=xs.sheet) xl.save() print('Phase optimization results written to: %s' % fn) return(results, opt)