It's fairly simple with a couple main functions (plotting requires the matplotlib library*):
Code: Select all
CalcDutyCycles(x_max, y_max):
Calculates the overall duty cycle for each turn-on count for each source.
Inputs:
x_max - the period of the primary input source
y_max - the period of the secondary input source
Returns:
A list containing elements of the form:
(x_turnon, y_turnon, dutycycle, deadtime)
x_turnon - threshhold cycle count when the primary input is asserted
y_turnon - threshhold cycle count when the secondary input is asserted
dutycycle - aggregate duty cycle of the two inputs
deadtime - percent time of total period spent off in period leading block
GetSortedDutyCycles(x_max, y_max):
Returns a list from CalcDutyCycles(), sorted ascending by dutycycle
GetDeadtimeDutyCycles(x_max, y_max, deadtimeThreshhold = 1.1):
returns a list from CalcDutyCycles(), sorted ascending by dutycycle,
below a maximum deadtime threshhold
PlotDutyCycles(x_max, y_max):
Creates a plot of the aggregate duty cycles and the deadtime for the
given input periods.
- Matplotlib
- dateutil
- pyparser
- numpy
Code: Select all
import collections
import functools
from functools import reduce
class memoized(object):
'''Decorator. Caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned
(not reevaluated).
'''
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if not isinstance(args, collections.Hashable):
# uncacheable. a list, for instance.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
value = self.func(*args)
self.cache[args] = value
return value
def __repr__(self):
'''Return the function's docstring.'''
return self.func.__doc__
def __get__(self, obj, objtype):
'''Support instance methods.'''
return functools.partial(self.__call__, obj)
# Greatest common divisor of more than 2 numbers. Am I terrible for doing it this way?
def gcd(*numbers):
"""Return the greatest common divisor of the given integers"""
from fractions import gcd
return reduce(gcd, numbers)
# Least common multiple is not in standard libraries? It's in gmpy, but this is simple enough:
def lcm(*numbers):
"""Return lowest common multiple."""
def lcm(a, b):
return (a * b) // gcd(a, b)
return reduce(lcm, numbers, 1)
def CalcDutyCycles(x_max, y_max):
"""
CalcDutyCycles(x_max, y_max):
Calculates the overall duty cycle for each turn-on count for each source.
Inputs:
x_max - the period of the primary input source
y_max - the period of the secondary input source
Returns:
A list containing elements of the form:
(x_turnon, y_turnon, dutycycle, deadtime)
x_turnon - threshhold cycle count when the primary input is asserted
y_turnon - threshhold cycle count when the secondary input is asserted
dutycycle - aggregate duty cycle of the two inputs
deadtime - percent time of total period spent off in period leading block
"""
dutyCycles = []
period = lcm(x_max, y_max)
for x_on in range(1, x_max):
for y_on in range(1, y_max):
onCount = 0
deadtime = 0
for tick in range(period):
if ( (tick % x_max) == 0 ):
started = False
if ( ((tick % x_max) >= x_on) and ((tick % y_max) >= y_on) ):
onCount += 1
started = True
if (not started):
deadtime += 1
dutyCycles.append((x_on, y_on, float(onCount)/period, float(deadtime)/period))
return dutyCycles
def CalcDutyCycles_surface(x_max, y_max):
xList, yList, zList, deadList = ([],[],[],[])
period = lcm(x_max, y_max)
for y_on in range(1, y_max):
xList_Curr = []
yList_Curr = []
zList_Curr = []
deadList_Curr = []
for x_on in range(1, x_max):
onCount = 0
deadtime = 0
started = False
for tick in range(period):
if ( (tick % x_max) == 0 ):
started = False
if ( ((tick % x_max) >= x_on) and ((tick % y_max) >= y_on) ):
onCount += 1
started = True
if (not started):
deadtime += 1
xList_Curr.append(x_on)
yList_Curr.append(y_on)
zList_Curr.append(float(onCount)/period)
deadList_Curr.append(float(deadtime)/period)
xList.append(xList_Curr)
yList.append(yList_Curr)
zList.append(zList_Curr)
deadList.append(deadList_Curr)
return xList, yList, zList, deadList
@memoized
def GetSortedDutyCycles(x_max, y_max):
"""
GetSortedDutyCycles(x_max, y_max):
Returns a list from CalcDutyCycles(), sorted ascending by dutycycle
"""
dutyCycles = CalcDutyCycles(x_max, y_max)
return sorted(CalcDutyCycles(x_max, y_max), key=lambda tup: tup[2])
def GetDeadtimeDutyCycles(x_max, y_max, deadtimeThreshhold = 1.1):
"""
GetDeadtimeDutyCycles(x_max, y_max, deadtimeThreshhold = 1.1):
returns a list from CalcDutyCycles(), sorted ascending by dutycycle,
below a maximum deadtime threshhold
"""
return [x for x in GetSortedDutyCycles(x_max, y_max) if x[3] <= deadtimeThreshhold]
try:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
def PlotDutyCycles(x_max, y_max):
"""
PlotDutyCycles(x_max, y_max):
Creates a plot of the aggregate duty cycles and the deadtime for the
given input periods.
"""
X,Y,Z,D = CalcDutyCycles_surface(x_max, y_max)
fig = plt.figure()
ax1 = fig.add_subplot(111,projection='3d')
ax2 = fig.add_subplot(222,projection='3d')
surf1 = ax1.plot_surface(X,Y,Z, rstride=1, cstride=10, cmap=cm.gist_rainbow, linewidth=0)
ax1.set_xlabel("X Cycles")
ax1.set_ylabel("Y Cycles")
ax1.set_zlabel("Duty Cycle")
surf2 = ax2.plot_surface(X,Y,D, rstride=1, cstride=10, cmap=cm.coolwarm, linewidth=0)
ax2.set_xlabel("X Cycles")
ax2.set_ylabel("Y Cycels")
ax2.set_zlabel("Dead Time")
plt.show()
except:
print('\n\nMatplotlib not available:\n Graphing functions unavailable\n\n')