Added Event Model Implementation
Signed-off-by: Lukas Krawczyk <eclipse@krawczyk.at>
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodic.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodic.java
new file mode 100644
index 0000000..0639f56
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodic.java
@@ -0,0 +1,185 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.PeriodicStimulus;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.util.FactoryUtil;
+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 PeriodicStimulus}.
+ * <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.1.
+ */
+public class EMPeriodic implements IEventModel {
+ /**
+ * The actual period of this trigger pattern
+ */
+ private Time tOuterPeriod;
+ /**
+ * Minimum distance between two subsequent activation events
+ */
+ private Time tMinDistance;
+ /**
+ * Maximum jitter by which an activation event can be delayed
+ */
+ private Time tJitter;
+
+ public EMPeriodic() {
+ // Empty on purpose
+ }
+
+ @Override
+ public long etaPlus(final Time dt) {
+ // Return 0 on dt=0
+ if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
+ return 0;
+ }
+
+ // LHS: ceil(dt+J/T)
+ final Time num = dt.add(this.tJitter);
+ final double eta_plus_p = Math.ceil(num.divide(this.tOuterPeriod));
+
+ // RHS: ceil(dt/d)
+ final double eta_plus;
+ if (this.tMinDistance.getValue().compareTo(BigInteger.ZERO) != 0) {
+ final double eta_plus_d = Math.ceil(dt.divide(this.tMinDistance));
+ eta_plus = Math.min(eta_plus_p, eta_plus_d);
+ } else {
+ eta_plus = eta_plus_p;
+ }
+ return (long) eta_plus;
+ }
+
+ @Override
+ public long etaMinus(final Time dt) {
+ // Return 0 on dt=0
+ if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
+ return 0;
+ }
+
+ // LHS: floor(dt-J/T)
+ final Time num = dt.subtract(this.tJitter);
+ final double eta_min_p = Math.floor(num.divide(this.tOuterPeriod));
+
+ // RHS: floor(dt/d)
+ final double eta_min;
+ if (this.tMinDistance.getValue().compareTo(BigInteger.ZERO) != 0) {
+ final double eta_min_d = Math.floor(dt.divide(this.tMinDistance));
+ eta_min = Math.max(eta_min_p, eta_min_d);
+ } else {
+ eta_min = eta_min_p;
+ }
+
+ return (long) Math.max(0, eta_min);
+ }
+
+ @Override
+ public Time deltaPlus(final long n) {
+ // Function is only valid for n >= 2, return null on invalid parameter
+ if (n < 2) {
+ return null;
+ }
+
+ // Compare recurrence and minDistance, use the *highest* of both for
+ // further calculation
+ final Time tMinDistanceCalc = EventModelFactory.max(this.tOuterPeriod, this.tMinDistance);
+
+ // Multiply this distance with the number of events (minus one)
+ final Time tDistance = tMinDistanceCalc.multiply(n - 1);
+
+ return tDistance.add(this.tJitter);
+
+ }
+
+ @Override
+ public Time deltaMinus(final long n) {
+ // Function is only valid for n >= 2, return 0 on invalid parameter
+ if (n < 2) {
+ return FactoryUtil.createTime("0ms");
+ }
+
+ // Compare recurrence and minDistance, use the *highest* of both for
+ // further calculation
+ final Time tMinDistanceCalc = EventModelFactory.max(this.tOuterPeriod, this.tMinDistance);
+
+ // Multiply this distance with the number of events (minus one)
+ final Time tDistance = tMinDistanceCalc.multiply(n - 1);
+
+ /*
+ * Subtract jitter if and only if it does not decrease the distance between two
+ * activation events to less than tMinDistance. Cap it otherwise to the max.
+ * permitted value that does not violate tMinDistance.
+ */
+ // Check if tRecurrence > tMinDistance (Prerequisite, otherwise jitter
+ // is capped at 0)
+ if (this.tOuterPeriod.compareTo(this.tMinDistance) > 0) {
+ final Time tMaxJitter;
+ final Time tDiffRecMinDistance = this.tOuterPeriod.subtract(this.tMinDistance);
+ // Check if Jitter exceeds (tRecurrence - tMinDistane)
+ if (this.tJitter.compareTo(tDiffRecMinDistance) > 0) {
+ // Yes, so max. Jitter must be capped at tRecurrence -
+ // tMinDistane
+ tMaxJitter = tDiffRecMinDistance;
+ } else {
+ // No, so max. Jitter can be taken as specified
+ tMaxJitter = this.tJitter;
+ }
+ return tDistance.subtract(tMaxJitter);
+ }
+
+ return tDistance;
+ }
+
+ @Override
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
+ final Time time = RuntimeUtil.getExecutionTimeForProcess(process, modes, tt);
+ return time.divide(this.tOuterPeriod);
+ }
+
+ public Time getRecurrence() {
+ return this.tOuterPeriod;
+ }
+
+ public void setRecurrence(Time recurrence) {
+ this.tOuterPeriod = recurrence;
+ }
+
+ public Time getMinDistance() {
+ return this.tMinDistance;
+ }
+
+ public void setMinDistance(Time minDistance) {
+ this.tMinDistance = minDistance;
+ }
+
+ public Time getJitter() {
+ return this.tJitter;
+ }
+
+ public void setJitter(Time jitter) {
+ this.tJitter = jitter;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicBurst.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicBurst.java
new file mode 100644
index 0000000..f334f80
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicBurst.java
@@ -0,0 +1,241 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.PeriodicBurstStimulus;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.util.FactoryUtil;
+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 PeriodicBurstStimulus}.
+ * <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.3.
+ */
+public class EMPeriodicBurst implements IEventModel {
+ /**
+ * Constant representing a Time object corresponding to 0ms.
+ */
+ private static final Time ZEROMS = FactoryUtil.createTime("0ms");
+ /**
+ * The actual period of this trigger pattern
+ */
+ private Time tOuterPeriod;
+ /**
+ * Duration of a single burst, i.e., the length of the time window in which
+ * activation events are released
+ */
+ private Time tBurstLength;
+ /**
+ * Number of activation events triggered per burst
+ */
+ private long iOccurrenceCount;
+ /**
+ * Minimum distance between two subsequent activation events
+ */
+ private Time tMinDistance;
+ /**
+ * Internal offset by how much a time window in which activation events are
+ * released can be delayed
+ */
+ private Time tDeltaBurst;
+
+ public EMPeriodicBurst() {
+ // Empty on purpose
+ }
+
+ @Override
+ public long etaPlus(final Time dt) {
+ // Return 0 on dt=0
+ if (EventModelFactory.isNullOrZero(dt)) {
+ // For dt = 0, the upper and lower bound functions return 0
+ return 0;
+ }
+
+ // Check for first case, tMin is unconstrained
+ if (EventModelFactory.isNullOrZero(this.tMinDistance)) {
+ final double dEtaScale = dt.add(this.tBurstLength).divide(this.tOuterPeriod);
+ return (long) (this.iOccurrenceCount * Math.ceil(dEtaScale));
+ }
+
+ // Second case, tMin is constrained
+ // EtaOuter
+ final double fraction = Math.floor(dt.add(this.tDeltaBurst).divide(this.tOuterPeriod));
+ final long iEtaOuter = (long) (this.iOccurrenceCount * fraction);
+
+ // EtaInner
+ // Check whether dt < TO - DeltaBurst
+ final long iEtaInner;
+
+ if (dt.compareTo(this.tOuterPeriod.subtract(this.tDeltaBurst)) < 0) {
+ // Return the minimum among b and ceil(dt/dmin)
+ iEtaInner = (long) Math.min(Math.ceil(dt.divide(this.tMinDistance)), this.iOccurrenceCount);
+ } else {
+ /*
+ * Calculate etaInner, i.e. the number of releases per "remainder" (dt mod
+ * T^Outer)
+ */
+ // dt + db - T^Outer * floor((dt + db) /T^Outer)
+ // Calculate remainder (dt')
+ final Time rhs = this.tOuterPeriod.multiply(Math.floor(dt.add(this.tDeltaBurst).divide(this.tOuterPeriod)));
+ final Time tRemainder = dt.add(this.tDeltaBurst).subtract(rhs);
+ iEtaInner = (long) Math.min(Math.ceil(tRemainder.divide(this.tMinDistance)), this.iOccurrenceCount);
+ }
+
+ return iEtaOuter + iEtaInner;
+ }
+
+ @Override
+ public long etaMinus(final Time dt) {
+ // Return 0 on dt=0
+ if (EventModelFactory.isNullOrZero(dt)) {
+ // For dt = 0, the upper and lower bound functions return 0
+ return 0;
+ }
+
+ // Check for first case, tMin is unconstrained
+ if (EventModelFactory.isNullOrZero(this.tMinDistance)) {
+ final double dEtaScale = dt.subtract(this.tBurstLength).divide(this.tOuterPeriod);
+ return (long) Math.max(0, this.iOccurrenceCount * Math.floor(dEtaScale));
+ }
+
+ // Calculate dt'
+ // dt - (R + BL - 2(b-1)tMin) + 1
+ final Time dtOffsetRaw = dt
+ .subtract(this.tOuterPeriod.add(this.tBurstLength)
+ .subtract(this.tMinDistance.multiply(this.iOccurrenceCount - 1).multiply(2)))
+ .add(this.tMinDistance);
+ // Max(dtOffsetRaw, 0)
+ final Time dtOffset = (dtOffsetRaw.compareTo(ZEROMS) > 0) ? dtOffsetRaw : ZEROMS;
+
+ // etaOuter
+ final double fraction = Math.floor(dtOffset.divide(this.tOuterPeriod));
+ final long iEtaOuter = (long) (this.iOccurrenceCount * fraction);
+
+ /*
+ * Calculate etaInner, i.e. the number of releases per "remainder" (dt mod
+ * T^Outer)
+ */
+ if (EventModelFactory.isNullOrZero(this.tMinDistance)) {
+ return iEtaOuter;
+ }
+ // dt - T^Outer * floor(dt/T^Outer)
+ final Time rhs = this.tOuterPeriod.multiply(Math.floor(dtOffset.divide(this.tOuterPeriod)));
+ final Time tRemainder = dtOffset.subtract(rhs);
+ final long iEtaInner = (long) Math.min(this.iOccurrenceCount, Math.ceil(tRemainder.divide(this.tMinDistance)));
+
+ 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;
+ }
+
+ // Integer division on purpose in order to round the result down
+ final long ceiledFraction = (n - 1) / this.iOccurrenceCount;
+ // Distance between full periods
+ final Time tFull = this.tOuterPeriod.multiply(ceiledFraction);
+
+ // Distance between remaining events
+ final Time tPartial;
+ final Time tSlack = this.tOuterPeriod.subtract(this.tMinDistance.multiply(this.iOccurrenceCount - 1));
+ if (tSlack.compareTo(this.tMinDistance) > 0) {
+ tPartial = this.tOuterPeriod.subtract(this.tMinDistance.multiply(this.iOccurrenceCount - (n - 1)));
+ } else {
+ tPartial = this.tMinDistance.multiply(n - 1);
+ }
+ 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;
+ }
+
+ // Integer division on purpose in order to round the result down
+ final long ceiledFraction = (n - 1) / this.iOccurrenceCount;
+
+ // Distance between full periods
+ final Time tFull = this.tOuterPeriod.multiply(ceiledFraction);
+ // Distance between remaining events
+ final long remainder = n - 1 - ceiledFraction * this.iOccurrenceCount;
+ final Time tPartial = this.tMinDistance.multiply(remainder);
+
+ return tFull.add(tPartial);
+ }
+
+ @Override
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
+ // Note that bursts are considered to be strictly periodic in APP4MC.
+ // Accordingly, the outer period is assumed to be constant (without any drift)
+ // and the maximum number of activation events will be released with each burst.
+ // 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(getOccurrenceCount()).divide(getOuterPeriod());
+ }
+
+ public Time getOuterPeriod() {
+ return tOuterPeriod;
+ }
+
+ public void setOuterPeriod(Time tOuterPeriod) {
+ this.tOuterPeriod = tOuterPeriod;
+ }
+
+ public Time getBurstLength() {
+ return tBurstLength;
+ }
+
+ public void setBurstLength(Time tBurstLength) {
+ this.tBurstLength = tBurstLength;
+ }
+
+ public long getOccurrenceCount() {
+ return iOccurrenceCount;
+ }
+
+ public void setOccurrenceCount(long iOccurrenceCount) {
+ this.iOccurrenceCount = iOccurrenceCount;
+ }
+
+ public Time getMinDistance() {
+ return tMinDistance;
+ }
+
+ public void setMinDistance(Time tMinDistance) {
+ this.tMinDistance = tMinDistance;
+ }
+
+ public Time getDeltaBurst() {
+ return tDeltaBurst;
+ }
+
+ public void setDeltaBurst(Time tDeltaBurst) {
+ this.tDeltaBurst = tDeltaBurst;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicRelative.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicRelative.java
new file mode 100644
index 0000000..598f3cc
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicRelative.java
@@ -0,0 +1,121 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.RelativePeriodicStimulus;
+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 RelativePeriodicStimulus}.
+ * <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.2.
+ */
+public class EMPeriodicRelative implements IEventModel {
+ /**
+ * Upper bound (i.e., maximum) distance between two subsequent activation
+ * events.
+ */
+ private Time upperBoundDistance;
+ /**
+ * Lower bound (i.e., minimum) distance between two subsequent activation
+ * events.
+ */
+ private Time lowerBoundDistance;
+
+ public EMPeriodicRelative() {
+ // Empty on purpose
+ }
+
+ @Override
+ public long etaPlus(final Time dt) {
+ if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
+ return 0;
+ }
+
+ return (long) Math.ceil(dt.divide(this.lowerBoundDistance));
+ }
+
+ @Override
+ public long etaMinus(final Time dt) {
+ if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
+ return 0;
+ }
+
+ return (long) Math.floor(dt.divide(this.upperBoundDistance));
+ }
+
+ @Override
+ public Time deltaPlus(final long n) {
+ // Function is only valid for n >= 2, return null on invalid parameter
+ if (n < 2) {
+ return null;
+ }
+
+ return this.upperBoundDistance.multiply(n - 1);
+ }
+
+ @Override
+ public Time deltaMinus(final long n) {
+ // Function is only valid for n >= 2, return null on invalid parameter
+ if (n < 2) {
+ return null;
+ }
+
+ return this.lowerBoundDistance.multiply(n - 1);
+ }
+
+ @Override
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
+ final Time time = RuntimeUtil.getExecutionTimeForProcess(process, modes, tt);
+ if (tt.equals(TimeType.BCET)) {
+ // Minimum load occurs when activation events occur with maximum distance
+ return time.divide(this.upperBoundDistance);
+ } else if (tt.equals(TimeType.WCET)) {
+ // Maximum load occurs when activation events occur with minimum distance
+ return time.divide(this.lowerBoundDistance);
+ } else if (tt.equals(TimeType.ACET)) {
+ final Time averageDistance = this.lowerBoundDistance.add(this.upperBoundDistance).multiply(0.5);
+ return time.divide(averageDistance);
+ }
+ // Unsupported configuration
+ return -1;
+ }
+
+ public Time getUpperBoundDistance() {
+ return upperBoundDistance;
+ }
+
+ public void setUpperBoundDistance(Time upperBoundDistance) {
+ this.upperBoundDistance = upperBoundDistance;
+ }
+
+ public Time getLowerBoundDistance() {
+ return lowerBoundDistance;
+ }
+
+ public void setLowerBoundDistance(Time lowerBoundDistance) {
+ this.lowerBoundDistance = lowerBoundDistance;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicSynthetic.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicSynthetic.java
new file mode 100644
index 0000000..72dcc3c
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMPeriodicSynthetic.java
@@ -0,0 +1,273 @@
+/**
+ ********************************************************************************
+ * 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;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMSingle.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMSingle.java
new file mode 100644
index 0000000..23b0c79
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMSingle.java
@@ -0,0 +1,73 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.SingleStimulus;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.TimeUnit;
+import org.eclipse.app4mc.amalthea.model.util.FactoryUtil;
+import org.eclipse.app4mc.amalthea.model.util.RuntimeUtil.TimeType;
+import org.eclipse.emf.common.util.EMap;
+
+/**
+ * Event Model functions for APP4MC's {@link SingleStimulus}.
+ * <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.5.
+ */
+public class EMSingle implements IEventModel {
+ // To the average human being, 292 Billion years should feel like infinity.
+ private static final Time INFINITY = FactoryUtil.createTime(Long.MAX_VALUE, TimeUnit.S);
+
+ public EMSingle() {
+ // Empty on purpose
+ }
+
+ @Override
+ public long etaPlus(final Time dt) {
+ if (dt.getValue().compareTo(BigInteger.ZERO) == 0) {
+ return 0;
+ }
+ return 1;
+ }
+
+ @Override
+ public long etaMinus(final Time dt) {
+ return 0;
+ }
+
+ @Override
+ public Time deltaPlus(final long n) {
+ return INFINITY;
+ }
+
+ @Override
+ public Time deltaMinus(final long n) {
+ return INFINITY;
+ }
+
+ @Override
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
+ // The utilization of a process that is only activated once throughout an
+ // application's execution will converge towards 0.
+ return 0.0;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMVariableRate.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMVariableRate.java
new file mode 100644
index 0000000..3b33c74
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EMVariableRate.java
@@ -0,0 +1,142 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.AmaltheaServices;
+import org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.VariableRateStimulus;
+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 VariableRateStimulus}.
+ */
+public class EMVariableRate implements IEventModel {
+ private Time tStep;
+ private double dLowerBoundOccurrences;
+ private double dUpperBoundOccurrences;
+ private double dAverageBoundOccurrences;
+
+ public EMVariableRate() {
+ // Empty on purpose
+ }
+
+ @Override
+ public long etaPlus(final Time dt) {
+ // Return 0 on dt=0
+ if (EventModelFactory.isNullOrZero(dt)) {
+ // For dt = 0, the upper and lower bound functions return 0
+ return 0;
+ }
+
+ final double result = dt.multiply(this.dUpperBoundOccurrences).divide(this.tStep);
+ return (long) Math.ceil(result);
+ }
+
+ @Override
+ public long etaMinus(final Time dt) {
+ // Return 0 on dt=0
+ if (EventModelFactory.isNullOrZero(dt)) {
+ // For dt = 0, the upper and lower bound functions return 0
+ return 0;
+ }
+
+ final double result = dt.multiply(this.dLowerBoundOccurrences).divide(this.tStep);
+ // Equivalent to Math.floor
+ return (long) result;
+ }
+
+ @Override
+ public Time deltaPlus(final long n) {
+ // Function is only valid for n >= 2, return null on invalid parameter
+ if (n < 2) {
+ return null;
+ }
+
+ final double scale = (n - 1) / this.dUpperBoundOccurrences;
+ final Time tDistance = this.tStep.multiply(scale);
+ if (tDistance == null) {
+ return null;
+ }
+ return AmaltheaServices.adjustTimeUnit(tDistance);
+ }
+
+ @Override
+ public Time deltaMinus(final long n) {
+ // Function is only valid for n >= 2, return null on invalid parameter
+ if (n < 2) {
+ return null;
+ }
+
+ final double scale = (n - 1) / this.dLowerBoundOccurrences;
+ final Time tDistance = this.tStep.multiply(scale);
+ if (tDistance == null) {
+ return null;
+ }
+ return AmaltheaServices.adjustTimeUnit(tDistance);
+ }
+
+ @Override
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes) {
+ final Time executionTime = RuntimeUtil.getExecutionTimeForProcess(process, modes, tt);
+ if (tt.equals(TimeType.BCET)) {
+ // Minimum load occurs when the minimum of activation events occurs per step
+ return executionTime.multiply(dLowerBoundOccurrences).divide(getStep());
+ } else if (tt.equals(TimeType.WCET)) {
+ // Maximum load occurs when the maximum of activation events occurs per step
+ return executionTime.multiply(dUpperBoundOccurrences).divide(getStep());
+ } else if (tt.equals(TimeType.ACET)) {
+ return executionTime.multiply(dAverageBoundOccurrences).divide(getStep());
+ }
+ // Unsupported configuration
+ return -1;
+ }
+
+ public Time getStep() {
+ return tStep;
+ }
+
+ public void setStep(Time step) {
+ this.tStep = step;
+ }
+
+ public double getLowerBoundOccurrences() {
+ return dLowerBoundOccurrences;
+ }
+
+ public void setLowerBoundOccurrences(double dLowerBoundOccurrences) {
+ this.dLowerBoundOccurrences = dLowerBoundOccurrences;
+ }
+
+ public double getAverageBoundOccurrences() {
+ return dAverageBoundOccurrences;
+ }
+
+ public void setAverageBoundOccurrences(double dAverageBoundOccurrences) {
+ this.dAverageBoundOccurrences = dAverageBoundOccurrences;
+ }
+
+ public double getUpperBoundOccurrences() {
+ return dUpperBoundOccurrences;
+ }
+
+ public void setUpperBoundOccurrences(double dUpperBoundOccurrences) {
+ this.dUpperBoundOccurrences = dUpperBoundOccurrences;
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EventModelFactory.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EventModelFactory.java
new file mode 100644
index 0000000..d8bcf1b
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/EventModelFactory.java
@@ -0,0 +1,206 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.PeriodicBurstStimulus;
+import org.eclipse.app4mc.amalthea.model.PeriodicStimulus;
+import org.eclipse.app4mc.amalthea.model.PeriodicSyntheticStimulus;
+import org.eclipse.app4mc.amalthea.model.RelativePeriodicStimulus;
+import org.eclipse.app4mc.amalthea.model.SingleStimulus;
+import org.eclipse.app4mc.amalthea.model.Stimulus;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.VariableRateStimulus;
+import org.eclipse.app4mc.amalthea.model.util.FactoryUtil;
+
+public class EventModelFactory {
+ /**
+ * Constant representing a Time object corresponding to 0ms.
+ */
+ private static final Time ZEROMS = FactoryUtil.createTime("0ms");
+
+ // Suppress default constructor
+ private EventModelFactory() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static IEventModel of(final Stimulus s) {
+ if (s instanceof PeriodicStimulus) {
+ return ofStimuli((PeriodicStimulus) s);
+ } else if (s instanceof PeriodicSyntheticStimulus) {
+ return ofStimuli((PeriodicSyntheticStimulus) s);
+ } else if (s instanceof RelativePeriodicStimulus) {
+ return ofStimuli((RelativePeriodicStimulus) s);
+ } else if (s instanceof PeriodicBurstStimulus) {
+ return ofStimuli((PeriodicBurstStimulus) s);
+ } else if (s instanceof SingleStimulus) {
+ return ofStimuli((SingleStimulus) s);
+ } else if (s instanceof VariableRateStimulus) {
+ return ofStimuli((VariableRateStimulus) s);
+ }
+ // Stimulus is not supported
+ return null;
+ }
+
+ /*********************************************************
+ * Factory methods for individual APP4MC stimuli
+ *********************************************************/
+
+ private static IEventModel ofStimuli(PeriodicStimulus s) {
+ EMPeriodic em = new EMPeriodic();
+ em.setRecurrence(s.getRecurrence());
+ /*
+ * Check if minDistance is set. If not, we are safe to initialize this variable
+ * with a dummy value of zero, as it will only be used as part of max functions
+ * (i.e. to make sure a lower bound on the distance is not exceeded)
+ */
+ if (s.getMinDistance() == null) {
+ em.setMinDistance(FactoryUtil.createTime("0ms"));
+ } else {
+ em.setMinDistance(s.getMinDistance());
+ }
+
+ /**
+ * Check if jitter is is set. If not, we are safe to initialize this variable
+ * with a dummy value of zero, as it will only be used as in additions /
+ * subtractions.
+ */
+ if (s.getJitter() == null) {
+ em.setJitter(FactoryUtil.createTime("0ms"));
+ } else {
+ em.setJitter(s.getJitter().getUpperBound());
+ }
+ return em;
+ }
+
+ private static IEventModel ofStimuli(PeriodicSyntheticStimulus s) {
+ EMPeriodicSynthetic em = new EMPeriodicSynthetic();
+ em.setOuterPeriod(s.getRecurrence());
+ em.setEntries(s.getOccurrenceTimes());
+ return em;
+ }
+
+ private static IEventModel ofStimuli(RelativePeriodicStimulus periodicRelative) {
+ EMPeriodicRelative em = new EMPeriodicRelative();
+ em.setLowerBoundDistance(periodicRelative.getNextOccurrence().getLowerBound());
+ em.setUpperBoundDistance(periodicRelative.getNextOccurrence().getUpperBound());
+ return em;
+ }
+
+ private static IEventModel ofStimuli(PeriodicBurstStimulus s) {
+ EMPeriodicBurst em = new EMPeriodicBurst();
+
+ // Feasibility check to prevent the creation of an invalid object that would
+ // cause exceptions.
+ if (EventModelFactory.isNullOrZero(s.getRecurrence()) || EventModelFactory.isNullOrZero(s.getBurstLength())) {
+ return null;
+ }
+
+ // Set Mandatory parameters
+ em.setOuterPeriod(s.getRecurrence());
+ em.setBurstLength(s.getBurstLength());
+
+ /*
+ * From APP4MC Help: The number of occurrences per burst are specified via
+ * occurrenceCount. The occurrenceMinDinstance defines the minimal distance
+ * between them. The burstLength defines the maximum time the burst pattern can
+ * last. If the number of occurrences multiplied with the minimum distance
+ * between activations is bigger than the burstLength only the number of
+ * activations that fit into the burstLength are executed.
+ */
+
+ // Check whether or not OccurrenceCount * OccurrenceMinDistance <
+ // BurstLength, and limit the number of events accordingly.
+ // if tMinDistance != 0 && tMinDistance * (occurranceCount - 1) >
+ // tBurstLength
+ if (!EventModelFactory.isNullOrZero(s.getOccurrenceMinDistance())
+ && !EventModelFactory.isNullOrZero(em.getBurstLength()) && s.getOccurrenceMinDistance()
+ .multiply(s.getOccurrenceCount() - (long) 1).compareTo(em.getBurstLength()) > 0) {
+ // OccurrenceCount * OccurrenceMinDistance > BurstLength, i.e. limit
+ // nb. of occurrences
+ em.setOccurrenceCount((int) Math.ceil(em.getBurstLength().divide(s.getOccurrenceMinDistance())));
+ } else {
+ // BurstLength is either not set, or does not constrain the nb. of
+ // Occurrences
+ em.setOccurrenceCount(s.getOccurrenceCount());
+ }
+
+ // Initialize optional parameter
+ if (s.getOccurrenceMinDistance() != null) {
+ em.setMinDistance(s.getOccurrenceMinDistance());
+ em.setDeltaBurst(em.getBurstLength().subtract(em.getMinDistance().multiply(em.getOccurrenceCount() - 1)));
+ } else {
+ em.setMinDistance(ZEROMS);
+ em.setDeltaBurst(ZEROMS);
+ }
+ return em;
+ }
+
+ @java.lang.SuppressWarnings("squid:S1172")
+ private static IEventModel ofStimuli(SingleStimulus singleStimulus) {
+ // Warnings disabled on purpose, since it is intended that neither
+ // singleStimulus nor em are used within this method.
+ @java.lang.SuppressWarnings("squid:S1488")
+ EMSingle em = new EMSingle();
+ return em;
+ }
+
+ private static IEventModel ofStimuli(VariableRateStimulus s) {
+ EMVariableRate em = new EMVariableRate();
+ em.setStep(s.getStep());
+ em.setLowerBoundOccurrences(s.getOccurrencesPerStep().getLowerBound());
+ em.setUpperBoundOccurrences(s.getOccurrencesPerStep().getUpperBound());
+ em.setAverageBoundOccurrences(s.getOccurrencesPerStep().getAverage());
+ return em;
+ }
+
+ /*********************************************************
+ * Utility functions used to support arithmetic operations
+ *********************************************************/
+
+ /**
+ * Returns the maximum between <a href="#{@link}">{@link Time}</a> a and b
+ *
+ * @param a
+ * @param b
+ * @return
+ */
+ public static Time max(final Time a, final Time b) {
+ return (a.compareTo(b) > 0) ? a : b;
+ }
+
+ /**
+ * Returns the minimum between <a href="#{@link}">{@link Time}</a> a and b
+ *
+ * @param a
+ * @param b
+ * @return
+ */
+ public static Time min(final Time a, final Time b) {
+ return (a.compareTo(b) < 0) ? a : b;
+ }
+
+ /**
+ * Returns {@code True} if the given {@link Time} is either {@code Null} or 0.
+ *
+ * @param t
+ * @return
+ */
+ public static boolean isNullOrZero(final Time t) {
+ return (t == null || t.getValue().compareTo(BigInteger.ZERO) == 0);
+ }
+}
diff --git a/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/IEventModel.java b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/IEventModel.java
new file mode 100644
index 0000000..b507649
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.amalthea.model/src/org/eclipse/app4mc/amalthea/model/util/stimuli/IEventModel.java
@@ -0,0 +1,72 @@
+/**
+ ********************************************************************************
+ * 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 org.eclipse.app4mc.amalthea.model.ModeLabel;
+import org.eclipse.app4mc.amalthea.model.Process;
+import org.eclipse.app4mc.amalthea.model.Time;
+import org.eclipse.app4mc.amalthea.model.util.RuntimeUtil.TimeType;
+import org.eclipse.emf.common.util.EMap;
+
+public interface IEventModel {
+ /**
+ * Returns the upper bound on the number of times a process is released in the
+ * <a href="#{@link}">{@link Time}</a> interval dt.
+ *
+ * @param dt size of the time interval
+ * @return maximum number of times a process is released
+ */
+ public long etaPlus(Time dt);
+
+ /**
+ * Returns the lower bound on the number of times a process is released in the
+ * <a href="#{@link}">{@link Time}</a> interval dt.
+ *
+ * @param dt size of the time interval
+ * @return minimum number of times a process is released
+ */
+ public long etaMinus(Time dt);
+
+ /**
+ * Returns the upper bound on the distance between n activation events.
+ *
+ * @param n number of activation events
+ * @return maximum distance between activation events
+ */
+ public Time deltaPlus(long n);
+
+ /**
+ * Returns the lower bound on the distance between n activation events.
+ *
+ * @param n number of activation events
+ * @return minimum distance between activation events
+ */
+ public Time deltaMinus(long n);
+
+ /**
+ * Returns the {@link TimeType} utilization of the given {@link Process} when
+ * activated using the given {@link IEventModel}.
+ *
+ * @param process The process for which utilization shall be calculated for.
+ * @param tt Type of the utilization bound (worst, average, or best case
+ * assumption) to calculate.
+ * @param modes What modes to consider during utilization calculation (use
+ * {@code null} to disable filtering and consider all operation
+ * modes)
+ * @return
+ */
+ public double getUtilization(Process process, TimeType tt, EMap<ModeLabel, String> modes);
+}