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);
+}