"""
Class for ultimate analysis.
"""
import numpy as np
[docs]
class Ultimate:
"""
Ultimate analysis.
Ultimate analysis values expressed as different bases. Such bases are
as-determined (ad), as-received (ar), dry (d), and dry ash-free (daf).
Parameters
----------
vals : list
Ultimate analysis values given as weight percent (wt. %), μg/g
(trace elements), or Btu/lb (gross calorific value). Order of values
is [C, H, O, N, S, ash, moisture].
basis : str
Basis of given ultimate analysis values. Options are 'ad' for
as-determined basis or 'ar' for as-received basis
ADL : float, optional
Air-dry loss as weight percent
HO : bool, optional
If `True` then the given H and O values include H and O from moisture.
If `False` then the given H and O values exclude H and O from the
moisture content.
Attributes
----------
ad_basis : ndarray
As-determined basis (ad)
ar_basis : ndarray
As-received basis (ar)
d_basis : ndarray
Dry basis (d)
daf_basis : ndarray
Dry ash-free basis (daf)
Raises
------
ValueError
If basis is not ad or ar.
Example
-------
>>> ult = cm.Ultimate([60.08, 5.44, 25.01, 0.88, 0.73, 7.86, 9.00], 'ad')
>>> ult.ar_basis
array([46.8624, 6.705 , 39.046 , 0.6864, 0.5694, 6.1308, 29.02 ])
References
----------
ASTM D3180-15, Standard Practice for Calculating Coal and Coke Analyses
from As-Determined to Different Bases, ASTM International, West
Conshohocken, PA, 2015.
"""
def __init__(self, vals, basis, ADL=22, HO=True):
self.ADL = ADL
self.HO = HO
if basis == "ad":
self.ad_basis = np.array(vals)
self._convert_from_ad()
elif basis == "ar":
self.ar_basis = np.array(vals)
self._convert_from_ar()
else:
raise ValueError("Basis must be 'ad' or 'ar'")
[docs]
def __str__(self):
"""
Return string representation.
"""
ad = self.ad_basis
ar = self.ar_basis
d = self.d_basis
daf = self.daf_basis
s = (
' ad ar d daf\n'
f'C {ad[0]:>10.2f}{ar[0]:>10.2f}{d[0]:>10.2f}{daf[0]:>10.2f}\n'
f'H {ad[1]:>10.2f}{ar[1]:>10.2f}{d[1]:>10.2f}{daf[1]:>10.2f}\n'
f'O {ad[2]:>10.2f}{ar[2]:>10.2f}{d[2]:>10.2f}{daf[2]:>10.2f}\n'
f'N {ad[3]:>10.2f}{ar[3]:>10.2f}{d[3]:>10.2f}{daf[3]:>10.2f}\n'
f'S {ad[4]:>10.2f}{ar[4]:>10.2f}{d[4]:>10.2f}{daf[4]:>10.2f}\n'
f'ash {ad[5]:>10.2f}{ar[5]:>10.2f}{d[5]:>10.2f}{"-":>10}\n'
f'moisture {ad[6]:>6.2f}{ar[6]:>10.2f}{"-":>10}{"-":>10}\n'
f'total {sum(ad):>9.2f}{sum(ar):>10.2f}{sum(d):>10.2f}{sum(daf):>10.2f}'
)
return s
def _convert_from_ad(self):
C_ad = self.ad_basis[0]
H_ad = self.ad_basis[1]
O_ad = self.ad_basis[2]
N_ad = self.ad_basis[3]
S_ad = self.ad_basis[4]
ash_ad = self.ad_basis[5]
M_ad = self.ad_basis[6]
ADL = self.ADL
# As-received basis (ar)
M_ar = (M_ad * (100 - ADL) / 100) + ADL
C_ar = C_ad * (100 - M_ar) / (100 - M_ad)
if self.HO:
H_ar = (H_ad - 0.1119 * M_ad) * (100 - M_ar) / (100 - M_ad) + 0.1119 * M_ar
O_ar = (O_ad - 0.8881 * M_ad) * (100 - M_ar) / (100 - M_ad) + 0.8881 * M_ar
else:
H_ar = (H_ad - 0.1119 * M_ad) * (100 - M_ar) / (100 - M_ad)
O_ar = (O_ad - 0.8881 * M_ad) * (100 - M_ar) / (100 - M_ad)
N_ar = N_ad * (100 - M_ar) / (100 - M_ad)
S_ar = S_ad * (100 - M_ar) / (100 - M_ad)
ash_ar = ash_ad * (100 - M_ar) / (100 - M_ad)
self.ar_basis = np.array([C_ar, H_ar, O_ar, N_ar, S_ar, ash_ar, M_ar])
# Dry basis (d)
C_d = C_ad * 100 / (100 - M_ad)
H_d = (H_ad - 0.1119 * M_ad) * 100 / (100 - M_ad)
O_d = (O_ad - 0.8881 * M_ad) * 100 / (100 - M_ad)
N_d = N_ad * 100 / (100 - M_ad)
S_d = S_ad * 100 / (100 - M_ad)
ash_d = ash_ad * 100 / (100 - M_ad)
self.d_basis = np.array([C_d, H_d, O_d, N_d, S_d, ash_d])
# Dry ash-free basis (daf)
C_daf = C_ad * 100 / (100 - M_ad - ash_ad)
H_daf = (H_ad - 0.1119 * M_ad) * 100 / (100 - M_ad - ash_ad)
O_daf = (O_ad - 0.8881 * M_ad) * 100 / (100 - M_ad - ash_ad)
N_daf = N_ad * 100 / (100 - M_ad - ash_ad)
S_daf = S_ad * 100 / (100 - M_ad - ash_ad)
self.daf_basis = np.array([C_daf, H_daf, O_daf, N_daf, S_daf])
def _convert_from_ar(self):
C_ar = self.ar_basis[0]
H_ar = self.ar_basis[1]
O_ar = self.ar_basis[2]
N_ar = self.ar_basis[3]
S_ar = self.ar_basis[4]
ash_ar = self.ar_basis[5]
M_ar = self.ar_basis[6]
ADL = self.ADL
# As-determined basis (ad)
M_ad = (M_ar - ADL) / ((100 - ADL) / 100)
C_ad = C_ar * (100 - M_ad) / (100 - M_ar)
if self.HO:
H_ad = (H_ar - 0.1119 * M_ar) / ((100 - M_ar) / (100 - M_ad)) + 0.1119 * M_ad
O_ad = (O_ar - 0.8881 * M_ar) / ((100 - M_ar) / (100 - M_ad)) + 0.8881 * M_ad
else:
H_ad = H_ar / ((100 - M_ar) / (100 - M_ad)) + 0.1119 * M_ad
O_ad = O_ar / ((100 - M_ar) / (100 - M_ad)) + 0.8881 * M_ad
N_ad = N_ar * (100 - M_ad) / (100 - M_ar)
S_ad = S_ar * (100 - M_ad) / (100 - M_ar)
ash_ad = ash_ar * (100 - M_ad) / (100 - M_ar)
self.ad_basis = np.array([C_ad, H_ad, O_ad, N_ad, S_ad, ash_ad, M_ad])
# Dry basis (d)
C_d = C_ad * 100 / (100 - M_ad)
H_d = (H_ad - 0.1119 * M_ad) * 100 / (100 - M_ad)
O_d = (O_ad - 0.8881 * M_ad) * 100 / (100 - M_ad)
N_d = N_ad * 100 / (100 - M_ad)
S_d = S_ad * 100 / (100 - M_ad)
ash_d = ash_ad * 100 / (100 - M_ad)
self.d_basis = np.array([C_d, H_d, O_d, N_d, S_d, ash_d])
# Dry ash-free basis (daf)
C_daf = C_ad * 100 / (100 - M_ad - ash_ad)
H_daf = (H_ad - 0.1119 * M_ad) * 100 / (100 - M_ad - ash_ad)
O_daf = (O_ad - 0.8881 * M_ad) * 100 / (100 - M_ad - ash_ad)
N_daf = N_ad * 100 / (100 - M_ad - ash_ad)
S_daf = S_ad * 100 / (100 - M_ad - ash_ad)
self.daf_basis = np.array([C_daf, H_daf, O_daf, N_daf, S_daf])