fpex0.CP API documentation

# fpex0.CP

View Source

      1from scipy import interpolate
      2from numpy.polynomial import Polynomial
      3import numpy as np
      4
      5from fpex0.baseline import getBaseline
      6from fpex0.baseline import subtractBaseline
      7
      8
      9
     10class HeatCapacity():
     11   """
     12   Stores cp, usually calculated by fpex0.CP.addCP().
     13
     14   ## Parameters
     15   **T**: Temperatures
     16
     17   **values**: Corresponding heat capacities
     18
     19   **fun**: Interpolant (of T, values) (function handle).
     20
     21   **latentdata**
     22   <br> Values with subtracted baseline.
     23
     24   **latentfun**
     25   <br> Interpolant of latentdata as (function handle).
     26   """
     27   def __init__(self, T=None, values=None, fun=None, latentdata=None, latentfun=None):
     28      self.T            = T
     29      self.values       = values
     30      self.fun          = fun
     31      self.latentdata   = latentdata
     32      self.latentfun    = latentfun
     33
     34def CP_DIN11357(T, mS, dscS, cpR, mR, dscR, dsc0=0):
     35   """
     36
     37   Calculates the (apparent) specific heat capacity.
     38
     39   It applies the "heat flow calibration" method: A known reference cp (of sapphire)
     40   is rescaled using the mass-ratio and signal ratio of reference and sample.
     41
     42   >     cpS(T) = cpR(T) * mR/mS * (dscS(T)-dsc0(T)) / (dscR(T)-dsc0(T))
     43
     44   ### Reference
     45   DIN EN ISO 11357-4:2014 <br>
     46   Plastics - Differential scanning calorimetry <br>
     47   Determination of specific heat capacity
     48
     49
     50   ## Takes
     51
     52   **T**: Vector of temperatures to evaluate the cp-value
     53
     54   **mS**: Mass of sample
     55
     56   **dscS**: DSC signal of sample (microvolts)
     57
     58   **cpR**: cp-values of reference (cf. fpex0.CP_sapphire_DIN11357())
     59
     60   **mR**: Mass of reference
     61
     62   **dscR**: DSC signal of reference (microvolts)
     63
     64   **dsc0**: DSC signal with two empty crucibles
     65
     66
     67   ## Returns
     68   **cpS**: cp-values of sample at specified temperatures.
     69
     70   ### Comments
     71     * If vectors are given, they must coincide in size and meaning, <br>
     72              i.e.  dscS[k], dscR[k], dsc0[k], cpR[k] all correspond to temperature T[k]
     73     * If dscS and dscR are already baseline-corrected, set dsc0 to 0
     74
     75   """
     76   # calculate cp
     77   cpS = cpR * mR/mS * (dscS-dsc0) / (dscR-dsc0)
     78
     79   return cpS
     80
     81
     82def addCP(DSCsample, DSCreference, DSC0=0, Tmin=55, Tmax=160, bltype='linear'):
     83   """
     84   Adds cp values to DSC data structure (setup.DSC_Data).
     85   Calculation of cp is done with CP_DIN11357.
     86
     87   ## Takes
     88   **DSCsample**
     89   <br> fpex0.setup.DSC_Data object or list of objects, sample - (e.g. pcm)
     90
     91   **DSCreference**
     92   <br> fpex0.setup.DSC_Data object, reference (e.g. saphire)
     93
     94   **DSC0**
     95   <br> DSC signal with two empty crucibles.                 [default: 0]
     96   <br> Only needed if signals are not zero-corrected yet.
     97
     98   **Tmin**
     99   <br> Lower temperature bound.                             [default:  55 degC]
    100
    101   **Tmax**
    102   <br> Upper temperature bound.                             [default: 160 degC]
    103
    104
    105   ## Returns
    106   **DSCsample**
    107   <br> fpex0.setup.DSC_Data object or list of objects with additional field cp.
    108   """
    109
    110   # (TODO) Make these controllable
    111   scaleByMass = True  # multiply signaly by mass (signals are often normed to mass 1)
    112   scaleToRate = True  # scale signals to heatrate (higher rates induce higher signals, see below)
    113   TrangeWarn  = True  # warn if Tmin or Tmax exceed sample's or reference's temperature range
    114
    115   # make function applicable for struct arrays
    116   if type(DSCsample) is list:
    117      DSCsample = [addCP(DSCsample[i], DSCreference, DSC0, Tmin, Tmax, bltype) for i in range(len(DSCsample))]
    118      return np.array(DSCsample)
    119
    120   # retrieve the heating rates
    121   betaS = DSCsample.rate
    122   betaR = DSCreference.rate # possibly a vector
    123
    124   # reference rates not unique?
    125   if type(betaR) is np.ndarray and len(betaR) != len(set(betaR)):
    126      print(f'DSC204_addCP: Reference heat rates not unique! rate vector = {betaR}\n')
    127      raise ValueError('Reference heat rates not unique!')
    128
    129   print(f'DSC204_addCP: Processing ID={DSCsample.ID}')
    130   # Variable naming: AB
    131   #    A:    T -> Temperature   m -> mass   dsc -> dsc data
    132   #    B:    S -> Sample    R -> Reference
    133
    134   # quick accessors
    135   TR   = DSCreference.T
    136   TS   = DSCsample.T
    137   dscS = DSCsample.dsc
    138   dscR = DSCreference.dsc
    139
    140   # calculate minima and maxima of sample and reference temperatures
    141   minTS = min(TS)
    142   maxTS = max(TS)
    143   minTR = min(TR)
    144   maxTR = max(TR)
    145
    146   # issue a warning if Tmax or Tmin exceeds reference or sample temperature range
    147   if ( Tmin < max(minTS,minTR)  or  Tmax > min(maxTS,maxTR) ):
    148      if TrangeWarn:
    149         print('WARNING: Specified Tmin or Tmax exceeds sample or reference temperature range. Will be adjusted!')
    150
    151
    152   # determine Tmin and Tmax
    153   Tmin = max( [minTS, minTR, Tmin] )
    154   Tmax = min( [maxTS, maxTR, Tmax] )
    155
    156   # restrict temperatures and align everything at the temperature information of the sample measurement
    157   # note: this is the temperature of the empty reference crucible, not of the sample itself
    158   # get the signals of the reference corresponding to the temperatures of the sample.
    159   # we use linear interpolation and extrapolation here
    160   idxR = [i for i in range(len(TR)) if Tmin <= TR[i] and TR[i] <= Tmax]
    161   idxS = [i for i in range(len(TS)) if Tmin <= TS[i] and TS[i] <= Tmax]
    162   dscR = dscR[idxR]
    163   dscS = dscS[idxS]
    164   TR   = TR[idxR]
    165   TS   = TS[idxS]
    166   dscR_interp = interpolate.interp1d(TR, dscR, kind='linear', fill_value='extrapolate')
    167   dscR = dscR_interp(TS)
    168
    169   # masses
    170   mS = DSCsample.mass     # mass of sample
    171   mR = DSCreference.mass  # mass of reference
    172
    173   # measurements are normalized to uV/mg, so we recover the original signal by multiplying with mass
    174   if scaleByMass:
    175      dscS = mS * dscS
    176      dscR = mR * dscR
    177
    178   # from carefully looking at the measurement data, we see that the voltage signal is proportional
    179   # to the heating rate, with proportionality constant approximately 1.
    180   # so we normalize both the sample and the reference signal to a heating rate of 1.0 K/min.
    181   # NOTE: this does also not interfere if betaR==betaS
    182   if scaleToRate:
    183      dscS = dscS / betaS
    184      dscR = dscR / betaR
    185
    186   # now retrieve the reference cp values of saphire (unit: degC)
    187   cpR = CP_sapphire_DIN11357(TS, 'degC')
    188
    189   # calculate cp of sample
    190   cpS = CP_DIN11357(TS, mS, dscS, cpR, mR, dscR, DSC0)
    191
    192   # store cp values and associated temperatures
    193   cp = HeatCapacity()
    194   cp.values = cpS
    195   cp.T      = TS
    196
    197   # store the piecewise polynomial with pchip interpolation; python checks bounds (Tmin, Tmax) for us
    198   cp.fun     = interpolate.PchipInterpolator(TS, cpS)
    199
    200   # get the baseline
    201   blfun, bldata = getBaseline(TS, cpS, bltype)
    202
    203   # build the latent cp function  subtractBaseline(X,  Yin, blfun, onset,        endset       , clearzero, nonnegative)
    204   latentCPvals, latentCPfun =     subtractBaseline(TS, cpS, blfun, bldata.onset, bldata.endset, False    , True       )
    205   cp.latentdata  = latentCPvals
    206   cp.latentfun   = latentCPfun
    207
    208   # store it in DSC data
    209   DSCsample.cp = cp
    210   DSCsample.bldata = bldata
    211   DSCsample.blfun = blfun
    212   return DSCsample
    213
    214
    215def CP_sapphire_DIN11357(T, unit='degC'):
    216   """
    217   Delivers specific heat capacity of saphire for specified temperature according to DIN EN ISO 11357-4:2014-10.
    218
    219   ## Takes
    220   **T**: Temperature <br>
    221   **unit**: one of "degC" or "K"    [default: degC]
    222
    223   ## Returns
    224   **cp**
    225   <br> Literature value of sapphire as functor.
    226
    227
    228   [!] *Note*: This approximation is only valid in the interval 100K < T < 1200K.
    229   """
    230
    231   # coefficients in J/(g*K)
    232   A = [
    233       1.12705,
    234       0.23260,
    235      -0.21704,
    236       0.26410,
    237      -0.23778,
    238      -0.10023,
    239       0.15393,
    240       0.54579,
    241      -0.47824,
    242      -0.37623,
    243       0.34407
    244   ]
    245
    246   # linear transformations of temperature
    247   if unit.lower() in ['degc', 'c', 'celsius']:
    248      x = (T - 376.85) / 550
    249   elif unit.lower() in ['k', 'kelvin']:
    250      x = (T - 650) / 550
    251   else:
    252      raise ValueError(f"Unknown unit: {unit}")
    253
    254   # build and evaluate polynomial
    255   cp_poly = Polynomial(A)
    256   cp = cp_poly(x)
    257
    258   return cp

class HeatCapacity: View Source

    11class HeatCapacity():
    12   """
    13   Stores cp, usually calculated by fpex0.CP.addCP().
    14
    15   ## Parameters
    16   **T**: Temperatures
    17
    18   **values**: Corresponding heat capacities
    19
    20   **fun**: Interpolant (of T, values) (function handle).
    21
    22   **latentdata**
    23   <br> Values with subtracted baseline.
    24
    25   **latentfun**
    26   <br> Interpolant of latentdata as (function handle).
    27   """
    28   def __init__(self, T=None, values=None, fun=None, latentdata=None, latentfun=None):
    29      self.T            = T
    30      self.values       = values
    31      self.fun          = fun
    32      self.latentdata   = latentdata
    33      self.latentfun    = latentfun

Stores cp, usually calculated by fpex0.CP.addCP().

## Parameters

T: Temperatures

values: Corresponding heat capacities

fun: Interpolant (of T, values) (function handle).

latentdata
Values with subtracted baseline.

latentfun
Interpolant of latentdata as (function handle).

HeatCapacity(T=None, values=None, fun=None, latentdata=None, latentfun=None) View Source

    28   def __init__(self, T=None, values=None, fun=None, latentdata=None, latentfun=None):
    29      self.T            = T
    30      self.values       = values
    31      self.fun          = fun
    32      self.latentdata   = latentdata
    33      self.latentfun    = latentfun

def CP_DIN11357(T, mS, dscS, cpR, mR, dscR, dsc0=0): View Source

    35def CP_DIN11357(T, mS, dscS, cpR, mR, dscR, dsc0=0):
    36   """
    37
    38   Calculates the (apparent) specific heat capacity.
    39
    40   It applies the "heat flow calibration" method: A known reference cp (of sapphire)
    41   is rescaled using the mass-ratio and signal ratio of reference and sample.
    42
    43   >     cpS(T) = cpR(T) * mR/mS * (dscS(T)-dsc0(T)) / (dscR(T)-dsc0(T))
    44
    45   ### Reference
    46   DIN EN ISO 11357-4:2014 <br>
    47   Plastics - Differential scanning calorimetry <br>
    48   Determination of specific heat capacity
    49
    50
    51   ## Takes
    52
    53   **T**: Vector of temperatures to evaluate the cp-value
    54
    55   **mS**: Mass of sample
    56
    57   **dscS**: DSC signal of sample (microvolts)
    58
    59   **cpR**: cp-values of reference (cf. fpex0.CP_sapphire_DIN11357())
    60
    61   **mR**: Mass of reference
    62
    63   **dscR**: DSC signal of reference (microvolts)
    64
    65   **dsc0**: DSC signal with two empty crucibles
    66
    67
    68   ## Returns
    69   **cpS**: cp-values of sample at specified temperatures.
    70
    71   ### Comments
    72     * If vectors are given, they must coincide in size and meaning, <br>
    73              i.e.  dscS[k], dscR[k], dsc0[k], cpR[k] all correspond to temperature T[k]
    74     * If dscS and dscR are already baseline-corrected, set dsc0 to 0
    75
    76   """
    77   # calculate cp
    78   cpS = cpR * mR/mS * (dscS-dsc0) / (dscR-dsc0)
    79
    80   return cpS

Calculates the (apparent) specific heat capacity.

It applies the "heat flow calibration" method: A known reference cp (of sapphire) is rescaled using the mass-ratio and signal ratio of reference and sample.

>     cpS(T) = cpR(T) * mR/mS * (dscS(T)-dsc0(T)) / (dscR(T)-dsc0(T))

### Reference

DIN EN ISO 11357-4:2014
Plastics - Differential scanning calorimetry
Determination of specific heat capacity

## Takes

T: Vector of temperatures to evaluate the cp-value

mS: Mass of sample

dscS: DSC signal of sample (microvolts)

cpR: cp-values of reference (cf. fpex0.CP_sapphire_DIN11357())

mR: Mass of reference

dscR: DSC signal of reference (microvolts)

dsc0: DSC signal with two empty crucibles

## Returns

cpS: cp-values of sample at specified temperatures.

### Comments

- If vectors are given, they must coincide in size and meaning,
  i.e. dscS[k], dscR[k], dsc0[k], cpR[k] all correspond to temperature T[k]
- If dscS and dscR are already baseline-corrected, set dsc0 to 0

def addCP(DSCsample, DSCreference, DSC0=0, Tmin=55, Tmax=160, bltype='linear'): View Source

     83def addCP(DSCsample, DSCreference, DSC0=0, Tmin=55, Tmax=160, bltype='linear'):
     84   """
     85   Adds cp values to DSC data structure (setup.DSC_Data).
     86   Calculation of cp is done with CP_DIN11357.
     87
     88   ## Takes
     89   **DSCsample**
     90   <br> fpex0.setup.DSC_Data object or list of objects, sample - (e.g. pcm)
     91
     92   **DSCreference**
     93   <br> fpex0.setup.DSC_Data object, reference (e.g. saphire)
     94
     95   **DSC0**
     96   <br> DSC signal with two empty crucibles.                 [default: 0]
     97   <br> Only needed if signals are not zero-corrected yet.
     98
     99   **Tmin**
    100   <br> Lower temperature bound.                             [default:  55 degC]
    101
    102   **Tmax**
    103   <br> Upper temperature bound.                             [default: 160 degC]
    104
    105
    106   ## Returns
    107   **DSCsample**
    108   <br> fpex0.setup.DSC_Data object or list of objects with additional field cp.
    109   """
    110
    111   # (TODO) Make these controllable
    112   scaleByMass = True  # multiply signaly by mass (signals are often normed to mass 1)
    113   scaleToRate = True  # scale signals to heatrate (higher rates induce higher signals, see below)
    114   TrangeWarn  = True  # warn if Tmin or Tmax exceed sample's or reference's temperature range
    115
    116   # make function applicable for struct arrays
    117   if type(DSCsample) is list:
    118      DSCsample = [addCP(DSCsample[i], DSCreference, DSC0, Tmin, Tmax, bltype) for i in range(len(DSCsample))]
    119      return np.array(DSCsample)
    120
    121   # retrieve the heating rates
    122   betaS = DSCsample.rate
    123   betaR = DSCreference.rate # possibly a vector
    124
    125   # reference rates not unique?
    126   if type(betaR) is np.ndarray and len(betaR) != len(set(betaR)):
    127      print(f'DSC204_addCP: Reference heat rates not unique! rate vector = {betaR}\n')
    128      raise ValueError('Reference heat rates not unique!')
    129
    130   print(f'DSC204_addCP: Processing ID={DSCsample.ID}')
    131   # Variable naming: AB
    132   #    A:    T -> Temperature   m -> mass   dsc -> dsc data
    133   #    B:    S -> Sample    R -> Reference
    134
    135   # quick accessors
    136   TR   = DSCreference.T
    137   TS   = DSCsample.T
    138   dscS = DSCsample.dsc
    139   dscR = DSCreference.dsc
    140
    141   # calculate minima and maxima of sample and reference temperatures
    142   minTS = min(TS)
    143   maxTS = max(TS)
    144   minTR = min(TR)
    145   maxTR = max(TR)
    146
    147   # issue a warning if Tmax or Tmin exceeds reference or sample temperature range
    148   if ( Tmin < max(minTS,minTR)  or  Tmax > min(maxTS,maxTR) ):
    149      if TrangeWarn:
    150         print('WARNING: Specified Tmin or Tmax exceeds sample or reference temperature range. Will be adjusted!')
    151
    152
    153   # determine Tmin and Tmax
    154   Tmin = max( [minTS, minTR, Tmin] )
    155   Tmax = min( [maxTS, maxTR, Tmax] )
    156
    157   # restrict temperatures and align everything at the temperature information of the sample measurement
    158   # note: this is the temperature of the empty reference crucible, not of the sample itself
    159   # get the signals of the reference corresponding to the temperatures of the sample.
    160   # we use linear interpolation and extrapolation here
    161   idxR = [i for i in range(len(TR)) if Tmin <= TR[i] and TR[i] <= Tmax]
    162   idxS = [i for i in range(len(TS)) if Tmin <= TS[i] and TS[i] <= Tmax]
    163   dscR = dscR[idxR]
    164   dscS = dscS[idxS]
    165   TR   = TR[idxR]
    166   TS   = TS[idxS]
    167   dscR_interp = interpolate.interp1d(TR, dscR, kind='linear', fill_value='extrapolate')
    168   dscR = dscR_interp(TS)
    169
    170   # masses
    171   mS = DSCsample.mass     # mass of sample
    172   mR = DSCreference.mass  # mass of reference
    173
    174   # measurements are normalized to uV/mg, so we recover the original signal by multiplying with mass
    175   if scaleByMass:
    176      dscS = mS * dscS
    177      dscR = mR * dscR
    178
    179   # from carefully looking at the measurement data, we see that the voltage signal is proportional
    180   # to the heating rate, with proportionality constant approximately 1.
    181   # so we normalize both the sample and the reference signal to a heating rate of 1.0 K/min.
    182   # NOTE: this does also not interfere if betaR==betaS
    183   if scaleToRate:
    184      dscS = dscS / betaS
    185      dscR = dscR / betaR
    186
    187   # now retrieve the reference cp values of saphire (unit: degC)
    188   cpR = CP_sapphire_DIN11357(TS, 'degC')
    189
    190   # calculate cp of sample
    191   cpS = CP_DIN11357(TS, mS, dscS, cpR, mR, dscR, DSC0)
    192
    193   # store cp values and associated temperatures
    194   cp = HeatCapacity()
    195   cp.values = cpS
    196   cp.T      = TS
    197
    198   # store the piecewise polynomial with pchip interpolation; python checks bounds (Tmin, Tmax) for us
    199   cp.fun     = interpolate.PchipInterpolator(TS, cpS)
    200
    201   # get the baseline
    202   blfun, bldata = getBaseline(TS, cpS, bltype)
    203
    204   # build the latent cp function  subtractBaseline(X,  Yin, blfun, onset,        endset       , clearzero, nonnegative)
    205   latentCPvals, latentCPfun =     subtractBaseline(TS, cpS, blfun, bldata.onset, bldata.endset, False    , True       )
    206   cp.latentdata  = latentCPvals
    207   cp.latentfun   = latentCPfun
    208
    209   # store it in DSC data
    210   DSCsample.cp = cp
    211   DSCsample.bldata = bldata
    212   DSCsample.blfun = blfun
    213   return DSCsample

Adds cp values to DSC data structure (setup.DSC_Data). Calculation of cp is done with CP_DIN11357.

## Takes

DSCsample
fpex0.setup.DSC_Data object or list of objects, sample - (e.g. pcm)

DSCreference
fpex0.setup.DSC_Data object, reference (e.g. saphire)

DSC0
DSC signal with two empty crucibles. [default: 0]
Only needed if signals are not zero-corrected yet.

Tmin
Lower temperature bound. [default: 55 degC]

Tmax
Upper temperature bound. [default: 160 degC]

## Returns

DSCsample
fpex0.setup.DSC_Data object or list of objects with additional field cp.

def CP_sapphire_DIN11357(T, unit='degC'): View Source

    216def CP_sapphire_DIN11357(T, unit='degC'):
    217   """
    218   Delivers specific heat capacity of saphire for specified temperature according to DIN EN ISO 11357-4:2014-10.
    219
    220   ## Takes
    221   **T**: Temperature <br>
    222   **unit**: one of "degC" or "K"    [default: degC]
    223
    224   ## Returns
    225   **cp**
    226   <br> Literature value of sapphire as functor.
    227
    228
    229   [!] *Note*: This approximation is only valid in the interval 100K < T < 1200K.
    230   """
    231
    232   # coefficients in J/(g*K)
    233   A = [
    234       1.12705,
    235       0.23260,
    236      -0.21704,
    237       0.26410,
    238      -0.23778,
    239      -0.10023,
    240       0.15393,
    241       0.54579,
    242      -0.47824,
    243      -0.37623,
    244       0.34407
    245   ]
    246
    247   # linear transformations of temperature
    248   if unit.lower() in ['degc', 'c', 'celsius']:
    249      x = (T - 376.85) / 550
    250   elif unit.lower() in ['k', 'kelvin']:
    251      x = (T - 650) / 550
    252   else:
    253      raise ValueError(f"Unknown unit: {unit}")
    254
    255   # build and evaluate polynomial
    256   cp_poly = Polynomial(A)
    257   cp = cp_poly(x)
    258
    259   return cp

Delivers specific heat capacity of saphire for specified temperature according to DIN EN ISO 11357-4:2014-10.

## Takes

T: Temperature
unit: one of "degC" or "K" [default: degC]

## Returns

cp
Literature value of sapphire as functor.

[!] Note: This approximation is only valid in the interval 100K < T < 1200K.