227 lines
5.3 KiB
TypeScript
227 lines
5.3 KiB
TypeScript
import { Data as PlotlyData, Plot } from './plot/mod.ts'
|
|
|
|
type Data = {
|
|
timestamp: string //ISO string
|
|
volume: number //mL
|
|
ipam: number //ipam
|
|
record: {
|
|
pression: number //bar
|
|
mass: number //g
|
|
timestamp: number //s
|
|
}[]
|
|
}
|
|
|
|
function parseDatas(file: string): Data {
|
|
const lines = file.split('\n')
|
|
|
|
lines.shift() //remove title
|
|
const [day, month, year, time] = lines.shift()!.split(' ')
|
|
const monthList = [
|
|
'_',
|
|
'janvier',
|
|
'fevrier',
|
|
'mars',
|
|
'avril',
|
|
'mai',
|
|
'juin',
|
|
'juillet',
|
|
'aout',
|
|
'septembre',
|
|
'decembre',
|
|
]
|
|
const timestamp = `${year}-${
|
|
monthList.indexOf(month).toString().padStart(2, '0')
|
|
}-${day.padStart(2, '0')}T${time}`
|
|
|
|
lines.shift() //remove empty line
|
|
const volume = lines.shift()!.split(';').slice(1).map(Number).at(0)!
|
|
const ipam = lines.shift()!.split(';').slice(1).map(Number).at(0)!
|
|
|
|
lines.shift() //remove empty line
|
|
lines.shift() //remove title
|
|
const record = lines
|
|
.map((line) => line.split(';').slice(1).map(Number))
|
|
.map(([pression, mass, timestamp]) => ({ pression, mass, timestamp }))
|
|
|
|
return {
|
|
timestamp,
|
|
volume,
|
|
ipam,
|
|
record,
|
|
}
|
|
}
|
|
|
|
function filterRecord(record: Data['record'], ceil = 0.4): Data['record'] {
|
|
const filteredRecord: Data['record'] = []
|
|
record.forEach((value, index) => {
|
|
if (index === 0) filteredRecord.push(value)
|
|
if (value.mass < 10) filteredRecord.push(value)
|
|
const previousMass = filteredRecord.at(-1)?.mass ?? value.mass
|
|
const delta = Math.abs(value.mass - previousMass) / value.mass
|
|
if (delta >= ceil) filteredRecord.push({ ...value, mass: previousMass })
|
|
else filteredRecord.push(value)
|
|
})
|
|
|
|
return filteredRecord
|
|
}
|
|
|
|
function cutRecord(record: Data['record']): Data['record'] {
|
|
let minMass = record[0].mass
|
|
let minMassIndex = 0
|
|
for (const value of record) {
|
|
minMassIndex++
|
|
if (value.mass < minMass) minMass = value.mass
|
|
if (value.mass > minMass) break
|
|
}
|
|
|
|
return record.slice(minMassIndex)
|
|
}
|
|
|
|
function lowpass(array: number[], window: number): number[] {
|
|
return array.slice(0, -window)
|
|
.map(
|
|
(_, index) =>
|
|
array.slice(index, index + window).reduce(
|
|
(prev, curr) => prev + curr / window,
|
|
0,
|
|
),
|
|
)
|
|
}
|
|
|
|
function derivative(x: number[], y: number[]): number[] {
|
|
return y.slice(0, -1).map((_, index) =>
|
|
(y[index + 1] - y[index]) / (x[index + 1] - x[index])
|
|
)
|
|
}
|
|
|
|
function getDatasPlot(datas: Data) {
|
|
const record = cutRecord(filterRecord(datas.record))
|
|
// const minMassIndex = record.findIndex((value) => )
|
|
|
|
// const record2 = record.filter(({mass}) => mass)
|
|
|
|
const x = record.map((
|
|
{ timestamp },
|
|
) => (timestamp - datas.record[0].timestamp)).map((x) => x / 6e3)
|
|
const y = record.map((data) => data.mass)
|
|
.map((y) => y / record.slice(-2, -1)[0].mass)
|
|
.map((y) => 1 - y)
|
|
.map(Math.log)
|
|
|
|
const meta = `${datas.volume}mL ${datas.ipam}IPAM ${
|
|
datas.timestamp.split('T')[1]
|
|
}`
|
|
|
|
const { ipam } = datas
|
|
|
|
// const dY = lowpass(derivative(x, lowpass(y, 100)), 100)
|
|
const dY = lowpass(derivative(x, y), 100)
|
|
const xMinIndex = x.findIndex((xi) => xi > 5)
|
|
const xMaxIndex = x.findIndex((xi) => xi > 10)
|
|
const expSlope = dY.slice(
|
|
xMinIndex,
|
|
xMaxIndex,
|
|
).reduce((prev, curr, _, array) => prev + curr / array.length, 0)
|
|
|
|
const yRaw = record.map((data) => data.mass)
|
|
const yMax = yRaw.reduce((max, curr) => curr > max ? curr : max)
|
|
|
|
const linearY = x.map((_, index) =>
|
|
yRaw[index] - (yMax * (1 - Math.exp(expSlope * x[index] * 6)))
|
|
)
|
|
const fitY = x.map((_, index) =>
|
|
yMax * (1 - Math.exp(expSlope * x[index] * 6))
|
|
)
|
|
|
|
const yLinDeriv = derivative(x, linearY)
|
|
|
|
const ipamSlope = yLinDeriv.slice(xMinIndex, xMaxIndex).reduce(
|
|
(p, c, _, a) => p + c / a.length,
|
|
0,
|
|
) * 6
|
|
|
|
console.log({
|
|
ipam,
|
|
ipamSlope: ipamSlope.toFixed(2),
|
|
expSlope: expSlope.toFixed(4),
|
|
})
|
|
|
|
console.log(x.at(-2))
|
|
|
|
return ([
|
|
{
|
|
x: x,
|
|
// y: dY,
|
|
// y: linearY,
|
|
// y: lowpass(linearY, 200),
|
|
// y: lowpass(yLinDeriv, 200).map((y) => Math.abs(y)),
|
|
// y: fitY,
|
|
y: yRaw,
|
|
name: `mass (${meta})`,
|
|
line: {
|
|
color: `rgb(${50 * Math.log(ipam)}, 0, ${255 - 50 * Math.log(ipam)})`,
|
|
},
|
|
},
|
|
{
|
|
x: x,
|
|
y: dY,
|
|
yaxis: 'y2',
|
|
name: `fit (${meta})`,
|
|
line: {
|
|
color: `rgb(${50 * Math.log(ipam)}, 127, ${255 - 50 * Math.log(ipam)})`,
|
|
},
|
|
},
|
|
] satisfies Partial<PlotlyData>[])
|
|
}
|
|
|
|
const plot = new Plot()
|
|
|
|
const plots: Partial<PlotlyData>[] = []
|
|
|
|
for await (const file of Deno.readDir('./datas')) {
|
|
if (!file.isFile) continue
|
|
if (!file.name.match(/Vrr3_\d{2}-\d{2}_\d{2}-\d{2}\.csv/)) continue
|
|
const path = `./datas/${file.name}`
|
|
|
|
const csv = await Deno.readTextFile(path)
|
|
const datas = parseDatas(csv)
|
|
// if (datas.ipam !== 4) continue
|
|
if (Number.isNaN(datas.ipam)) continue
|
|
|
|
const plotDatas = getDatasPlot(datas)
|
|
|
|
// while (true) {
|
|
// const slope =
|
|
// }
|
|
|
|
// if (datas.ipam === 4) {
|
|
// const xFit: number[] = plotDatas.x
|
|
// const yFit: number[] = xFit.map((x) => 200 * (1 - Math.exp(-0.021 * x * 6)))
|
|
// .map((x) => x / 200)
|
|
// .map((x) => 1 - x)
|
|
// .map(Math.log)
|
|
|
|
// const dYFit = lowpass(derivative(xFit, lowpass(yFit, 100)), 100)
|
|
// const dYFit = lowpass(derivative(xFit, yFit), 100)
|
|
|
|
// const testFit = {
|
|
// x: xFit,
|
|
// // y: dYFit,
|
|
// y: yFit,
|
|
// name: 'test fit',
|
|
// }
|
|
|
|
// plots.push(testFit)
|
|
// }
|
|
|
|
plots.push(...plotDatas)
|
|
}
|
|
|
|
plot.plot(plots, {
|
|
xaxis: { title: { text: 'timestamp' } },
|
|
yaxis: { title: { text: 'mass [g]' } },
|
|
yaxis2: { title: { text: 'fit' }, side: 'right' },
|
|
})
|
|
|
|
//RMD 4005 x2
|