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