diff --git a/docs/generate-spreadsheet.md b/docs/generate-spreadsheet.md index 51ca9d44c37ca7a5f2d50e14fc001d811603eef3..ff87c08675324d45a2836853a08e5b1746dd0fa3 100644 --- a/docs/generate-spreadsheet.md +++ b/docs/generate-spreadsheet.md @@ -43,5 +43,5 @@ To generate the files defining the different DLC the following lines need to be python /home/MET/repositories/toolbox/WindEnergyToolbox/wetb/prepost/GenerateDLCs.py --folder=DLCs the first two lines activate the virtual environment. The third calls the routine *GenerateDLCs.py * that generates the files. -The routine should be called from the folder *htc* where also the master preadsheet *DLCs.xlsx* need to be located. +The routine should be called from the folder *htc* where also the master spreadsheet *DLCs.xlsx* need to be located. The generated files are placed in the folder *DLCs*. diff --git a/docs/install-anaconda.md b/docs/install-anaconda.md index db7b2bd7d12655f48e5eb4ccb9f1fe2b5371e4b5..4ba6915c47ce027619eb075d9cf8f10f9ac7bc8f 100644 --- a/docs/install-anaconda.md +++ b/docs/install-anaconda.md @@ -9,7 +9,7 @@ conda update --all conda create -n wetb_py3 python=3.5 source activate wetb_py3 conda install setuptools_scm future h5py pytables pytest nose sphinx -conda install scipy pandas matplotlib cython xlrd coverage xlwt +conda install scipy pandas matplotlib cython xlrd coverage xlwt openpyxl pip install pyscaffold pytest-cov ``` diff --git a/docs/install-manual-detailed.md b/docs/install-manual-detailed.md index aa2216a782555a026b521dc1e46eb6f0a60f6d8b..81acbbbf1f66f1df6eca725f17ec283dc09f15e6 100644 --- a/docs/install-manual-detailed.md +++ b/docs/install-manual-detailed.md @@ -165,15 +165,15 @@ activate py27 * Install the necessary Python dependencies using the conda package manager: ``` -conda install setuptools_scm future h5py pytables pytest pytest-cov nose sphinx -conda install scipy pandas matplotlib cython xlrd sphinx +conda install setuptools_scm future h5py pytables pytest nose sphinx +conda install scipy pandas matplotlib cython xlrd coverage xlwt openpyxl ``` * Not all packages are available in the conda repositories, but they can be easily installed with pip: ``` -pip install pyscaffold pytest +pip install pyscaffold pytest-cov ``` diff --git a/wetb/prepost/Simulations.py b/wetb/prepost/Simulations.py index fa55cdccdbcab0915d37d27ba0fb7f414d63e4f2..4c5d7bd2c6da30fd153caa1714763a2603d3b88a 100755 --- a/wetb/prepost/Simulations.py +++ b/wetb/prepost/Simulations.py @@ -1043,10 +1043,11 @@ def launch(cases, runmethod='local', verbose=False, copyback_turb=True, elif runmethod == 'none': pass else: - msg = 'unsupported runmethod, valid options: local, thyra, gorm or opt' + msg = 'unsupported runmethod, valid options: local, local-script, ' \ + 'linux-script, windows-script, local-ram, none' raise ValueError(msg) -def post_launch(cases, save_iter=False): +def post_launch(cases, save_iter=False, silent=False): """ Do some basics checks: do all launched cases have a result and LOG file and are there any errors in the LOG files? @@ -1101,8 +1102,9 @@ def post_launch(cases, save_iter=False): nr_tot = len(cases) tmp = list(cases.keys())[0] - print('checking logs, path (from a random item in cases):') - print(os.path.join(run_dir, log_dir)) + if not silent: + print('checking logs, path (from a random item in cases):') + print(os.path.join(run_dir, log_dir)) for k in sorted(cases.keys()): # a case could not have a result, but a log file might still exist @@ -1117,12 +1119,15 @@ def post_launch(cases, save_iter=False): errorlogs.PathToLogs = os.path.join(run_dir, log_dir, kk) try: errorlogs.check(save_iter=save_iter) - print('checking logfile progress: ' + str(nr) + '/' + str(nr_tot)) + if not silent: + print('checking logfile progress: % 6i/% 6i' % (nr, nr_tot)) except IOError: - print(' no logfile for: %s' % (errorlogs.PathToLogs)) + if not silent: + print(' no logfile for: %s' % (errorlogs.PathToLogs)) except Exception as e: - print(' log analysis failed for: %s' % kk) - print(e) + if not silent: + print(' log analysis failed for: %s' % kk) + print(e) nr += 1 # if simulation did not ended correctly, put it on the fail list @@ -1161,18 +1166,27 @@ def post_launch(cases, save_iter=False): return cases_fail -def copy_pbs_in_failedcases(cases_fail, pbs_fail='pbs_in_fail'): + +def copy_pbs_in_failedcases(cases_fail, pbs_fail='pbs_in_fail', silent=True): """ Copy all the pbs_in files from failed cases to a new directory so it is easy to re-launch them """ - + if not silent: + print('Following failed cases pbs_in files are copied:') for cname in cases_fail.keys(): case = cases_fail[cname] pbs_in_fname = '%s.p' % (case['[case_id]']) - pbs_in_dir = case['[pbs_in_dir]'].replace('pbs_in', pbs_fail) run_dir = case['[run_dir]'] - fname = os.path.join(run_dir, pbs_in_dir, pbs_in_fname) + + src = os.path.join(run_dir, case['[pbs_in_dir]'], pbs_in_fname) + + pbs_in_dir_fail = case['[pbs_in_dir]'].replace('pbs_in', pbs_fail) + dst = os.path.join(run_dir, pbs_in_dir_fail, pbs_in_fname) + + if not silent: + print(dst) + shutil.copy2(src, dst) def logcheck_case(errorlogs, cases, case, silent=False): @@ -3302,6 +3316,182 @@ class WeibullParameters(object): self.Vstep = 2. self.shape_k = 2. +def compute_env_of_env(envelope, dlc_list, Nx=300, Nsectors=12, Ntheta=181): + """ + The function computes load envelopes for given channels and a groups of + load cases starting from the envelopes computed for single simulations. + The output is the envelope of the envelopes of the single simulations. + This total envelope is projected on defined polar directions. + + Parameters + ---------- + + envelope : dict, dictionaries of interpolated envelopes of a given + channel (it's important that each entry of the dictonary + contains a matrix of the same dimensions). The dictonary + is organized by load case + + dlc_list : list, list of load cases + + Nx : int, default=300 + Number of points for the envelope interpolation + + Nsectors: int, default=12 + Number of sectors in which the total envelope will be divided. The + default is every 30deg + + Ntheta; int, default=181 + Number of angles in which the envelope is interpolated in polar + coordinates. + + Returns + ------- + + envelope : array (Nsectors x 6), + Total envelope projected on the number of angles defined in Nsectors. + The envelope is projected in Mx and My and the other cross-sectional + moments and forces are fetched accordingly (at the same time step where + the corresponding Mx and My are occuring) + + """ + + # Group all the single DLCs + cloud = np.zeros(((Nx+1)*len(envelope),6)) + for i in range(len(envelope)): + cloud[(Nx+1)*i:(Nx+1)*(i+1),:] = envelope[dlc_list[i]] + # Compute total Hull of all the envelopes + hull = scipy.spatial.ConvexHull(cloud[:,:2]) + cc = np.append(cloud[hull.vertices,:2], + cloud[hull.vertices[0],:2].reshape(1,2),axis=0) + # Interpolate full envelope + cc_x,cc_up,cc_low,cc_int= int_envelope(cc[:,0], cc[:,1], Nx=Nx) + # Project full envelope on given direction + cc_proj = proj_envelope(cc_x, cc_up, cc_low, cc_int, Nx, Nsectors, Ntheta) + + env_proj = np.zeros([len(cc_proj),6]) + env_proj[:,:2] = cc_proj + + # Based on Mx and My, gather the remaining cross-sectional forces and + # moments + for ich in range(2, 6): + s0 = np.array(cloud[hull.vertices, ich]).reshape(-1, 1) + s1 = np.array(cloud[hull.vertices[0], ich]).reshape(-1, 1) + s0 = np.append(s0, s1, axis=0) + cc = np.append(cc, s0, axis=1) + + _,_,_,extra_sensor = int_envelope(cc[:,0],cc[:,ich],Nx) + es = np.atleast_2d(np.array(extra_sensor[:,1])).T + cc_int = np.append(cc_int,es,axis=1) + + for isec in range(Nsectors): + ids = (np.abs(cc_int[:,0]-cc_proj[isec,0])).argmin() + env_proj[isec,ich] = (cc_int[ids-1,ich]+cc_int[ids,ich]+\ + cc_int[ids+1,ich])/3 + + return env_proj + +def int_envelope(ch1,ch2,Nx): + # Function to interpolate envelopes and output arrays of same length + + # Number of points is defined by Nx + 1, where the + 1 is needed to + # close the curve + + upper = [] + lower = [] + + indmax = np.argmax(ch1) + indmin = np.argmin(ch1) + if indmax > indmin: + lower = np.array([ch1[indmin:indmax+1],ch2[indmin:indmax+1]]).T + upper = np.concatenate((np.array([ch1[indmax:],ch2[indmax:]]).T, + np.array([ch1[:indmin+1],ch2[:indmin+1]]).T), + axis=0) + else: + upper = np.array([ch1[indmax:indmin+1],ch2[indmax:indmin+1]]).T + lower = np.concatenate((np.array([ch1[indmin:],ch2[indmin:]]).T, + np.array([ch1[:indmax+1],ch2[:indmax+1]]).T), + axis=0) + + + int_1 = np.linspace(min(upper[:,0].min(),lower[:,0].min()), + max(upper[:,0].max(),lower[:,0].max()),Nx/2+1) + upper = np.flipud(upper) + int_2_up = np.interp(int_1,np.array(upper[:,0]),np.array(upper[:,1])) + int_2_low = np.interp(int_1,np.array(lower[:,0]),np.array(lower[:,1])) + + int_env = np.concatenate((np.array([int_1[:-1],int_2_up[:-1]]).T, + np.array([int_1[::-1],int_2_low[::-1]]).T), + axis=0) + + return int_1, int_2_up, int_2_low, int_env + +def proj_envelope(env_x, env_up, env_low, env, Nx, Nsectors, Ntheta): + # Function to project envelope on given angles + + # Angles of projection is defined by Nsectors + # Projections are obtained in polar coordinates and outputted in + # cartesian + + theta_int = np.linspace(-np.pi,np.pi,Ntheta) + sectors = np.linspace(-np.pi,np.pi,Nsectors+1) + proj = np.zeros([Nsectors,2]) + + R_up = np.sqrt(env_x**2+env_up**2) + theta_up = np.arctan2(env_up,env_x) + + R_low = np.sqrt(env_x**2+env_low**2) + theta_low = np.arctan2(env_low,env_x) + + R = np.concatenate((R_up,R_low)) + theta = np.concatenate((theta_up,theta_low)) + R = R[np.argsort(theta)] + theta = np.sort(theta) + + R_int = np.interp(theta_int,theta,R,period=2*np.pi) + + for i in range(Nsectors): + if sectors[i]>=-np.pi and sectors[i+1]<-np.pi/2: + indices = np.where(np.logical_and(theta_int >= sectors[i], + theta_int <= sectors[i+1])) + maxR = R_int[indices].max() + proj[i+1,0] = maxR*np.cos(sectors[i+1]) + proj[i+1,1] = maxR*np.sin(sectors[i+1]) + elif sectors[i]==-np.pi/2: + continue + elif sectors[i]>-np.pi/2 and sectors[i+1]<=0: + indices = np.where(np.logical_and(theta_int >= sectors[i], + theta_int <= sectors[i+1])) + maxR = R_int[indices].max() + proj[i,0] = maxR*np.cos(sectors[i]) + proj[i,1] = maxR*np.sin(sectors[i]) + elif sectors[i]>=0 and sectors[i+1]<np.pi/2: + indices = np.where(np.logical_and(theta_int >= sectors[i], + theta_int <= sectors[i+1])) + maxR = R_int[indices].max() + proj[i+1,0] = maxR*np.cos(sectors[i+1]) + proj[i+1,1] = maxR*np.sin(sectors[i+1]) + elif sectors[i]==np.pi/2: + continue + elif sectors[i]>np.pi/2 and sectors[i+1]<=np.pi: + indices = np.where(np.logical_and(theta_int >= sectors[i], + theta_int <= sectors[i+1])) + maxR = R_int[indices].max() + proj[i,0] = maxR*np.cos(sectors[i]) + proj[i,1] = maxR*np.sin(sectors[i]) + + ind = np.where(sectors==0) + proj[ind,0] = env[:,0].max() + + ind = np.where(sectors==np.pi/2) + proj[ind,1] = env[:,1].max() + + ind = np.where(sectors==-np.pi) + proj[ind,0] = env[:,0].min() + + ind = np.where(sectors==-np.pi/2) + proj[ind,1] = env[:,1].min() + + return proj # FIXME: Cases has a memory leek somewhere, this whole thing needs to be # reconsidered and rely on a DataFrame instead of a dict! @@ -3470,7 +3660,7 @@ class Cases(object): launch(self.cases, runmethod=runmethod, verbose=verbose, silent=silent, check_log=check_log, copyback_turb=copyback_turb) - def post_launch(self, save_iter=False): + def post_launch(self, save_iter=False, copy_pbs_failed=True): """ Post Launching Maintenance @@ -3480,6 +3670,10 @@ class Cases(object): # TODO: integrate global post_launch in here self.cases_fail = post_launch(self.cases, save_iter=save_iter) + if copy_pbs_failed: + copy_pbs_in_failedcases(self.cases_fail, pbs_in_fail='pbs_in_fail', + silent=self.silent) + if self.rem_failed: self.remove_failed() @@ -4898,7 +5092,35 @@ class Cases(object): return result - def compute_envelope(self, sig, ch_list): + def compute_envelope(self, sig, ch_list, int_env=False, Nx=300): + """ + The function computes load envelopes for given signals and a single + load case. Starting from Mx and My moments, the other cross-sectional + forces are identified. + + Parameters + ---------- + + sig : list, time-series signal + + ch_list : list, list of channels for enevelope computation + + int_env : boolean, default=False + If the logic parameter is True, the function will interpolate the + envelope on a given number of points + + Nx : int, default=300 + Number of points for the envelope interpolation + + Returns + ------- + + envelope : dictionary, + The dictionary has entries refered to the channels selected. + Inside the dictonary under each entry there is a matrix with 6 + columns, each for the sectional forces and moments + + """ envelope= {} for ch in ch_list: @@ -4906,19 +5128,73 @@ class Cases(object): chi1 = self.res.ch_dict[ch[1]]['chi'] s0 = np.array(sig[:, chi0]).reshape(-1, 1) s1 = np.array(sig[:, chi1]).reshape(-1, 1) + # Compute a Convex Hull, the vertices number varies according to + # the shape of the poligon cloud = np.append(s0, s1, axis=1) hull = scipy.spatial.ConvexHull(cloud) closed_contour = np.append(cloud[hull.vertices,:], cloud[hull.vertices[0],:].reshape(1,2), axis=0) + + # Interpolate envelope for a given number of points + if int_env: + _,_,_,closed_contour_int = int_envelope(closed_contour[:,0], + closed_contour[:,1],Nx) + + + # Based on Mx and My envelope, the other cross-sectional moments + # and forces components are identified and appended to the initial + # envelope for ich in range(2, len(ch)): chix = self.res.ch_dict[ch[ich]]['chi'] s0 = np.array(sig[hull.vertices, chix]).reshape(-1, 1) s1 = np.array(sig[hull.vertices[0], chix]).reshape(-1, 1) s0 = np.append(s0, s1, axis=0) closed_contour = np.append(closed_contour, s0, axis=1) - envelope[ch[0]] = closed_contour + if int_env: + _,_,_,extra_sensor = int_envelope(closed_contour[:,0], + closed_contour[:,ich],Nx) + es = np.atleast_2d(np.array(extra_sensor[:,1])).T + closed_contour_int = np.append(closed_contour_int,es,axis=1) + + if int_env: + envelope[ch[0]] = closed_contour_int + else: + envelope[ch[0]] = closed_contour + return envelope + + def int_envelope(ch1,ch2,Nx): + # Function to interpolate envelopes and output arrays of same length + + # Number of points is defined by Nx + 1, where the + 1 is needed to + # close the curve + + upper = [] + lower = [] + + indmax = np.argmax(ch1) + indmin = np.argmin(ch1) + if indmax > indmin: + lower = np.array([ch1[indmin:indmax+1],ch2[indmin:indmax+1]]).T + upper = np.concatenate((np.array([ch1[indmax:],ch2[indmax:]]).T,\ + np.array([ch1[:indmin+1],ch2[:indmin+1]]).T),axis=0) + else: + upper = np.array([ch1[indmax:indmin+1,:],ch2[indmax:indmin+1,:]]).T + lower = np.concatenate((np.array([ch1[indmin:],ch2[indmin:]]).T,\ + np.array([ch1[:indmax+1],ch2[:indmax+1]]).T),axis=0) + + + int_1 = np.linspace(min(min(upper[:,0]),min(lower[:,0])),\ + max(max(upper[:,0]),max(upper[:,0])),Nx/2+1) + upper = np.flipud(upper) + int_2_up = np.interp(int_1,np.array(upper[:,0]),np.array(upper[:,1])) + int_2_low = np.interp(int_1,np.array(lower[:,0]),np.array(lower[:,1])) + + int_env = np.concatenate((np.array([int_1[:-1],int_2_up[:-1]]).T,\ + np.array([int_1[::-1],int_2_low[::-1]]).T),axis=0) + + return int_env def envelope(self, silent=False, ch_list=[], append=''): """ @@ -5089,37 +5365,68 @@ class Results(object): return M_x_equiv -class ManTurb64(object): +class MannTurb64(prepost.PBSScript): """ alfaeps, L, gamma, seed, nr_u, nr_v, nr_w, du, dv, dw high_freq_comp mann_turb_x64.exe fname 1.0 29.4 3.0 1209 256 32 32 2.0 5 5 true """ - def __init__(self): - self.man64_exe = 'mann_turb_x64.exe' - self.wine = 'WINEARCH=win64 WINEPREFIX=~/.wine64 wine' - - def run(): - pass + def __init__(self, silent=False): + super(MannTurb64, self).__init__() + self.exe = 'time wine mann_turb_x64.exe' + # PBS configuration + self.umask = '003' + self.walltime = '00:59:59' + self.queue = 'workq' + self.lnodes = '1' + self.ppn = '1' + self.silent = silent + self.pbs_in_dir = 'pbs_in_turb/' - def gen_pbs(cases): + def gen_pbs(self, cases): case0 = cases[list(cases.keys())[0]] - pbs = prepost.PBSScript() - # make sure the path's end with a trailing separator - pbs.pbsworkdir = os.path.join(case0['[run_dir]'], '') - pbs.path_pbs_e = os.path.join(case0['[pbs_out_dir]'], '') - pbs.path_pbs_o = os.path.join(case0['[pbs_out_dir]'], '') - pbs.path_pbs_i = os.path.join(case0['[pbs_in_dir]'], '') - pbs.check_dirs() + # make sure the path's end with a trailing separator, why?? + self.pbsworkdir = os.path.join(case0['[run_dir]'], '') + if not self.silent: + print('\nStart creating PBS files for turbulence with Mann64...') for cname, case in cases.items(): - base = case['[case_id]'] - pbs.path_pbs_e = os.path.join(case['[pbs_out_dir]'], base + '.err') - pbs.path_pbs_o = os.path.join(case['[pbs_out_dir]'], base + '.out') - pbs.path_pbs_i = os.path.join(case['[pbs_in_dir]'], base + '.pbs') - pbs.execute() - pbs.create() + # only relevant for cases with turbulence + if '[tu_model]' in case and int(case['[tu_model]']) == 0: + continue + if '[Turb base name]' not in case: + continue + + base_name = case['[Turb base name]'] + # pbs_in/out dir can contain subdirs, only take the inner directory + out_base = misc.path_split_dirs(case['[pbs_out_dir]'])[0] + turb = case['[turb_dir]'] + + self.path_pbs_e = os.path.join(out_base, turb, base_name + '.err') + self.path_pbs_o = os.path.join(out_base, turb, base_name + '.out') + self.path_pbs_i = os.path.join(self.pbs_in_dir, base_name + '.p') + + if case['[turb_db_dir]'] is not None: + self.prelude = 'cd %s' % case['[turb_db_dir]'] + else: + self.prelude = 'cd %s' % case['[turb_dir]'] + + # alfaeps, L, gamma, seed, nr_u, nr_v, nr_w, du, dv, dw high_freq_comp + rpl = (float(case['[AlfaEpsilon]']), + float(case['[L_mann]']), + float(case['[Gamma]']), + int(case['[tu_seed]']), + int(case['[turb_nr_u]']), + int(case['[turb_nr_v]']), + int(case['[turb_nr_w]']), + float(case['[turb_dx]']), + float(case['[turb_dy]']), + float(case['[turb_dz]']), + int(case['[high_freq_comp]'])) + params = '%1.6f %1.6f %1.6f %i %i %i %i %1.4f %1.4f %1.4f %i' % rpl + self.execution = '%s %s %s' % (self.exe, base_name, params) + self.create(check_dirs=True) def eigenbody(cases, debug=False): diff --git a/wetb/prepost/dlcdefs.py b/wetb/prepost/dlcdefs.py index 78a968547c910f712d245ef27a1bfc30bc4211ec..e7ef0a6a43b75347d75473d136007016c6f3032c 100644 --- a/wetb/prepost/dlcdefs.py +++ b/wetb/prepost/dlcdefs.py @@ -208,6 +208,7 @@ def tags_defaults(master): master.tags['[log_dir]'] = 'logfiles/' master.tags['[meander_dir]'] = False master.tags['[opt_dir]'] = False + master.tags['[pbs_in_dir]'] = 'pbs_in/' master.tags['[pbs_out_dir]'] = 'pbs_out/' master.tags['[res_dir]'] = 'res/' master.tags['[iter_dir]'] = 'iter/' diff --git a/wetb/prepost/dlcplots.py b/wetb/prepost/dlcplots.py index 1d5b3e12fd6703793139c665d6b62281b9cb694e..8594b90e0ab00a14f5d7ffe280178f0c00e4970c 100644 --- a/wetb/prepost/dlcplots.py +++ b/wetb/prepost/dlcplots.py @@ -12,8 +12,6 @@ from builtins import str from future import standard_library standard_library.install_aliases() - - #print(*objects, sep=' ', end='\n', file=sys.stdout) import os @@ -47,7 +45,7 @@ plt.rc('xtick', labelsize=10) plt.rc('ytick', labelsize=10) plt.rc('axes', labelsize=12) # do not use tex on Gorm -if not socket.gethostname()[:2] == 'g-': +if not socket.gethostname()[:2] in ['g-', 'je']: plt.rc('text', usetex=True) plt.rc('legend', fontsize=11) plt.rc('legend', numpoints=1) diff --git a/wetb/prepost/dlctemplate.py b/wetb/prepost/dlctemplate.py index bf42c8a2e9d0d8e95b650b584d649dbbc2157c3c..ed8f40472c932572ba0837ac9ff7e4b5d6ff2db4 100755 --- a/wetb/prepost/dlctemplate.py +++ b/wetb/prepost/dlctemplate.py @@ -32,7 +32,11 @@ plt.rc('xtick', labelsize=10) plt.rc('ytick', labelsize=10) plt.rc('axes', labelsize=12) # on Gorm tex printing doesn't work -if not socket.gethostname()[:2] == 'g-': +if socket.gethostname()[:2] == 'g-': + RUNMETHOD = 'gorm' +elif socket.gethostname()[:4] == 'jess': + RUNMETHOD = 'jess' +else: plt.rc('text', usetex=True) plt.rc('legend', fontsize=11) plt.rc('legend', numpoints=1) @@ -109,6 +113,22 @@ def master_tags(sim_id, runmethod='local', silent=False, verbose=False): master.tags['[model_zip]'] = PROJECT master.tags['[model_zip]'] += '_' + master.tags['[sim_id]'] + '.zip' # ------------------------------------------------------------------------- + # FIXME: this is very ugly. We should read default values set in the htc + # master file with the HAWC2Wrapper !! + # default tags turbulence generator (required for 64-bit Mann generator) + # alfaeps, L, gamma, seed, nr_u, nr_v, nr_w, du, dv, dw high_freq_comp + master.tags['[AlfaEpsilon]'] = 1.0 + master.tags['[L_mann]'] = 29.4 + master.tags['[Gamma]'] = 3.0 + master.tags['[tu_seed]'] = 0 + master.tags['[turb_nr_u]'] = 8192 + master.tags['[turb_nr_v]'] = 32 + master.tags['[turb_nr_w]'] = 32 + master.tags['[turb_dx]'] = 1 + master.tags['[turb_dy]'] = 6.5 + master.tags['[turb_dz]'] = 6.5 + master.tags['[high_freq_comp]'] = 1 + # ------------------------------------------------------------------------- return master @@ -162,7 +182,8 @@ def variable_tag_func(master, case_id_short=False): ### PRE- POST # ============================================================================= -def launch_dlcs_excel(sim_id, silent=False): +def launch_dlcs_excel(sim_id, silent=False, verbose=False, pbs_turb=True, + runmethod=None, write_htc=True): """ Launch load cases defined in Excel files """ @@ -195,12 +216,11 @@ def launch_dlcs_excel(sim_id, silent=False): for opt in opt_tags: opt['[zip_root_files]'] = f_ziproot - runmethod = 'gorm' -# runmethod = 'local-script' -# runmethod = 'windows-script' -# runmethod = 'jess' + if runmethod == None: + runmethod = RUNMETHOD + master = master_tags(sim_id, runmethod=runmethod, silent=silent, - verbose=False) + verbose=verbose) master.tags['[sim_id]'] = sim_id master.output_dirs.append('[Case folder]') master.output_dirs.append('[Case id.]') @@ -215,12 +235,18 @@ def launch_dlcs_excel(sim_id, silent=False): # variable_tag func is not required because everything is already done # in dlcdefs.excel_stabcon no_variable_tag_func = None - sim.prepare_launch(iter_dict, opt_tags, master, no_variable_tag_func, - write_htc=True, runmethod=runmethod, verbose=False, - copyback_turb=True, msg='', update_cases=False, - ignore_non_unique=False, run_only_new=False, - pbs_fname_appendix=False, short_job_names=False, - silent=silent) + cases = sim.prepare_launch(iter_dict, opt_tags, master, no_variable_tag_func, + write_htc=write_htc, runmethod=runmethod, + copyback_turb=True, update_cases=False, msg='', + ignore_non_unique=False, run_only_new=False, + pbs_fname_appendix=False, short_job_names=False, + silent=silent, verbose=verbose) + + if pbs_turb: + # to avoid confusing HAWC2 simulations and Mann64 generator PBS files, + # MannTurb64 places PBS launch scripts in a "pbs_in_turb" folder + mann64 = sim.MannTurb64(silent=silent) + mann64.gen_pbs(cases) def launch_param(sim_id): @@ -464,7 +490,7 @@ if __name__ == '__main__': # create HTC files and PBS launch scripts (*.p) if opt.prep: print('Start creating all the htc files and pbs_in files...') - launch_dlcs_excel(sim_id) + launch_dlcs_excel(sim_id, silent=False) # post processing: check log files, calculate statistics if opt.check_logs or opt.stats or opt.fatigue or opt.envelopeblade or opt.envelopeturbine: post_launch(sim_id, check_logs=opt.check_logs, update=False, diff --git a/wetb/prepost/misc.py b/wetb/prepost/misc.py index 0c1ab9b0d9c3b95738847d0c5d7127027d453d43..73aca12551262ff28b8124ab3f3d6deb5f8a3032 100644 --- a/wetb/prepost/misc.py +++ b/wetb/prepost/misc.py @@ -68,6 +68,16 @@ class Logger(object): self.logFile.flush() +def path_split_dirs(path): + """ + Return a list with dirnames. Ignore any leading "./" + """ + dirs = path.split(os.path.sep) + if dirs[0] == '.': + dirs.pop(0) + return dirs + + def print_both(f, text, end='\n'): """ Print both to a file and the console diff --git a/wetb/prepost/prepost.py b/wetb/prepost/prepost.py index bb6a864633a2f837c53622ea8b5793f2a0395367..c706aa79cb69e6f97edcc2a9e50cc8763c5a3a8b 100644 --- a/wetb/prepost/prepost.py +++ b/wetb/prepost/prepost.py @@ -12,9 +12,6 @@ from io import open from future import standard_library standard_library.install_aliases() - - - import os import copy @@ -37,6 +34,9 @@ class PBSScript(object): ### Queue name #PBS -q [queue] +### #PBS -a [start_time] +### #PBS -W depend=afterany:[job_id] + ### Browse to current working dir echo "" cd $PBS_O_WORKDIR @@ -75,13 +75,13 @@ exit def __init__(self): # PBS configuration - self.jobname = None + self.jobname = 'no_name_job' # relative paths with respect to PBS working directory self.path_pbs_o = 'pbs_out/dummy.out' self.path_pbs_e = 'pbs_out/dummy.err' self.path_pbs_i = 'pbs_in/dummy.pbs' # absolute path of the PBS working directory - self.pbsworkdir = None + self.pbsworkdir = './' self.umask = '003' self.walltime = '00:59:59' self.queue = 'workq' diff --git a/wetb/prepost/tests/data/demo_dlc/ref/demo_dlc_remote.zip b/wetb/prepost/tests/data/demo_dlc/ref/demo_dlc_remote.zip index 60fe434ad3dc15cf5e1dd1977c630107c611ac55..3c02a1d7a11acaeff10fdae72416c4856fcbc4bc 100644 Binary files a/wetb/prepost/tests/data/demo_dlc/ref/demo_dlc_remote.zip and b/wetb/prepost/tests/data/demo_dlc/ref/demo_dlc_remote.zip differ diff --git a/wetb/prepost/tests/data/demo_dlc/ref/htc/DLCs/dlc01_demos.xlsx b/wetb/prepost/tests/data/demo_dlc/ref/htc/DLCs/dlc01_demos.xlsx index 1f4c2311367ccea9d06f57db32793e8948f0e959..246cbf5e6e5c94b732349be0a6a09336fb1f715f 100755 Binary files a/wetb/prepost/tests/data/demo_dlc/ref/htc/DLCs/dlc01_demos.xlsx and b/wetb/prepost/tests/data/demo_dlc/ref/htc/DLCs/dlc01_demos.xlsx differ diff --git a/wetb/prepost/tests/data/demo_dlc/ref/htc/_master/demo_dlc_master_A0001.htc b/wetb/prepost/tests/data/demo_dlc/ref/htc/_master/demo_dlc_master_A0001.htc index 6e956f80d62257709ddd129408b8d77305da4339..bc61a68f9b4130bdf42526dec5101aeacbfeadcd 100755 --- a/wetb/prepost/tests/data/demo_dlc/ref/htc/_master/demo_dlc_master_A0001.htc +++ b/wetb/prepost/tests/data/demo_dlc/ref/htc/_master/demo_dlc_master_A0001.htc @@ -382,7 +382,7 @@ begin wind ; filename_u ./[turb_dir][Turb base name]u.bin ; filename_v ./[turb_dir][Turb base name]v.bin ; filename_w ./[turb_dir][Turb base name]w.bin ; - box_dim_u 8192 [turb_dx] ; + box_dim_u 512 [turb_dx] ; box_dim_v 32 7.5; box_dim_w 32 7.5; std_scaling 1.0 0.7 0.5 ; diff --git a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_noturb.htc b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_s100.htc similarity index 98% rename from wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_noturb.htc rename to wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_s100.htc index c54c20d0df50daf94a80962feaefcd924123559f..83b325ad95ebc9df30b2f10f26d1365d6aa05b65 100644 --- a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_noturb.htc +++ b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp10_s100.htc @@ -4,7 +4,7 @@ begin simulation; solvertype 1 ; (newmark) on_no_convergence continue ; ; convergence_limits 1E3 1.0 1E-7 ; - logfile ./logfiles/dlc01_demos/dlc01_steady_wsp10_noturb.log ; + logfile ./logfiles/dlc01_demos/dlc01_steady_wsp10_s100.log ; begin newmark; deltat 0.02; end newmark; @@ -13,11 +13,11 @@ end simulation; ;---------------------------------------------------------------------------------------------------------------------------------------------------------------- begin new_htc_structure; ;-------------------------------------------------------------------------------------------------- -; beam_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_noturb/dlc01_steady_wsp10_noturb_beam.dat; -; body_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_noturb/dlc01_steady_wsp10_noturb_body.dat; -; struct_inertia_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_noturb/dlc01_steady_wsp10_noturb_struct.dat; -; body_eigenanalysis_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_noturb/dlc01_steady_wsp10_noturb_body_eigen.dat; -; structure_eigenanalysis_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_noturb/dlc01_steady_wsp10_noturb_strc_eigen.dat; +; beam_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_s100/dlc01_steady_wsp10_s100_beam.dat; +; body_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_s100/dlc01_steady_wsp10_s100_body.dat; +; struct_inertia_output_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_s100/dlc01_steady_wsp10_s100_struct.dat; +; body_eigenanalysis_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_s100/dlc01_steady_wsp10_s100_body_eigen.dat; +; structure_eigenanalysis_file_name ./res_eigen/dlc01_demos/dlc01_steady_wsp10_s100/dlc01_steady_wsp10_s100_strc_eigen.dat; ;--------------------------------------------------------------------------------------------------- begin main_body; tower 123.6m name tower ; @@ -346,7 +346,7 @@ begin wind ; windfield_rotations 0 8.0 0.0 ; yaw, tilt (positive=upflow=wind coming from below), rotation center_pos0 0.0 0.0 -127 ; hub heigth shear_format 3 0 ; - turb_format 0 ; 0=none, 1=mann,2=flex + turb_format 1 ; 0=none, 1=mann,2=flex tower_shadow_method 3 ; 0=none, 1=potential flow, 2=jet scale_time_start 20 ; wind_ramp_factor 0.0 20 0.8 1.0 ; @@ -378,11 +378,11 @@ begin wind ; ; wind_ramp_abs 2400.0 4200.0 0.0 -21.0 ; wsp. after the step: 25.0 ; begin mann ; - create_turb_parameters 29.4 1.0 3.9 0 1.0 ; L, alfaeps, gamma, seed, highfrq compensation - filename_u ./turb/noneu.bin ; - filename_v ./turb/nonev.bin ; - filename_w ./turb/nonew.bin ; - box_dim_u 8192 0.048828125 ; + create_turb_parameters 29.4 1.0 3.9 100 1.0 ; L, alfaeps, gamma, seed, highfrq compensation + filename_u ./turb/turb_s100_10msu.bin ; + filename_v ./turb/turb_s100_10msv.bin ; + filename_w ./turb/turb_s100_10msw.bin ; + box_dim_u 512 0.78125 ; box_dim_v 32 7.5; box_dim_w 32 7.5; std_scaling 1.0 0.7 0.5 ; @@ -645,7 +645,7 @@ end dll; ;---------------------------------------------------------------------------------------------------------------------------------------------------------------- ; begin output; - filename ./res/dlc01_demos/dlc01_steady_wsp10_noturb ; + filename ./res/dlc01_demos/dlc01_steady_wsp10_s100 ; time 20 40 ; data_format hawc_binary; buffer 1 ; diff --git a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp8_noturb.htc b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp8_noturb.htc index 94abe5e13faf4dbd2ffcf86f0bb95df884a573a8..670f943f7bf345a12acbf4095f98e2d51b41144a 100644 --- a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp8_noturb.htc +++ b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp8_noturb.htc @@ -382,7 +382,7 @@ begin wind ; filename_u ./turb/noneu.bin ; filename_v ./turb/nonev.bin ; filename_w ./turb/nonew.bin ; - box_dim_u 8192 0.0390625 ; + box_dim_u 512 0.0390625 ; box_dim_v 32 7.5; box_dim_w 32 7.5; std_scaling 1.0 0.7 0.5 ; diff --git a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp9_noturb.htc b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp9_noturb.htc index 537e8ef73085b26598c46f2986c11fc8ad738464..11e58632838311500ae0549609c245cf0620ead0 100644 --- a/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp9_noturb.htc +++ b/wetb/prepost/tests/data/demo_dlc/ref/htc/dlc01_demos/dlc01_steady_wsp9_noturb.htc @@ -382,7 +382,7 @@ begin wind ; filename_u ./turb/noneu.bin ; filename_v ./turb/nonev.bin ; filename_w ./turb/nonew.bin ; - box_dim_u 8192 0.0439453125 ; + box_dim_u 512 0.0439453125 ; box_dim_v 32 7.5; box_dim_w 32 7.5; std_scaling 1.0 0.7 0.5 ; diff --git a/wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_noturb.p b/wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_s100.p similarity index 80% rename from wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_noturb.p rename to wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_s100.p index 1bc115f781b7821f729e97bddcf49683bb623c20..6a62ecceb1be436ba34a622fdb95db9f66890924 100644 --- a/wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_noturb.p +++ b/wetb/prepost/tests/data/demo_dlc/ref/pbs_in/dlc01_demos/dlc01_steady_wsp10_s100.p @@ -1,8 +1,8 @@ ### Standard Output -#PBS -N dlc01_steady_wsp10_noturb -#PBS -o ./pbs_out/dlc01_demos/dlc01_steady_wsp10_noturb.out +#PBS -N dlc01_steady_wsp10_s100 +#PBS -o ./pbs_out/dlc01_demos/dlc01_steady_wsp10_s100.out ### Standard Error -#PBS -e ./pbs_out/dlc01_demos/dlc01_steady_wsp10_noturb.err +#PBS -e ./pbs_out/dlc01_demos/dlc01_steady_wsp10_s100.err #PBS -W umask=003 ### Maximum wallclock time format HOURS:MINUTES:SECONDS #PBS -l walltime=04:00:00 @@ -25,9 +25,9 @@ mkdir -p htc/dlc01_demos/ mkdir -p res/dlc01_demos/ mkdir -p logfiles/dlc01_demos/ mkdir -p turb/ -cp -R $PBS_O_WORKDIR/htc/dlc01_demos/dlc01_steady_wsp10_noturb.htc ./htc/dlc01_demos/ -cp -R $PBS_O_WORKDIR/../turb/none*.bin turb/ -time WINEARCH=win32 WINEPREFIX=~/.wine32 wine hawc2-latest ./htc/dlc01_demos/dlc01_steady_wsp10_noturb.htc & +cp -R $PBS_O_WORKDIR/htc/dlc01_demos/dlc01_steady_wsp10_s100.htc ./htc/dlc01_demos/ +cp -R $PBS_O_WORKDIR/../turb/turb_s100_10ms*.bin turb/ +time WINEARCH=win32 WINEPREFIX=~/.wine32 wine hawc2-latest ./htc/dlc01_demos/dlc01_steady_wsp10_s100.htc & ### wait for jobs to finish wait echo "" diff --git a/wetb/prepost/tests/data/demo_dlc/ref/pbs_in_turb/turb_s100_10ms.p b/wetb/prepost/tests/data/demo_dlc/ref/pbs_in_turb/turb_s100_10ms.p new file mode 100644 index 0000000000000000000000000000000000000000..df16aba35748c37417076913bbd9b514fce34301 --- /dev/null +++ b/wetb/prepost/tests/data/demo_dlc/ref/pbs_in_turb/turb_s100_10ms.p @@ -0,0 +1,49 @@ + +### Standard Output +#PBS -N no_name_job +#PBS -o ./pbs_out/turb/turb_s100_10ms.out +### Standard Error +#PBS -e ./pbs_out/turb/turb_s100_10ms.err +#PBS -W umask=003 +### Maximum wallclock time format HOURS:MINUTES:SECONDS +#PBS -l walltime=00:59:59 +#PBS -lnodes=1:ppn=1 +### Queue name +#PBS -q workq + +### #PBS -a [start_time] +### #PBS -W depend=afterany:[job_id] + +### Browse to current working dir +echo "" +cd $PBS_O_WORKDIR +echo "current working dir:" +pwd +echo "" + +### =========================================================================== +echo "------------------------------------------------------------------------" +echo "PRELUDE" +echo "------------------------------------------------------------------------" + +cd ../turb/ + +echo "" +echo "------------------------------------------------------------------------" +echo "EXECUTION" +echo "------------------------------------------------------------------------" + +time wine mann_turb_x64.exe turb_s100_10ms 1.000000 29.400000 3.000000 100 8192 32 32 0.7812 6.5000 6.5000 1 +### wait for jobs to finish +wait + +echo "" +echo "------------------------------------------------------------------------" +echo "CODA" +echo "------------------------------------------------------------------------" + + + +echo "" +### =========================================================================== +exit diff --git a/wetb/prepost/tests/data/demo_dlc/ref/prepost/remote_tags.txt b/wetb/prepost/tests/data/demo_dlc/ref/prepost/remote_tags.txt index 21c9f3dd8e9452763faab41e0e8920e87c22a63d..c191125c1237fc1732a98ab1327ff1ae1734004a 100644 --- a/wetb/prepost/tests/data/demo_dlc/ref/prepost/remote_tags.txt +++ b/wetb/prepost/tests/data/demo_dlc/ref/prepost/remote_tags.txt @@ -125,7 +125,7 @@ opt_tags set ------------------------------------------------------------------------------- [Case folder] : dlc01_demos - [Case id.] : dlc01_steady_wsp10_noturb + [Case id.] : dlc01_steady_wsp10_s100 [Cut-in time] : -1 [Cut-out time] : -1 [DLC] : 01 @@ -146,9 +146,9 @@ [TI] : 0.2096 [Time pitch runaway] : 5000 [Time stuck DLC22b] : -1 - [Turb base name] : none + [Turb base name] : turb_s100_10ms [Windspeed] : 10 - [case_id] : dlc01_steady_wsp10_noturb + [case_id] : dlc01_steady_wsp10_s100 [data_dir] : data/ [dis_setbeta] : True [duration] : 20.0 @@ -168,10 +168,10 @@ [t0] : 20 [time stop] : 40 [time_stop] : 40 - [tu_model] : 0 - [tu_seed] : 0 - [turb_base_name] : none - [turb_dx] : 0.048828125 + [tu_model] : 1 + [tu_seed] : 100 + [turb_base_name] : turb_s100_10ms + [turb_dx] : 0.78125 [wdir] : 0 [windramp] : False [wsp factor] : 0.8 diff --git a/wetb/prepost/tests/data/demo_dlc/source/demo_dlc_remote.zip b/wetb/prepost/tests/data/demo_dlc/source/demo_dlc_remote.zip index 60fe434ad3dc15cf5e1dd1977c630107c611ac55..3c02a1d7a11acaeff10fdae72416c4856fcbc4bc 100644 Binary files a/wetb/prepost/tests/data/demo_dlc/source/demo_dlc_remote.zip and b/wetb/prepost/tests/data/demo_dlc/source/demo_dlc_remote.zip differ diff --git a/wetb/prepost/tests/data/demo_dlc/source/htc/DLCs/dlc01_demos.xlsx b/wetb/prepost/tests/data/demo_dlc/source/htc/DLCs/dlc01_demos.xlsx index 1f4c2311367ccea9d06f57db32793e8948f0e959..246cbf5e6e5c94b732349be0a6a09336fb1f715f 100755 Binary files a/wetb/prepost/tests/data/demo_dlc/source/htc/DLCs/dlc01_demos.xlsx and b/wetb/prepost/tests/data/demo_dlc/source/htc/DLCs/dlc01_demos.xlsx differ diff --git a/wetb/prepost/tests/data/demo_dlc/source/htc/_master/demo_dlc_master_A0001.htc b/wetb/prepost/tests/data/demo_dlc/source/htc/_master/demo_dlc_master_A0001.htc index 6e956f80d62257709ddd129408b8d77305da4339..bc61a68f9b4130bdf42526dec5101aeacbfeadcd 100755 --- a/wetb/prepost/tests/data/demo_dlc/source/htc/_master/demo_dlc_master_A0001.htc +++ b/wetb/prepost/tests/data/demo_dlc/source/htc/_master/demo_dlc_master_A0001.htc @@ -382,7 +382,7 @@ begin wind ; filename_u ./[turb_dir][Turb base name]u.bin ; filename_v ./[turb_dir][Turb base name]v.bin ; filename_w ./[turb_dir][Turb base name]w.bin ; - box_dim_u 8192 [turb_dx] ; + box_dim_u 512 [turb_dx] ; box_dim_v 32 7.5; box_dim_w 32 7.5; std_scaling 1.0 0.7 0.5 ; diff --git a/wetb/prepost/tests/test_Simulations.py b/wetb/prepost/tests/test_Simulations.py index 61628546afc1110717316b1ec79f6269a2926fc5..a439a53e04a92992cab62e9637ce8a11ad5e3e89 100644 --- a/wetb/prepost/tests/test_Simulations.py +++ b/wetb/prepost/tests/test_Simulations.py @@ -13,7 +13,8 @@ standard_library.install_aliases() import unittest import os import filecmp -import pickle +import shutil +#import pickle from wetb.prepost import dlctemplate as tmpl @@ -40,17 +41,25 @@ class TestGenerateInputs(unittest.TestCase): # location of the pre and post processing data tmpl.POST_DIR = os.path.join(p_root, tmpl.PROJECT, 'remote', 'prepost/') + + # make sure the remote dir is empty so a test does not pass on data + # generated during a previous cycle + if os.path.exists(os.path.join(p_root, tmpl.PROJECT, 'remote')): + shutil.rmtree(os.path.join(p_root, tmpl.PROJECT, 'remote')) + tmpl.force_dir = tmpl.P_RUN - tmpl.launch_dlcs_excel('remote', silent=True) + tmpl.launch_dlcs_excel('remote', silent=True, runmethod='gorm') - # we can not check-in empty dirs in git - for subdir in ['control', 'data', 'htc', 'pbs_in']: + # we can not check-in empty dirs so we can not compare the complete + # directory structure withouth manually creating the empty dirs here + for subdir in ['control', 'data', 'htc', 'pbs_in', 'pbs_in_turb', + 'htc/_master', 'htc/dlc01_demos', 'pbs_in/dlc01_demos']: remote = os.path.join(p_root, tmpl.PROJECT, 'remote', subdir) ref = os.path.join(p_root, tmpl.PROJECT, 'ref', subdir) cmp = filecmp.dircmp(remote, ref) - self.assertTrue(len(cmp.diff_files)==0) - self.assertTrue(len(cmp.right_only)==0) - self.assertTrue(len(cmp.left_only)==0) + self.assertEqual(len(cmp.diff_files), 0, cmp.diff_files) + self.assertEqual(len(cmp.right_only), 0, cmp.right_only) + self.assertEqual(len(cmp.left_only), 0, cmp.left_only) # for the pickled file we can just read it remote = os.path.join(p_root, tmpl.PROJECT, 'remote', 'prepost')