Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Shivani-Gonde/ec7e8885c16a28559755897773768c6e to your computer and use it in GitHub Desktop.

Select an option

Save Shivani-Gonde/ec7e8885c16a28559755897773768c6e to your computer and use it in GitHub Desktop.
Cu optimization w.r.t smelter temperature and Feed FeS2 wt%

Optimizing Copper Extraction: Ore Purity–Dependent Smelter Temperature with ChemApp for Python – Part II

Introduction

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.

Challenges affecting blister Cu

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.

Why Optimization of several parameters is necessary?

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.

Process flowsheet for Cu metallurgy

Targets to achieve

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.

Function for computing blister copper mass and grade at a given smelter temperature

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 %

Overcoming the mitigating ore challenge by process parameter optimization

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.

How about optimizing the smelter temperature as a function of impurity in the feed?

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_pct

Example 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:

Process flowsheet for Cu metallurgy

  • 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.

Python multi-objective optimization Results for varying feed Pyrite (FeS₂) wt%

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.

Process flowsheet for Cu metallurgy

Learnings from multi-parameter multi-objective optimization

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)

How can CA4Py optimization results impact real life-operation?

  • 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.

Conclusion

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.

Future work (Part III)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment