-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support concentric neutral cables #16
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
5187fa1
use delta for more concise formulation
AnjoMan 8845a58
Add test case based on Kersting
AnjoMan 949a7fd
Implement Carson's Equations for concentric neutral cables
veronicaguo 18fe874
Simplify equations before computing to avoid undefined terms
veronicaguo 8351582
Fix lint error
veronicaguo 9c9c85f
Refactor
veronicaguo 7ffb2b0
Get neutral wire positions from corresponding phase conductors
veronicaguo c6a6ed6
Put concentric model in init.py
veronicaguo 9d7e4ed
Test with IEEE37 underground cable config
veronicaguo 04b4c1a
Refactor tests
veronicaguo d2193b1
Fix flake8
veronicaguo 11c786a
Use separate helper for concentric model
veronicaguo 18a8d84
Flake8
veronicaguo c1c5a13
Assign set evaluations to variables
veronicaguo 53fa95f
Drop duplicating compute_P()
veronicaguo 7cdfd20
Add tests for single/two phase cables
veronicaguo 723ff87
Expose `impedance` as a stand-alone function
veronicaguo c578e9a
Update readme
veronicaguo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from carsons.carsons import convert_geometric_model # noqa 401 | ||
from carsons.carsons import (convert_geometric_model, # noqa 401 | ||
impedance, # noqa 401 | ||
ConcentricNeutralCarsonsEquations) # noqa 401 | ||
|
||
name = "carsons" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,9 @@ | ||
from numpy import pi as π | ||
from collections import defaultdict | ||
from itertools import islice | ||
|
||
from numpy import zeros | ||
from numpy import arctan, cos, log, sin, sqrt, zeros | ||
from numpy import pi as π | ||
from numpy.linalg import inv | ||
from numpy import sqrt | ||
from numpy import log | ||
from numpy import cos | ||
from numpy import sin | ||
from numpy import arctan | ||
from itertools import islice | ||
|
||
|
||
def convert_geometric_model(geometric_model): | ||
|
@@ -18,6 +14,12 @@ def convert_geometric_model(geometric_model): | |
return z_abc | ||
|
||
|
||
def impedance(model): | ||
z_primitive = model.build_z_primitive() | ||
z_abc = perform_kron_reduction(z_primitive) | ||
return z_abc | ||
|
||
|
||
def perform_kron_reduction(z_primitive): | ||
""" Reduces the primitive impedance matrix to an equivalent impedance | ||
matrix. | ||
|
@@ -107,16 +109,17 @@ def compute_X(self, i, j): | |
Qᵢⱼ = self.compute_Q(i, j) | ||
ΔX = self.μ * self.ω / π * Qᵢⱼ | ||
|
||
# calculate geometry ratio 𝛥G | ||
if i != j: | ||
Dᵢⱼ = self.compute_D(i, j) | ||
dᵢⱼ = self.compute_d(i, j) | ||
geometry_ratio = Dᵢⱼ / dᵢⱼ | ||
𝛥G = Dᵢⱼ / dᵢⱼ | ||
else: | ||
hᵢ = self.get_h(i) | ||
gmrⱼ = self.gmr[j] | ||
geometry_ratio = 2.0 * hᵢ / gmrⱼ | ||
𝛥G = 2.0 * hᵢ / gmrⱼ | ||
|
||
X_o = self.ω * self.μ / (2 * π) * log(geometry_ratio) | ||
X_o = self.ω * self.μ / (2 * π) * log(𝛥G) | ||
|
||
return X_o + ΔX | ||
|
||
|
@@ -172,6 +175,7 @@ def compute_d(self, i, j): | |
|
||
def compute_D(self, i, j): | ||
xⱼ, yⱼ = self.phase_positions[j] | ||
|
||
return self.calculate_distance(self.phase_positions[i], (xⱼ, -yⱼ)) | ||
|
||
@staticmethod | ||
|
@@ -183,3 +187,74 @@ def calculate_distance(positionᵢ, positionⱼ): | |
def get_h(self, i): | ||
_, yᵢ = self.phase_positions[i] | ||
return yᵢ | ||
|
||
|
||
class ConcentricNeutralCarsonsEquations(CarsonsEquations): | ||
def __init__(self, model, *args, **kwargs): | ||
super().__init__(model) | ||
self.neutral_strand_gmr = model.neutral_strand_gmr | ||
self.neutral_strand_count = defaultdict( | ||
lambda: None, model.neutral_strand_count) | ||
self.neutral_strand_resistance = model.neutral_strand_resistance | ||
self.radius = defaultdict(lambda: None, { | ||
phase: (diameter_over_neutral - | ||
model.neutral_strand_diameter[phase]) / 2 | ||
for phase, diameter_over_neutral | ||
in model.diameter_over_neutral.items() | ||
}) | ||
self.phase_positions.update({ | ||
f"N{phase}": self.phase_positions[phase] | ||
for phase in self.phase_positions.keys() | ||
}) | ||
self.gmr.update({ | ||
phase: self.GMR_cn(phase) | ||
for phase in model.diameter_over_neutral.keys() | ||
}) | ||
self.r.update({ | ||
phase: resistance / model.neutral_strand_count[phase] | ||
for phase, resistance in model.neutral_strand_resistance.items() | ||
}) | ||
return | ||
|
||
def compute_d(self, i, j): | ||
I, J = set(i), set(j) | ||
r = self.radius[i] or self.radius[j] | ||
|
||
one_neutral_same_phase = I ^ J == set('N') | ||
different_phase = not I & J | ||
one_neutral = 'N' in I ^ J | ||
|
||
if one_neutral_same_phase: | ||
# Distance between a neutral/phase conductor of same phase | ||
return r | ||
|
||
distance_ij = self.calculate_distance(self.phase_positions[i], | ||
self.phase_positions[j]) | ||
if different_phase and one_neutral: | ||
# Distance between a neutral/phase conductor of different phase | ||
# approximate by modelling the concentric neutral cables as one | ||
# equivalent conductor directly above the phase conductor | ||
return (distance_ij**2 + r**2) ** 0.5 | ||
else: | ||
# Distance between two neutral/phase conductors | ||
return distance_ij | ||
|
||
def compute_X(self, i, j): | ||
Q_first_term = super().compute_Q(i, j, 1) | ||
|
||
# Simplify equations and don't compute Dᵢⱼ explicitly | ||
kᵢⱼ_Dᵢⱼ_ratio = sqrt(self.ω * self.μ / self.ρ) | ||
ΔX = Q_first_term * 2 + log(2) | ||
|
||
if i == j: | ||
X_o = -log(self.gmr[i]) - log(kᵢⱼ_Dᵢⱼ_ratio) | ||
else: | ||
X_o = -log(self.compute_d(i, j)) - log(kᵢⱼ_Dᵢⱼ_ratio) | ||
|
||
return (X_o + ΔX) * self.ω * self.μ / (2 * π) | ||
|
||
def GMR_cn(self, phase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I audited this against gridlab-d. The gridlab-d implementation is shown below GMRCN(4) = !(has_phase(PHASE_A) && strands_4 > 0) ? 0.0 : pow(GMR(4) * strands_4 * pow(rad_14, (strands_4 - 1)), (1.0 / strands_4)); Differences
|
||
GMR_s = self.neutral_strand_gmr[phase] | ||
k = self.neutral_strand_count[phase] | ||
R = self.radius[phase] | ||
return (GMR_s * k * R**(k-1))**(1/k) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comparison to Gridlab-d
To try and find the issues with the impedance calculations, I audited the gridlab-d source. I think this method differs from the Gridlab-d distance calculations in the following cases.
Background
Gridlab-d appears to use the following indexes for different phases. The snippets below use these phase indexes
Conductor to Neutral Cable
Python Implementation
In this case, the inputs are
I = {'A'}, J = {'N'}
This leads to the following:
Thus the code passed the check on line 233 and returns
(distance_ij**2 + r**2) ** 0.5
.Gridlab-D Implementation
However, gridlab-d does the following in the case of A -> N:
Solution
I think the correct solution in this case is to return
distance_ij
Conductor to Own Concentric Neutral
Python Implementation
In this case, the inputs are
I = {'A'}, J = {'A', 'N'}
This leads to the following:
Thus the check on line 227 is true, and so
r
is returned. This value was calculated earlier on lines 200 and 201 asGridlab-d Distance
Gridlab-d does the following:
Solution
Divide by 24 on line 201 instead of 2. Additionally, check that the gridlab-d outer diameter is the diameter over the neutral.
Conductor to Different Phase Concentric Neutral
Python Implementation
In this case, the inputs are
I = {'A'}, J = {'B', 'N'}
This leads to the following:
Thus the check on line 233 is True, and so the python code returns
(distance_ij**2 + r**2) ** 0.5
Gridlab-d Implementation
In otherwords, the distance from A -> BN is the same as the distance from A -> B.
Concentric Neutral to Another Concentric Neutral
Python Implementation
In this case, the inputs are
I = {'A', 'N'}, J = {'B', 'N'}
This leads to the following:
Thus the check on line 227 and the check on line 233 are False and so
distance_ij
is returnedGridlab-d Implementation
In other words, the distance from AN -> BN is the same as the distance from A -> B. The code appears to be correct here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the thorough audit @etimberg ! A couple points here:
For case
Conductor to Own Concentric Neutral
, I think gridlab-d divides by24
to include unit conversion frominch
toft
. We handle everything using metric units, so dividing by2
to get radius from diameter.For case
Conductor to Different Phase Concentric Neutral
, our implementation follows the assumption and example given in the Kersting's book, approximating the concentric neutrals as one single wire directly above their respective phase conductors. So there comes the Pythagorean theorem.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense @veronicaguo re 2 instead of 24. For the
Conductor to Different Phase Concentric Neutral
case, I'm not sure that the distance calculation is correct. It assumes all the cables are in one horizontal plane because that's the only way the triangle becomes right angle. If the cables were arranged in a geometry similar to this image, the distance from the top conductor to one of the bottom concentric neutrals is formed by a non right triangle and so the extra term from the cosine law would need to be added.Maybe a way to simplify the code here is to calculate the position (x,y) of the concentric neutral in a prior step. Then, all you'd do here is lookup
distance_ij
. It might be worthwhile testing this function independently too for the following cases: