From f3588bea84d6498b59ba8788b4a2944b0eaea1e8 Mon Sep 17 00:00:00 2001 From: Steven Dahdah Date: Fri, 20 Dec 2024 15:03:13 -0500 Subject: [PATCH 1/4] Add references to mussv --- src/dkpy/d_scale_fit.py | 12 ++++++++++-- src/dkpy/dk_iteration.py | 24 ++++++++++++++++++++---- src/dkpy/structured_singular_value.py | 12 ++++++++++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/dkpy/d_scale_fit.py b/src/dkpy/d_scale_fit.py index 3628241..c3281c7 100644 --- a/src/dkpy/d_scale_fit.py +++ b/src/dkpy/d_scale_fit.py @@ -42,7 +42,7 @@ def fit( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- @@ -54,6 +54,10 @@ def fit( ValueError If ``order`` is an array but its dimensions are inconsistent with ``block_structure``. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ raise NotImplementedError() @@ -162,13 +166,17 @@ def _mask_from_block_structure(block_structure: np.ndarray) -> np.ndarray: block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- np.ndarray Array of integers indicating zero, one, and unknown elements in the block structure. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ X_lst = [] for i in range(block_structure.shape[0]): diff --git a/src/dkpy/dk_iteration.py b/src/dkpy/dk_iteration.py index c7f3ace..d27e2be 100644 --- a/src/dkpy/dk_iteration.py +++ b/src/dkpy/dk_iteration.py @@ -60,7 +60,11 @@ def __init__( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ self.omega = omega self.mu_omega = mu_omega @@ -103,7 +107,7 @@ def create_from_fit( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- @@ -134,6 +138,10 @@ def create_from_fit( ... D_inv, ... block_structure, ... ) + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ # Compute ``mu(omega)`` based on fit D-scales N = P.lft(K) @@ -213,7 +221,7 @@ def synthesize( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- @@ -223,6 +231,10 @@ def synthesize( controller cannot by synthesized, the first three elements of the tuple are ``None``, but fit and solution information are still returned. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ # Solution information info = {} @@ -330,12 +342,16 @@ def _get_fit_order( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- Optional[Union[int, np.ndarray]] D-scale fit order. If ``None``, iteration ends. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ raise NotImplementedError() diff --git a/src/dkpy/structured_singular_value.py b/src/dkpy/structured_singular_value.py index 5c43dd5..06463a5 100644 --- a/src/dkpy/structured_singular_value.py +++ b/src/dkpy/structured_singular_value.py @@ -35,7 +35,7 @@ def compute_ssv( block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- @@ -44,6 +44,10 @@ def compute_ssv( frequency, and solution information. If the structured singular value cannot be computed, the first two elements of the tuple are ``None``, but solution information is still returned. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ raise NotImplementedError() @@ -323,12 +327,16 @@ def _variable_from_block_structure(block_structure: np.ndarray) -> cvxpy.Variabl block_structure : np.ndarray 2D array with 2 columns and as many rows as uncertainty blocks in Delta. The columns represent the number of rows and columns in - each uncertainty block. + each uncertainty block. See [#mussv]_. Returns ------- cvxpy.Variable CVXPY variable with specified block structure. + + References + ---------- + .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html """ X_lst = [] for i in range(block_structure.shape[0]): From 79a3cd483fd30d69fc8bf391dff49ebc3cbd01fc Mon Sep 17 00:00:00 2001 From: Steven Dahdah Date: Fri, 20 Dec 2024 15:57:01 -0500 Subject: [PATCH 2/4] Rename DScaleFitInfo and elaborate in docs --- doc/dkpy.rst | 36 ++++++++++++++++--- doc/examples.rst | 7 ++-- src/dkpy/dk_iteration.py | 76 +++++++++++++++++++++++++--------------- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/doc/dkpy.rst b/doc/dkpy.rst index 57397a6..5d9170f 100644 --- a/doc/dkpy.rst +++ b/doc/dkpy.rst @@ -1,6 +1,12 @@ D-K iteration methods ===================== +The D-K iteration methods provided by ``dkpy`` are presented below. Each one +implements the interface specified in :class:`DkIteration`. The difference +between these methods is the way the D-scale fit order is selected. It can +either be fixed, specified via a list, selected automatically, or selected +interactively. + .. autosummary:: :toctree: _autosummary/ @@ -9,19 +15,26 @@ D-K iteration methods dkpy.DkIterAutoOrder dkpy.DkIterInteractiveOrder -D-K iteration extras -==================== +Each :func:`DkIteration.synthesize` method returns (among other things) a list +of :class:`IterResult` objects. These objects summarize the status of the D-K +iteration process at each step. They can be plotted with :func:`plot_D` and +:func:`plot_mu` to assess the accuracy of the D-scale fit and its impact on the +structured singular value. .. autosummary:: :toctree: _autosummary/ - dkpy.DScaleFitInfo - dkpy.plot_D + dkpy.IterResult dkpy.plot_mu + dkpy.plot_D Controller synthesis ==================== +Supported continuous-time H-infinity controller synthesis methods are provided +below. Each one implements the interface specified in +:class:`ControllerSynthesis`. + .. autosummary:: :toctree: _autosummary/ @@ -33,6 +46,11 @@ Controller synthesis Structured singular value ========================= +Supported structured singular value computation methods are provided below. +Only one approach is provided, which implements the interface in +:class:`StructuredSingularValue`. The LMI solver settings may need to be +adjusted depending on the problem. + .. autosummary:: :toctree: _autosummary/ @@ -41,6 +59,11 @@ Structured singular value D-scale fit =========== +Supported D-scale fitting methods are provided below. Only one approach is +provided currently, which implements the interface in :class:`DScaleFit`. There +are currently no ways to customize the D-scale magnitude fitting process beyond +selecting the order in :func:`DScaleFit.fit`. + .. autosummary:: :toctree: _autosummary/ @@ -48,6 +71,11 @@ D-scale fit Extending ``dkpy`` ================== + +The abstract classes defining the structure of ``dkpy`` are presented below. +Anyone aiming to extend or customize ``dkpy`` should familiarize themselves +with them. + .. autosummary:: :toctree: _autosummary/ diff --git a/doc/examples.rst b/doc/examples.rst index 2dc74a1..68d128f 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -2,10 +2,13 @@ Examples ======== In all the examples on this page, three iterations with 4th order D-scale fits -are used to reprouce the example from [SP06]_, Table 8.2 (p. 325). Each example +are used to reproduce the example from [SP06]_, Table 8.2 (p. 325). Each example recovers the same result, but with a different way to specify the D-scale fit orders. +This example is quite numerically challenging, so if you encounter a solver +error, you may need to experiment with solver tolerances. + D-K iteration with fixed fit order ---------------------------------- @@ -42,7 +45,7 @@ Output:: D-K iteration with automatically selected fit orders ---------------------------------------------------- -In this example, multiple fit orders are atempted up to a maximum, and the one +In this example, multiple fit orders are attempted up to a maximum, and the one with the lowest relative error is selected. .. literalinclude:: ../examples/3_example_dk_iter_auto_order.py diff --git a/src/dkpy/dk_iteration.py b/src/dkpy/dk_iteration.py index d27e2be..d875a0e 100644 --- a/src/dkpy/dk_iteration.py +++ b/src/dkpy/dk_iteration.py @@ -1,7 +1,7 @@ """D-K iteration classes.""" __all__ = [ - "DScaleFitInfo", + "IterResult", "DkIteration", "DkIterFixedOrder", "DkIterListOrder", @@ -28,8 +28,15 @@ ) -class DScaleFitInfo: - """Information about the D-scale fit accuracy.""" +class IterResult: + """Information about the current iteration of the D-K iteration process. + + All :class:`DkIteration` objects return a list of :class:`IterResult` + objects (one for each iteration). + + This class is used mainly to assess the accuracy of the D-scale fit. Can be + plotted using :func:`plot_mu` and :func:`plot_D`. + """ def __init__( self, @@ -41,7 +48,7 @@ def __init__( D_fit: control.StateSpace, block_structure: np.ndarray, ): - """Instantiate :class:`DScaleFitInfo`. + """Instantiate :class:`IterResult`. Parameters ---------- @@ -85,8 +92,8 @@ def create_from_fit( D_fit: control.StateSpace, D_fit_inv: control.StateSpace, block_structure: np.ndarray, - ) -> "DScaleFitInfo": - """Instantiate :class:`DScaleFitInfo` from fit D-scales. + ) -> "IterResult": + """Instantiate :class:`IterResult` from fit D-scales. Parameters ---------- @@ -111,12 +118,12 @@ def create_from_fit( Returns ------- - DScaleFitInfo - Instance of :class:`DScaleFitInfo` + IterResult + Instance of :class:`IterResult` Examples -------- - Create a ``DScaleFitInfo`` object from fit data + Create a ``IterResult`` object from fit data >>> P, n_y, n_u, K = example_skogestad2006_p325 >>> block_structure = np.array([[1, 1], [1, 1], [2, 2]]) @@ -128,7 +135,7 @@ def create_from_fit( ... block_structure, ... ) >>> D, D_inv = dkpy.DScaleFitSlicot().fit(omega, D_omega, 2, block_structure) - >>> d_scale_fit_info = DScaleFitInfo.create_from_fit( + >>> d_scale_fit_info = IterResult.create_from_fit( ... omega, ... mu_omega, ... D_omega, @@ -202,10 +209,13 @@ def synthesize( control.StateSpace, control.StateSpace, float, - List[DScaleFitInfo], + List[IterResult], Dict[str, Any], ]: - """Synthesize controller. + """Synthesize controller using D-K iteration. + + The :class:`IterResult` objects returned by this function can be + plotted using :func:`plot_mu` and :func:`plot_D`. Parameters ---------- @@ -225,13 +235,23 @@ def synthesize( Returns ------- - Tuple[control.StateSpace, control.StateSpace, float, List[DScaleFitInfo], Dict[str, Any]] - Controller, closed-loop system, structured singular value, D-scale - fit info for each iteration, and solution information. If a - controller cannot by synthesized, the first three elements of the - tuple are ``None``, but fit and solution information are still + Tuple[control.StateSpace, control.StateSpace, float, List[IterResult], Dict[str, Any]] + Controller, closed-loop system, structured singular value, + intermediate results for each iteration, and solution information. + If a controller cannot by synthesized, the first three elements of + the tuple are ``None``, but fit and solution information are still returned. + See Also + -------- + :class:`IterResult` + Intermediate results for each iteration. + :func:`plot_mu` + Plot structured singular value fit from an :class:`IterResult` + object. + :func:`plot_D` + Plot D-scale fit from an :class:`IterResult` object. + References ---------- .. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html @@ -278,7 +298,7 @@ def synthesize( ) # Add D-scale fit info d_scale_fit_info.append( - DScaleFitInfo.create_from_fit( + IterResult.create_from_fit( omega, mu_omega, D_omega, @@ -588,7 +608,7 @@ def _get_fit_order( order=fit_order, block_structure=block_structure, ) - d_scale_fit_info = DScaleFitInfo.create_from_fit( + d_scale_fit_info = IterResult.create_from_fit( omega, mu_omega, D_omega, @@ -683,7 +703,7 @@ def _get_fit_order( block_structure=block_structure, ) d_info.append( - DScaleFitInfo.create_from_fit( + IterResult.create_from_fit( omega, mu_omega, D_omega, @@ -724,7 +744,7 @@ def _get_fit_order( def plot_mu( - d_scale_info: DScaleFitInfo, + d_scale_info: IterResult, ax: Optional[plt.Axes] = None, plot_kw: Optional[Dict[str, Any]] = None, hide: Optional[str] = None, @@ -733,7 +753,7 @@ def plot_mu( Parameters ---------- - d_scale_fit_info : dkpy.DScaleFitInfo + d_scale_fit_info : dkpy.IterResult Object containing information about the D-scale fit. ax : Optional[plt.Axes] Matplotlib axes to use. @@ -750,7 +770,7 @@ def plot_mu( Examples -------- - Create a ``DScaleFitInfo`` object from fit data and plot ``mu`` + Create a ``IterResult`` object from fit data and plot ``mu`` >>> P, n_y, n_u, K = example_skogestad2006_p325 >>> block_structure = np.array([[1, 1], [1, 1], [2, 2]]) @@ -762,7 +782,7 @@ def plot_mu( ... block_structure, ... ) >>> D, D_inv = dkpy.DScaleFitSlicot().fit(omega, D_omega, 2, block_structure) - >>> d_scale_fit_info = DScaleFitInfo.create_from_fit( + >>> d_scale_fit_info = IterResult.create_from_fit( ... omega, ... mu_omega, ... D_omega, @@ -814,7 +834,7 @@ def plot_mu( def plot_D( - d_scale_info: DScaleFitInfo, + d_scale_info: IterResult, ax: Optional[np.ndarray] = None, plot_kw: Optional[Dict[str, Any]] = None, hide: Optional[str] = None, @@ -823,7 +843,7 @@ def plot_D( Parameters ---------- - d_scale_fit_info : dkpy.DScaleFitInfo + d_scale_fit_info : dkpy.IterResult Object containing information about the D-scale fit. ax : Optional[np.ndarray] Array of Matplotlib axes to use. @@ -841,7 +861,7 @@ def plot_D( Examples -------- - Create a ``DScaleFitInfo`` object from fit data and plot ``D`` + Create a ``IterResult`` object from fit data and plot ``D`` >>> P, n_y, n_u, K = example_skogestad2006_p325 >>> block_structure = np.array([[1, 1], [1, 1], [2, 2]]) @@ -853,7 +873,7 @@ def plot_D( ... block_structure, ... ) >>> D, D_inv = dkpy.DScaleFitSlicot().fit(omega, D_omega, 2, block_structure) - >>> d_scale_fit_info = DScaleFitInfo.create_from_fit( + >>> d_scale_fit_info = IterResult.create_from_fit( ... omega, ... mu_omega, ... D_omega, From a393ffbe261a844b4ad33bff217ffa0b3efa5ad5 Mon Sep 17 00:00:00 2001 From: Steven Dahdah Date: Fri, 20 Dec 2024 15:57:15 -0500 Subject: [PATCH 3/4] Rename d_scale_fit_info in examples --- examples/1_example_dk_iter_fixed_order.py | 6 +++--- examples/2_example_dk_iter_list_order.py | 6 +++--- examples/3_example_dk_iter_auto_order.py | 6 +++--- examples/4_example_dk_iter_interactive.py | 6 +++--- examples/5_example_dk_iteration_custom.py | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/1_example_dk_iter_fixed_order.py b/examples/1_example_dk_iter_fixed_order.py index 748cfe4..ba9340a 100644 --- a/examples/1_example_dk_iter_fixed_order.py +++ b/examples/1_example_dk_iter_fixed_order.py @@ -20,7 +20,7 @@ def example_dk_iter_fixed_order(): omega = np.logspace(-3, 3, 61) block_structure = np.array([[1, 1], [1, 1], [2, 2]]) - K, N, mu, d_scale_fit_info, info = dk_iter.synthesize( + K, N, mu, iter_results, info = dk_iter.synthesize( eg["P"], eg["n_y"], eg["n_u"], @@ -31,11 +31,11 @@ def example_dk_iter_fixed_order(): print(f"mu={mu}") fig, ax = plt.subplots() - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): dkpy.plot_mu(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) ax = None - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): _, ax = dkpy.plot_D(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) plt.show() diff --git a/examples/2_example_dk_iter_list_order.py b/examples/2_example_dk_iter_list_order.py index a4060aa..2bcbe2d 100644 --- a/examples/2_example_dk_iter_list_order.py +++ b/examples/2_example_dk_iter_list_order.py @@ -24,7 +24,7 @@ def example_dk_iter_list_order(): omega = np.logspace(-3, 3, 61) block_structure = np.array([[1, 1], [1, 1], [2, 2]]) - K, N, mu, d_scale_fit_info, info = dk_iter.synthesize( + K, N, mu, iter_results, info = dk_iter.synthesize( eg["P"], eg["n_y"], eg["n_u"], @@ -35,11 +35,11 @@ def example_dk_iter_list_order(): print(f"mu={mu}") fig, ax = plt.subplots() - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): dkpy.plot_mu(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) ax = None - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): _, ax = dkpy.plot_D(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) plt.show() diff --git a/examples/3_example_dk_iter_auto_order.py b/examples/3_example_dk_iter_auto_order.py index 4f16a6f..1770546 100644 --- a/examples/3_example_dk_iter_auto_order.py +++ b/examples/3_example_dk_iter_auto_order.py @@ -45,7 +45,7 @@ def example_dk_iter_auto_order(): omega = np.logspace(-3, 3, 61) block_structure = np.array([[1, 1], [1, 1], [2, 2]]) - K, N, mu, d_scale_fit_info, info = dk_iter.synthesize( + K, N, mu, iter_results, info = dk_iter.synthesize( eg["P"], eg["n_y"], eg["n_u"], @@ -56,11 +56,11 @@ def example_dk_iter_auto_order(): print(f"mu={mu}") fig, ax = plt.subplots() - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): dkpy.plot_mu(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) ax = None - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): _, ax = dkpy.plot_D(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) plt.show() diff --git a/examples/4_example_dk_iter_interactive.py b/examples/4_example_dk_iter_interactive.py index cde141f..d713ff3 100644 --- a/examples/4_example_dk_iter_interactive.py +++ b/examples/4_example_dk_iter_interactive.py @@ -38,7 +38,7 @@ def example_dk_iter_interactive(): omega = np.logspace(-3, 3, 61) block_structure = np.array([[1, 1], [1, 1], [2, 2]]) - K, N, mu, d_scale_fit_info, info = dk_iter.synthesize( + K, N, mu, iter_results, info = dk_iter.synthesize( eg["P"], eg["n_y"], eg["n_u"], @@ -49,11 +49,11 @@ def example_dk_iter_interactive(): print(f"mu={mu}") fig, ax = plt.subplots() - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): dkpy.plot_mu(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) ax = None - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): _, ax = dkpy.plot_D(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) plt.show() diff --git a/examples/5_example_dk_iteration_custom.py b/examples/5_example_dk_iteration_custom.py index 40c64b6..0c33e6b 100644 --- a/examples/5_example_dk_iteration_custom.py +++ b/examples/5_example_dk_iteration_custom.py @@ -71,7 +71,7 @@ def example_dk_iter_custom(): omega = np.logspace(-3, 3, 61) block_structure = np.array([[1, 1], [1, 1], [2, 2]]) - K, N, mu, d_scale_fit_info, info = dk_iter.synthesize( + K, N, mu, iter_results, info = dk_iter.synthesize( eg["P"], eg["n_y"], eg["n_u"], @@ -82,11 +82,11 @@ def example_dk_iter_custom(): print(f"mu={mu}") fig, ax = plt.subplots() - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): dkpy.plot_mu(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) ax = None - for i, ds in enumerate(d_scale_fit_info): + for i, ds in enumerate(iter_results): _, ax = dkpy.plot_D(ds, ax=ax, plot_kw=dict(label=f"iter{i}")) plt.show() From 61c4e4bb6485df3c4b2db302244267109580e0ff Mon Sep 17 00:00:00 2001 From: Steven Dahdah Date: Fri, 20 Dec 2024 15:58:14 -0500 Subject: [PATCH 4/4] Bump version --- CITATION.cff | 2 +- README.rst | 2 +- doc/conf.py | 4 ++-- pyproject.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 49d165c..72fc807 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -10,5 +10,5 @@ authors: orcid: "https://orcid.org/0000-0002-1987-9268" affiliation: "McGill University" title: "decargroup/dkpy" -version: v0.1.7 +version: v0.1.8 url: "https://github.com/decargroup/dkpy" diff --git a/README.rst b/README.rst index 43e8581..afa1cdc 100644 --- a/README.rst +++ b/README.rst @@ -137,6 +137,6 @@ If you use this software in your research, please cite it as below or see url={https://github.com/decargroup/dkpy}, publisher={Zenodo}, author={Steven Dahdah and James Richard Forbes}, - version = {{v0.1.7}}, + version = {{v0.1.8}}, year={2024}, } diff --git a/doc/conf.py b/doc/conf.py index ccfd66b..b8174b5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,8 +9,8 @@ project = "dkpy" copyright = "2024, Steven Dahdah and James Richard Forbes" author = "Steven Dahdah and James Richard Forbes" -version = "0.1.7" -release = "0.1.7" +version = "0.1.8" +release = "0.1.8" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index f9577fd..61fc0d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "dkpy" -version = "0.1.7" +version = "0.1.8" dependencies = [ "numpy>=1.21.0", "scipy>=1.7.0",