fpex0.CP

  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:
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)
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):
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'):
 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'):
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.