| /** |
| ******************************************************************************** |
| * 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; |
| } |
| } |