From 33e711e0319152bdf8a3193bd7244df9dd476b76 Mon Sep 17 00:00:00 2001 From: William PIAT Date: Mon, 15 Jun 2026 09:25:53 +0200 Subject: [PATCH] add doc --- .github/workflows/doc.yaml | 29 ++++++ README.md | 12 +-- docs/components/frequency-system-builder.md | 89 ++++++++++++++++ docs/components/temporal-system-builder.md | 97 ++++++++++++++++++ ...es-hydraulic-or-thermal-system-modeling.md | 62 +++++++++++ {img => docs/img}/hydraulic.png | Bin {img => docs/img}/intensities_res.png | Bin {img => docs/img}/schema.png | Bin {img => docs/img}/schema2.png | Bin {img => docs/img}/schema3.png | Bin docs/index.md | 59 +++++++++++ docs/netlist-import-feature.md | 69 +++++++++++++ docs/solver-suggestions.md | 10 ++ zensical.toml | 22 ++++ 14 files changed, 443 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/doc.yaml create mode 100644 docs/components/frequency-system-builder.md create mode 100644 docs/components/temporal-system-builder.md create mode 100644 docs/extra-uses-hydraulic-or-thermal-system-modeling.md rename {img => docs/img}/hydraulic.png (100%) rename {img => docs/img}/intensities_res.png (100%) rename {img => docs/img}/schema.png (100%) rename {img => docs/img}/schema2.png (100%) rename {img => docs/img}/schema3.png (100%) create mode 100644 docs/index.md create mode 100644 docs/netlist-import-feature.md create mode 100644 docs/solver-suggestions.md create mode 100644 zensical.toml diff --git a/.github/workflows/doc.yaml b/.github/workflows/doc.yaml new file mode 100644 index 0000000..2213adc --- /dev/null +++ b/.github/workflows/doc.yaml @@ -0,0 +1,29 @@ +name: Documentation +on: + push: + branches: + - master + - main +permissions: + contents: read + pages: write + id-token: write +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/configure-pages@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install zensical + - run: zensical build --clean + - uses: actions/upload-pages-artifact@v4 + with: + path: site + - uses: actions/deploy-pages@v4 + id: deployment \ No newline at end of file diff --git a/README.md b/README.md index 17e2b65..2028084 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ For now this package is distributed on pypi and can be installed using pip and c ``` pip install ElecSolver ``` -or +or ``` conda install elecsolver ``` @@ -82,7 +82,7 @@ This class handles **frequency-domain** analysis of linear electric systems. #### Example We would like to study the following system: -![Multiple system](img/schema.png) +![Multiple system](docs/img/schema.png) this can simply be defined in the following manner (We took R=1, L=1 and M=2): ```python @@ -128,7 +128,7 @@ print(frequencial_response.potentials[3]-frequencial_response.potentials[4]) ``` #### Adding a Parallel Resistance We want to add components in parallel with existing components for instance inserting a resistor in parallel with the first inductance (between nodes 0 and 2) -![Parallel system](img/schema3.png) +![Parallel system](docs/img/schema3.png) In python, simply add the resistance to the list of impedence in the very first lines of the script: @@ -165,7 +165,7 @@ This class models **time-dependent** systems using resistors, capacitors, coils, We would like to study the following system: -![Temporal system](img/schema2.png) +![Temporal system](docs/img/schema2.png) with R=1, L=0.1, C=2 this gives: @@ -228,7 +228,7 @@ plt.savefig("intensities_res.png") ``` This outputs the following graph that displays the intensity passing through the resistances -![Temporal system](img/intensities_res.png) +![Temporal system](docs/img/intensities_res.png) ## Solver suggestions @@ -247,7 +247,7 @@ This repository can be used as is in order to model the mass flow or thermal flu We are considering the following hydraulic problem: -![Hydraulic system](img/hydraulic.png) +![Hydraulic system](docs/img/hydraulic.png) Taking R=1 this gives diff --git a/docs/components/frequency-system-builder.md b/docs/components/frequency-system-builder.md new file mode 100644 index 0000000..b7f5572 --- /dev/null +++ b/docs/components/frequency-system-builder.md @@ -0,0 +1,89 @@ +# FrequencySystemBuilder + +This class handles **frequency-domain** analysis of linear electric systems. + +## Features + +- Supports tension and intensity sources +- Models inductive and resistive mutuals +- Detects and couples multiple subsystems +- Accepts arbitrary complex impedances and mutuals +- Constructs sparse linear systems in COO format + +!!! tip + + Some solvers do not support complex-valued systems. Use `cast_complex_system_in_real_system` from `utils.py` to convert an `n`-dimensional complex system into a `2n`-dimensional real system. + +## Example + +We would like to study the following system: + +![Multiple system](../img/schema.png) + +This can be defined in the following manner. We took `R=1`, `L=1` and `M=2`. + +```python +import numpy as np +from scipy.sparse.linalg import spsolve +from ElecSolver import FrequencySystemBuilder + + +# Complex and sparse impedance matrix +# notice coil impedence between points 0 and 2, and coil impedence between 3 and 4 +impedence_coords = np.array([[0, 0, 1, 3], [1, 2, 2, 4]], dtype=int) +impedence_data = np.array([1, 1j, 1, 1j], dtype=complex) + +# Mutual inductance or coupling +# The indexes here are the impedence indexes in impedence_data +# The coupling is inductive +mutuals_coords = np.array([[1], [3]], dtype=int) +mutuals_data = np.array([2.0j], dtype=complex) + +electric_sys = FrequencySystemBuilder( + impedence_coords, + impedence_data, + mutuals_coords, + mutuals_data, +) + +# Add source (current source here) +electric_sys.add_current_source(intensity=10, input_node=2, output_node=0) + +# Set ground +# 2 values because one for each subsystem +electric_sys.set_ground(0, 3) + +# Build system +electric_sys.build_system() + +# Get and solve the system +sys, b = electric_sys.get_system() +sol = spsolve(sys.tocsr(), b) +frequencial_response = electric_sys.build_intensity_and_voltage_from_vector(sol) + +# We see a tension appearing on the lonely coil (between node 3 and 4) +print(frequencial_response.potentials[3] - frequencial_response.potentials[4]) +``` + +## Adding a Parallel Resistance + +We want to add components in parallel with existing components, for instance inserting a resistor in parallel with the first inductance between nodes 0 and 2. + +![Parallel system](../img/schema3.png) + +In Python, simply add the resistance to the list of impedances in the first lines of the script: + +```python +import numpy as np +from scipy.sparse.linalg import spsolve +from ElecSolver import FrequencySystemBuilder + + +# We add an additional resistance between 0 and 2 +impedence_coords = np.array([[0, 0, 1, 3, 0], [1, 2, 2, 4, 2]], dtype=int) +impedence_data = np.array([1, 1j, 1, 1j, 1], dtype=complex) + +# No need to change the couplings since indexes of the coils did not change +mutuals_coords = np.array([[1], [3]], dtype=int) +mutuals_data = np.array([2.0j], dtype=complex) +``` diff --git a/docs/components/temporal-system-builder.md b/docs/components/temporal-system-builder.md new file mode 100644 index 0000000..2193f79 --- /dev/null +++ b/docs/components/temporal-system-builder.md @@ -0,0 +1,97 @@ +# TemporalSystemBuilder + +This class models **time-dependent** systems using resistors, capacitors, coils, and mutuals. + +## Features + +- Supports tension and intensity sources +- Models inductive and resistive mutuals +- Detects and couples multiple subsystems +- Accepts resistances, capacities, and coils +- Constructs sparse linear systems in COO format + +## Example + +We would like to study the following system: + +![Temporal system](../img/schema2.png) + +With `R=1`, `L=0.1`, `C=2` this gives: + +```python +import matplotlib.pyplot as plt +import numpy as np +from scipy.sparse.linalg import spsolve +from ElecSolver import TemporalSystemBuilder + +## Defining resistances +res_coords = np.array([[0, 2], [1, 3]], dtype=int) +res_data = np.array([1, 1], dtype=float) + +## Defining coils +coil_coords = np.array([[1, 0], [3, 2]], dtype=int) +coil_data = np.array([0.1, 0.1], dtype=float) + +## Defining capacities +capa_coords = np.array([[1, 3], [2, 0]], dtype=int) +capa_data = np.array([2, 2], dtype=float) + +## Defining empty mutuals here +mutuals_coords = np.array([[], []], dtype=int) +mutuals_data = np.array([], dtype=float) + +res_mutuals_coords = np.array([[], []], dtype=int) +res_mutuals_data = np.array([], dtype=float) + +## Initializing system +elec_sys = TemporalSystemBuilder( + coil_coords, + coil_data, + res_coords, + res_data, + capa_coords, + capa_data, + mutuals_coords, + mutuals_data, + res_mutuals_coords, + res_mutuals_data, +) + +## Add source +elec_sys.add_current_source(10, 1, 0) + +## Setting ground at point 0 +elec_sys.set_ground(0) + +## Build system +elec_sys.build_system() + +# Getting initial condition system +S_i, b = elec_sys.get_init_system() +sol = spsolve(S_i.tocsr(), b) + +# Get system (S1 is real part, S2 derivative part) +S1, S2, rhs = elec_sys.get_system() + +## Solving using implicit Euler scheme +dt = 0.08 +vals_res1 = [] +vals_res2 = [] + +for _ in range(50): + temporal_response = elec_sys.build_intensity_and_voltage_from_vector(sol) + vals_res1.append(temporal_response.intensities_res[1]) + vals_res2.append(temporal_response.intensities_res[0]) + sol = spsolve(S2 + dt * S1, b * dt + S2 @ sol) + +plt.xlabel("Time") +plt.ylabel("Intensity") +plt.plot(vals_res1, label="intensity res 1") +plt.plot(vals_res2, label="intensity res 2") +plt.legend() +plt.savefig("intensities_res.png") +``` + +This outputs the following graph that displays the intensity passing through the resistances: + +![Intensity through resistances](../img/intensities_res.png) diff --git a/docs/extra-uses-hydraulic-or-thermal-system-modeling.md b/docs/extra-uses-hydraulic-or-thermal-system-modeling.md new file mode 100644 index 0000000..33b4212 --- /dev/null +++ b/docs/extra-uses-hydraulic-or-thermal-system-modeling.md @@ -0,0 +1,62 @@ +# Extra uses: Hydraulic or Thermal system modeling + +ElecSolver can also be used to model mass flow or thermal flux in hydraulic and thermal networks where a pressure or temperature difference can be assimilated to a tension source. + +Since electric potentials are always computed relative to the ground node, you may need to rescale the resulting potentials. + +We consider the following hydraulic problem: + +![Hydraulic system](img/hydraulic.png) + +Taking `R=1` gives: + +```python +import numpy as np +from scipy.sparse.linalg import spsolve +from ElecSolver import TemporalSystemBuilder + +## Defining resistances +R = 1 +res_coords = np.array([[0, 2, 1, 0, 1, 3], [1, 3, 3, 2, 2, 0]], dtype=int) +res_data = R * np.array([2, 3, 1, 1, 1, 1], dtype=float) + +## Here we are not using coils, capacities or mutuals +coil_coords = np.array([[], []], dtype=int) +coil_data = np.array([], dtype=float) +capa_coords = np.array([[], []], dtype=int) +capa_data = np.array([], dtype=float) +mutuals_coords = np.array([[], []], dtype=int) +mutuals_data = np.array([], dtype=float) +res_mutuals_coords = np.array([[], []], dtype=int) +res_mutuals_data = np.array([], dtype=float) + +## Initializing system +hydraulic_sys = TemporalSystemBuilder( + coil_coords, + coil_data, + res_coords, + res_data, + capa_coords, + capa_data, + mutuals_coords, + mutuals_data, + res_mutuals_coords, + res_mutuals_data, +) + +## Enforcing a pressure delta of 10 Pa +hydraulic_sys.add_voltage_source(10, 1, 0) +hydraulic_sys.set_ground(0) +hydraulic_sys.build_system() + +S1, S2, rhs = hydraulic_sys.get_system() +sol = spsolve(S1.tocsr(), rhs) +solution = hydraulic_sys.build_intensity_and_voltage_from_vector(sol) + +pressure_input = 10000 +pressure_node = 0 +potentials = solution.potentials - solution.potentials[pressure_node] + pressure_input + +print("Pressures in the system:", potentials) +print("Debit through the system", solution.intensities_sources[0]) +``` diff --git a/img/hydraulic.png b/docs/img/hydraulic.png similarity index 100% rename from img/hydraulic.png rename to docs/img/hydraulic.png diff --git a/img/intensities_res.png b/docs/img/intensities_res.png similarity index 100% rename from img/intensities_res.png rename to docs/img/intensities_res.png diff --git a/img/schema.png b/docs/img/schema.png similarity index 100% rename from img/schema.png rename to docs/img/schema.png diff --git a/img/schema2.png b/docs/img/schema2.png similarity index 100% rename from img/schema2.png rename to docs/img/schema2.png diff --git a/img/schema3.png b/docs/img/schema3.png similarity index 100% rename from img/schema3.png rename to docs/img/schema3.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d65fb25 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,59 @@ +# ElecSolver + +## Overview + +**ElecSolver** formalizes electric systems as linear problems, suitable for both **temporal** and **frequency-domain** studies. +It focuses on constructing the linear system representation, leaving the actual numerical resolution to the user. + +This repository is **not** a general-purpose electrical system solver. Instead, it acts as a **bridge** between: + +- The graph-based description of an electric network +- The corresponding sparse linear system to solve + +Its main goal is to provide a friendly Python interface for simulating analog electric systems. While suitable for small circuit simulations, its strength lies in its scalability: it is able to build linear systems with millions of nodes and components. + +!!! warning + + ElecSolver has been designed with the following specifications in mind: + + - The time needed for building the linear system must be negligible compared to the time needed for solving it. + - Handle natively inductive mutuals and resistive mutuals. + - Handle as many coupled electric systems as needed. + - Deal with lonely nodes and lonely edges in the electric graph when the problem is still well posed. + +!!! note + + Non-linear components are not supported. You must manage event detection and system updates yourself. + +## How to install + +ElecSolver is distributed on PyPI and can be installed with `pip` or with `conda`/`mamba`. + +```bash +pip install ElecSolver +``` + +or + +```bash +conda install elecsolver +``` + +For solving the linear systems, `python-mumps` is recommended when it is available on your platform. + +```bash +conda install python-mumps +``` + +## Components + +The documentation is organized around the same component split as the README: + +- [FrequencySystemBuilder](components/frequency-system-builder.md) +- [TemporalSystemBuilder](components/temporal-system-builder.md) + +Then follow the same supporting sections: + +- [Solver suggestions](solver-suggestions.md) +- [Extra uses: Hydraulic or Thermal system modeling](extra-uses-hydraulic-or-thermal-system-modeling.md) +- [Netlist import feature](netlist-import-feature.md) diff --git a/docs/netlist-import-feature.md b/docs/netlist-import-feature.md new file mode 100644 index 0000000..d9aa80c --- /dev/null +++ b/docs/netlist-import-feature.md @@ -0,0 +1,69 @@ +# Netlist import feature + +`NetlistParser` can import passive netlists and build a `TemporalSystemBuilder` instance. Solving the system then follows the same workflow as the other temporal examples. + +```python +""" +*test netlist for python solver square.net +Iin 0 1 PWL(0 0 0.000000001 10) +L0 1 3 0.1 +L1 2 0 0.1 +R1 1 0 1 +R2 2 3 1 +c2 1 2 2 +c3 0 3 2 +.tran 0 4 0 0.08 +.end +""" + +import matplotlib.pyplot as plt +from numpy import arange +from scipy.sparse.linalg import spsolve +from ElecSolver import NetlistParser + +parser = NetlistParser("square.net") +parser.map_netlist() + +node_zero = parser.node_map["0"] +node_one = parser.node_map["1"] + +elec_sys = parser.generate_temporal_system() +elec_sys.add_current_source(10, node_one, node_zero) +elec_sys.set_ground(node_zero) +elec_sys.build_system() + +S_i, b = elec_sys.get_init_system() +sol = spsolve(S_i.tocsr(), b) +S1, S2, rhs = elec_sys.get_system() + +dt = 0.08 +steps = 50 +vals_res1 = [] +vals_res2 = [] +vals_L1 = [] +voltage_src = [] + +R1_index = list(parser.resistors.keys()).index("R1") +R2_index = list(parser.resistors.keys()).index("R2") +L1_index = list(parser.inductors.keys()).index("L1") + +for i in range(steps): + temporal_response = elec_sys.build_intensity_and_voltage_from_vector(sol) + vals_res1.append(temporal_response.intensities_res[R1_index]) + vals_res2.append(temporal_response.intensities_res[R2_index]) + vals_L1.append(temporal_response.intensities_coil[L1_index]) + voltage_src.append( + temporal_response.potentials[node_one] - temporal_response.potentials[node_zero] + ) + sol = spsolve(S2 + dt * S1, b * dt + S2 @ sol) + +plt.xlabel("Time") +plt.ylabel("Intensity") +plt.plot(arange(steps, dtype=float) * dt, vals_res1, label="intensity res 1") +plt.plot(arange(steps, dtype=float) * dt, vals_res2, label="intensity res 2") +plt.plot(arange(steps, dtype=float) * dt, vals_L1, label="intensity L1") +plt.plot(arange(steps, dtype=float) * dt, voltage_src, label="V(1-0)") + +plt.legend() +plt.savefig("intensities_res.png") +``` \ No newline at end of file diff --git a/docs/solver-suggestions.md b/docs/solver-suggestions.md new file mode 100644 index 0000000..e1bf9ef --- /dev/null +++ b/docs/solver-suggestions.md @@ -0,0 +1,10 @@ +# Solver suggestions + +- For **small or moderately sized systems**, `scipy.sparse.linalg.spsolve` is effective. +- For **large-scale temporal problems**, consider **MUMPS** through `python-mumps`. + +MUMPS is more efficient when only the second member changes during time-stepping. + +!!! tip + + See `tests.test_temporal_system` for an example of using `python-mumps` to solve the resulting system efficiently. diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..76c3f45 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,22 @@ +[project] +site_name = "ElecSolver" +site_description = "Formalizes electric systems as linear problems for temporal and frequency-domain studies." +site_author = "William Piat" +repo_url = "https://github.com/williampiat3/ElecSolver" +repo_name = "GitHub" +docs_dir = "docs" +site_dir = "site" + +nav = [ + "index.md", + { Components = [ + "components/frequency-system-builder.md", + "components/temporal-system-builder.md", + ] }, + "solver-suggestions.md", + "extra-uses-hydraulic-or-thermal-system-modeling.md", + "netlist-import-feature.md", +] + +[project.theme] +name = "zensical" \ No newline at end of file