Skip to content

Commit 2997ae0

Browse files
Merge remote-tracking branch 'origin/main' into feature/full-clang-tidy
2 parents 17e82fa + 81e0334 commit 2997ae0

34 files changed

+705
-153
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<!--
2+
SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
3+
4+
SPDX-License-Identifier: MPL-2.0
5+
-->
6+
7+
# High-level design
8+
9+
The power-grid-model follows a typical dynamic/shared library approach, in which the user
10+
interface is separated from the core implementation using a strict system boundary. Depending
11+
on the use case and programming language used by the user to call the interface, the user can
12+
opt to interface with the C API in different ways.
13+
14+
## Layers of abstraction
15+
16+
For the sake of explanation, we consider the
17+
following layers of abstraction, in increasing order of ease-of-use and abstraction.
18+
19+
* Raw system interface
20+
* System symbols only
21+
* Everything is handled by the user, including which symbols are supported
22+
* Exposition-only
23+
* Exposes the supported symbols in a language supported by the user
24+
* Memory management and error handling is the responsibility of the user
25+
* Simple wrapper
26+
* Wraps the interface in a language supported by the user
27+
* Handles memory management, basic error handling and type conversions
28+
* Contains no additional features except maybe some basic utility tools
29+
* The feature-rich layer
30+
* Extensive wrapper around the interface with easy functionality exposure and utility functions
31+
* Extensive type checks
32+
33+
## Existing library interfaces
34+
35+
The following library interfaces are currently included in the power-grid-model.
36+
37+
| Interface type | Status | Layer | Explanation | Supported by |
38+
| -------------- | ------------ | ------------------ | --------------------------------------------------------------- | --------------------------------------------------- |
39+
| C API | Stable | Raw interface | Shared object / DLL that contains the core implementation | All programming languages with dynamic load support |
40+
| C headers | Stable | Exposition-only | Exposition-only library using dynamic linking | C and C++ |
41+
| C++ headers | Experimental | Wrapper | Handles memory management and basic error handling | C++ |
42+
| Python library | Stable | Feature-rich layer | Library with useful functions, conversions and extensive checks | Python |
43+
44+
Note that the Python library in turn also follows the pattern of a feature-rich library that uses a
45+
module-internal wrapper layer core module, that wraps the exposition-only core module, that exposes
46+
the raw interface.
47+
48+
This can be visualized graphically as follows.
49+
50+
```{mermaid}
51+
:title: Full design
52+
53+
flowchart TD
54+
classDef user_node fill:#9f6,stroke:#333,stroke-width:2px
55+
classDef public_interface fill:#69f,stroke:#333,stroke-width:2px
56+
classDef experimental_interface fill:#99b,stroke:#333,stroke-width:2px
57+
classDef private_interface fill:#999,stroke:#333,stroke-width:2px
58+
classDef inclusion_method fill:#ddd,stroke:#fff,stroke-width:2px
59+
60+
subgraph User
61+
any_language_user(["Any language user"]):::user_node
62+
c_user(["C user"]):::user_node
63+
cpp_user(["C++ user"]):::user_node
64+
python_user(["Python user"]):::user_node
65+
end
66+
67+
dynamic_loading{ }:::inclusion_method
68+
c_includes{ }:::inclusion_method
69+
70+
subgraph Raw interface
71+
power_grid_model_c_dll("power_grid_model_c<br>(shared library)"):::public_interface
72+
end
73+
74+
subgraph Exposition
75+
power_grid_model_c("power_grid_model_c<br>(C library)"):::public_interface
76+
power_grid_core_python("power_grid_model._core<br>.power_grid_core.py<br>(exposition-only<br>Python module)"):::private_interface
77+
end
78+
79+
subgraph Wrapper
80+
power_grid_model_cpp("power_grid_model_cpp<br>(experimental,<br>C++ library)"):::experimental_interface
81+
power_grid_model_core_python("power_grid_model._core<br>(Python wrapper library)"):::private_interface
82+
end
83+
84+
subgraph Feature-rich library
85+
power_grid_model_python("power_grid_model<br>(Python library)"):::public_interface
86+
end
87+
88+
any_language_user --> dynamic_loading
89+
c_includes --> dynamic_loading
90+
dynamic_loading -->|dynamic loading| power_grid_model_c_dll
91+
c_user --> c_includes
92+
cpp_user --> c_includes
93+
c_includes -->|links +<br>includes| power_grid_model_c -->|dynamic linking| power_grid_model_c_dll
94+
cpp_user -->|experimental<br>links +<br>includes| power_grid_model_cpp -->|links +<br>includes| power_grid_model_c
95+
python_user -->|import| power_grid_model_python -->|internal import| power_grid_model_core_python -->|internal import| power_grid_core_python -->|"CDLL<br>(dynamic loading)"| power_grid_model_c_dll
96+
```
97+
98+
## Creating a custom library or interface
99+
100+
We seek to provide an optimal user experience, but with the sheer amount of programming languages and
101+
features, it would be impossible to provide a full feature-rich library for every single one. We,
102+
being a {{ "[community-driven]({}/GOVERNANCE.md)".format(pgm_project_contribution) }} project strongly in
103+
favor of modern software engineering practices, therefore encourage people to create their own
104+
libraries and interfaces to improve their own experience. There are several possible reasons a user
105+
may want to create their own library or interface, e.g.:
106+
107+
* Support for a new programming language
108+
* Extending library support for a specific programming language
109+
* A custom wrapper that provides extra features or useful commands for specific custom requirements
110+
111+
In all cases, it is recommended that the user determines their own desired
112+
[layer of abstraction](#layers-of-abstraction) and then creates internal wrappers for all
113+
lower-level ones, following the same pattern as the power-grid-model
114+
[uses internally](#existing-library-interfaces) for the custom interfaces.
115+
116+
### Hosting a custom library or interface
117+
118+
The Power Grid Model organization supports people creating and hosting custom libraries and
119+
interfaces. If you are doing so and are willing to notify us, please create an item on our
120+
[discussion board](https://github.com/orgs/PowerGridModel/discussions) on GitHub. The Power Grid
121+
Model organization will review your item and we may decide to mention your custom library on our
122+
project website and documentation.
123+
124+
### Contributing a custom library or interface
125+
126+
When a custom library or interface becomes mature enough and the circumstances allow making it
127+
publicly available, please consider contributing it to the Power Grid Model organization. If you are
128+
considering contributing your custom library or interface, please read and follow our
129+
{{ "[contributing guidelines]({}/CONTRIBUTING.md)".format(pgm_project_contribution) }} and open an
130+
item on our [discussion board](https://github.com/orgs/PowerGridModel/discussions) on GitHub. The
131+
Power Grid Model organization will review your item and contact you accordingly.

docs/conf.py

+6
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@
2525
commit_version = git.Repo(search_parent_directories=True).head.object.hexsha
2626
link_head_gh_blob = link_head_gh + "blob/" + commit_version
2727
link_head_gh_tree = link_head_gh + "tree/" + commit_version
28+
pgm_project_root = ""
29+
pgm_project_contribution = pgm_project_root + "/contribution"
2830
else:
2931
link_head_gh_blob = ""
3032
link_head_gh_tree = ""
33+
pgm_project_root = "https://github.com/PowerGridModel/.github/blob/main"
34+
pgm_project_contribution = pgm_project_root
3135

3236

3337
# -- General configuration ---------------------------------------------------
@@ -78,6 +82,8 @@
7882
myst_substitutions = {
7983
"gh_link_head_blob": link_head_gh_blob,
8084
"gh_link_head_tree": link_head_gh_tree,
85+
"pgm_project_root": pgm_project_root,
86+
"pgm_project_contribution": pgm_project_contribution,
8187
}
8288

8389

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ algorithms/lu-solver
108108
advanced_documentation/native-data-interface
109109
advanced_documentation/build-guide
110110
advanced_documentation/c-api
111+
advanced_documentation/high-level-design
111112
advanced_documentation/core-design
112113
advanced_documentation/python-wrapper-design
113114
```

power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp

-12
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,4 @@ using AllComponents =
3131
ComponentList<Node, Line, Link, GenericBranch, Transformer, ThreeWindingTransformer, Shunt, Source, SymGenerator,
3232
AsymGenerator, SymLoad, AsymLoad, SymPowerSensor, AsymPowerSensor, SymVoltageSensor,
3333
AsymVoltageSensor, SymCurrentSensor, AsymCurrentSensor, Fault, TransformerTapRegulator>;
34-
35-
template <typename T>
36-
concept power_or_current_sensor_c =
37-
std::derived_from<T, GenericPowerSensor> || std::derived_from<T, GenericCurrentSensor>;
38-
39-
static_assert(power_or_current_sensor_c<SymPowerSensor>);
40-
static_assert(power_or_current_sensor_c<AsymPowerSensor>);
41-
static_assert(power_or_current_sensor_c<SymCurrentSensor>);
42-
static_assert(power_or_current_sensor_c<AsymCurrentSensor>);
43-
static_assert(!power_or_current_sensor_c<SymVoltageSensor>);
44-
static_assert(!power_or_current_sensor_c<AsymVoltageSensor>);
45-
4634
} // namespace power_grid_model

power_grid_model_c/power_grid_model/include/power_grid_model/component/current_sensor.hpp

+39-13
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class GenericCurrentSensor : public Sensor {
2626
MeasuredTerminalType get_terminal_type() const { return terminal_type_; }
2727
AngleMeasurementType get_angle_measurement_type() const { return angle_measurement_type_; }
2828

29-
template <symmetry_tag sym> CurrentSensorOutput<sym> get_output(ComplexValue<sym> const& i) const {
29+
template <symmetry_tag sym>
30+
CurrentSensorOutput<sym> get_output(ComplexValue<sym> const& i, ComplexValue<sym> const& u) const {
3031
if constexpr (is_symmetric_v<sym>) {
31-
return get_sym_output(i);
32+
return get_sym_output(i, u);
3233
} else {
33-
return get_asym_output(i);
34+
return get_asym_output(i, u);
3435
}
3536
}
3637

@@ -62,8 +63,10 @@ class GenericCurrentSensor : public Sensor {
6263
virtual CurrentSensorCalcParam<symmetric_t> sym_calc_param() const = 0;
6364
virtual CurrentSensorCalcParam<asymmetric_t> asym_calc_param() const = 0;
6465

65-
virtual CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i) const = 0;
66-
virtual CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i) const = 0;
66+
virtual CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i,
67+
ComplexValue<symmetric_t> const& u) const = 0;
68+
virtual CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i,
69+
ComplexValue<asymmetric_t> const& u) const = 0;
6770
};
6871

6972
template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public GenericCurrentSensor {
@@ -137,20 +140,43 @@ template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public Ge
137140
return CurrentSensorCalcParam<sym_calc>{.angle_measurement_type = angle_measurement_type(),
138141
.measurement = DecomposedComplexRandVar<sym_calc>(i_polar)};
139142
}
140-
CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i) const final {
141-
return get_generic_output<symmetric_t>(i);
143+
CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i,
144+
ComplexValue<symmetric_t> const& u) const final {
145+
return get_generic_output<symmetric_t>(i, u);
142146
}
143-
CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i) const final {
144-
return get_generic_output<asymmetric_t>(i);
147+
CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i,
148+
ComplexValue<asymmetric_t> const& u) const final {
149+
return get_generic_output<asymmetric_t>(i, u);
145150
}
146151
template <symmetry_tag sym_calc>
147-
CurrentSensorOutput<sym_calc> get_generic_output(ComplexValue<sym_calc> const& i) const {
152+
CurrentSensorOutput<sym_calc> get_generic_output(ComplexValue<sym_calc> const& i,
153+
ComplexValue<sym_calc> const& u) const {
148154
CurrentSensorOutput<sym_calc> output{};
149155
output.id = id();
150-
ComplexValue<sym_calc> const i_residual{process_mean_val<sym_calc>(i_measured_ - i) * base_current_};
151156
output.energized = 1; // current sensor is always energized
152-
output.i_residual = cabs(i_residual);
153-
output.i_angle_residual = arg(i_residual);
157+
auto const i_calc_param = calc_param<sym_calc>();
158+
auto const angle_measurement_type = i_calc_param.angle_measurement_type;
159+
auto const i_measured_complex = i_calc_param.measurement.value();
160+
ComplexValue<sym_calc> i_output = [&i, &u, &angle_measurement_type] {
161+
switch (angle_measurement_type) {
162+
case AngleMeasurementType::global_angle: {
163+
return i;
164+
}
165+
case AngleMeasurementType::local_angle: {
166+
// I_l = conj(I_g) * exp(i * u_angle)
167+
// Tranform back the output angle to the local angle frame of reference
168+
auto const u_angle = arg(u);
169+
return ComplexValue<sym_calc>{conj(i) * (cos(u_angle) + 1i * sin(u_angle))};
170+
}
171+
default: {
172+
throw MissingCaseForEnumError{"generic output angle measurement type", angle_measurement_type};
173+
}
174+
}
175+
}();
176+
output.i_residual = (cabs(i_measured_complex) - cabs(i_output)) * base_current_;
177+
// arg(e^i * u_angle) = u_angle in (-pi, pi]
178+
auto const unbounded_i_angle_residual = arg(i_measured_complex) - arg(i_output);
179+
output.i_angle_residual = arg(ComplexValue<sym_calc>{exp(1.0i * unbounded_i_angle_residual)});
154180
return output;
155181
}
156182
};

0 commit comments

Comments
 (0)