Calculating relay operating times, according to IEC 60255

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite
2












The script below is used to determine relay operating times, as defined in the standard IEC 60255. The equation and relevant information can be found here.



curve_equation takes three input values, and outputs a list of values representing the trip times for currents in the fixed range np.logspace(np.log10(Is), 5, 1e2).



The part in the bottom plots the trip times for the different curves. The function curve_equation will be used to much more than that, so I'm mostly interested in improvements to that one.



# -*- coding: utf-8 -*-
"""
Created on Sun Apr 29 13:28:26 2018

@author: Stewie
"""
import numpy as np
import matplotlib.pyplot as plt

def curve_equation(curve_name, T, Is):
"""
This function calculates and returns the inverse curves for
the selected curve type.
Input:
curve_name: one of the strings: "SIT", "VIT", "LTI", "EIT", "UIT"
T: The time dial setting. Typically in the range 0.1 ... 1
Is: The current setting. Typically in the range 100-10000

The appropriate k, alpha and theta values are selected based on the
curve type.
"""

# Define constants to be used in the curve equation
k = (0.14, 13.5, 120, 80, 315.2)
alpha = (0.02, 1, 2, 2, 2.5)
beta = (2.97, 1.5, 13.33, 0.808, 1)
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")

# idx is the index of the values we want
try:
idx = curve_types.index(curve_name)
except:
print("Non-existing curve-type")
return

I = np.logspace(np.log10(Is), 5, 1e2)

def td_equation():
td = k[idx]/((I/Is)**alpha[idx]-1) * (T / beta[idx])
return(td)

return(td_equation())

# For testing / plotting purposes:
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")
I = np.logspace(1, 5, 1e2)
for curve_type in curve_types:
plt.loglog(I, curve_equation(curve_type, 1, 1e2), label=curve_type)

plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()


enter image description here







share|improve this question





















  • I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
    – 200_success
    Apr 29 at 19:19










  • @200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
    – Stewie Griffin
    Apr 29 at 19:26

















up vote
6
down vote

favorite
2












The script below is used to determine relay operating times, as defined in the standard IEC 60255. The equation and relevant information can be found here.



curve_equation takes three input values, and outputs a list of values representing the trip times for currents in the fixed range np.logspace(np.log10(Is), 5, 1e2).



The part in the bottom plots the trip times for the different curves. The function curve_equation will be used to much more than that, so I'm mostly interested in improvements to that one.



# -*- coding: utf-8 -*-
"""
Created on Sun Apr 29 13:28:26 2018

@author: Stewie
"""
import numpy as np
import matplotlib.pyplot as plt

def curve_equation(curve_name, T, Is):
"""
This function calculates and returns the inverse curves for
the selected curve type.
Input:
curve_name: one of the strings: "SIT", "VIT", "LTI", "EIT", "UIT"
T: The time dial setting. Typically in the range 0.1 ... 1
Is: The current setting. Typically in the range 100-10000

The appropriate k, alpha and theta values are selected based on the
curve type.
"""

# Define constants to be used in the curve equation
k = (0.14, 13.5, 120, 80, 315.2)
alpha = (0.02, 1, 2, 2, 2.5)
beta = (2.97, 1.5, 13.33, 0.808, 1)
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")

# idx is the index of the values we want
try:
idx = curve_types.index(curve_name)
except:
print("Non-existing curve-type")
return

I = np.logspace(np.log10(Is), 5, 1e2)

def td_equation():
td = k[idx]/((I/Is)**alpha[idx]-1) * (T / beta[idx])
return(td)

return(td_equation())

# For testing / plotting purposes:
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")
I = np.logspace(1, 5, 1e2)
for curve_type in curve_types:
plt.loglog(I, curve_equation(curve_type, 1, 1e2), label=curve_type)

plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()


enter image description here







share|improve this question





















  • I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
    – 200_success
    Apr 29 at 19:19










  • @200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
    – Stewie Griffin
    Apr 29 at 19:26













up vote
6
down vote

favorite
2









up vote
6
down vote

favorite
2






2





The script below is used to determine relay operating times, as defined in the standard IEC 60255. The equation and relevant information can be found here.



curve_equation takes three input values, and outputs a list of values representing the trip times for currents in the fixed range np.logspace(np.log10(Is), 5, 1e2).



The part in the bottom plots the trip times for the different curves. The function curve_equation will be used to much more than that, so I'm mostly interested in improvements to that one.



# -*- coding: utf-8 -*-
"""
Created on Sun Apr 29 13:28:26 2018

@author: Stewie
"""
import numpy as np
import matplotlib.pyplot as plt

def curve_equation(curve_name, T, Is):
"""
This function calculates and returns the inverse curves for
the selected curve type.
Input:
curve_name: one of the strings: "SIT", "VIT", "LTI", "EIT", "UIT"
T: The time dial setting. Typically in the range 0.1 ... 1
Is: The current setting. Typically in the range 100-10000

The appropriate k, alpha and theta values are selected based on the
curve type.
"""

# Define constants to be used in the curve equation
k = (0.14, 13.5, 120, 80, 315.2)
alpha = (0.02, 1, 2, 2, 2.5)
beta = (2.97, 1.5, 13.33, 0.808, 1)
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")

# idx is the index of the values we want
try:
idx = curve_types.index(curve_name)
except:
print("Non-existing curve-type")
return

I = np.logspace(np.log10(Is), 5, 1e2)

def td_equation():
td = k[idx]/((I/Is)**alpha[idx]-1) * (T / beta[idx])
return(td)

return(td_equation())

# For testing / plotting purposes:
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")
I = np.logspace(1, 5, 1e2)
for curve_type in curve_types:
plt.loglog(I, curve_equation(curve_type, 1, 1e2), label=curve_type)

plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()


enter image description here







share|improve this question













The script below is used to determine relay operating times, as defined in the standard IEC 60255. The equation and relevant information can be found here.



curve_equation takes three input values, and outputs a list of values representing the trip times for currents in the fixed range np.logspace(np.log10(Is), 5, 1e2).



The part in the bottom plots the trip times for the different curves. The function curve_equation will be used to much more than that, so I'm mostly interested in improvements to that one.



# -*- coding: utf-8 -*-
"""
Created on Sun Apr 29 13:28:26 2018

@author: Stewie
"""
import numpy as np
import matplotlib.pyplot as plt

def curve_equation(curve_name, T, Is):
"""
This function calculates and returns the inverse curves for
the selected curve type.
Input:
curve_name: one of the strings: "SIT", "VIT", "LTI", "EIT", "UIT"
T: The time dial setting. Typically in the range 0.1 ... 1
Is: The current setting. Typically in the range 100-10000

The appropriate k, alpha and theta values are selected based on the
curve type.
"""

# Define constants to be used in the curve equation
k = (0.14, 13.5, 120, 80, 315.2)
alpha = (0.02, 1, 2, 2, 2.5)
beta = (2.97, 1.5, 13.33, 0.808, 1)
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")

# idx is the index of the values we want
try:
idx = curve_types.index(curve_name)
except:
print("Non-existing curve-type")
return

I = np.logspace(np.log10(Is), 5, 1e2)

def td_equation():
td = k[idx]/((I/Is)**alpha[idx]-1) * (T / beta[idx])
return(td)

return(td_equation())

# For testing / plotting purposes:
curve_types = ("SIT", "VIT", "LTI", "EIT", "UIT")
I = np.logspace(1, 5, 1e2)
for curve_type in curve_types:
plt.loglog(I, curve_equation(curve_type, 1, 1e2), label=curve_type)

plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()


enter image description here









share|improve this question












share|improve this question




share|improve this question








edited Apr 29 at 19:24
























asked Apr 29 at 18:45









Stewie Griffin

1,717831




1,717831











  • I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
    – 200_success
    Apr 29 at 19:19










  • @200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
    – Stewie Griffin
    Apr 29 at 19:26

















  • I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
    – 200_success
    Apr 29 at 19:19










  • @200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
    – Stewie Griffin
    Apr 29 at 19:26
















I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
– 200_success
Apr 29 at 19:19




I get a NameError: name 'I' is not defined error on the plt.loglog(I, …) line.
– 200_success
Apr 29 at 19:19












@200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
– Stewie Griffin
Apr 29 at 19:26





@200_success thanks for noticing! It should work now. I made a mistake and removed that line.I was already stored as a variable outside the function so I didn't get an error message when I removed it.
– Stewie Griffin
Apr 29 at 19:26











1 Answer
1






active

oldest

votes

















up vote
6
down vote



accepted










You need not declare # -*- coding: utf-8 -*- in Python 3: UTF-8 is the default.



Writing I = np.logspace(…) inside your curve_equation function is bad practice, because it hard-codes certain x-values for your plot. In fact, your plot is wrong, because the calculations are for $I_s$ values ranging from 102 A to 105 A (due to the inner I = np.logspace(np.log10(Is), 5, 1e2)), but your plot's x axis is labelled as if the values ranged from 101 A to 105 A (due to the outer I = np.logspace(1, 5, 1e2)). Specifically, four of the curves should intersect at 1 kA, 10 s.



I also find the way in which the curves are specified very cumbersome. You have five magic strings ("SIT", "VIT", etc.) which you search for in curve_types. Then, you retrieve the k, alpha, and beta parameters by using the corresponding index. Printing an error message and returning None is an inappropriate error-handling mechanism; you should raise or propagate an exception for such a fatal error.



I'd rather specify the parameters the other way: each curve should be defined as a $(k, alpha, beta)$ tuple. To do that, I would use namedtuples. I'd go a bit further and make each namedtuple smart enough to know what its own $td(I)$ function looks like, when $T$ and $I_s$ are specified.



from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt

class IEC_Curve(namedtuple('IEC_Curve', 'name k alpha beta')):
def td(self, T, Is):
"""
The td(I) function for a given IEC curve, time dial setting, and
current setting.
"""
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)

SIT = IEC_Curve('SIT', k=0.14, alpha=0.02, beta=2.97) # Standard inverse / A
VIT = IEC_Curve('VIT', k=13.5, alpha=1, beta=1.5) # Very inverse / B
LTI = IEC_Curve('LTI', k=120, alpha=2, beta=13.33) # Long time inverse / B
EIT = IEC_Curve('EIT', k=80, alpha=2, beta=0.808) # Extremely inverse / C
UIT = IEC_Curve('UIT', k=315.2, alpha=2.5, beta=1) # Ultra inverse

I = np.logspace(2, 5, 1e2)
for curve in (SIT, VIT, LTI, EIT, UIT):
plt.loglog(I, curve.td(T=1, Is=1e2)(I), label=curve.name)
plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()
plt.show()



Note that td is a method that returns a function. To calculate values of $td$ with fixed $T$, fixed $I_s$, and swept $I$:



UIT.td(T=1, Is=1e2)(I=np.logspace(2, 5, 1e2))


To calculate a value of $td$ for a given $T$, $I_s$, and $I$:



UIT.td(T=1, Is=1e2)(I=1e2)


(This happens to cause a ZeroDivisionError, which manifests itself as a RuntimeWarning: divide by zero encountered in divide at the beginning of the sweep.)




In case you feel intimidated by my use of inheritance of namedtuple, you could use a manually written constructor instead:



class IEC_Curve:
def __init__(self, name, k, alpha, beta):
self.name = name
self.k = k
self.alpha = alpha
self.beta = beta

def td(self, T, Is):
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)


One difference is that namedtuples are immutable, whereas the members here are mutable.






share|improve this answer























  • Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
    – Stewie Griffin
    Apr 29 at 20:24











  • Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
    – Stewie Griffin
    Apr 29 at 20:28











  • How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
    – Stewie Griffin
    Apr 29 at 20:36










  • @StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
    – Graipher
    Apr 29 at 20:44











  • Thank you so much for explaining how I can use it! This is great!
    – Stewie Griffin
    Apr 30 at 6:52










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193223%2fcalculating-relay-operating-times-according-to-iec-60255%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
6
down vote



accepted










You need not declare # -*- coding: utf-8 -*- in Python 3: UTF-8 is the default.



Writing I = np.logspace(…) inside your curve_equation function is bad practice, because it hard-codes certain x-values for your plot. In fact, your plot is wrong, because the calculations are for $I_s$ values ranging from 102 A to 105 A (due to the inner I = np.logspace(np.log10(Is), 5, 1e2)), but your plot's x axis is labelled as if the values ranged from 101 A to 105 A (due to the outer I = np.logspace(1, 5, 1e2)). Specifically, four of the curves should intersect at 1 kA, 10 s.



I also find the way in which the curves are specified very cumbersome. You have five magic strings ("SIT", "VIT", etc.) which you search for in curve_types. Then, you retrieve the k, alpha, and beta parameters by using the corresponding index. Printing an error message and returning None is an inappropriate error-handling mechanism; you should raise or propagate an exception for such a fatal error.



I'd rather specify the parameters the other way: each curve should be defined as a $(k, alpha, beta)$ tuple. To do that, I would use namedtuples. I'd go a bit further and make each namedtuple smart enough to know what its own $td(I)$ function looks like, when $T$ and $I_s$ are specified.



from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt

class IEC_Curve(namedtuple('IEC_Curve', 'name k alpha beta')):
def td(self, T, Is):
"""
The td(I) function for a given IEC curve, time dial setting, and
current setting.
"""
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)

SIT = IEC_Curve('SIT', k=0.14, alpha=0.02, beta=2.97) # Standard inverse / A
VIT = IEC_Curve('VIT', k=13.5, alpha=1, beta=1.5) # Very inverse / B
LTI = IEC_Curve('LTI', k=120, alpha=2, beta=13.33) # Long time inverse / B
EIT = IEC_Curve('EIT', k=80, alpha=2, beta=0.808) # Extremely inverse / C
UIT = IEC_Curve('UIT', k=315.2, alpha=2.5, beta=1) # Ultra inverse

I = np.logspace(2, 5, 1e2)
for curve in (SIT, VIT, LTI, EIT, UIT):
plt.loglog(I, curve.td(T=1, Is=1e2)(I), label=curve.name)
plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()
plt.show()



Note that td is a method that returns a function. To calculate values of $td$ with fixed $T$, fixed $I_s$, and swept $I$:



UIT.td(T=1, Is=1e2)(I=np.logspace(2, 5, 1e2))


To calculate a value of $td$ for a given $T$, $I_s$, and $I$:



UIT.td(T=1, Is=1e2)(I=1e2)


(This happens to cause a ZeroDivisionError, which manifests itself as a RuntimeWarning: divide by zero encountered in divide at the beginning of the sweep.)




In case you feel intimidated by my use of inheritance of namedtuple, you could use a manually written constructor instead:



class IEC_Curve:
def __init__(self, name, k, alpha, beta):
self.name = name
self.k = k
self.alpha = alpha
self.beta = beta

def td(self, T, Is):
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)


One difference is that namedtuples are immutable, whereas the members here are mutable.






share|improve this answer























  • Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
    – Stewie Griffin
    Apr 29 at 20:24











  • Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
    – Stewie Griffin
    Apr 29 at 20:28











  • How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
    – Stewie Griffin
    Apr 29 at 20:36










  • @StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
    – Graipher
    Apr 29 at 20:44











  • Thank you so much for explaining how I can use it! This is great!
    – Stewie Griffin
    Apr 30 at 6:52














up vote
6
down vote



accepted










You need not declare # -*- coding: utf-8 -*- in Python 3: UTF-8 is the default.



Writing I = np.logspace(…) inside your curve_equation function is bad practice, because it hard-codes certain x-values for your plot. In fact, your plot is wrong, because the calculations are for $I_s$ values ranging from 102 A to 105 A (due to the inner I = np.logspace(np.log10(Is), 5, 1e2)), but your plot's x axis is labelled as if the values ranged from 101 A to 105 A (due to the outer I = np.logspace(1, 5, 1e2)). Specifically, four of the curves should intersect at 1 kA, 10 s.



I also find the way in which the curves are specified very cumbersome. You have five magic strings ("SIT", "VIT", etc.) which you search for in curve_types. Then, you retrieve the k, alpha, and beta parameters by using the corresponding index. Printing an error message and returning None is an inappropriate error-handling mechanism; you should raise or propagate an exception for such a fatal error.



I'd rather specify the parameters the other way: each curve should be defined as a $(k, alpha, beta)$ tuple. To do that, I would use namedtuples. I'd go a bit further and make each namedtuple smart enough to know what its own $td(I)$ function looks like, when $T$ and $I_s$ are specified.



from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt

class IEC_Curve(namedtuple('IEC_Curve', 'name k alpha beta')):
def td(self, T, Is):
"""
The td(I) function for a given IEC curve, time dial setting, and
current setting.
"""
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)

SIT = IEC_Curve('SIT', k=0.14, alpha=0.02, beta=2.97) # Standard inverse / A
VIT = IEC_Curve('VIT', k=13.5, alpha=1, beta=1.5) # Very inverse / B
LTI = IEC_Curve('LTI', k=120, alpha=2, beta=13.33) # Long time inverse / B
EIT = IEC_Curve('EIT', k=80, alpha=2, beta=0.808) # Extremely inverse / C
UIT = IEC_Curve('UIT', k=315.2, alpha=2.5, beta=1) # Ultra inverse

I = np.logspace(2, 5, 1e2)
for curve in (SIT, VIT, LTI, EIT, UIT):
plt.loglog(I, curve.td(T=1, Is=1e2)(I), label=curve.name)
plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()
plt.show()



Note that td is a method that returns a function. To calculate values of $td$ with fixed $T$, fixed $I_s$, and swept $I$:



UIT.td(T=1, Is=1e2)(I=np.logspace(2, 5, 1e2))


To calculate a value of $td$ for a given $T$, $I_s$, and $I$:



UIT.td(T=1, Is=1e2)(I=1e2)


(This happens to cause a ZeroDivisionError, which manifests itself as a RuntimeWarning: divide by zero encountered in divide at the beginning of the sweep.)




In case you feel intimidated by my use of inheritance of namedtuple, you could use a manually written constructor instead:



class IEC_Curve:
def __init__(self, name, k, alpha, beta):
self.name = name
self.k = k
self.alpha = alpha
self.beta = beta

def td(self, T, Is):
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)


One difference is that namedtuples are immutable, whereas the members here are mutable.






share|improve this answer























  • Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
    – Stewie Griffin
    Apr 29 at 20:24











  • Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
    – Stewie Griffin
    Apr 29 at 20:28











  • How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
    – Stewie Griffin
    Apr 29 at 20:36










  • @StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
    – Graipher
    Apr 29 at 20:44











  • Thank you so much for explaining how I can use it! This is great!
    – Stewie Griffin
    Apr 30 at 6:52












up vote
6
down vote



accepted







up vote
6
down vote



accepted






You need not declare # -*- coding: utf-8 -*- in Python 3: UTF-8 is the default.



Writing I = np.logspace(…) inside your curve_equation function is bad practice, because it hard-codes certain x-values for your plot. In fact, your plot is wrong, because the calculations are for $I_s$ values ranging from 102 A to 105 A (due to the inner I = np.logspace(np.log10(Is), 5, 1e2)), but your plot's x axis is labelled as if the values ranged from 101 A to 105 A (due to the outer I = np.logspace(1, 5, 1e2)). Specifically, four of the curves should intersect at 1 kA, 10 s.



I also find the way in which the curves are specified very cumbersome. You have five magic strings ("SIT", "VIT", etc.) which you search for in curve_types. Then, you retrieve the k, alpha, and beta parameters by using the corresponding index. Printing an error message and returning None is an inappropriate error-handling mechanism; you should raise or propagate an exception for such a fatal error.



I'd rather specify the parameters the other way: each curve should be defined as a $(k, alpha, beta)$ tuple. To do that, I would use namedtuples. I'd go a bit further and make each namedtuple smart enough to know what its own $td(I)$ function looks like, when $T$ and $I_s$ are specified.



from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt

class IEC_Curve(namedtuple('IEC_Curve', 'name k alpha beta')):
def td(self, T, Is):
"""
The td(I) function for a given IEC curve, time dial setting, and
current setting.
"""
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)

SIT = IEC_Curve('SIT', k=0.14, alpha=0.02, beta=2.97) # Standard inverse / A
VIT = IEC_Curve('VIT', k=13.5, alpha=1, beta=1.5) # Very inverse / B
LTI = IEC_Curve('LTI', k=120, alpha=2, beta=13.33) # Long time inverse / B
EIT = IEC_Curve('EIT', k=80, alpha=2, beta=0.808) # Extremely inverse / C
UIT = IEC_Curve('UIT', k=315.2, alpha=2.5, beta=1) # Ultra inverse

I = np.logspace(2, 5, 1e2)
for curve in (SIT, VIT, LTI, EIT, UIT):
plt.loglog(I, curve.td(T=1, Is=1e2)(I), label=curve.name)
plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()
plt.show()



Note that td is a method that returns a function. To calculate values of $td$ with fixed $T$, fixed $I_s$, and swept $I$:



UIT.td(T=1, Is=1e2)(I=np.logspace(2, 5, 1e2))


To calculate a value of $td$ for a given $T$, $I_s$, and $I$:



UIT.td(T=1, Is=1e2)(I=1e2)


(This happens to cause a ZeroDivisionError, which manifests itself as a RuntimeWarning: divide by zero encountered in divide at the beginning of the sweep.)




In case you feel intimidated by my use of inheritance of namedtuple, you could use a manually written constructor instead:



class IEC_Curve:
def __init__(self, name, k, alpha, beta):
self.name = name
self.k = k
self.alpha = alpha
self.beta = beta

def td(self, T, Is):
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)


One difference is that namedtuples are immutable, whereas the members here are mutable.






share|improve this answer















You need not declare # -*- coding: utf-8 -*- in Python 3: UTF-8 is the default.



Writing I = np.logspace(…) inside your curve_equation function is bad practice, because it hard-codes certain x-values for your plot. In fact, your plot is wrong, because the calculations are for $I_s$ values ranging from 102 A to 105 A (due to the inner I = np.logspace(np.log10(Is), 5, 1e2)), but your plot's x axis is labelled as if the values ranged from 101 A to 105 A (due to the outer I = np.logspace(1, 5, 1e2)). Specifically, four of the curves should intersect at 1 kA, 10 s.



I also find the way in which the curves are specified very cumbersome. You have five magic strings ("SIT", "VIT", etc.) which you search for in curve_types. Then, you retrieve the k, alpha, and beta parameters by using the corresponding index. Printing an error message and returning None is an inappropriate error-handling mechanism; you should raise or propagate an exception for such a fatal error.



I'd rather specify the parameters the other way: each curve should be defined as a $(k, alpha, beta)$ tuple. To do that, I would use namedtuples. I'd go a bit further and make each namedtuple smart enough to know what its own $td(I)$ function looks like, when $T$ and $I_s$ are specified.



from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt

class IEC_Curve(namedtuple('IEC_Curve', 'name k alpha beta')):
def td(self, T, Is):
"""
The td(I) function for a given IEC curve, time dial setting, and
current setting.
"""
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)

SIT = IEC_Curve('SIT', k=0.14, alpha=0.02, beta=2.97) # Standard inverse / A
VIT = IEC_Curve('VIT', k=13.5, alpha=1, beta=1.5) # Very inverse / B
LTI = IEC_Curve('LTI', k=120, alpha=2, beta=13.33) # Long time inverse / B
EIT = IEC_Curve('EIT', k=80, alpha=2, beta=0.808) # Extremely inverse / C
UIT = IEC_Curve('UIT', k=315.2, alpha=2.5, beta=1) # Ultra inverse

I = np.logspace(2, 5, 1e2)
for curve in (SIT, VIT, LTI, EIT, UIT):
plt.loglog(I, curve.td(T=1, Is=1e2)(I), label=curve.name)
plt.xlabel("Current [A]")
plt.ylabel("Time [s]")
plt.title("IEC Time/current curves")
plt.grid()
plt.legend()
plt.show()



Note that td is a method that returns a function. To calculate values of $td$ with fixed $T$, fixed $I_s$, and swept $I$:



UIT.td(T=1, Is=1e2)(I=np.logspace(2, 5, 1e2))


To calculate a value of $td$ for a given $T$, $I_s$, and $I$:



UIT.td(T=1, Is=1e2)(I=1e2)


(This happens to cause a ZeroDivisionError, which manifests itself as a RuntimeWarning: divide by zero encountered in divide at the beginning of the sweep.)




In case you feel intimidated by my use of inheritance of namedtuple, you could use a manually written constructor instead:



class IEC_Curve:
def __init__(self, name, k, alpha, beta):
self.name = name
self.k = k
self.alpha = alpha
self.beta = beta

def td(self, T, Is):
return lambda I: self.k * T / self.beta / ((I / Is)**self.alpha - 1)


One difference is that namedtuples are immutable, whereas the members here are mutable.







share|improve this answer















share|improve this answer



share|improve this answer








edited Apr 30 at 0:12


























answered Apr 29 at 20:16









200_success

123k14142399




123k14142399











  • Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
    – Stewie Griffin
    Apr 29 at 20:24











  • Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
    – Stewie Griffin
    Apr 29 at 20:28











  • How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
    – Stewie Griffin
    Apr 29 at 20:36










  • @StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
    – Graipher
    Apr 29 at 20:44











  • Thank you so much for explaining how I can use it! This is great!
    – Stewie Griffin
    Apr 30 at 6:52
















  • Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
    – Stewie Griffin
    Apr 29 at 20:24











  • Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
    – Stewie Griffin
    Apr 29 at 20:28











  • How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
    – Stewie Griffin
    Apr 29 at 20:36










  • @StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
    – Graipher
    Apr 29 at 20:44











  • Thank you so much for explaining how I can use it! This is great!
    – Stewie Griffin
    Apr 30 at 6:52















Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
– Stewie Griffin
Apr 29 at 20:24





Thanks! I was originally planning on using the namedtuple approach, but didn't know how to do it, since I've never heard of it. I only knew about key, value pairs, and I needed more than one value per key.
– Stewie Griffin
Apr 29 at 20:24













Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
– Stewie Griffin
Apr 29 at 20:28





Hardcoding the I-values was actually intentional, since the range of the plot is usually fixed, and giving I as input would indicate that it's a variable, when it actually isn't. It's always from Is (which is already an input), and up to 10**5. Passing both Is and I is unnecessary since I doesn't give any information. I agree with you though... This is better!
– Stewie Griffin
Apr 29 at 20:28













How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
– Stewie Griffin
Apr 29 at 20:36




How do I get the operating times (the list of values) from the function, when I know the curve type? I seldom want all values (that's why I have it as an input).
– Stewie Griffin
Apr 29 at 20:36












@StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
– Graipher
Apr 29 at 20:44





@StewieGriffin Just make it a dictionary with the names as the keys and the IEC_Curves as values.
– Graipher
Apr 29 at 20:44













Thank you so much for explaining how I can use it! This is great!
– Stewie Griffin
Apr 30 at 6:52




Thank you so much for explaining how I can use it! This is great!
– Stewie Griffin
Apr 30 at 6:52












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193223%2fcalculating-relay-operating-times-according-to-iec-60255%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation