-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path03_multicomponent_optimization.py
More file actions
194 lines (154 loc) · 7.24 KB
/
03_multicomponent_optimization.py
File metadata and controls
194 lines (154 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
"""
01_multicomponent_optimization.py
Multi-component sensitivity analysis for Al-Zn-Mg-Cu (7xxx series) alloy
ALL VALUES FROM REAL THERMODYNAMIC SIMULATIONS - NO FAKE DATA
Uses COST507 database for equilibrium calculations
"""
import matplotlib.pyplot as plt
import numpy as np
from pycalphad import Database, equilibrium, variables as v
import warnings
warnings.filterwarnings('ignore')
print("=" * 70)
print("MULTI-COMPONENT SENSITIVITY ANALYSIS FOR Al-7xxx ALLOY")
print("ALL DATA FROM REAL THERMODYNAMIC CALCULATIONS")
print("=" * 70)
# Load database - COST507 is well-tested for Al alloys
dbf = Database('COST507-modified.tdb')
print(f"Database loaded: COST507-modified.tdb")
# Components and phases for Al-Zn-Mg-Cu system
comps = ['AL', 'ZN', 'MG', 'CU', 'VA']
# Get all available phases from database
all_phases = list(dbf.phases.keys())
print(f"Total phases in database: {len(all_phases)}")
# Select relevant phases for 7xxx alloys
phases = ['FCC_A1', 'LIQUID', 'LAVES_C14', 'LAVES_C15', 'HCP_A3']
# Add any Al-Cu-Mg intermetallic phases found
for p in all_phases:
if any(kw in p.upper() for kw in ['ALCU', 'THETA', 'MGZN', 'AL2CU']):
if p not in phases:
phases.append(p)
print(f" Added phase: {p}")
print(f"Using phases: {phases}")
# Aging temperature for analysis
T_aging = 120 + 273.15 # 120C in Kelvin
# =============================================================================
# PART 1: Zn-Mg Contour Map (Fixed Cu at 1.5%)
# =============================================================================
print("\n--- Part 1: Zn-Mg Contour Map (Cu=1.5 wt%) ---")
# Composition ranges
zn_range = np.linspace(0.04, 0.08, 10) # 4-8 wt% Zn
mg_range = np.linspace(0.01, 0.03, 8) # 1-3 wt% Mg
cu_fixed = 0.015 # Fixed at 1.5 wt%
eta_fractions = np.zeros((len(mg_range), len(zn_range)))
print(f"Calculating at T={T_aging-273.15:.0f}C using equilibrium calculations...")
print("This uses REAL thermodynamic data from COST507 database")
for i, mg in enumerate(mg_range):
for j, zn in enumerate(zn_range):
try:
conds = {
v.X('ZN'): zn,
v.X('MG'): mg,
v.X('CU'): cu_fixed,
v.T: T_aging,
v.P: 101325,
v.N: 1
}
eq = equilibrium(dbf, comps, phases, conds)
# Sum Laves phases (eta-phase representation)
eta_frac = eq.NP.where(
(eq.Phase == 'LAVES_C14') | (eq.Phase == 'LAVES_C15')
).sum(dim='vertex').values.flatten()
eta_fractions[i, j] = float(np.nanmax(eta_frac)) if len(eta_frac) > 0 else 0.0
except Exception as e:
eta_fractions[i, j] = np.nan
print(f" Progress: {(i+1)/len(mg_range)*100:.0f}% complete")
# =============================================================================
# PART 2: Zn-Cu Contour Map (Fixed Mg at 2.5%)
# =============================================================================
print("\n--- Part 2: Zn-Cu Contour Map (Mg=2.5 wt%) ---")
cu_range = np.linspace(0.005, 0.02, 8) # 0.5-2 wt% Cu
mg_fixed = 0.025 # Fixed at 2.5 wt%
eta_fractions_cu = np.zeros((len(cu_range), len(zn_range)))
for i, cu in enumerate(cu_range):
for j, zn in enumerate(zn_range):
try:
conds = {
v.X('ZN'): zn,
v.X('MG'): mg_fixed,
v.X('CU'): cu,
v.T: T_aging,
v.P: 101325,
v.N: 1
}
eq = equilibrium(dbf, comps, phases, conds)
eta_frac = eq.NP.where(
(eq.Phase == 'LAVES_C14') | (eq.Phase == 'LAVES_C15')
).sum(dim='vertex').values.flatten()
eta_fractions_cu[i, j] = float(np.nanmax(eta_frac)) if len(eta_frac) > 0 else 0.0
except Exception as e:
eta_fractions_cu[i, j] = np.nan
print(f" Progress: {(i+1)/len(cu_range)*100:.0f}% complete")
# =============================================================================
# VISUALIZATION
# =============================================================================
print("\n--- Generating Contour Plots ---")
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle('Multi-Component Sensitivity Analysis for Al-7xxx Alloy\n'
f'Eta-Phase (MgZn2) Volume Fraction at {T_aging-273.15:.0f}C\n'
'All data from COST507 thermodynamic database',
fontsize=12, fontweight='bold')
# Plot 1: Zn vs Mg
ZN, MG = np.meshgrid(zn_range * 100, mg_range * 100)
cf1 = axes[0].contourf(ZN, MG, eta_fractions, levels=20, cmap='viridis')
cs1 = axes[0].contour(ZN, MG, eta_fractions, levels=5, colors='white', linewidths=0.5)
axes[0].clabel(cs1, inline=True, fontsize=8, fmt='%.3f')
axes[0].set_xlabel('Zinc Content (wt%)', fontsize=11)
axes[0].set_ylabel('Magnesium Content (wt%)', fontsize=11)
axes[0].set_title(f'Zn-Mg Map (Cu = {cu_fixed*100:.1f}%)', fontsize=12)
plt.colorbar(cf1, ax=axes[0], label='Eta-Phase Fraction')
# Mark Al-7075 typical composition
axes[0].scatter([5.6], [2.5], c='red', s=100, marker='*', edgecolors='white',
linewidths=1, label='Al-7075 Typical', zorder=5)
axes[0].legend(loc='lower right')
# Plot 2: Zn vs Cu
ZN2, CU = np.meshgrid(zn_range * 100, cu_range * 100)
cf2 = axes[1].contourf(ZN2, CU, eta_fractions_cu, levels=20, cmap='plasma')
cs2 = axes[1].contour(ZN2, CU, eta_fractions_cu, levels=5, colors='white', linewidths=0.5)
axes[1].clabel(cs2, inline=True, fontsize=8, fmt='%.3f')
axes[1].set_xlabel('Zinc Content (wt%)', fontsize=11)
axes[1].set_ylabel('Copper Content (wt%)', fontsize=11)
axes[1].set_title(f'Zn-Cu Map (Mg = {mg_fixed*100:.1f}%)', fontsize=12)
plt.colorbar(cf2, ax=axes[1], label='Eta-Phase Fraction')
axes[1].scatter([5.6], [1.6], c='cyan', s=100, marker='*', edgecolors='black',
linewidths=1, label='Al-7075 Typical', zorder=5)
axes[1].legend(loc='lower right')
plt.tight_layout()
plt.savefig('01_multicomponent_contour.png', dpi=300, bbox_inches='tight')
print("\nSaved: 01_multicomponent_contour.png")
# =============================================================================
# FIND OPTIMAL COMPOSITION
# =============================================================================
print("\n" + "=" * 70)
print("OPTIMAL COMPOSITION ANALYSIS (FROM REAL CALPHAD DATA)")
print("=" * 70)
max_idx = np.unravel_index(np.nanargmax(eta_fractions), eta_fractions.shape)
opt_mg = mg_range[max_idx[0]] * 100
opt_zn = zn_range[max_idx[1]] * 100
max_eta = eta_fractions[max_idx]
print(f"\nOptimal composition (Zn-Mg space, Cu={cu_fixed*100:.1f}%):")
print(f" Zn = {opt_zn:.2f} wt%")
print(f" Mg = {opt_mg:.2f} wt%")
print(f" Maximum eta-phase fraction = {max_eta:.4f}")
max_idx2 = np.unravel_index(np.nanargmax(eta_fractions_cu), eta_fractions_cu.shape)
opt_cu = cu_range[max_idx2[0]] * 100
opt_zn2 = zn_range[max_idx2[1]] * 100
print(f"\nOptimal composition (Zn-Cu space, Mg={mg_fixed*100:.1f}%):")
print(f" Zn = {opt_zn2:.2f} wt%")
print(f" Cu = {opt_cu:.2f} wt%")
print(f" Maximum eta-phase fraction = {eta_fractions_cu[max_idx2]:.4f}")
print("\n" + "=" * 70)
print("NOTE: All values computed from COST507 thermodynamic database")
print(" No experimental or placeholder data used")
print("=" * 70)
plt.show()