struct_post 0.1.4.3__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
struct_post/beam.py CHANGED
@@ -1,20 +1,133 @@
1
+ from pandas import DataFrame
1
2
  import pandas as pd
2
- def four_point_bending (data: pd.DataFrame,
3
+
4
+ def beam_lvmdata_file(file_name, force_index=2, displ_index=3,
5
+ mid_lvdt_ind=None, left_lvdt_ind=None, right_lvdt_ind=None):
6
+ """
7
+ Read a .lvm file and extract force, displacement, and LVDT data in groups.
8
+
9
+ Parameters
10
+ ----------
11
+ file_name : str
12
+ Path to the .lvm file to read.
13
+ force_index : int, default=2
14
+ 1-based column index for the force data.
15
+ displ_index : int, default=3
16
+ 1-based column index for the displacement (stroke) data.
17
+ mid_lvdt_ind : int or list of int, optional
18
+ Column index/indices for the middle LVDTs.
19
+ left_lvdt_ind : int or list of int, optional
20
+ Column index/indices for the left LVDTs.
21
+ right_lvdt_ind : int or list of int, optional
22
+ Column index/indices for the right LVDTs.
23
+
24
+ Returns
25
+ -------
26
+ df_new : pandas.DataFrame
27
+ Extracted data as a DataFrame with columns renamed:
28
+ ['Force (kN)', 'Stroke (mm)', 'Mid_1 (mm)', 'Mid_2 (mm)',
29
+ 'Left_1 (mm)', 'Left_2 (mm)', 'Right_1 (mm)', 'Right_2 (mm)'].
30
+ Only existing columns are included; missing columns are ignored.
31
+ file_base_name : str
32
+ Base name of the file without the '.lvm' extension.
33
+
34
+ Notes
35
+ -----
36
+ - Automatically detects the start of the data by locating the last
37
+ occurrence of '***End_of_Header***' in the file.
38
+ - Converts all columns to numeric values; non-numeric entries
39
+ are coerced to NaN.
40
+ - The last column is dropped assuming it is empty.
41
+ - Single integer indices are automatically converted to lists.
42
+ - LVDT columns are combined in the order: mid -> left -> right.
43
+ """
44
+
45
+ mid_lvdt_ind = mid_lvdt_ind or []
46
+ left_lvdt_ind = left_lvdt_ind or []
47
+ right_lvdt_ind = right_lvdt_ind or []
48
+
49
+ if isinstance(mid_lvdt_ind, int):
50
+ mid_lvdt_ind = [mid_lvdt_ind]
51
+ if isinstance(left_lvdt_ind, int):
52
+ left_lvdt_ind = [left_lvdt_ind]
53
+ if isinstance(right_lvdt_ind, int):
54
+ right_lvdt_ind = [right_lvdt_ind]
55
+
56
+ lvdt_indices = mid_lvdt_ind + left_lvdt_ind + right_lvdt_ind
57
+
58
+ with open(file_name, 'r') as f:
59
+ lines = f.readlines()
60
+ start_idx = max(i for i, line in enumerate(lines) if "***End_of_Header***" in line) + 1
61
+ df = pd.read_csv(file_name, sep="\t", skiprows=start_idx)
62
+ df = df.apply(pd.to_numeric, errors='coerce')
63
+ df = df.drop(df.columns[-1], axis=1)
64
+
65
+ user_indices = [force_index, displ_index] + lvdt_indices
66
+ existing_cols = [df.columns[i-1] for i in user_indices if i-1 < len(df.columns)]
67
+
68
+ df_new = df[existing_cols].copy()
69
+
70
+ col_names = ['Force (kN)','Stroke (mm)','Mid_1 (mm)','Mid_2 (mm)','Left_1 (mm)','Left_2 (mm)','Right_1 (mm)','Right_2 (mm)']
71
+ df_new.columns = col_names[:len(existing_cols)]
72
+
73
+ return df_new, file_name[:-4]
74
+
75
+ def four_point_bending (data: DataFrame,
3
76
  width: float,
4
77
  depth: float,
5
78
  beam_span: float):
79
+ """
80
+ Perform four-point bending analysis to calculate apparent and true modulus of elasticity.
81
+
82
+ Parameters
83
+ ----------
84
+ data : tuple
85
+ A tuple containing:
86
+ - data[0] : pandas.DataFrame
87
+ Experimental dataset with columns:
88
+ - 'Moog Force_kN' : Applied load (kN).
89
+ - 'LVDT 1_mm' ... 'LVDT 6_mm' : Deflections from six LVDTs (mm).
90
+ - data[1] : str
91
+ Sample name identifier.
92
+ width : float
93
+ Beam specimen width (mm).
94
+ depth : float
95
+ Beam specimen depth (mm).
96
+ beam_span : float
97
+ Beam span length (mm).
98
+
99
+ Returns
100
+ -------
101
+ tuple
102
+ (sample_name, results) where:
103
+ - sample_name : str
104
+ Name of the processed sample.
105
+ - results : dict
106
+ Dictionary containing:
107
+ - "E_app" : float
108
+ Apparent modulus of elasticity (MPa).
109
+ - "E_true" : float
110
+ True modulus of elasticity (MPa).
111
+
112
+ Notes
113
+ -----
114
+ - Apparent modulus (E_app) is calculated from mid-span deflection (LVDT 3 & 4).
115
+ - True modulus (E_true) is calculated from relative deflection (mid-span vs. supports).
116
+ - Load range for regression is limited to 10–40% of ultimate load.
117
+ """
118
+
6
119
  import pandas as pd
7
120
  import numpy as np
8
121
 
9
- #geo
122
+ #Experimental test data post-process
10
123
  sample_name = data[1]
11
- force = data[0]['Moog Force_kN'] * 1000
12
- delta_1 = abs(data[0]['LVDT 1_mm'])
13
- delta_2 = abs(data[0]['LVDT 2_mm'])
14
- delta_3 = abs(data[0]['LVDT 3_mm'])
15
- delta_4 = abs(data[0]['LVDT 4_mm'])
16
- delta_5 = abs(data[0]['LVDT 5_mm'])
17
- delta_6 = abs(data[0]['LVDT 6_mm'])
124
+ force = data[0]['Force (kN)'] * 1000
125
+ delta_1 = abs(data[0]['Left_1 (mm)'])
126
+ delta_2 = abs(data[0]['Left_2 (mm)'])
127
+ delta_3 = abs(data[0]['Mid_1 (mm)'])
128
+ delta_4 = abs(data[0]['Mid_2 (mm)'])
129
+ delta_5 = abs(data[0]['Right_1 (mm)'])
130
+ delta_6 = abs(data[0]['Right_2 (mm)'])
18
131
 
19
132
  F_ult = force.max()
20
133
  f_b = (F_ult * beam_span) / (width * depth **2) #MPa
@@ -31,10 +144,10 @@ def four_point_bending (data: pd.DataFrame,
31
144
 
32
145
  F_ms = force[calcs_reg]
33
146
  delta_ms_calcs = delta_ms[calcs_reg]
34
- delat_rel_calcs = delta_rel[calcs_reg]
147
+ delta_rel_calcs = delta_rel[calcs_reg]
35
148
 
36
149
  Delta_ms, intercept_ms = np.polyfit(delta_ms_calcs,F_ms,1)
37
- Delta_rel, intercept_rel = np.polyfit(delat_rel_calcs,F_ms,1)
150
+ Delta_rel, intercept_rel = np.polyfit(delta_rel_calcs,F_ms,1)
38
151
 
39
152
  E_app = (23/108) * (beam_span/depth)**3 * Delta_ms * (1/width)
40
153
  E_true = (1/36) * (beam_span/depth)**3 * Delta_rel * (1/width)
@@ -47,3 +160,4 @@ def four_point_bending (data: pd.DataFrame,
47
160
  print(f"Sample Name: {sample_name}")
48
161
  print('-' * 40)
49
162
  return sample_name, results
163
+
struct_post/coupon.py CHANGED
@@ -1,10 +1,44 @@
1
- def coupon_test_analysis (testdata_file_name: str,
2
- thickness: float = 2.5,
3
- width: float = 10,
4
- showfig: bool = True,
5
- savefig: bool = False,
6
- lower_bound: float = 0.1,
7
- upper_bound: float = 0.3,
1
+ def coupon_csvdata_read(testdata_file_name: str,
2
+ force_index: int,
3
+ strain_index: int):
4
+ """
5
+ Read tensile coupon test data from a CSV file and extract force and strain columns.
6
+
7
+ Args:
8
+ testdata_file_name (str): Path to the CSV file containing the test data.
9
+ force_index (int): 1-based column index for the force data.
10
+ strain_index (int): 1-based column index for the strain data.
11
+
12
+ Returns:
13
+ tuple:
14
+ df_new (pd.DataFrame): DataFrame containing only 'Force' and 'Strain' columns.
15
+ sample_name (str): Name of the sample extracted from the file name (without extension).
16
+
17
+ Notes:
18
+ - The CSV file is expected to have a header in the first row.
19
+ - The second row is skipped during reading (`skiprows=[1]`).
20
+ - The first column is treated as the index column (`index_col=0`).
21
+ - Force and strain columns are extracted based on the provided 1-based indices.
22
+ """
23
+
24
+ import pandas as pd
25
+ df = pd.read_csv(testdata_file_name, skiprows=[1])
26
+ Force = df[df.columns[force_index - 1]]
27
+ Strain = df[df.columns[strain_index - 1]]
28
+ df_new = pd.concat([Force, Strain], axis=1)
29
+ df_new.columns = ['Force', 'Strain']
30
+ sample_name = testdata_file_name[:-4]
31
+ return sample_name, df_new
32
+
33
+ def coupon_test_analysis (sample_name: str,
34
+ Force: float,
35
+ Strain: float,
36
+ thickness: float = 2.5,
37
+ width: float = 10,
38
+ showfig: bool = True,
39
+ savefig: bool = False,
40
+ low_bound: float = 0.1,
41
+ up_bound: float = 0.3,
8
42
  ):
9
43
  """
10
44
  Post-process a tensile coupon test and plot stress-strain curve.
@@ -25,36 +59,27 @@ def coupon_test_analysis (testdata_file_name: str,
25
59
  import numpy as np
26
60
  import matplotlib.pyplot as plt
27
61
  #%matplotlib widget
62
+
28
63
  # Constants
29
- thickness # mm
30
- width # mm
31
64
  area = thickness * width # Calculate the area of the specimen
32
65
 
33
66
  # Load tensile test data
34
- df = pd.read_csv(testdata_file_name, header=[0])
35
-
36
- #df.columns = [f"{col[0]} {col[1]}" for col in df.columns]
37
-
38
- # Extract relevant columns
39
- #time = df["Time (sec)"]
40
- #displacement = df["Crosshead separation (mm)"]
41
- force = df[df.columns[1]]
42
- elongation = df[df.columns[2]]
43
- strain = elongation # Strain in mm/mm
67
+
68
+ strain = Strain # Strain in mm/mm
44
69
 
45
70
  # Calculate stress and strain
46
- force = force * 1000 # Convert kN to N
47
- stress = (force / area) # N/m^2 or Pa
71
+ force = Force * 1000 # Convert kN to N
72
+ stress = (force / area) # N/mm^2 or MPa
48
73
  uts = stress.max()
49
74
 
50
75
  #find the data before uts
51
76
  idx_peak = np.argmax(stress)
52
- strain_up = strain[:idx_peak+1]
77
+ strain_up = Strain[:idx_peak+1]
53
78
  stress_up = stress[:idx_peak+1]
54
79
 
55
- #Boundary for 20% - 50% of UTS
56
- lower_bound = 0.1 * uts
57
- upper_bound = 0.3 * uts
80
+ #Boundary for [low bound] - [up bound] of uts
81
+ lower_bound = low_bound * uts
82
+ upper_bound = up_bound * uts
58
83
 
59
84
  elastic_reg = (lower_bound <= stress_up) & (stress_up <= upper_bound)
60
85
 
@@ -66,8 +91,8 @@ def coupon_test_analysis (testdata_file_name: str,
66
91
  E_GPa = E / 1000 # Convert MPa to GPa
67
92
  #print(f"Intercept: {intercept} MPa")
68
93
 
69
- # Select over 30% of UTS, as yield stress will over 30% uts
70
- strain_new = elongation
94
+ # Select over [lower bound] of UTS, as yield stress will over [lower bound] uts
95
+ strain_new = strain
71
96
  stress_new = force / area
72
97
  mask = (lower_bound <= stress)
73
98
  strain_mask = strain_new[mask]
@@ -78,7 +103,7 @@ def coupon_test_analysis (testdata_file_name: str,
78
103
 
79
104
  #Find the Yield strength
80
105
  diff = stress_mask - offset_line
81
- cross_index = np.where(diff <= 0)[0][0]
106
+ cross_index = np.where(diff <= 0)[0][0] +1
82
107
  x1 = strain_mask[cross_index-1]
83
108
  x2 = strain_mask[cross_index]
84
109
  y1 = diff[cross_index-1]
@@ -98,7 +123,7 @@ def coupon_test_analysis (testdata_file_name: str,
98
123
 
99
124
  ax.set_xlabel('Strain (mm/mm)')
100
125
  ax.set_ylabel('Stress (MPa)')
101
- ax.set_title(testdata_file_name[:-4]+' Stress-Strain Curve with Mechanical Properties')
126
+ ax.set_title(sample_name + ' Stress-Strain Curve with Mechanical Properties')
102
127
  ax.legend()
103
128
  ax.grid(True)
104
129
  ax.set_ylim(0, 1.2 *uts)
@@ -111,10 +136,10 @@ def coupon_test_analysis (testdata_file_name: str,
111
136
 
112
137
  # Save fig or not
113
138
  if savefig == True:
114
- fig.savefig(testdata_file_name[:-4], dpi=300, bbox_inches='tight')
139
+ fig.savefig(sample_name, dpi=300, bbox_inches='tight')
115
140
 
116
141
  # Print results
117
- print(f"Sample: {testdata_file_name[:-4]}")
142
+ print(f"Sample: {sample_name}")
118
143
  print(f"Young's Modulus (E): {E:.2f} MPa")
119
144
  print(f"Ultimate Tensile Strength (UTS): {uts:.2f} MPa")
120
145
  print(f"Yield Strength: {yield_strength:.2f} MPa")
@@ -126,7 +151,7 @@ def coupon_test_analysis (testdata_file_name: str,
126
151
  "UTS_MPa": uts,
127
152
  "Yield_Strength_MPa": yield_strength
128
153
  }
129
- return testdata_file_name[:-4], results
154
+ return sample_name, results
130
155
 
131
156
  def coupon_sample_geodata_read (Excelfile_name: str):
132
157
  '''
@@ -155,39 +180,61 @@ def coupon_sample_geodata_read (Excelfile_name: str):
155
180
  for row in ws.iter_rows(min_row=2, values_only=True):
156
181
  if all(cell is None for cell in row): # skip empty
157
182
  continue
158
- # 取前三列数据
159
183
  sample_file_name = str (row[0] + ".csv")
160
184
  sample_name = coupon_SampleDetails(row[0], row[1], row[2], sample_file_name)
161
185
  samples.append(sample_name)
162
186
  return samples
163
187
 
164
188
  def coupon_batch_analysis(Coupon_geodata: str,
165
- showfig: bool = True,
166
- savefig: bool = False):
189
+ force_index: float,
190
+ strain_index:float,
191
+ showfig: bool = True,
192
+ savefig: bool = False):
167
193
  """
168
- Perform batch analysis on a list of samples and return the results.
194
+ Perform batch analysis on a list of tensile coupon samples and return their results.
169
195
 
170
196
  Parameters
171
197
  ----------
172
- Coupon_geodata : list
198
+ Coupon_geodata : list of SampleDetails
173
199
  A list of SampleDetails objects, each containing:
174
- - sample_file_name : Name of the sample file
175
- - thickness : Sample thickness
176
- - width : Sample width
200
+ - sample_file_name : str
201
+ Path to the sample CSV file.
202
+ - thickness : float
203
+ Sample thickness (mm).
204
+ - width : float
205
+ Sample width (mm).
206
+ force_index : int
207
+ Column index of the Force data in the CSV file (1-based).
208
+ strain_index : int
209
+ Column index of the Strain data in the CSV file (1-based).
210
+ showfig : bool, optional
211
+ Whether to display stress-strain plots during analysis. Default is True.
177
212
  savefig : bool, optional
178
- Whether to save the figures generated during analysis. Default is False.
213
+ Whether to save the stress-strain plots to files. Default is False.
179
214
 
180
215
  Returns
181
216
  -------
182
- list
217
+ list of SampleAnalysisResults
183
218
  A list of SampleAnalysisResults objects, each containing:
184
- - modulus_of_elasticity : Elastic modulus (E_GPa)
185
- - ultimate_tensile_strength : Ultimate tensile strength (UTS_MPa)
186
- - yield_Strength : Yield strength (Yield_Strength_MPa)
219
+ - sample_name : str
220
+ Name of the processed sample.
221
+ - modulus_of_elasticity : float
222
+ Elastic modulus (E_GPa).
223
+ - ultimate_tensile_strength : float
224
+ Ultimate tensile strength (UTS_MPa).
225
+ - yield_strength : float
226
+ Yield strength (Yield_Strength_MPa).
187
227
  """
188
228
  SARS = []
189
229
  for Coupon_detail in Coupon_geodata:
190
- result = coupon_test_analysis(Coupon_detail.sample_file_name, Coupon_detail.thickness, Coupon_detail.width, showfig, savefig)
230
+ csvdata = coupon_csvdata_read(Coupon_detail.sample_file_name,force_index,strain_index)
231
+ result = coupon_test_analysis(Coupon_detail.sample_file_name[:-4],
232
+ csvdata[1]['Force'],
233
+ csvdata[1]['Strain'],
234
+ Coupon_detail.thickness,
235
+ Coupon_detail.width,
236
+ showfig,
237
+ savefig)
191
238
  SAR = coupon_SampleAnalysisResults(result[0], result[1]['E_MPa'], result[1]['UTS_MPa'], result[1]['Yield_Strength_MPa'])
192
239
  SARS.append(SAR)
193
240
  return SARS
@@ -237,7 +284,7 @@ def coupon_results_save(Excelfile_name: str, analysis_results: list):
237
284
  print('The coupon test data analysis is complete.')
238
285
 
239
286
  from dataclasses import dataclass
240
- @dataclass # this thing is called a "decorator"
287
+ @dataclass
241
288
  class coupon_SampleDetails:
242
289
  """
243
290
  Holds basic information for a sample.
@@ -258,7 +305,7 @@ class coupon_SampleDetails:
258
305
  thickness: float
259
306
  sample_file_name: str
260
307
 
261
- @dataclass # this thing is called a "decorator"
308
+ @dataclass
262
309
  class coupon_SampleAnalysisResults:
263
310
  """
264
311
  Stores the analysis results for a sample after mechanical testing.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: struct_post
3
- Version: 0.1.4.3
3
+ Version: 0.1.7
4
4
  Summary: A module designed to analyse common structural test results.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +8,7 @@ License-File: LICENSE
8
8
  Requires-Dist: matplotlib>=3.10.6
9
9
  Requires-Dist: openpyxl>=3.1.5
10
10
  Requires-Dist: pandas>=2.3.2
11
+ Requires-Dist: requests>=2.32.5
11
12
  Dynamic: license-file
12
13
 
13
14
  # struct_post
@@ -0,0 +1,9 @@
1
+ struct_post/__init__.py,sha256=aNaAaoGGLodY5FTsVGCnUhzuXW9F50Mi_7xm-nQ-xFg,182
2
+ struct_post/beam.py,sha256=ftoh-J_7SCJUdptUnLgCXrvvHSKyACr0D8ZFJT0358s,5748
3
+ struct_post/coupon.py,sha256=vLHHSc37DaZFYQmx8zDHpa0S0CEgPlfbtedx9gGZY6o,11886
4
+ struct_post/read_lvm_file.py,sha256=3x-vbkgEQ-G3f939euIMqwjzVEt9zHtvNneYd1-ViDw,2539
5
+ struct_post-0.1.7.dist-info/licenses/LICENSE,sha256=0KCwPGoSiY1zA6U97FZJob9MJbaJOo6sRZF-xv6A1YI,1089
6
+ struct_post-0.1.7.dist-info/METADATA,sha256=i34uSxS4iIRHSFZHE6qXm2b2OxY9nNjaJ0Wc8oZj6v0,689
7
+ struct_post-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ struct_post-0.1.7.dist-info/top_level.txt,sha256=jgiqPN0SMTMUomzOey9HfIrScTFmOzyuLpwIJ-Sz1-0,12
9
+ struct_post-0.1.7.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- struct_post/__init__.py,sha256=aNaAaoGGLodY5FTsVGCnUhzuXW9F50Mi_7xm-nQ-xFg,182
2
- struct_post/beam.py,sha256=y-dV-taU1JNcyZYVTeJDe0i2aOkDoCUp4Ar5I8QC9iw,1495
3
- struct_post/coupon.py,sha256=pi-aGs-EnKosq7QwPR1unskTVoNZ8kZ3o3gQjlFSf34,9835
4
- struct_post/read_lvm_file.py,sha256=3x-vbkgEQ-G3f939euIMqwjzVEt9zHtvNneYd1-ViDw,2539
5
- struct_post-0.1.4.3.dist-info/licenses/LICENSE,sha256=0KCwPGoSiY1zA6U97FZJob9MJbaJOo6sRZF-xv6A1YI,1089
6
- struct_post-0.1.4.3.dist-info/METADATA,sha256=R4qNtqJsWhPf2MLAK6z063xNdw3gbGOsUTUg52nozfE,658
7
- struct_post-0.1.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- struct_post-0.1.4.3.dist-info/top_level.txt,sha256=jgiqPN0SMTMUomzOey9HfIrScTFmOzyuLpwIJ-Sz1-0,12
9
- struct_post-0.1.4.3.dist-info/RECORD,,