struct_post 0.1.2__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CHUNHAO LYU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: struct_post
3
+ Version: 0.1.2
4
+ Summary: A module designed to analyse common structural test results.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: matplotlib>=3.10.6
9
+ Requires-Dist: openpyxl>=3.1.5
10
+ Requires-Dist: pandas>=2.3.2
11
+ Dynamic: license-file
12
+
13
+ # struct_post
14
+ A module designed to analyze common structural test results. The common stuctural tests include:
15
+ - Material coupon test
16
+ - Four-pont bending test
17
+ - Three-point bending test
@@ -0,0 +1,5 @@
1
+ # struct_post
2
+ A module designed to analyze common structural test results. The common stuctural tests include:
3
+ - Material coupon test
4
+ - Four-pont bending test
5
+ - Three-point bending test
@@ -0,0 +1,280 @@
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,
8
+ ):
9
+ """
10
+ Post-process a tensile coupon test and plot stress-strain curve.
11
+
12
+ To active with Jupyter Lab, '%matplotlib widget' is required
13
+
14
+ Args:
15
+ Thickness (float): Specimen thickness in mm.
16
+ Width (float): Specimen width in mm.
17
+ file_name (str): CSV file containing test data.
18
+ low_bound (float): Lower bound of elastic region as fraction of UTS.
19
+ upper_bound (float): Upper bound of elastic region as fraction of UTS.
20
+
21
+ Returns:
22
+ fig (matplotlib.figure.Figure): Figure object containing the plot.
23
+ """
24
+ import pandas as pd
25
+ import numpy as np
26
+ import matplotlib.pyplot as plt
27
+ #%matplotlib widget
28
+ # Constants
29
+ thickness # mm
30
+ width # mm
31
+ area = thickness * width # Calculate the area of the specimen
32
+
33
+ # 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
44
+
45
+ # Calculate stress and strain
46
+ force = force * 1000 # Convert kN to N
47
+ stress = (force / area) # N/m^2 or Pa
48
+ uts = stress.max()
49
+
50
+ #find the data before uts
51
+ idx_peak = np.argmax(stress)
52
+ strain_up = strain[:idx_peak+1]
53
+ stress_up = stress[:idx_peak+1]
54
+
55
+ #Boundary for 20% - 50% of UTS
56
+ lower_bound = 0.1 * uts
57
+ upper_bound = 0.3 * uts
58
+
59
+ elastic_reg = (lower_bound <= stress_up) & (stress_up <= upper_bound)
60
+
61
+ stress_ela = stress_up[elastic_reg]
62
+ strain_ela = strain_up[elastic_reg]
63
+
64
+ E, intercept = np.polyfit(strain_ela, stress_ela, 1)
65
+ #print(f"Young's Modulus is: {E} MPa",)
66
+ E_GPa = E / 1000 # Convert MPa to GPa
67
+ #print(f"Intercept: {intercept} MPa")
68
+
69
+ # Select over 30% of UTS, as yield stress will over 30% uts
70
+ strain_new = elongation
71
+ stress_new = force / area
72
+ mask = (lower_bound <= stress)
73
+ strain_mask = strain_new[mask]
74
+ stress_mask = stress_new[mask]
75
+
76
+ offset_decimal = 0.002 # 0.2% in decimal
77
+ offset_line = E * (strain_new - offset_decimal) + intercept
78
+
79
+ #Find the Yield strength
80
+ diff = stress_mask - offset_line
81
+ cross_index = np.where(diff <= 0)[0][0]
82
+ x1 = strain_mask[cross_index-1]
83
+ x2 = strain_mask[cross_index]
84
+ y1 = diff[cross_index-1]
85
+ y2 = diff[cross_index]
86
+ yield_strain = x1 - y1 * (x2 - x1) / (y2 - y1)
87
+ yield_strength = np.interp(yield_strain, strain_new, stress_new)
88
+
89
+ #Plot
90
+ fig, ax = plt.subplots(figsize=(10,6))
91
+
92
+ ax.plot(strain, stress, label='Stress-Strain Curve', color='blue')
93
+ ax.plot(strain_new, offset_line, label='0.2% Offset Strain Line', color='yellow',linestyle = '-.')
94
+
95
+ ax.axhline(y=yield_strength, label=f'Yield Strength = {yield_strength:.2f} MPa', color='green', linestyle = 'dotted')
96
+ ax.axhline(y=uts, color='red', linestyle = '--', label=f'UTS = {uts:.2f} MPa')
97
+ ax.plot(yield_strain, yield_strength, 'ro', label='Yield Point')
98
+
99
+ ax.set_xlabel('Strain (mm/mm)')
100
+ ax.set_ylabel('Stress (MPa)')
101
+ ax.set_title(testdata_file_name[:-4]+' Stress-Strain Curve with Mechanical Properties')
102
+ ax.legend()
103
+ ax.grid(True)
104
+ ax.set_ylim(0, 1.2 *uts)
105
+
106
+ #Show fig or not
107
+ if showfig == True:
108
+ plt.show()
109
+ else:
110
+ plt.close(fig)
111
+
112
+ # Save fig or not
113
+ if savefig == True:
114
+ fig.savefig(testdata_file_name[:-4], dpi=300, bbox_inches='tight')
115
+
116
+ # Print results
117
+ print(f"Sample: {testdata_file_name[:-4]}")
118
+ print(f"Young's Modulus (E): {E:.2f} MPa")
119
+ print(f"Ultimate Tensile Strength (UTS): {uts:.2f} MPa")
120
+ print(f"Yield Strength: {yield_strength:.2f} MPa")
121
+ print('-' * 40)
122
+
123
+ # Prepare results dictionary
124
+ results = {
125
+ "E_MPa": E,
126
+ "UTS_MPa": uts,
127
+ "Yield_Strength_MPa": yield_strength
128
+ }
129
+ return testdata_file_name[:-4], results
130
+
131
+ def coupon_sample_geodata_read (Excelfile_name: str):
132
+ '''
133
+ Reads sample geometric properties from an Excel file.
134
+
135
+ Parameters
136
+ ----------
137
+ Excelfile_name : str
138
+ The name of the Excel file to read, located in the current working directory.
139
+
140
+ Returns
141
+ -------
142
+ list
143
+ A list containing:
144
+ - sample_file_name (sample name from cell A2 with ".csv" extension)
145
+ - thickness (value from cell C2)
146
+ - width (value from cell B2)
147
+ '''
148
+
149
+ from openpyxl import load_workbook
150
+ from pathlib import Path
151
+ sample_data_file = Path.cwd()/Excelfile_name
152
+ wb = load_workbook(sample_data_file)
153
+ ws = wb.worksheets[0]
154
+ samples = []
155
+ for row in ws.iter_rows(min_row=2, values_only=True):
156
+ if all(cell is None for cell in row): # skip empty
157
+ continue
158
+ # 取前三列数据
159
+ sample_file_name = str (row[0] + ".csv")
160
+ sample_name = coupon_SampleDetails(row[0], row[1], row[2], sample_file_name)
161
+ samples.append(sample_name)
162
+ return samples
163
+
164
+ def coupon_batch_analysis(Coupon_geodata: str,
165
+ showfig: bool = True,
166
+ savefig: bool = False):
167
+ """
168
+ Perform batch analysis on a list of samples and return the results.
169
+
170
+ Parameters
171
+ ----------
172
+ Coupon_geodata : list
173
+ A list of SampleDetails objects, each containing:
174
+ - sample_file_name : Name of the sample file
175
+ - thickness : Sample thickness
176
+ - width : Sample width
177
+ savefig : bool, optional
178
+ Whether to save the figures generated during analysis. Default is False.
179
+
180
+ Returns
181
+ -------
182
+ list
183
+ 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)
187
+ """
188
+ SARS = []
189
+ for Coupon_detail in Coupon_geodata:
190
+ result = coupon_test_analysis(Coupon_detail.sample_file_name, Coupon_detail.thickness, Coupon_detail.width, showfig, savefig)
191
+ SAR = coupon_SampleAnalysisResults(result[0], result[1]['E_MPa'], result[1]['UTS_MPa'], result[1]['Yield_Strength_MPa'])
192
+ SARS.append(SAR)
193
+ return SARS
194
+
195
+ def coupon_results_save(Excelfile_name: str, analysis_results: list):
196
+ """
197
+ Save a list of sample analysis results into an Excel file, matching by sample name.
198
+
199
+ Parameters
200
+ ----------
201
+ Excelfile_name: str
202
+ Path to the Excel file to save the results.
203
+ analysis_results : list
204
+ A list of SampleAnalysisResults objects, each containing:
205
+ - sample_name
206
+ - modulus_of_elasticity
207
+ - ultimate_tensile_strength
208
+ - yield_Strength
209
+
210
+ Notes
211
+ -----
212
+ - Assumes the Excel file has sample names in column A, starting from row 2.
213
+ - Data will be written starting from column D (fourth column).
214
+ - Overwrites the original Excel file.
215
+ """
216
+ from openpyxl import load_workbook
217
+
218
+ wb = load_workbook(Excelfile_name)
219
+ ws = wb.worksheets[0]
220
+
221
+ # Build a dictionary for faster lookup
222
+ result_dict = {res.sample_name: res for res in analysis_results}
223
+
224
+ for row_idx, row in enumerate(ws.iter_rows(min_row=2, max_col=ws.max_column), start=2):
225
+ excel_sample_name = row[0].value
226
+ if excel_sample_name in result_dict:
227
+ analysis_result = result_dict[excel_sample_name]
228
+ values_to_write = [
229
+ analysis_result.modulus_of_elasticity,
230
+ analysis_result.ultimate_tensile_strength,
231
+ analysis_result.yield_Strength
232
+ ]
233
+ for col_idx, value in enumerate(values_to_write, start=4): # D列开始
234
+ ws.cell(row=row_idx, column=col_idx, value=float(value))
235
+
236
+ wb.save(Excelfile_name)
237
+ print('The coupon test data analysis is complete.')
238
+
239
+ from dataclasses import dataclass
240
+ @dataclass # this thing is called a "decorator"
241
+ class coupon_SampleDetails:
242
+ """
243
+ Holds basic information for a sample.
244
+
245
+ Attributes
246
+ ----------
247
+ sample_name : str
248
+ Name or ID of the sample.
249
+ width : float
250
+ Width of the sample (geometric property, in consistent units).
251
+ thickness : float
252
+ Thickness of the sample (geometric property, in consistent units).
253
+ sample_file_name : str
254
+ Name of the associated data file for the sample, e.g., CSV file.
255
+ """
256
+ sample_name: str
257
+ width: float
258
+ thickness: float
259
+ sample_file_name: str
260
+
261
+ @dataclass # this thing is called a "decorator"
262
+ class coupon_SampleAnalysisResults:
263
+ """
264
+ Stores the analysis results for a sample after mechanical testing.
265
+
266
+ Attributes
267
+ ----------
268
+ sample_name : str
269
+ Name or ID of the sample corresponding to the analysis.
270
+ modulus_of_elasticity : float
271
+ Elastic modulus of the sample (E, in GPa).
272
+ ultimate_tensile_strength : float
273
+ Maximum tensile strength of the sample (UTS, in MPa).
274
+ yield_Strength : float
275
+ Yield strength of the sample (in MPa).
276
+ """
277
+ sample_name: str
278
+ modulus_of_elasticity: float
279
+ ultimate_tensile_strength: float
280
+ yield_Strength: float
@@ -0,0 +1,11 @@
1
+ [project]
2
+ name = "struct_post"
3
+ version = "0.1.2"
4
+ description = "A module designed to analyse common structural test results."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "matplotlib>=3.10.6",
9
+ "openpyxl>=3.1.5",
10
+ "pandas>=2.3.2",
11
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: struct_post
3
+ Version: 0.1.2
4
+ Summary: A module designed to analyse common structural test results.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: matplotlib>=3.10.6
9
+ Requires-Dist: openpyxl>=3.1.5
10
+ Requires-Dist: pandas>=2.3.2
11
+ Dynamic: license-file
12
+
13
+ # struct_post
14
+ A module designed to analyze common structural test results. The common stuctural tests include:
15
+ - Material coupon test
16
+ - Four-pont bending test
17
+ - Three-point bending test
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ postanalysis.py
4
+ pyproject.toml
5
+ struct_post.egg-info/PKG-INFO
6
+ struct_post.egg-info/SOURCES.txt
7
+ struct_post.egg-info/dependency_links.txt
8
+ struct_post.egg-info/requires.txt
9
+ struct_post.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ matplotlib>=3.10.6
2
+ openpyxl>=3.1.5
3
+ pandas>=2.3.2
@@ -0,0 +1 @@
1
+ postanalysis