Optimizing Copper Extraction: Ore Purity–Dependent Smelter Temperature with ChemApp for Python – Part II
In the previous blogpost (Part I), the development of the integrated process model for Smelter -> Converter -> Converter for Blister has been described. In this series of blogpost II, III, and IV, we will utilize the created process model (CuMetallurgy_Flow) from the Blogpost I to address the challenges that Cu metallurgy faces.
Degradation of copper ore quality is becoming a critical factor in achieving stable blister copper production, because lower grades and more complex ore textures tend to reduce recovery and increase slag losses. Pyrometallurgical copper producers, therefore, face the combined pressure of deteriorating feed quality, high energy demand, tighter environmental regulations, and rising expectations around resource efficiency and circularity. In this series of blog posts, the focus will be on how declining ore grades and more variable concentrates affect blister Cu output, and how these effects can be mitigated through systematic process parameter optimization, particularly by stabilizing smelting and converting operations to maximize copper recovery.
Real-life copper production flowsheets exhibit considerable complexity, with multiple interdependent process parameters governing the final blister copper output. Even in a simplified model, both process parameters (such as smelting temperature, converter temperature, gas flow rate, and oxygen enrichment (O₂/N₂ ratio)) and input parameters (including Cu concentrate composition, silica flux additions, and oxygen injection rate), strongly influence matte grade, slag chemistry, and ultimately blister copper yield and purity. These parameters interact in complex, nonlinear ways. Because each parameter influences not only the final product but also the behavior of other parameters, individual tuning is insufficient. Instead, systematic multi-parameter optimization is essential to navigate the interconnected effects and achieve the desired blister output while maintaining stable operation.
In this case, Pyrite (FeS₂) is introduced as an impurity in the input feed, with its content varied from 0 to 10 wt%, the balance being Chalcopyrite (CuFeS₂). The targets that are set to maximize absolute amount of the blister phase (called Cu-liq_or_speiss), while maintaining an elemental Cu concentration in the blister phase of 98 wt% or higher. In this Blogpost, this will be achieved by optimization of multiple paramters: input feed (input parameter), and process parameter, such as temperature of the smelter to achieve multiple targets described above.
The solution phase model for the speiss and blister corresponds to Cu-liq_or_speiss. In the scope of this Blogpost series, metallic Cu blister liquid is formed (called Cu-liq_or_speiss), and speiss is never formed.
A helper function T_in_Smelter_opt is defined that takes the smelter temperature and the input streams as arguments and returns the blister copper mass and its Cu grade. It sets the smelter temperature, calls the CuMetallurgy_Flow function introduced in Part I of the blog series, and uses its results to report the copper amount and copper percentage in the blister copper. The matte from the smelter goes as input to the converter, after which slag is separated (SiO₂ output), and the matte after the converting process, serves further as an input for the converter for blister. The total amount of resulting blister that is obtained is specific to the input and process parameters.
def T_in_Smelter_opt(T_in_Smelter, Input_streams):
[Copper_input, Gas_input, SiO2_input, Gas_input_converter, SiO2_input_converter, Gas_input_ConverterForBlister] = Input_streams
Smelter_T = T_in_Smelter
Smelter_P = 2.6
Converter_T = 1250
Converter_P = 2.6
ConverterForBlister_T = 1250
ConverterForBlister_P = 2.6
# Passing in all the streams and conditions to calculate the Cu amount and percent at the output of ConverterForBlister in the flow sheet
Cu_in_Cu_liq_speiss, Cu_pct_in_Cu_liq_speiss = CuMetallurgy_Flow(
Copper_input,
Gas_input,
SiO2_input,
Gas_input_converter,
SiO2_input_converter,
Gas_input_ConverterForBlister,
Smelter_T,
Smelter_P,
Converter_T,
Converter_P,
ConverterForBlister_T,
ConverterForBlister_P
)
return [Cu_in_Cu_liq_speiss, Cu_pct_in_Cu_liq_speiss]Example call:
# Smelter streams
Copper_input = casc.create_st_cfs("Copper_input", ["CuFeS2", "H2O"], [920, 80], T=25.0, P=1.0)
Gas_input = casc.create_st_cfs("Gas_input", ["O2", "N2"], [294.032, 385.968], T=25.0, P=2.6)
SiO2_input = casc.create_st_cfs("SiO2_input", ["SiO2"], [100], T=25.0, P=1.0)
# Converter streams
SiO2_input_converter = casc.create_st_cfs("SiO2_input_cnv", ["SiO2"], [17], T=25.0, P=1.0)
# Gas_input_converter
Gas_input_converter = casc.create_st_cfs("Gas_input_converter", ["O2", "N2"], [25.944, 34.056], T=25.0, P=2.6)
#ConverterForBlister
Gas_input_ConverterForBlister = casc.create_st_cfs("Gas_input_ConverterForBlister", ["O2", "N2"], [43.24, 56.76], T=25.0, P=2.6)
# Collecting all input streams
Input_streams=[Copper_input, Gas_input, SiO2_input, Gas_input_converter, SiO2_input_converter, Gas_input_ConverterForBlister]
# Setting smelter temperature
Smelter_T = 1250
# Calling the T_in_Smelter_opt function
Cu_in_Cu_liq_speiss, Cu_pct_in_Cu_liq_speiss = T_in_Smelter_opt(Smelter_T, Input_streams)
print(f"Cu in Blister: {Cu_in_Cu_liq_speiss:.2f} kg")
print(f"Cu wt% in Blister: {Cu_pct_in_Cu_liq_speiss:.2f} %")Cu in Blister: 211.97 kg Cu wt% in Blister: 94.31 %
In this case, the idea was to optimize the smelter temperature for a given feed input. The optimization is repeated at different Pyrite (FeS₂) concentrations in the feed.
- Process parameter (smelter temperature) optimization
To achieve multiple targets:
- Cu wt% ≥98
- Maximizing Cu amount
Optimization repeated easily with a simple for loop in ChemApp for Python.
For a specific Pyrite (FeS₂) wt% in the feed, we obtain a Pareto front, as the amount of Cu and the purity of the Cu in blister are inversely related. The function T_in_Smelter_opt_feed_FeS2_var takes the Pyrite (FeS₂) content in the feed as input and returns the smelter temperature that maximizes blister copper production while maintaining at least 98% Cu purity. Inside the function, all relevant input streams are defined, and T_in_Smelter_opt function is called to evaluate blister copper mass and purity for a given smelter temperature. A multi-objective optimization based on the NSGA‑II genetic algorithm is then used to simultaneously maximize copper mass and purity. The number of generations can be increased to explore a wider temperature range, at the cost of longer computation times. From the resulting Pareto front, only solutions with Cu purity above 98% are retained; among these, the temperature that yields the highest blister copper mass is selected as the optimum, and the corresponding Pareto front is visualized.
def T_in_Smelter_opt_feed_FeS2_var(feed_FeS2_pct):
# ----- Define input streams -----
copper_input = casc.create_st_cfs(
"Copper_input",
["CuFeS2", "FeS2", "H2O"],
[1000 * (92 - feed_FeS2_pct) / 100, 1000 * feed_FeS2_pct / 100, 80],
T=25.0, P=1.0)
gas_input = casc.create_st_cfs(
"Gas_input",
["O2", "N2"],
[294.032, 385.968],
T=25.0, P=2.6)
sio2_input = casc.create_st_cfs(
"SiO2_input",
["SiO2"],
[100],
T=25.0, P=1.0
)
gas_input_converter = casc.create_st_cfs(
"Gas_input_converter",
["O2", "N2"],
[25.944, 34.056],
T=25.0, P=2.6)
sio2_input_converter = casc.create_st_cfs(
"SiO2_input_cnv",
["SiO2"],
[17],
T=25.0, P=1.0)
gas_input_converter_blister = casc.create_st_cfs(
"Gas_input_ConverterForBlister",
["O2", "N2"],
[180 * 0.4324, 180 * 0.5676],
T=25.0, P=2.6)
# ----- Optimization setup -----
from pymoo.core.problem import ElementwiseProblem
from pymoo.optimize import minimize
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.termination import get_termination
class CuBlisterMOO(ElementwiseProblem):
def __init__(self):
super().__init__(
n_var=1,
n_obj=2,
n_constr=1,
xl=1000,
xu=1600,
)
def _evaluate(self, x, out, *args, **kwargs):
T = x[0]
Cu_in, Cu_pct = T_in_Smelter_opt(
T,
Input_streams=[
copper_input,
gas_input,
sio2_input,
gas_input_converter,
sio2_input_converter,
gas_input_converter_blister,
],
)
# maximize Cu_in and Cu_pct -> minimize negatives
out["F"] = [-Cu_in, -Cu_pct]
# constraint: Cu_pct >= 90
out["G"] = [90 - Cu_pct]
problem = CuBlisterMOO()
algorithm = NSGA2(pop_size=100)
termination = get_termination("n_gen", 8)
res = minimize(
problem,
algorithm,
termination,
seed=1,
save_history=True,
verbose=True,
)
# ----- Pareto data -----
pareto_T = res.X
pareto_Cu_in = -res.F[:, 0]
pareto_Cu_pct = -res.F[:, 1]
# sort by Cu_pct (ascending)
sorted_indices = np.argsort(pareto_Cu_pct)
sorted_pareto_Cu_pct = pareto_Cu_pct[sorted_indices]
sorted_pareto_Cu_in = pareto_Cu_in[sorted_indices]
sorted_pareto_T = pareto_T[sorted_indices]
df_sorted = pd.DataFrame(
{
"pareto_Cu_pct": sorted_pareto_Cu_pct,
"pareto_Cu_in": sorted_pareto_Cu_in,
"pareto_T": np.asarray(sorted_pareto_T).flatten(),
}
)
# sort by Cu_in (descending) and pick first with Cu_pct >= 98
desc_indices = np.argsort(-pareto_Cu_in)
sorted_Cu_pct = pareto_Cu_pct[desc_indices]
sorted_Cu_in = pareto_Cu_in[desc_indices]
sorted_T = pareto_T[desc_indices]
optimum_idx = None
for i, Cu_pct in enumerate(sorted_Cu_pct):
if Cu_pct >= 98:
optimum_idx = i
break
if optimum_idx is None:
raise RuntimeError("No Pareto point satisfies Cu_pct >= 98")
optimum_T = float(np.atleast_1d(sorted_T[optimum_idx])[0])
optimum_Cu_in = sorted_Cu_in[optimum_idx]
optimum_Cu_pct = sorted_Cu_pct[optimum_idx]
print("pareto_T value:", optimum_T)
print("pareto_Cu_in:", optimum_Cu_in)
print("pareto_Cu_pct:", optimum_Cu_pct)
return optimum_T, optimum_Cu_in, optimum_Cu_pctExample call:
T_in_Smelter_opt_feed_FeS2_var(feed_FeS2_pct=10)pareto_T value: 1371.651760296213 pareto_Cu_in: 273.0226327344225 pareto_Cu_pct: 98.01292213404946
Pareto front plot:
- The star in the plot represents the optimum smelter T that you could obtain where wt% Cu wt% ≥98 and also the Cu amount obtained is maximum.
For Pyrite (FeS₂) contents between 0 and 10 wt% in the feed (with the gas flow to the converter for blister fixed at 180 kg), an optimum smelter temperature can be identified for each impurity level. The optimization procedure from the above section is repeated for different feed streams containing 0-10 wt% Pyrite impuirty using a FOR loop in ChemApp For Python. As the Pyrite (FeS₂) content increases, the required optimal smelting temperature also rises. At the same time, higher Pyrite (FeS₂) means more iron and relatively less copper in the feed, so the optimum copper amount available in the converter for blister production decreases, leading to a lower achievable copper extraction.
As Fe content in the feed increases
- Optimum T at which the smelter should be run increases attributing to higher melting point of Fe (the maximum temperature in practice is roughly ~1350 degree C)
- Optimum Cu blister amount (maximum) that can be extracted decreases attributing to lower Cu wt% in the concentrate (as Fe wt% rises in the impurity)
- Estimate of Optimum T at which the smelter should be run at a given Pyrite (FeS₂) impurity in the feed
- Maximum possible amount of Cu output at a given Pyrite (FeS₂) impurity in the feed.
- Financial estimations on what to promise the customers, depending on the optimum Cu you would be able to extract
- Financial decisions on whether a given ore (raw material) is useful enough to help you achieve a given Cu amount and wt% as target, whether processing is feasible.
In this case study, the integrated ChemApp for Python model was used to link ore quality, smelter temperature, and downstream converting behavior to blister copper yield and grade in a consistent thermochemical framework. By coupling the CuMetallurgy_Flow flowsheet with NSGA‑II-based multi-objective optimization, it became possible to quantify how increasing Pyrite (FeS₂) impurities demand higher smelting temperatures while simultaneously constraining the maximum achievable blister Cu output. This workflow shows how advanced thermodynamic modeling, combined with modern optimization tools, can translate complex, interdependent process and feed parameters into actionable guidance on furnace setpoints, realistic production targets, and ore selection decisions: ultimately supporting more profitable copper operations.
Part III (coming soon) will explore how multi parameter optimization, with optimizing input parameter (gas input in the converter for Blister) along with another input parameter (input feed) can be performed to achieve multiple objectives, as demonstrated in this example.


