Skip to content
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

Implementation of feature 720 asym_line #762

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

leovsch
Copy link

@leovsch leovsch commented Oct 4, 2024

Implement feature: # 720

Changes proposed in this PR include:

Implementation of feature 720.
Add a new component to support asym_line specification with R, X and C matrix values

Could you please pay extra attention to the points below when reviewing the PR:

I'm a new contributor so pay attention to the coding standards applicable to this project along with achitectural rules etc.

Check list

  • Check if the available input/update/output data suffices (check power-grid-model/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/ input.hpp / update.hpp / output.hpp or in the documentation directly)
  • If not, add the new data format to code_generation/data/attribute_classes/ input.json / update.json / output.json + run code_generation/code_gen.py
  • Create a new component in a new power-grid-model/power_grid_model_c/power_grid_model/include/power_grid_model/component/[component].hpp file that at least inherits from Base, but in this case GenericBranch should inherit from Branch
  • If necessary: add new enums or exceptions
  • Create the necessary unit tests in power-grid-model/tests/cpp_unit_tests/test_[component].cpp
  • Add the test_[component].cpp to power-grid-model/tests/cpp_unit_tests/CMakeLists.txt
  • Add component to power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp
  • Not necessary for this component (If necessary update main_core/topology.hpp / input.hpp / output.hpp / update.hpp)
  • Add component to code_generation/data/dataset_class_maps/dataset_definitions.json + re-run code_generation/code_gen.py
  • Add validation test cases to tests/data
  • Update input/update data validator for the new component: src/power_grid_model/validation/validation.py + add corresponding tests

@leovsch leovsch marked this pull request as draft October 4, 2024 09:57
@TonyXiang8787
Copy link
Member

Hi @leovsch, thanks for the draft PR. I have put the check list in the description of the PR to track where we are.

@TonyXiang8787 TonyXiang8787 linked an issue Oct 4, 2024 that may be closed by this pull request
@TonyXiang8787
Copy link
Member

TonyXiang8787 commented Oct 7, 2024

Hi @leovsch,

Herewith I come back with some suggestions on the implementation of AsymLine class.

Class member

I suggest having the following two class members for the parameters. The y_series_ and y_shunt_ are both 3*3 tensor with complex values.

double i_n_;
double base_i_;
ComplexTensor<asymmetric_t> y_series_;
ComplexTensor<asymmetric_t> y_shunt_;

Construction

The construction of i_n_ and base_i_ is trivial.

For the construction of y_series_, we can do it in the construction body:

if (is_nan(r_na)) {
// if r_na is nan, it means user already does the kron reduction
// we just assign the 3*3 z_series by fill-in with (r + x * imaginary_unit) for each entry
// also fill-in the upper triangle based on symmetry.
}
else {
// if r_na is not nan, the user specifies the neutral conductor parameters, we need to do kron reduction
// in a way that
// z_kl_reduced = z_kl - z_nk * z_nl / z_nn
// k, l = a, b, c
// note: z_nk = z_kn, etc.
// fill-in all the values of 3*3 z_series, also the symmetry
}
y_series_ = inverse(z_series);

We do the similar for y_shunt_. To translate c to y_shunt, use the formula y_shunt = 2 * pi * frequency * c * imaginary_unit

Calculation parameters

Symmetric parameter

To implement sym_calc_param(), we need to calculate y1_series and y1_shunt. Below I give the formula to calculate y1_series. It is the same for y1_shunt.

y_s = average of diagonal of y_series_;
y_m = average of off-diagonal of y_series_;
y1_series = y_s - y_m

You put the y1_series and y1_shunt into calc_param_y_sym.

Asymmetric parameter

To implement asym_calc_param(), there are no helper functions in the Branch to calculate asymmetric parameter in this case. We need to do it to translate y_series_ and y_shunt_ into yff, yft, ytf, ytt depending on the branch connection status.

Please learn the function calc_param_y_sym in Branch and implement something similar. Note all the values are now 3*3 tensor so the division becomes inverse of the tensor. And there is no tap_ratio so the equation is a bit simpler.

calc_param_y_sym(DoubleComplex const& y_series, // y_series must be converted to the "to" side of the branch
DoubleComplex const& y_shunt, // y_shunt must be converted to the "to" side of the branch
DoubleComplex const& tap_ratio) const {
double const tap = cabs(tap_ratio);
BranchCalcParam<symmetric_t> param{};
// not both connected
if (!branch_status()) {
// single connected
if (from_status() || to_status()) {
DoubleComplex branch_shunt;
// shunt value
if (cabs(y_shunt) < numerical_tolerance) {
branch_shunt = 0.0;
} else {
// branch_shunt = y_shunt/2 + 1/(1/y_series + 2/y_shunt)
branch_shunt = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt);
}
// from or to connected
param.yff() = from_status() ? (1.0 / tap / tap) * branch_shunt : 0.0;
param.ytt() = to_status() ? branch_shunt : 0.0;
}
}
// both connected
else {
param.ytt() = y_series + 0.5 * y_shunt;
param.yff() = (1.0 / tap / tap) * param.ytt();
param.yft() = (-1.0 / conj(tap_ratio)) * y_series;
param.ytf() = (-1.0 / tap_ratio) * y_series;
}
return param;
}

@TonyXiang8787 TonyXiang8787 changed the title Implementation of feature 720 Implementation of feature 720 asym_line Oct 8, 2024
@leovsch
Copy link
Author

leovsch commented Nov 6, 2024

First implementation version is here some general remarks and questions:

  1. During the implementation I required a 4*4 matrix I've added a new class which makes use of Eigen named ComplexTensor4 to implement this. I can imagine that you would want this somewhere else or with a different name, could anyone give me some insights here?
  2. I have added some functions inside the AsymLine class that could serve a more general purpose these being: kron_reduction, average_of_diagonal_of_matrix and average_of_off_diagonal_of_matrix. Is it an idea to move these to a more general place if that is already there?
  3. The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?
  4. For now I've mainly copied the tests from line and adjusted them to the asym_line implementation. However I have the feeling that I'm missing some context to accuratly write good tests. Could anyone help me out here?

I like working on the project, hope my questions are clear otherwise reach out 👍

@mgovers
Copy link
Member

mgovers commented Nov 6, 2024

Hi @leovsch ,

I will most likely spend some time tomorrow to review this, but at first glance, you've made the right decisions and followed the correct path. Without digging more into it, here's some initial response on your questions.

First implementation version is here some general remarks and questions:

1. During the implementation I required a 4*4 matrix I've added a new class which makes use of `Eigen` named `ComplexTensor4` to implement this. I can imagine that you would want this somewhere else or with a different name, could anyone give me some insights here?

we try to follow the paradigm "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck", especially on the things in <power_grid_model/common>. If it has all the properties of a 4D complex tensor, then it's a tensor and ComplexTensor4 is a good name. If it has properties that are not tensor-like (e.g. different transformation behavior), then ComplexMatrix4x4 is probably better. Don't be too scared, though, it's not part of the public interface, so we may change it at any later time anyways.

2. I have added some functions inside the `AsymLine` class that could serve a more general purpose these being: `kron_reduction`, `average_of_diagonal_of_matrix` and `average_of_off_diagonal_of_matrix`. Is it an idea to move these to a more general place if that is already there?

we do have something similar for transformers. That functionality resides in transformer_utils.hpp. You can make a similar branch_utils.hpp or line_utils.hpp for kron_reduction. for the other two, maybe you can come up with a nice name in the common/ subdirectory. Please do add tests for things that are in those locations, though, as they might (and probably will) be reused.

3. The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See [their documentation](https://opendss.epri.com/LineCode1.html) for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

I put it as a topic on the agenda for tomorrow with the rest of the team.

4. For now I've mainly copied the tests from `line` and adjusted them to the `asym_line` implementation. However I have the feeling that I'm missing some context to accuratly write good tests. Could anyone help me out here?

If you like, we can have a call about this. We do have some guidelines, but also a lot of heuristics, so it might be easier to talk to you directly. You can send an email to me (martijn.govers@alliander.com) if that is alright with you, and we can schedule a meeting.

I like working on the project, hope my questions are clear otherwise reach out 👍

That's great to hear! It's very nice to see external contributors working on the project, and we're happy to help you out at any time. Any feedback you have is also highly valued.

Again, thank you for your input so far!

Martijn

@nitbharambe
Copy link
Member

  1. During the implementation I required a 4*4 matrix I've added a new class which makes use of Eigen named ComplexTensor4 to implement this. I can imagine that you would want this somewhere else or with a different name, could anyone give me some insights here?
  1. Agreed with your thinking as the physical interpretation of ComplexTensor4 strays from ComplexTensor. However its generic enough for three_phase_tensor. Adding to @mgovers' comment, maybe if a better name is thought of, you can make an alias in this file's namespace.

Thanks again for the contribution!

Comment on lines +59 to +63
Eigen::Array<DoubleComplex, 1, 3> Y_ba;
Y_ba << Y(3,0), Y(3,1), Y(3,2);
DoubleComplex Y_bb_inv = 1.0 / Y(3,3);

return Y_aa - ((Y_ab * Y_bb_inv).matrix() * Y_ba.matrix()).array();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its always tricky with eigen! Is this what you intended to do?

Suggested change
Eigen::Array<DoubleComplex, 1, 3> Y_ba;
Y_ba << Y(3,0), Y(3,1), Y(3,2);
DoubleComplex Y_bb_inv = 1.0 / Y(3,3);
return Y_aa - ((Y_ab * Y_bb_inv).matrix() * Y_ba.matrix()).array();
ComplexValue<asymmetric_t> Y_ba(Y(3,0), Y(3,1), Y(3,2));
DoubleComplex Y_bb_inv = 1.0 / Y(3,3);
return Y_aa - vector_outer_product(Y_ba, Y_ab) * Y_bb_inv;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes I needed a row times column vector implementation, I'll change it to the vector_outer_product function

Comment on lines +80 to +84
DoubleComplex a = std::pow(e, 1.0i * (2.0 / 3.0) * pi);
ComplexTensor<asymmetric_t> a_matrix = ComplexTensor<asymmetric_t>(1, 1, pow(a, 2), 1, a, pow(a, 2));
ComplexTensor<asymmetric_t> a_matrix_inv = (1.0/3.0) * ComplexTensor<asymmetric_t>(1, 1, a, 1, pow(a, 2), a);
ComplexTensor<asymmetric_t> z_series = (a_matrix_inv.matrix() * z_series_abc.matrix() * a_matrix.matrix()).array();
return z_series;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have get_sym_matrix() and get_sym_matrix_inv() available for the transformation matrix.

Copy link
Member

@mgovers mgovers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like I said: you're definitely on the right track. a couple ideas.

@@ -56,6 +56,7 @@ using DoubleComplex = std::complex<double>;
using std::numbers::inv_sqrt3;
using std::numbers::pi;
using std::numbers::sqrt3;
using std::numbers::e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this being in the full namespace of this project, i'm a little bit hesitant to add single-letter names here. let's think about whether this is the way to go or whether it's preferable to just use std::numbers::e where it's used (since it's not that common in the code base anyways). alternatively, we can put them all in a power_grid_model::numbers namespace and expose only the longer ones outside.

namespace numbers {
using std::numbers::inv_sqrt3;
using std::numbers::pi;
using std::numbers::sqrt3;
using std::numbers::e;
} // namespace numbers

using numbers::inv_sqrt3;
using numbers::pi;
using numbers::sqrt3;

Comment on lines +62 to +71
class UnsupportedInputDescriptionAsymLine : public PowerGridError {
public:
UnsupportedInputDescriptionAsymLine() {
append_msg("Invalid or missing parameters supplied for component asym_line. The following input specifications are allowed");
append_msg("3 phase x_matrix, 3 phase r_matrix and 3 phase c_matrix");
append_msg("3 phase + neutral x_matrix, 3 phase + neutral r_matrix and 3 phase + neutral c_matrix");
append_msg("3 phase x_matrix, 3 phase r_matrix and c1, c0");
append_msg("3 phase + neutral x_matrix, 3 phase + neutral r_matrix and c1, c0");
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we explicitly check input parameters in any of the other components. only things like ID references to other components are checked. I would just consider this UB (undefined behavior; in this case in particular unspecified behavior). The data validator in Python can check that it is supported, but the core should not.

@@ -61,6 +62,8 @@ template <scalar_value T> class Tensor : public Eigen3Tensor<T> {
// additional constructors
explicit Tensor(T const& x) { (*this) << x, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, x; }
explicit Tensor(T const& s, T const& m) { (*this) << s, m, m, m, s, m, m, m, s; }
explicit Tensor(T const& x1, T const& x2, T const& x3, T const& x4, T const& x5, T const& x6) { (*this) << x1, x2, x4, x2, x3, x5, x4, x5, x6; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd go for something more explicit. It's very easy to make a mistake in the order.

A proposal is the one below (BEWARE: this changes the order, so usage needs to be adjusted)

Suggested change
explicit Tensor(T const& x1, T const& x2, T const& x3, T const& x4, T const& x5, T const& x6) { (*this) << x1, x2, x4, x2, x3, x5, x4, x5, x6; }
explicit Tensor(T const& s1, T const& s2, T const& s3, T const& m12, T const& m13, T const& m23) { (*this) << s1, m12, m13, m12, s2, m23, m13, m23, s3; }

alternatively, you can provide 2 Vector<T> as input for readability

// additional constructors
explicit Tensor4(T const& x) { (*this) << x, 0.0, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, 0.0, x; }
explicit Tensor4(T const& s, T const& m) { (*this) << s, m, m, m, m, s, m, m, m, m, s, m, m, m, m, s; }
explicit Tensor4(T const& x1, T const& x2, T const& x3, T const& x4, T const& x5, T const& x6, T const& x7, T const& x8, T const& x9, T const& x10) { (*this) << x1, x2, x4, x7, x2, x3, x5, x8, x4, x5, x6, x9, x7, x8, x9, x10; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem to my comment on Tensor3

ComplexTensor<asymmetric_t> Y_aa = ComplexTensor<asymmetric_t>(Y(0,0), Y(1,0), Y(1, 1), Y(2,0), Y(2,1), Y(2,2));
ComplexValue<asymmetric_t> Y_ab(Y(0,3), Y(1,3), Y(2,3));
Eigen::Array<DoubleComplex, 1, 3> Y_ba;
Y_ba << Y(3,0), Y(3,1), Y(3,2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can initialize it as a ComplexValue<asymmetric_t>, right? or even do (untested/might be syntactically incorrect) ComplexValue<asymmetric_t> Y_ba{Y.row(3).transpose()};

Comment on lines +72 to +73
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting can be automatically solved using pre-commit (see https://power-grid-model.readthedocs.io/en/stable/contribution/CONTRIBUTING.html#pre-commit-hooks ; you can apply the format changes retroactively using pre-commit run --all-files and run it before committing using pre-commit install)

Suggested change
}
else {
} else {

return param;
}
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code convention: newline at end of file (EOF)

Suggested change
}
}

}

DoubleComplex average_of_diagonal_of_matrix(const ComplexTensor<asymmetric_t> &matrix) const {
return (matrix(0,0) + matrix(1,1) + matrix(2,2)) / 3.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we already have functionality in three_phase_tensor.hpp for this. not sure, though

}
}

} // namespace power_grid_model
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline at EOF

Suggested change
} // namespace power_grid_model
} // namespace power_grid_model

ComplexTensor4 y = r_matrix + 1.0i * x_matrix;
z_series_abc = kron_reduction(y);
}
DoubleComplex a = std::pow(e, 1.0i * (2.0 / 3.0) * pi);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of using std::pow(e, ...), please use std::exp (https://en.cppreference.com/w/cpp/numeric/math/exp). as a result, you may even remove the using declaration from the common.hpp

@TonyXiang8787
Copy link
Member

The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

@leovsch I have compared with OpenDSS and I could not find the different you mentioned. Maybe can you elaborate a bit more about what exactly are we still missing? Then we can make a choice.

}

DoubleComplex average_of_off_diagonal_of_matrix(const ComplexTensor<asymmetric_t> &matrix) const {
return (matrix(0,2) + matrix(1,1) + matrix(2,0)) / 3.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (matrix(0,2) + matrix(1,1) + matrix(2,0)) / 3.0;
return (matrix(0,1) + matrix(0,2) + matrix(1,0) + matrix(1,2) + matrix(2,0) + matrix(2,1)) / 6.0;

@leovsch
Copy link
Author

leovsch commented Nov 12, 2024

The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

@leovsch I have compared with OpenDSS and I could not find the different you mentioned. Maybe can you elaborate a bit more about what exactly are we still missing? Then we can make a choice.

Hi Tony,

In OpenDSS you can per parameter (R, X, C) decide if you want to supply the full matrix or just the 0th and 1st order values.
For example, valid input for OpenDSS might be:

Example 1:
R = full r matrix
X = full x matrix
C = C0, C1

Example 2:
R = R0, R1
X = full x matrix
C = C0, C1

And all posible other permutations of the input format. For now I've implemented only the possibility that would support example 1. I can imagine that you would want to generalize such functionality to all possible permutations of the input format just as in OpenDSS.

Hope this clarifies it a bit for you.

@TonyXiang8787
Copy link
Member

The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

@leovsch I have compared with OpenDSS and I could not find the different you mentioned. Maybe can you elaborate a bit more about what exactly are we still missing? Then we can make a choice.

Hi Tony,

In OpenDSS you can per parameter (R, X, C) decide if you want to supply the full matrix or just the 0th and 1st order values. For example, valid input for OpenDSS might be:

Example 1: R = full r matrix X = full x matrix C = C0, C1

Example 2: R = R0, R1 X = full x matrix C = C0, C1

And all posible other permutations of the input format. For now I've implemented only the possibility that would support example 1. I can imagine that you would want to generalize such functionality to all possible permutations of the input format just as in OpenDSS.

Hope this clarifies it a bit for you.

@leovsch This is a valid point. Thanks for clarification. We need to discuss this and come back to you later.

@TonyXiang8787
Copy link
Member

TonyXiang8787 commented Nov 15, 2024

The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

@leovsch I have compared with OpenDSS and I could not find the different you mentioned. Maybe can you elaborate a bit more about what exactly are we still missing? Then we can make a choice.

Hi Tony,

In OpenDSS you can per parameter (R, X, C) decide if you want to supply the full matrix or just the 0th and 1st order values. For example, valid input for OpenDSS might be:

Example 1: R = full r matrix X = full x matrix C = C0, C1

Example 2: R = R0, R1 X = full x matrix C = C0, C1

And all posible other permutations of the input format. For now I've implemented only the possibility that would support example 1. I can imagine that you would want to generalize such functionality to all possible permutations of the input format just as in OpenDSS.

Hope this clarifies it a bit for you.

Hi @leovsch,

Comeback to your point, we have thoroughly considered different options and decide to go with the attributes you have already defined in this PR. Concretely means:

R = always full r matrix (no r1, r0 possible)
X = always full x matrix (no x1, x0 possible)
C = full c matrix OR C0/C1. If C1 is specified, we use C0/C1 to populate the C matrix and ignore the input c matrix attributes.

From practical applications, if the user want to specify a r1/r0, they will almost certain specify x1/x0 instead of full x matrix, verse versa is also true. In that case, the user can just use normal line instead of asym_line.

We allow C to be specified in both c0/c1 or full c matrix way because this could be a use-case for the user.

@leovsch
Copy link
Author

leovsch commented Nov 15, 2024

The input parameters I've now implemented would suit my usecase but it would still not fully be equivalent to how opendss handles it. Namely, that you can specify a matrix or two values for the r, x and c values. See their documentation for more information. In my view it would be intuitive for the user to support the OpenDSS way. Can anyone share their thoughts on this?

@leovsch I have compared with OpenDSS and I could not find the different you mentioned. Maybe can you elaborate a bit more about what exactly are we still missing? Then we can make a choice.

Hi Tony,
In OpenDSS you can per parameter (R, X, C) decide if you want to supply the full matrix or just the 0th and 1st order values. For example, valid input for OpenDSS might be:
Example 1: R = full r matrix X = full x matrix C = C0, C1
Example 2: R = R0, R1 X = full x matrix C = C0, C1
And all posible other permutations of the input format. For now I've implemented only the possibility that would support example 1. I can imagine that you would want to generalize such functionality to all possible permutations of the input format just as in OpenDSS.
Hope this clarifies it a bit for you.

Hi @leovsch,

Comeback to your point, we have thoroughly considered different options and decide to go with the attributes you have already defined in this PR. Concretely means:

R = always full r matrix (no r1, r0 possible) X = always full x matrix (no x1, x0 possible) C = full c matrix OR C0/C1. If C1 is specified, we use C0/C1 to populate the C matrix and ignore the input c matrix attributes.

From practical applications, if the user want to specify a r1/r0, they will almost certain specify x1/x0 instead of full x matrix, verse versa is also true. In that case, the user can just use normal line instead of asym_line.

We allow C to be specified in both c0/c1 or full c matrix way because this could be a use-case for the user.

Thanks for the clarifification. I will leave it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] support asymmetric line
4 participants