diff --git a/src/__tests__/if-run/builtins/time-sync.test.ts b/src/__tests__/if-run/builtins/time-sync.test.ts index 58863272b..0dcdb1eb7 100644 --- a/src/__tests__/if-run/builtins/time-sync.test.ts +++ b/src/__tests__/if-run/builtins/time-sync.test.ts @@ -945,7 +945,7 @@ describe('builtins/time-sync:', () => { expect(result).toStrictEqual(expectedResult); }); - it('should throw an error if the upsampling resolution is not compatible with the interval', async () => { + it('should throw an error if the upsampling resolution is not compatible with the interval (interval is bigger than upsampling).', async () => { const basicConfig = { 'start-time': '2023-12-12T00:00:00.000Z', 'end-time': '2023-12-12T00:00:03.000Z', @@ -970,7 +970,7 @@ describe('builtins/time-sync:', () => { } }); - it('should throw an error if the upsampling resolution is not compatible with paddings', async () => { + it('should throw an error if the upsampling resolution is not compatible with paddings (interval is equal to upsampling).', async () => { const basicConfig = { 'start-time': '2023-12-12T00:00:00.000Z', 'end-time': '2023-12-12T00:00:12.000Z', diff --git a/src/if-run/builtins/time-sync/index.ts b/src/if-run/builtins/time-sync/index.ts index 0c1ecfb22..dfcca7780 100644 --- a/src/if-run/builtins/time-sync/index.ts +++ b/src/if-run/builtins/time-sync/index.ts @@ -216,6 +216,7 @@ export const TimeSync = PluginFactory({ timeStep: number ) => { const metrics = Object.keys(input); + return metrics.reduce((acc, metric) => { if (metric === 'timestamp') { acc[metric] = @@ -483,15 +484,70 @@ export const TimeSync = PluginFactory({ }, [] as PluginParams[]); /** Implementation */ + /** + * Calculates the greatest common divisor (GCD) of two numbers using the Euclidean algorithm. + */ + const greatestCommonDivisor = (a: number, b: number): number => { + while (b !== 0) { + [a, b] = [b, a % b]; + } + + return a; + }; + + /** + * Calculates the greatest common divisor (GCD) of an array of numbers. + */ + const calculateGCD = (numbers: number[]) => + numbers.reduce((acc, val) => greatestCommonDivisor(acc, val)); + + /** + * Returns an array of unique values from the given array. + */ + const getUniqueValues = (arr: number[]) => Array.from(new Set(arr)); + + /** + * Finds the most efficient resampling resolution based on the given inputs and user-defined interval. + * + * This function calculates the greatest common divisor (GCD) of the combined unique values from: + * - A: All unique values of (timestamp + duration) for each input. + * - B: All unique values of (next timestamp - current timestamp + duration) for each input. + */ + const findResamplingResolution = ( + inputs: PluginParams[], + userInterval: number + ): number => { + const A: number[] = []; + const B: number[] = []; + + for (let i = 0; i < inputs.length - 1; i++) { + const currentTimestamp = parseDate(inputs[i].timestamp).toMillis(); + const nextTimestamp = parseDate(inputs[i + 1].timestamp).toMillis(); + + A.push(currentTimestamp + inputs[i].duration); + B.push(nextTimestamp - currentTimestamp + inputs[i].duration); + } + + const uniqueA = getUniqueValues(A); + const uniqueB = getUniqueValues(B); + const combined = [...uniqueA, ...uniqueB, userInterval]; + + return calculateGCD(combined); + }; + + const upsamplingResolution = + config['upsampling-resolution'] || + findResamplingResolution(inputs, config.interval); + console.log(`Choosen upsampling resolution is ${upsamplingResolution}\n`); + const timeParams = { startTime: DateTime.fromISO(config['start-time'] as string), endTime: DateTime.fromISO(config['end-time'] as string), interval: config.interval, allowPadding: config['allow-padding'], - upsamplingResolution: config['upsampling-resolution'] - ? config['upsampling-resolution'] - : 1, + upsamplingResolution, }; + validateIntervalForResample( timeParams.interval, timeParams.upsamplingResolution, diff --git a/src/if-run/lib/compute.ts b/src/if-run/lib/compute.ts index f77b1b995..50664de5c 100644 --- a/src/if-run/lib/compute.ts +++ b/src/if-run/lib/compute.ts @@ -26,7 +26,8 @@ const { const childNames = new Set(); /** - * Traverses all child nodes based on children grouping. + * Traverses through the children nodes and computes each child node. + * It also adds the child name to the `childNames` set. */ const traverse = async (children: any, params: ComputeParams) => { for (const child in children) {