Best Practices

Re-use of the single equilibrium calculation state

The Thermo-Calc core keeps an internal state containing the data from previously performed calculations (such as composition of sublattices, previously formed phases, …). This will be used for start values of future calculations (if not explicitly overwritten) and can strongly influence their convergence and calculation time. It can be useful to save and restore later the core-state in advanced use cases, these include:

  • Improving the convergence speed in case of very complicated equilibria if a similar equilibrium had been calculated already before. Similarity refers here primarily to composition, temperature and entered phase set. This case can occur for example with the Nickel-database TCNi.

  • Convenient and fast switching between states that have changed a lot (for example regarding suspended phases, numerical settings, …)

The mechanism of saving and restoring the state is called bookmarking and is controlled with the two methods tc_python.single_equilibrium.SingleEquilibriumCalculation.bookmark_state() and tc_python.single_equilibrium.SingleEquilibriumCalculation.set_state_to_bookmark(). The following short example demonstrates how to switch between two different states easily in practice:

from tc_python import *

with TCPython() as session:
    calc = (session.
            select_database_and_elements("FEDEMO", ["Fe", "C"]).
            get_system().
            with_single_equilibrium_calculation().
            set_condition(ThermodynamicQuantity.temperature(), 2000.0).
            set_condition("X(C)", 0.01))

    calc.calculate()
    bookmark_temp_condition = calc.bookmark_state()

    calc.set_phase_to_fixed("BCC", 0.5)
    calc.remove_condition(ThermodynamicQuantity.temperature())
    bookmark_fixed_phase_condition = calc.bookmark_state()

    result_temp = calc.set_state_to_bookmark(bookmark_temp_condition)
    print("Conditions do contain temperature: {}".format(result_temp.get_conditions()))
    # this calculation had already been performed
    print("Stable phases (do not contain BCC): {}".format(result_temp.get_stable_phases()))

    result_fixed_phase = calc.set_state_to_bookmark(bookmark_fixed_phase_condition)
    print("Conditions do not contain temperature: {}".format(result_fixed_phase.get_conditions()))
    # this calculation had **not yet** been performed
    print("Stable phases (do contain BCC): {}".format(calc.calculate().get_stable_phases()))

Re-use and saving of results

Before a calculation is run in TC-Python, a check is made to see if the exact same calculation has run before, and if that is the case, the result from the calculation can be loaded from disk instead of being re-calculated.

This functionality is always enabled within a script running TC-Python, but you can make it work the same way when re-running a script, or even when running a completely different script.

To use results from previous calculations, set a folder where TC-Python saves results, and looks for previous results.

This is controlled by the method tc_python.server.SetUp.set_cache_folder().

from tc_python import *

with TCPython() as start:
   start.set_cache_folder("cache")

This folder can be a network folder and shared by many users. The calculation is not re-run if there is a previous TC-Python calculation with the same cache folder and exactly the same settings; the result is instead loaded from disk.

Another possibility is to explicitly save the result to disk and reload it later:

from tc_python import *

with TCPython() as start:
    # ... the system and calculator are set up and the calculation is performed
    result = calculator.calculate()

    result.save_to_disk("./result_dir")

You can then load the result again in another session:

from tc_python import *

with TCPython() as start:
    result = SetUp().load_result_from_disk().diffusion("./result_dir")
    x, frac = result.get_mole_fraction_of_component_at_time("Cr", 1000.0)

All TC-Python objects are non-copyable

Never create a copy of an instance of a class in TC-Python, neither by using the Python built-in function deepcopy() nor in any other way. All classes in TC-Python are proxies for classes in the underlying calculation server and normally hold references to result files. A copied class object in Python would consequently point to the same classes and result files in the calculation server.

Instead of making a copy, always create a new instance:

from tc_python import *

with TCPython() as start:
    system = start.select_database_and_elements("FEDEMO", ["Fe", "Cr"]).get_system()
    calculator = system.with_single_equilibrium_calculation()

    # *do not* copy the `calculator` object, create another one instead
    calculator_2 = system.with_single_equilibrium_calculation()

    # now you can use both calculators for different calculations ...

Python Virtual Environments

A Python installation can have several virtual environments. You can think of a virtual environment as a collection of third party packages that you have access to in your Python scripts. tc_python is such a package.

To run TC-Python, you need to install it into the same virtual environment as your Python scripts are running in. If your scripts fail on import tc_python, you need to execute the following command in the terminal of the same Python environment as your script is running in:

pip install TC_Python-<version>-py3-none-any.whl

If you use the PyCharm IDE, you should do that within the Terminal built into the IDE. This Terminal runs automatically within your actual (virtual) environment.

To prevent confusion, it is recommend in most cases to install TC-Python within your global interpreter, for example by running the pip install command within your default Anaconda prompt.

Using with TCPython() efficiently

Normally you should call with TCPython() only once within each process.

Note

When leaving the with-clause, the Java backend engine process is stopped and all temporary data is deleted. Finally when entering the next with-clause a new Java process is started. This can take several seconds.

If appropriate, it is safe to run with TCPython() in a loop. Due to the time it takes this only makes sense if the calculation time per iteration is longer than a minute.

To prevent calling with TCPython() multiple times and cleaning up temporary data, you can use the following pattern.

Example:

from tc_python import *

# ...

def calculation(calculator):
    # you could also pass the `session` or `system` object if more appropriate
    calculator.set_condition("W(Cr)", 0.1)
    # further configuration ...

    result = calculator.calculate()
    # ...
    result.invalidate()  # if the temporary data needs to be cleaned up immediately


if __name__ == '__main__':
    with TCPython() as session:
        system = session.select_database_and_elements("FEDEMO", ["Fe", "Cr"]).get_system()
        calculator = system.with_single_equilibrium_calculation()

        for i in range(50):
            calculation(calculator)

Parallel calculations

It is possible to perform parallel calculations with TC-Python using multi-processing.

Note

Please note that multi-threading is not suitable for parallelization of computationally intensive tasks in Python. Additionally the Thermo-Calc core is not thread-safe. Using suitable Python-frameworks it is also possible to dispatch the calculations on different computers of a cluster.

A general pattern that can be applied is shown below. This code snippet shows how to perform single equilibrium calculations for different compositions in parallel. In the same way all other calculators of Thermo-Calc can be used or combined. For performance reasons in a real application, probably numpy arrays instead of Python arrays should be used.

Example:

import concurrent.futures

from tc_python import *


def do_perform(parameters):
    # this function runs within an own process
    with TCPython() as start:
        elements = ["Fe", "Cr", "Ni", "C"]
        calculation = (start.select_database_and_elements("FEDEMO", elements).
                       get_system().
                       with_single_equilibrium_calculation().
                       set_condition("T", 1100).
                       set_condition("W(C)", 0.1 / 100).
                       set_condition("W(Ni)", 2.0 / 100))

        phase_fractions = []
        cr_contents = range(parameters["cr_min"],
                            parameters["cr_max"],
                            parameters["delta_cr"])
        for cr in cr_contents:
            result = (calculation.
                      set_condition("W(Cr)", cr / 100).
                      calculate())

            phase_fractions.append(result.get_value_of("NPM(BCC_A2)"))

    return phase_fractions


if __name__ == "__main__":
    parameters = [
        {"index": 0, "cr_min": 10, "cr_max": 15, "delta_cr": 1},
        {"index": 1, "cr_min": 15, "cr_max": 20, "delta_cr": 1}
    ]

    bcc_phase_fraction = []
    num_processes = 2

    with concurrent.futures.ProcessPoolExecutor(num_processes) as executor:
        for result_from_process in zip(parameters, executor.map(do_perform, parameters)):
            # params can be used to identify the process and its parameters
            params, phase_fractions_from_process = result_from_process
            bcc_phase_fraction.extend(phase_fractions_from_process)

    # use the result in `bcc_phase_fraction`, for example for plotting

Handling crashes of the calculation engine

In some cases the Thermo-Calc calculation engine can crash. If batch calculations are performed, this brings down the complete batch. To handle this situation there is an exception you can use.

UnrecoverableCalculationException

That exception is thrown if the calculation server enters a state where no further calculations are possible. You should catch that exception outside of the with TCPython() clause and continue within a new with-clause.

Example:

from tc_python import *

for temperature in range(900, 1100, 10):
    try:
        with TCPython() as start:
            diffusion_result = (
                start.
                    select_thermodynamic_and_kinetic_databases_with_elements("FEDEMO", "MFEDEMO", ["Fe", "Ni"]).
                    get_system().
                    with_isothermal_diffusion_calculation().
                        set_temperature(temperature).
                        set_simulation_time(108000.0).
                        add_region(Region("Austenite").
                            set_width(1E-4).
                            with_grid(CalculatedGrid.linear().set_no_of_points(50)).
                            with_composition_profile(CompositionProfile().
                                add("Ni", ElementProfile.linear(10.0, 50.0))
                            ).
                        add_phase("FCC_A1")).
                calculate())

            distance, ni_fraction = diffusion_result.get_mass_fraction_of_component_at_time("Ni", 108000.0)
            print(ni_fraction)

    except UnrecoverableCalculationException as e:
        print('Could not calculate. Continuing with next...')

Using TC-Python within a Jupyter Notebook or the Python console

TC-Python can also be used from within an interactive Jupyter Notebook and a Python console as well as similar products. The main difference from a regular Python program is that it is not recommended to use a with-clause to manage the TC-Python resources. That is only possible within a single Jupyter Notebook cell. Instead the standalone functions tc_python.server.start_api_server() and tc_python.server.stop_api_server() should be used for manually managing the resources.

Note

The resources of TC-Python are primarily the Java-process running on the backend side that performs the actual calculations and the temporary-directory of TC-Python that can grow to a large size over time, especially if precipitation calculations are performed. If a with-clause is used, these resources are automatically cleared after use.

You need to make sure that you execute the two functions tc_python.server.start_api_server() and tc_python.server.stop_api_server() exactly once within the Jupyter Notebook session. If not stopping TC-Python, extra Java-processes might be present and the temporary disk-space is not cleared. However, these issues can be resolved manually.

The temporary directories of TC-Python are named, for example, TC_TMP4747588488953835507 that has a random ID. The temporary directory on different operating systems varies according to the pattern shown in the table.

Operating system

Temporary directory

Windows

C:\Users{UserName}\AppData\Local\Temp\TC_TMP4747588488953835507

MacOS

/var/folders/g7/7du81ti_b7mm84n184fn3k910000lg/T/TC_TMP4747588488953835507

Linux

/tmp/TC_TMP4747588488953835507

In a Jupyter Notebook some features of an IDE such as auto-completion (TAB-key), available method lookup (press . and then TAB) and parameter lookup (set the cursor within the method-parenthesis and press SHIFT + TAB or SHIFT + TAB + TAB for the whole docstring) are also available.

Example using TC-Python with a Jupyter Notebook:

_images/jupyter_notebook_screenshot.png

Property Model Framework

Debugging Property Model code

You can debug property models while running them from Thermo-Calc.

  • Start Thermo-Calc and create a Property Model calculator.

  • Select the model you want to debug and check the debug checkbox in the lower right corner of the Python code tab.

_images/debugging_property_models_thermocalc.png

Now the model that you want to debug has been updated with code needed to connect with Thermo-Calc.

  • Start debugging the model in the IDE of your choice.

Note

You must use a Python interpreter where TC-Python is installed.

In PyCharm it looks like this:

_images/debugging_property_models_ide.png

Note

When your IDE and Thermo-Calc have sucessfully connected, you will see this in the Thermo-Calc log:

10:34:42,170 INFO  Waiting for developer(!) to start Python process in debugger...DrivingForcePythonModel
10:34:42,171 INFO  Connected successfully to the Python process for the model 'DrivingForcePythonModel' in DEBUG mode

You can stop the debug session in your IDE, change the model code, and start debugging again. The changes you made will take effect in Thermo-Calc without the need to restart. If you for instance changed the method evaluate_model(), the change will take effect the next time you press Perform.

It is also possible to start the models from TC-Python. The workflow is exactly the same as described above, except instead of starting Thermo-Calc graphical user interface, you start a Python script and use the parameter debug_model=True when selecting your model.

from tc_python import *

with TCPython() as start:
    property_model = (
        start.
            select_database_and_elements("FEDEMO", ["Fe", "C"]).
            get_system().
            with_property_model_calculation("my own Driving Force", debug_model=True).
            set_composition("C", 1.0).
            )
    property_model.calculate()

    ...

Developing Property Models in several files

You can split your Property Model code in several .py files, and there are two ways of doing that:

  • side-by-side modules

  • common modules

Side-by-side modules are Python files located in the same folder as the Property Model.

Common modules are Python files located in a folder outside of the Property Model folder, which makes it possible to share them with several models as a common library.

side-by-side modules

You are required to:

  • Add a __init__.py file to your Property Model folder

  • Add all imports of side-by-side modules in your main Property Model Python file also to the __init__.py file

Example:

CriticalTemperaturesPythonModel.py (The main Property Model file):

from CriticalTemperaturesPython import CriticalTemperatures
from tc_python import *
import numpy as np

class CriticalTemperaturesPythonModel(PropertyModel):
   ...

__init__.py:

from CriticalTemperaturesPython.critical_temperatures_library import CriticalTemperatures

If you are using PyCharm, the package name of the Property Model might be highlighted as an error, in this case you can mark the Property Model directory (i.e. the root of the present model directory) by right-clicking on it in the project window of PyCharm and marking it as Sources Root:

_images/property_models_with_multiple_files__pycharm_sources_root.png

critical_temperatures_library.py:

from tc_python import *
import numpy as np
from scipy import optimize
from enum import Enum

class CriticalTemperatures(object):
   ...

Note

Modules installed in the Python interpreter such as numpy, scipy, etc can be imported as normal. This only concerns files imported as side-by-side modules.

common modules

common modules work very similar to side-by-side modules except the import statements are done in the “main” __init__.py file in Property Model directory.

You are required to:

  • Add a __init__.py file to your property model folder.

  • Add all imports of common modules in your main property model python file also to both the __init__.py file in Property Model directory AND the __init__.py of the property model.

Example:

CriticalTemperaturesPythonModel.py (The main Property Model file):

from PropertyModels import Martensite
from tc_python import *

class CriticalTemperaturesPythonModel(PropertyModel):
   ...

__init__.py: (The init file located in the property model folder)

from PropertyModels import Martensite

__init__.py: (The init file located in Property Model directory)

from PropertyModels.common.martensite_library import Martensite

The file critical_temperatures_library.py should in this example be located in a folder called common in the Property Model directory.

critical_temperatures_library.py:

from tc_python import *
import numpy as np
from scipy import optimize
from enum import Enum

class CriticalTemperatures(object):
   ...

Note

common modules don’t have to be located in folder called common. It can be any name, as long as the imports match the folder name.

Note

In this example the Property Model directory is called ‘PropertyModels’. If you use a different directory for your property model, your imports have to match that.

Alternative Python for Property Models

Default bundled Python interpreter

Thermo-Calc is by default using a Python 3.7.2 interpreter bundled to the software for running the property models. It is containing the following major packages:

Package

Version

matplotlib

3.3.2

numpy

1.19.2

scikit-learn

0.23.2

scipy

1.5.2

TC-Python

2023a

Warning

Any changes to the interpreter packages can therefore break Thermo-Calc and should be avoided. If the installation gets broken, it can be fixed by reinstalling Thermo-Calc after having removed it.

Please contact the Thermo-Calc support if you think that further packages might be useful in future releases. If these packages are insufficient for you, it is possible to use another Python-interpreter: Configuring another Python interpreter.

The interpreter is located in different places depending on the platform:

Operating system

Path to the bundled Python-interpreter

Windows

C:\Program Files\Thermo-Calc\2023a\python\python.exe

Linux

/home/UserName/Thermo-Calc/2023a/python/bin/python3

MacOS

/Applications/Thermo-Calc-2023a.app/Contents/Resources/python/bin/python3

Configuring another Python interpreter

If you require additional Python-packages or prefer to use your own interpreter installed on your system, you can change the interpreter used by Thermo-Calc to run the property models. Select Tools→Options in the Thermo-Calc GUI and modify the path to that of your Python 3 interpreter of choice:

_images/python_interpreter_path_screenshot.png

Process Metallurgy Calculations

Equilibrium calculations with changing elements between calculations

It is possible to add, change or remove additions after performing an equilibrium calculation using tc_python.process_metallurgy.equilibrium.EquilibriumCalculation.calculate(). This will change the elements being present in the system if the elements of the additions are differing. The Process Metallurgy Module will handle this situation by reloading the database with the latest set of elements. While this is an appropriate approach in most cases, there can be some disadvantages: reloading the database takes some time and the internal engine state is lost, which may lead to successive calculations failures in some situations.

To avoid the database reload, it is possible to add the respective elements to additions being present in all calculations (with a zero-fraction):

from tc_python import *

with TCPython() as session:
    calc = session.with_metallurgy().with_adiabatic_equilibrium_calculation(ProcessDatabase.OXDEMO)

    # add the element Al with zero-fraction already
    steel = EquilibriumAddition({'Fe': None, 'C': 4, 'Al': 0}, amount=100.0e3, temperature=1700 + 273.15)
    slag = EquilibriumAddition({'CaO': 70, 'SiO2': 30}, amount=3.0e3, temperature=1700 + 273.15)

    al_addition = EquilibriumAddition({'Al': 100}, amount=1.0e3)

    (calc
     .add_addition(steel)
     .add_addition(slag))

    result_1 = calc.calculate()

    calc.add_addition(al_addition)

    result_2 = calc.calculate()
    # evaluate the result as required ...

Or to add a later addition already before the first call to calculate() with a zero amount:

from tc_python import *

with TCPython() as session:
    calc = session.with_metallurgy().with_adiabatic_equilibrium_calculation(ProcessDatabase.OXDEMO)

    steel = EquilibriumAddition({'Fe': None, 'C': 4}, amount=100.0e3, temperature=1700 + 273.15)
    slag = EquilibriumAddition({'CaO': 70, 'SiO2': 30}, amount=3.0e3, temperature=1700 + 273.15)

    # add the addition for now with zero-amount
    al_addition = EquilibriumAddition({'Al': 100}, amount=0)

    (calc
     .add_addition(al_addition)
     .add_addition(steel)
     .add_addition(slag))

    result_1 = calc.calculate()

    calc.update_addition(al_addition.set_amount(1.0e3))

    result_2 = calc.calculate()
    # evaluate the result as required ...

Zones

TC-Python is providing a framework for building time-dependent kinetic simulations of industrial and academic metallurgical processes where liquid phases are important. It is based on an Effective Equilibrium Reaction Zone (EERZ) approach which is separating a process into different zones. These zones have identical temperature and composition and are called bulk zones. Such zones can be in contact and react with each other by reaction zones. That means a reaction zone is modelling the interface between two bulk zones. One bulk zone is typically the steel melt and another bulk zone the top slag.

Applications

While this approach can in principle be extended to any number of zones, in the current release TC-Python is providing only one reaction zone. Practical work has however proven that this limitation is not critical for a lot of industrial processes, including ladle furnaces, AOD- and VOD-converters. Even more processes can be modelled with some limit of accuracy.

The reason for the power of the current implementation is that a number of important process features can be included:

Please note that many of these features are called as well a reaction zone in other EERZ model implementations.

Implementation of practical process models

The Process Metallurgy Module has been successfully applied to a number of industrial processes.

Due to the broad range of industrial metallurgical processes, TC-Python is not providing ready-to-use models for certain processes. There are however examples available for common processes and this collection will be extended over time. The implementation of a model is an abstraction of the real process and should always be kept as simple as possible. Practical experience has proven that in many situations not more than one reaction zone is required.

The mass transfer coefficient is a fundamental parameter describing the kinetics in a reaction zone and is generally an empirical parameter. It depends however mostly on the geometry and stirring conditions in the process and not on the material compositions. Further on, the mass transfer coefficient has usually typical values for a given process - regardless of the actual furnace. That means that existing suggestions from the literature can be used as a starting point to derive the actual mass transfer coefficient for the process of interest.