blob: 72dcc3c32723e4911d5fbe85eef490a089fab887 [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2022 Dortmund University of Applied Sciences and Arts and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dortmund University of Applied Sciences and Arts - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.model.util.stimuli;
import java.math.BigInteger;
import java.util.List;
import org.eclipse.app4mc.amalthea.model.ModeLabel;
import org.eclipse.app4mc.amalthea.model.PeriodicSyntheticStimulus;
import org.eclipse.app4mc.amalthea.model.Process;
import org.eclipse.app4mc.amalthea.model.Time;
import org.eclipse.app4mc.amalthea.model.util.RuntimeUtil;
import org.eclipse.app4mc.amalthea.model.util.RuntimeUtil.TimeType;
import org.eclipse.emf.common.util.EMap;
/**
* Event Model functions for APP4MC's {@link PeriodicSyntheticStimulus}.
* <p>
* For a full documentation of the underlying functions, please refer to the
* <a href="https://doi.org/10.1016/j.sysarc.2021.102343">related
* publication</a>, Section 3.4.
*/
public class EMPeriodicSynthetic implements IEventModel {
/**
* The actual period of this trigger pattern
*/
private Time tOuterPeriod;
/**
* List of activation events relative to the start of this pattern.
*/
private List<Time> lEntries;
public EMPeriodicSynthetic() {
// Empty on purpose
}
@Override
public long etaPlus(final Time dt) {
// Return 0 on dt=0
if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
// For dt = 0, the upper and lower bound functions return 0
return 0;
}
/*
* Calculate etaOuter, i.e. the number of releases per "full" outer period (
* |T^Inner| * floor(dt/T^Outer) )
*/
final double fraction = Math.floor(dt.divide(this.tOuterPeriod));
final long iEtaOuter = (long) (this.lEntries.size() * fraction);
/*
* Calculate etaInner, i.e. the number of releases per "remainder" (dt mod
* T^Outer)
*/
// dt - T^Outer * floor(dt/T^Outer)
final Time rhs = this.tOuterPeriod.multiply(Math.floor(dt.divide(this.tOuterPeriod)));
final Time tRemainder = dt.subtract(rhs);
// Check tRemainder (dt')
long iEtaInner = 0;
if (tRemainder.getValue().compareTo(BigInteger.ZERO) == 0) {
// No inner releases
iEtaInner = 0;
} else {
// More than one release: We need to find the **largest** n, for
// which the **minimum** of all deltaDash(n) is lower than dt'
// We start by iterating over n (distance)
for (int n = 1; n <= this.lEntries.size(); n++) {
Time tDeltaDashMin = null;
// Iteration over the inner activation events (occurance times)
// and remember the lowest value
for (int j = 0; j < this.lEntries.size(); j++) {
if (null == tDeltaDashMin) {
tDeltaDashMin = deltaDash(n, j);
} else {
final Time a = tDeltaDashMin;
final Time b = deltaDash(n, j);
tDeltaDashMin = EventModelFactory.min(a, b);
}
}
// delta_dash(n) has to be lower than tRemainder
if (tDeltaDashMin != null && tDeltaDashMin.compareTo(tRemainder) < 0 && n > iEtaInner) {
iEtaInner = n;
}
}
}
return iEtaOuter + iEtaInner;
}
@Override
public long etaMinus(final Time dt) {
// Return 0 on dt=0
if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
return 0;
}
/*
* Calculate etaOuter, i.e. the number of releases per "full" outer period (
* |T^Inner| * floor(dt/T^Outer) )
*/
final double fraction = Math.floor(dt.divide(this.tOuterPeriod));
final long iEtaOuter = (long) (this.lEntries.size() * fraction);
/*
* Calculate etaInner, i.e. the number of releases per "remainder" (dt mod
* T^Outer)
*/
// dt - T^Outer * floor(dt/T^Outer)
final Time rhs = this.tOuterPeriod.multiply(Math.floor(dt.divide(this.tOuterPeriod)));
final Time tRemainder = dt.subtract(rhs);
// Check tRemainder (dt')
long iEtaInner = 0;
if (tRemainder.compareTo(deltaPlus(2)) < 0) {
// If dt' <= delta_plus(2), no activation events
iEtaInner = 0;
} else {
// We need to find the **largest** n, for
// which the **maximum** of all deltaDash(n) is lower than dt'
// We start by iterating over n (distance)
for (long n = 1; n <= this.lEntries.size(); n++) {
Time tDeltaDashMax = null;
// Iteration over the inner activation events (occurance times)
for (int j = 0; j < this.lEntries.size(); j++) {
if (null == tDeltaDashMax) {
tDeltaDashMax = deltaDash(n, j);
} else {
final Time a = tDeltaDashMax;
final Time b = deltaDash(n, j);
tDeltaDashMax = EventModelFactory.max(a, b);
}
}
// delta_dash(n) has to be lower or equal to tRemainder
if (tDeltaDashMax != null && tDeltaDashMax.compareTo(tRemainder) <= 0 && n > iEtaInner) {
iEtaInner = n - 1;
}
}
}
return iEtaOuter + iEtaInner;
}
@Override
public Time deltaPlus(final long n) {
// Function is only valid for n >= 2, return null on invalid parameter
if (n < 2) {
return null;
}
// Distance between full periods
// Integer division on purpose in order to round the result down
final long fullPeriodCount = (n - 1) / this.lEntries.size();
final Time tFull = this.tOuterPeriod.multiply(fullPeriodCount);
// Distance between "remaining" (not covered by full periods) releases
Time tPartial = null;
// The distance between the remaining k(n) releases is the minimum among
// all n releases
for (int j = 0; j < this.lEntries.size(); j++) {
final Time difference = deltaDash((n - 1) % this.lEntries.size() + 1, j);
if (null == tPartial || difference.compareTo(tPartial) > 0) {
tPartial = difference;
}
}
return tFull.add(tPartial);
}
@Override
public Time deltaMinus(final long n) {
// Function is only valid for n >= 2, return null on invalid parameter
if (n < 2) {
return null;
}
// Distance between full periods
// Integer division on purpose in order to round the result down
final long fullPeriodCount = (n - 1) / this.lEntries.size();
final Time tFull = this.tOuterPeriod.multiply(fullPeriodCount);
// Distance between "remaining" (not covered by full periods) releases
Time tPartial = null;
// The distance between the remaining k(n) releases is the minimum among
// all n releases
for (int j = 0; j < this.lEntries.size(); j++) {
final Time difference = deltaDash((n - 1) % this.lEntries.size() + 1, j);
if (null == tPartial || difference.compareTo(tPartial) < 0) {
tPartial = difference;
}
}
return tFull.add(tPartial);
}
/**
* Returns the exact distance between n events with the first event being j
*
* @param l
* @param j
* @return
*/
private Time deltaDash(final long l, final int j) {
final Time a = this.lEntries.get(j);
Time b;
if (j + k(l) >= this.lEntries.size()) {
// Carry over
final int index = j + k(l) - this.lEntries.size();
b = this.lEntries.get(index);
b = b.add(this.tOuterPeriod);
} else {
b = this.lEntries.get(j + k(l));
}
return b.subtract(a);
}
/**
* Returns the nb. of events that are not covered by full periods (i.e. released
* as part of a periods fraction)
*
* @param l Number of total events
* @return Fraction of events
*/
private int k(final long l) {
assert (l > 0);
final int elementCount = this.lEntries.size();
// Return the remainder, i.e. n % noElements
return (int) ((l - 1) - ((l - 1) / elementCount) * elementCount);
}
@Override
public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
// Note that periodic synthetic stimuli are considered to be strictly periodic
// in APP4MC. Accordingly, the outer period is assumed to be constant (without
// any drift) and all activation events will be released with each outer period.
// Therefore, the load is only influenced by a process's execution time, thus
// making it unnecessary to handle different cases of periods.
final Time executionTime = RuntimeUtil.getExecutionTimeForProcess(process, modes, tt);
return executionTime.multiply(getEntries().size()).divide(getOuterPeriod());
}
public Time getOuterPeriod() {
return tOuterPeriod;
}
public void setOuterPeriod(Time tOuterPeriod) {
this.tOuterPeriod = tOuterPeriod;
}
public List<Time> getEntries() {
return lEntries;
}
public void setEntries(List<Time> lEntries) {
this.lEntries = lEntries;
}
}