def _set_stresses(cls, df): # == side_() '''Return updated DataFrame with stresses per side_ of neutral axis.''' #print('Assigning stress states to sides for a given stack.') cols = ['layer', 'side', 'matl', 'type', 't(um)'] n_rows = len(df.index) half_the_stack = n_rows // 2 #print(half_the_stack) n_middles = df['type'].str.contains(r'middle').sum() #print(n_middles) # Default df.loc[:, 'side'] = 'None' side_loc = df.columns.get_loc('side') # Middle for Snapshot if n_middles == 1: df.iloc[half_the_stack, side_loc] = 'INDET' # For the neutral axis elif n_rows % 2 != 0 and n_rows != 1: df.iloc[half_the_stack, side_loc] = 'None' # for odd p # Other plies '''Replace with p''' if n_rows > 1: df.iloc[:half_the_stack, side_loc] = 'Tens.' # applies to latest column 'side' df.iloc[-half_the_stack:, side_loc] = 'Comp.' df = ut.set_column_sequence(df, cols) return df
def stack_to_df(cls, stack): '''Return a DataFrame of converted stacks with materials (list of dicts).''' df = pd.DataFrame(stack).T df.reset_index(level=0, inplace=True) # reset index; make new column df.columns = ['layer', 'type', 't(um)', 'matl'] # rename columns recolumned = ['layer', 'matl', 'type', 't(um)'] df = ut.set_column_sequence(df, recolumned) # uses ext. f(x) df[['t(um)']] = df[['t(um)']].astype(float) # reset numeric dtypes return df
def test_set_columns_seq2(): '''Check reorders columns and adds columns not in sequence to the end.''' data = {'apple': 4, 'strawberry': 3, 'orange': 3, 'banana': 2} df = pd.DataFrame(data, index=['amount']) # Excluded names are appended to the end of the DataFrame seq = ['apple', 'strawberry'] # apple strawberry banana orange actual = ut.set_column_sequence(df, seq) expected = pd.DataFrame(data, index=['amount'], columns=['apple', 'strawberry', 'banana', 'orange']) ut.assertFrameEqual(actual, expected)
def test_set_columns_seq1(): '''Check reorders columns to existing sequence.''' # Pandas orders DataFrame columns alphabetically data = {'apple': 4, 'orange': 3, 'banana': 2, 'blueberry': 3} df = pd.DataFrame(data, index=['amount']) # apple banana blueberry orange # We can resequence the columns seq = ['apple', 'orange', 'banana', 'blueberry'] actual = ut.set_column_sequence(df, seq) expected = pd.DataFrame(data, index=['amount'], columns=seq) ut.assertFrameEqual(actual, expected)
def _update_dimensions(LFrame): '''Update Laminate DataFrame with new dimensional columns. This function takes a primitive LFrame (converted Stack) and adds columns: label, h(m), d(m), intf, k, Z(m), z(m), z(m)* A number of pandas-like implementations are performed to achieve this. So the coding has a different approach and feel. Variables ========= LFrame : DataFrame A primitive Laminate DateFrame containing ID columns. ''' # For Implementation nplies = self.nplies p = self.p t_total = self.total #print('nplies: {}, p: {}, t_total (m): {}'.format(nplies, p, t_total)) df = LFrame.copy() # WRANGLINGS -------------------------------------------------------------- # Indexers ---------------------------------------------------------------- # Many dimensional values are determined by index positions. # Revised Indexer df['idx'] = df.index # temp. index column for idxmin & idxmax interface_tens = df[df['side'] == 'Tens.'].groupby('layer')['idx'].idxmin() discontinuity_tens = df[(df['side'] == 'Tens.') & (df['type'] != 'middle')].groupby('layer')['idx'].idxmax() discontinuity_comp = df[(df['side'] == 'Comp.') & (df['type'] != 'middle')].groupby('layer')['idx'].idxmin() interface_comp = df[df['side'] == 'Comp.'].groupby('layer')['idx'].idxmax() interface_idx = pd.concat([interface_tens, interface_comp]) discont_idx = pd.concat([discontinuity_tens, discontinuity_comp]) #print(discontinuity_tens.values) if nplies > 1: pseudomid = [discontinuity_tens.values[-1], discontinuity_comp.values[0]] # get disconts indices near neutral axis; for even plies mid_idx = len(df.index) // 2 #print('middle index: ', mid_idx) # Indexer dict of outside and inside Indices idxs = { 'interfaces': interface_idx.values.tolist(), # for interfaces 'disconts': discont_idx.values.tolist(), # for disconts. 'middle': mid_idx, # for neut. axis 'intfTens': interface_tens.values.tolist(), # for side_ interfaces 'intfComp': interface_comp.values.tolist(), 'unboundIntfT': interface_tens.values.tolist()[1:], 'unboundIntfC': interface_comp.values.tolist()[:-1], 'disTens': discontinuity_tens.values.tolist(), # for disconts 'disComp': discontinuity_comp.values.tolist(), } # Masks ------------------------------------------------------------------- # Interface Mask s = df['idx'].copy() s[:] = False # convert series to bool values s.loc[idxs['interfaces']] = True mask = s # boolean mask for interfaces # COLUMNS ----------------------------------------------------------------- # label_ ------------------------------------------------------------------ # Gives name for point types df['label'] = np.where(mask, 'interface', 'internal') # yes!; applies values if interface, else internal if p != 1: df.loc[idxs['disconts'], 'label'] = 'discont.' # yes!; post-fix for disconts. if (p % 2 != 0) & ('middle' in df['type'].values): df.loc[idxs['middle'], 'label'] = 'neut. axis' internal_idx = df[df['label'] == 'internal'].index.tolist() # additional indexer # '''Add neut. axis in the middle''' # h_ ---------------------------------------------------------------------- # Gives the thickness (in m) and height w.r.t to the neut. axis (for middle) df['h(m)'] = df['t(um)'] * 1e-6 df.loc[df['type'] == 'middle', 'h(m)'] = df['t(um)'] * 1e-6 / 2. if p != 1: # at disconts. df.loc[idxs['disTens'], 'h(m)'] = df['h(m)'].shift(-1) df.loc[idxs['disComp'], 'h(m)'] = df['h(m)'].shift(1) # d_ ---------------------------------------------------------------------- # Gives the height for interfaces, neutral axes, disconts and internal points # Assign Laminate Surfaces and Neutral Axis to odd p, odd nply laminates df.loc[0, 'd(m)'] = 0 # first df.loc[idxs['middle'], 'd(m)'] = t_total / 2. # middle df.iloc[-1, df.columns.get_loc('d(m)')] = t_total # last # Assign Interfaces # Uses cumsum() for selected interfaces thickness to get d innerhTens = df.loc[df['label'] == 'interface', 'h(m)'].shift(1)[idxs['unboundIntfT']] # shift h down, select inner interfaces df.loc[idxs['unboundIntfT'], 'd(m)'] = 0 + np.cumsum(innerhTens) #print(np.cumsum(innerhTens)) innerhComp = df.loc[df['label'] == 'interface', 'h(m)'].shift(-1)[idxs['unboundIntfC']] # shift h up, select inner interfaces df.loc[idxs['unboundIntfC'], 'd(m)'] = t_total - np.cumsum(innerhComp[::-1])[::-1] #print(t_total - np.cumsum(innerhComp[::-1])[::-1]) # inverted cumsum() # Assign Other Points if p > 1: # at disconts. df.loc[idxs['disTens'], 'd(m)'] = df['d(m)'].shift(-1) df.loc[idxs['disComp'], 'd(m)'] = df['d(m)'].shift(1) if p > 2: df = Laminate._make_internals(df, p, column='d(m)') # at internals ##df = _make_internals(df, p, column='d(m)') # at internals # intf_ ------------------------------------------------------------------- # Enumerates proximal interfaces; n layer, but n+1 interfaces df['intf'] = df.loc[:, 'layer'] df.loc[df['side'] == 'Comp.', 'intf'] += 1 if (p % 2 != 0) & (nplies % 2 != 0): '''Need an INDET for numeric dtype. Default to Nan for now''' ##df.loc[df['label'] == 'neut. axis', 'intf'] = 'INDET' df.loc[idxs['middle'], 'intf'] = np.nan # using indep. indexer vs. label_ # Reset the dtype to float df[['intf']] = df[['intf']].astype(np.float64) # k_ ---------------------------------------------------------------------- # Normally the layer number, but now tracks the ith fractional level per layer # See definition in (Staab 197), k is is the region between k and k-1 level # Like intf_, k_ is aware of neutral axis # k_ == intf_ (proximal interface) df.loc[df['label'] == 'interface', 'k'] = df.loc[df['label'] == 'interface', 'intf'] # at interfaces ##'k'] = df.loc[df['label'] == 'interface', 'intf']-1 # at interfaces # if (p != 1) & (nplies%2 == 0): # df.loc[pseudomid, 'k'] = (nplies/2.)+1 # hack for even mids # #df.loc[pseudomid, 'k'] = nplies/2. # replace middle values # Interfaces and discontinuities share the same k_ if p > 1: # at disconts. df.loc[idxs['disTens'], 'k'] = df['k'].shift(-1) df.loc[idxs['disComp'], 'k'] = df['k'].shift(1) # Even plies have adjacent discontinuities at the neutral axis if nplies % 2 == 0: df.loc[pseudomid, 'k'] = (nplies / 2.) + 1 # hack for even mids ##df.loc[pseudomid, 'k'] = nplies / 2. # replace middle values # Auto calculate internal divisions if p > 2: # at internals df = Laminate._make_internals(df, p, column='k') ##df = _make_internals(df, p, column='k') '''Need an INDET. for numeric dtype. Default to Nan for now''' #df.loc[df['label'] == 'neut. axis', 'k'] = 'INDET' #df.loc[df['label'] == 'neut. axis', 'k'] = np.nan # Odd plies have nuetral axes if (p % 2 != 0) & (nplies % 2 != 0): # using indep. indexer vs. label_ df.loc[idxs['middle'], 'k'] = (df['k'].max() + df['k'].min()) / 2. ##df.loc[idxs['middle'], 'k'] = (df['k'].max()-df['k'].min())/2 ##df.loc[idxs['middle'], 'k'] = np.nan # using indep. indexer vs. label_ # Z_ ---------------------------------------------------------------------- # Distance from ith level to the neutral access middle = t_total / 2. df['Z(m)'] = middle - df['d(m)'] if (nplies == 1) & (p == 1): # d_ = t_total here, so must amend df['Z(m)'] = t_total / 2. # z_ ---------------------------------------------------------------------- # Distance from ith Z-midplane level to the neutral access # Two flavors are implemented for linearly and log-distributed z_ (z(m) and z(m)*) t_mid = df.loc[df['label'] == 'interface', 'h(m)'] / 2. # for midplane calc. df.loc[(df['label'] == 'interface') & (df['side'] == 'Tens.'), 'z(m)'] = df.loc[df['label'] == 'interface', 'Z(m)'] - t_mid # at interfaces df.loc[(df['label'] == 'interface') & (df['side'] == 'Comp.'), 'z(m)'] = df.loc[df['label'] == 'interface', 'Z(m)'] + t_mid # at interfaces if nplies % 2 == 0: df.loc[pseudomid, 'z(m)'] = 0 # replace middle values if p > 1: # at disconts. df.loc[idxs['disTens'], 'z(m)'] = df['z(m)'].shift(-1) df.loc[idxs['disComp'], 'z(m)'] = df['z(m)'].shift(1) if p > 2: # Equi-partitioned, Linear Intervals (legacy code); z(m) df = Laminate._make_internals(df, p, column='z(m)') ##df = _make_internals(df, p, column='z(m)') if p % 2 != 0: ##df.loc[df['label'] == 'neut. axis', 'z(m)'] = 0 df.loc[idxs['middle'], 'z(m)'] = 0 # using indep. indexer vs. label_ #### # Non-equi-partitioned Intervals; "Travelling" Midplanes; z(m)* '''Possibly offer user options to use either method''' lastT = df[(df['side'] == 'Tens.') & (df['type'] != 'middle')].groupby('layer')['Z(m)'].last() lastC = df[(df['side'] == 'Comp.') & (df['type'] != 'middle')].groupby('layer')['Z(m)'].first() last = pd.concat([lastT, lastC]) last.name = 'lasts' joined = df.join(last, on='layer') joined['z_intervals'] = (joined['Z(m)'] - joined['lasts']) / 2. #print(joined) #print(last) df['z(m)*'] = joined['Z(m)'] - joined['z_intervals'] df.loc[df['type'] == 'middle', 'z(m)*'] = df['Z(m)'] / 2. if (p == 1) & (nplies == 1): df.loc[0, 'z(m)*'] = 0 #### del df['idx'] sort_columns = ['layer', 'side', 'type', 'matl', 'label', 't(um)', 'h(m)', 'd(m)', 'intf', 'k', 'Z(m)', 'z(m)', 'z(m)*'] self.LFrame = ut.set_column_sequence(df, sort_columns)