Carbon Deposition in a C-H-O Ternary Diagram#
This example calculates the carbon deposition boundaries for gas mixtures of the elements carbon, hydrogen and oxygen and shows them in a ternary diagram. On the carbon-rich side of a boundary the formation of solid carbon is thermodynamically favored.
import gaspype as gp
import numpy as np
import matplotlib.pyplot as plt
import ternary
Setting temperatures and pressure. The temperatures get an additional dimension, so they are broadcast against the compositions later on:
t_range = np.array([500, 600, 700, 800]) # °C
t = t_range[:, None] + 273.15 # K
p = 1e5 # Pa
The search for the boundaries below runs through arbitrary atomic compositions.
Oxygen (O2) and atomic carbon gas (C) are part of the fluid system to
make sure that an equilibrium composition exists for each of them, also with
an excess of oxygen or carbon:
fs = gp.fluid_system(['H2', 'H2O', 'CO2', 'CO', 'CH4', 'O2', 'C'])
The mixtures are defined by their atomic composition. Therefore we create
an elements object with one mol of atoms for each of the three elements:
carbon = gp.elements({'C': 1}, fs)
hydrogen = gp.elements({'H': 1}, fs)
oxygen = gp.elements({'O': 1}, fs)
Each mixture is described by its molar fraction of carbon xc and by the
hydrogen fraction of the remaining atoms h_ratio = H / (H + O). The following
function returns the molar fractions of hydrogen and oxygen:
h_ratio = np.linspace(0.01, 0.99, 50)
def get_h_o_fractions(xc):
xh = (1 - xc) * h_ratio
xo = 1 - xc - xh
return xh, xo
gp.carbon_activity calculates the activity of solid carbon (graphite) for
the equilibrium state of a mixture. The shapes of the mixture, the temperature
and the pressure are broadcast against each other, so a single call returns
the activities of all compositions for all temperatures:
def get_carbon_activity(xc):
xh, xo = get_h_o_fractions(xc)
mix = carbon * xc + hydrogen * xh + oxygen * xo
return gp.carbon_activity(mix, t, p)
At the carbon deposition boundary the carbon activity is 1. For each hydrogen ratio and each temperature the carbon fraction of the boundary is searched by bisection: The carbon fraction is increased if the activity is below 1 and decreased otherwise, while the step size is halved with every iteration. All boundaries are searched at once:
xc = np.full((len(t_range), len(h_ratio)), 0.5)
step_size = 0.5
while step_size > 1e-5:
activity = get_carbon_activity(xc)
step_size /= 2
xc = np.where(activity < 1, xc + step_size, xc - step_size)
activity = get_carbon_activity(xc)
xh, xo = get_h_o_fractions(xc)
A search has only found a boundary if its activity ended up close to 1. The other points are discarded:
on_boundary = (activity > 0.9) & (activity < 1.1)
Plot the carbon deposition boundaries. The points of the ternary diagram are tuples of the coordinates for the bottom, the right and the left axis in percent:
scale = 100
fig, ax = plt.subplots(figsize=(10, 8))
tax = ternary.TernaryAxesSubplot(ax=ax, scale=scale)
# Draw boundary and gridlines
tax.boundary(linewidth=2.0)
tax.gridlines(color="black", multiple=10)
tax.left_axis_label("H", fontsize=20, offset=0.2)
tax.right_axis_label("C", fontsize=20, offset=0.2)
tax.bottom_axis_label("O", fontsize=20, offset=0.2)
# Set ticks
tax.ticks(ticks=list(range(0, 110, 20)), axis='lbr', linewidth=1, fontsize=15, offset=0.04)
# Remove default matplotlib ticks and box
tax.clear_matplotlib_ticks()
tax.get_axes().axis('off')
for i, tc in enumerate(t_range):
mask = on_boundary[i]
plot_data = list(zip(xo[i, mask] * scale, xc[i, mask] * scale, xh[i, mask] * scale))
tax.plot(plot_data, label=f'{tc} °C')
tax.legend(fontsize=15)
# The axis labels are only drawn by tax.show() or by this call
tax._redraw_labels()
