ltbams 1.0.11__py3-none-any.whl → 1.0.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ams/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-05-23T17:38:11-0400",
11
+ "date": "2025-05-29T20:16:06-0400",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "6d7b555f5f48c751648594a535711b9fd21d2a85",
15
- "version": "1.0.11"
14
+ "full-revisionid": "2166f4368af3d382720af38408153a70502eb9f5",
15
+ "version": "1.0.12"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
ams/core/matprocessor.py CHANGED
@@ -3,7 +3,6 @@ Module for system matrix make.
3
3
  """
4
4
 
5
5
  import logging
6
- import os
7
6
  import sys
8
7
  from typing import Optional
9
8
 
@@ -14,6 +13,9 @@ from andes.shared import tqdm, tqdm_nb
14
13
  from andes.utils.misc import elapsed, is_notebook
15
14
 
16
15
  from ams.opt import Param
16
+
17
+ from ams.utils.paths import get_export_path
18
+
17
19
  from ams.shared import pd, sps
18
20
 
19
21
  logger = logging.getLogger(__name__)
@@ -75,31 +77,28 @@ class MParam(Param):
75
77
  """
76
78
  Export the matrix to a CSV file.
77
79
 
80
+ In the exported CSV, columns are the bus idxes, and Line idxes are
81
+ used as row indexes.
82
+
78
83
  Parameters
79
84
  ----------
80
85
  path : str, optional
81
- Path to the output CSV file.
86
+ Path of the csv file to export.
82
87
 
83
88
  Returns
84
89
  -------
85
90
  str
86
- The path of the exported csv file
91
+ The exported csv file name
87
92
  """
88
93
 
89
- if path is None:
90
- if self.owner.system.files.fullname is None:
91
- logger.info("Input file name not detacted. Using `Untitled`.")
92
- file_name = f'Untitled_{self.name}'
93
- else:
94
- file_name = os.path.splitext(self.owner.system.files.fullname)[0]
95
- file_name += f'_{self.name}'
96
- path = os.path.join(os.getcwd(), file_name + '.csv')
97
- else:
98
- file_name = os.path.splitext(os.path.basename(path))[0]
94
+ path, file_name = get_export_path(self.owner.system,
95
+ self.name,
96
+ path=path,
97
+ fmt='csv')
99
98
 
100
99
  pd.DataFrame(data=self.v, columns=self.col_names, index=self.row_names).to_csv(path)
101
100
 
102
- return file_name + '.csv'
101
+ return file_name
103
102
 
104
103
  @property
105
104
  def v(self):
ams/io/matpower.py CHANGED
@@ -455,16 +455,6 @@ def system2mpc(system) -> dict:
455
455
 
456
456
  This function is revised from ``andes.io.matpower.system2mpc``.
457
457
 
458
- In the ``gen`` section, slack generators are listed before PV generators.
459
-
460
- In the converted MPC, the indices of area (bus[:, 6]) and zone (bus[:, 10])
461
- may differ from the original MPC. However, the mapping relationship is preserved.
462
- For example, if the original MPC numbers areas starting from 1, the converted
463
- MPC may number them starting from 0.
464
-
465
- The coefficients ``c2`` and ``c1`` in the generator cost data are scaled by
466
- ``base_mva`` to match MATPOWER's unit convention (MW).
467
-
468
458
  Parameters
469
459
  ----------
470
460
  system : ams.core.system.System
@@ -474,6 +464,21 @@ def system2mpc(system) -> dict:
474
464
  -------
475
465
  mpc : dict
476
466
  A dictionary in MATPOWER format representing the converted AMS system.
467
+
468
+ Notes
469
+ -----
470
+ - In the `gen` section, slack generators are listed before PV generators.
471
+ - For uncontrolled generators (`ctrl.v == 0`), their max and min power
472
+ limits are set to their initial power (`p0.v`) in the converted MPC.
473
+ - In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
474
+ may differ from the original MPC. However, the mapping relationship is preserved.
475
+ For example, if the original MPC numbers areas starting from 1, the converted
476
+ MPC may number them starting from 0.
477
+ - The coefficients `c2` and `c1` in the generator cost data are scaled by
478
+ `baseMVA`.
479
+ - Unlike the XLSX and JSON converters, this implementation uses value providers
480
+ (`v`) instead of vin. As a result, any changes made through `model.set` will be
481
+ reflected in the generated MPC.
477
482
  """
478
483
 
479
484
  mpc = dict(version='2',
@@ -514,15 +519,31 @@ def system2mpc(system) -> dict:
514
519
  # --- PQ ---
515
520
  if system.PQ.n > 0:
516
521
  pq_pos = system.Bus.idx2uid(system.PQ.bus.v)
517
- u = system.PQ.u.v
518
- bus[pq_pos, 2] = u * system.PQ.p0.v * base_mva
519
- bus[pq_pos, 3] = u * system.PQ.q0.v * base_mva
522
+
523
+ p0e = system.PQ.u.v * system.PQ.p0.v
524
+ q0e = system.PQ.u.v * system.PQ.q0.v
525
+
526
+ # NOTE: ensure multiple PQ on the same bus are summed
527
+ # rather than overwritten. Same for Shunt.
528
+ p = np.zeros(system.Bus.n)
529
+ q = np.zeros(system.Bus.n)
530
+ np.add.at(p, pq_pos, p0e)
531
+ np.add.at(q, pq_pos, q0e)
532
+ bus[:, 2] = p * base_mva
533
+ bus[:, 3] = q * base_mva
520
534
 
521
535
  # --- Shunt ---
522
536
  if system.Shunt.n > 0:
523
537
  shunt_pos = system.Bus.idx2uid(system.Shunt.bus.v)
524
- bus[shunt_pos, 4] = system.Shunt.g.v * base_mva
525
- bus[shunt_pos, 5] = system.Shunt.b.v * base_mva
538
+
539
+ ge = system.Shunt.u.v * system.Shunt.g.v
540
+ be = system.Shunt.u.v * system.Shunt.b.v
541
+ g = np.zeros(system.Bus.n)
542
+ b = np.zeros(system.Bus.n)
543
+ np.add.at(g, shunt_pos, ge)
544
+ np.add.at(b, shunt_pos, be)
545
+ bus[:, 4] = g * base_mva
546
+ bus[:, 5] = b * base_mva
526
547
 
527
548
  # --- PV ---
528
549
  if system.PV.n > 0:
@@ -709,11 +730,6 @@ def write(system, outfile: str, overwrite: bool = None) -> bool:
709
730
  This function converts an AMS system object into a MATPOWER-compatible
710
731
  mpc dictionary and writes it to a specified output file in MATPOWER format.
711
732
 
712
- In the converted MPC, the indices of area (bus[:, 6]) and zone (bus[:, 10])
713
- may differ from the original MPC. However, the mapping relationship is preserved.
714
- For example, if the original MPC numbers areas starting from 1, the converted
715
- MPC may number them starting from 0.
716
-
717
733
  Parameters
718
734
  ----------
719
735
  system : ams.system.System
@@ -727,6 +743,21 @@ def write(system, outfile: str, overwrite: bool = None) -> bool:
727
743
  -------
728
744
  bool
729
745
  True if the file was successfully written, False otherwise.
746
+
747
+ Notes
748
+ -----
749
+ - In the `gen` section, slack generators are listed before PV generators.
750
+ - For uncontrolled generators (`ctrl.v == 0`), their max and min power
751
+ limits are set to their initial power (`p0.v`) in the converted MPC.
752
+ - In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
753
+ may differ from the original MPC. However, the mapping relationship is preserved.
754
+ For example, if the original MPC numbers areas starting from 1, the converted
755
+ MPC may number them starting from 0.
756
+ - The coefficients `c2` and `c1` in the generator cost data are scaled by
757
+ `baseMVA`.
758
+ - Unlike the XLSX and JSON converters, this implementation uses value providers
759
+ (`v`) instead of vin. As a result, any changes made through `model.set` will be
760
+ reflected in the generated MPC.
730
761
  """
731
762
  if not confirm_overwrite(outfile, overwrite=overwrite):
732
763
  return False
ams/io/psse.py CHANGED
@@ -46,6 +46,8 @@ def write_raw(system, outfile: str, overwrite: bool = None):
46
46
  """
47
47
  Convert AMS system to PSS/E RAW file.
48
48
 
49
+ This method has not been fully benchmarked yet!
50
+
49
51
  Parameters
50
52
  ----------
51
53
  system : System
ams/routines/pypower.py CHANGED
@@ -131,6 +131,15 @@ class DCPF1(RoutineBase):
131
131
  unit='p.u.',
132
132
  name='vBus', tex_name=r'v_{Bus}',
133
133
  src='v', model='Bus',)
134
+ # --- load ---
135
+ self.pd = RParam(info='active demand',
136
+ name='pd', tex_name=r'p_{d}',
137
+ model='StaticLoad', src='p0',
138
+ unit='p.u.',)
139
+ self.qd = RParam(info='reactive demand',
140
+ name='qd', tex_name=r'q_{d}',
141
+ model='StaticLoad', src='q0',
142
+ unit='p.u.',)
134
143
  # --- gen ---
135
144
  self.pg = Var(info='Gen active power',
136
145
  unit='p.u.',
@@ -267,13 +276,13 @@ class DCPF1(RoutineBase):
267
276
  return False
268
277
 
269
278
  def _get_off_constrs(self):
270
- pass
279
+ logger.debug(f"{self.class_name} does not implement _get_off_constrs.")
271
280
 
272
281
  def _data_check(self, info=True, **kwargs):
273
- pass
282
+ logger.debug(f"{self.class_name} does not implement _data_check.")
274
283
 
275
284
  def update(self, params=None, build_mats=False, **kwargs):
276
- pass
285
+ logger.debug(f"{self.class_name} does not implement update.")
277
286
 
278
287
  def enable(self, name):
279
288
  raise NotImplementedError
@@ -282,7 +291,7 @@ class DCPF1(RoutineBase):
282
291
  raise NotImplementedError
283
292
 
284
293
  def _post_add_check(self):
285
- pass
294
+ logger.debug(f"{self.class_name} does not implement _post_add_check.")
286
295
 
287
296
  def addRParam(self,
288
297
  name: str,
ams/routines/routine.py CHANGED
@@ -3,7 +3,6 @@ Module for routine data.
3
3
  """
4
4
 
5
5
  import logging
6
- import os
7
6
  from typing import Optional, Union, Type, Iterable, Dict
8
7
  from collections import OrderedDict
9
8
 
@@ -19,6 +18,8 @@ from ams.core.service import RBaseService, ValueService
19
18
  from ams.opt import OModel
20
19
  from ams.opt import Param, Var, Constraint, Objective, ExpressionCalc, Expression
21
20
 
21
+ from ams.utils.paths import get_export_path
22
+
22
23
  from ams.shared import pd
23
24
 
24
25
  logger = logging.getLogger(__name__)
@@ -441,26 +442,20 @@ class RoutineBase:
441
442
 
442
443
  Parameters
443
444
  ----------
444
- path : str
445
- path of the csv file to save
445
+ path : str, optional
446
+ Path of the csv file to export.
446
447
 
447
448
  Returns
448
449
  -------
449
- export_path
450
- The path of the exported csv file
450
+ str
451
+ The exported csv file name
451
452
  """
452
453
  if not self.converged:
453
454
  logger.warning("Routine did not converge, aborting export.")
454
455
  return None
455
456
 
456
- if not path:
457
- if self.system.files.fullname is None:
458
- logger.info("Input file name not detacted. Using `Untitled`.")
459
- file_name = f'Untitled_{self.class_name}'
460
- else:
461
- file_name = os.path.splitext(self.system.files.fullname)[0]
462
- file_name += f'_{self.class_name}'
463
- path = os.path.join(os.getcwd(), file_name + '.csv')
457
+ path, file_name = get_export_path(self.system, self.class_name,
458
+ path=path, fmt='csv')
464
459
 
465
460
  data_dict = initialize_data_dict(self)
466
461
 
@@ -473,7 +468,7 @@ class RoutineBase:
473
468
 
474
469
  pd.DataFrame(data_dict).to_csv(path, index=False)
475
470
 
476
- return file_name + '.csv'
471
+ return file_name
477
472
 
478
473
  def summary(self, **kwargs):
479
474
  """
ams/system.py CHANGED
@@ -29,7 +29,7 @@ from ams.report import Report
29
29
  from ams.shared import ad_dyn_models
30
30
 
31
31
  from ams.io.matpower import system2mpc
32
- from ams.io.matpower import write as wrtite_m
32
+ from ams.io.matpower import write as write_m
33
33
  from ams.io.xlsx import write as write_xlsx
34
34
  from ams.io.json import write as write_json
35
35
  from ams.io.psse import write_raw
@@ -602,6 +602,7 @@ class System(adSystem):
602
602
  **kwargs):
603
603
  """
604
604
  Convert the AMS system to an ANDES system.
605
+ Wrapper method for `ams.interface.to_andes`.
605
606
 
606
607
  A preferred dynamic system file to be added has following features:
607
608
  1. The file contains both power flow and dynamic models.
@@ -718,17 +719,34 @@ class System(adSystem):
718
719
  def to_mpc(self):
719
720
  """
720
721
  Export an AMS system to a MATPOWER dict.
722
+ Wrapper method for `ams.io.matpower.system2mpc`.
721
723
 
722
724
  Returns
723
725
  -------
724
726
  dict
725
727
  A dictionary representing the MATPOWER case.
728
+
729
+ Notes
730
+ -----
731
+ - In the `gen` section, slack generators are listed before PV generators.
732
+ - For uncontrolled generators (`ctrl.v == 0`), their max and min power
733
+ limits are set to their initial power (`p0.v`) in the converted MPC.
734
+ - In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
735
+ may differ from the original MPC. However, the mapping relationship is preserved.
736
+ For example, if the original MPC numbers areas starting from 1, the converted
737
+ MPC may number them starting from 0.
738
+ - The coefficients `c2` and `c1` in the generator cost data are scaled by
739
+ `baseMVA`.
740
+ - Unlike the XLSX and JSON converters, this implementation uses value providers
741
+ (`v`) instead of vin. As a result, any changes made through `model.set` will be
742
+ reflected in the generated MPC.
726
743
  """
727
744
  return system2mpc(self)
728
745
 
729
746
  def to_m(self, outfile: str, overwrite: bool = None):
730
747
  """
731
748
  Export an AMS system to a MATPOWER M-file.
749
+ Wrapper method for `ams.io.matpower.write`.
732
750
 
733
751
  Parameters
734
752
  ----------
@@ -736,12 +754,28 @@ class System(adSystem):
736
754
  The output file name.
737
755
  overwrite : bool, optional
738
756
  If True, overwrite the existing file. Default is None.
739
- """
740
- return wrtite_m(self, outfile=outfile, overwrite=overwrite)
757
+
758
+ Notes
759
+ -----
760
+ - In the `gen` section, slack generators are listed before PV generators.
761
+ - For uncontrolled generators (`ctrl.v == 0`), their max and min power
762
+ limits are set to their initial power (`p0.v`) in the converted MPC.
763
+ - In the converted MPC, the indices of area (`bus[:, 6]`) and zone (`bus[:, 10]`)
764
+ may differ from the original MPC. However, the mapping relationship is preserved.
765
+ For example, if the original MPC numbers areas starting from 1, the converted
766
+ MPC may number them starting from 0.
767
+ - The coefficients `c2` and `c1` in the generator cost data are scaled by
768
+ `baseMVA`.
769
+ - Unlike the XLSX and JSON converters, this implementation uses value providers
770
+ (`v`) instead of vin. As a result, any changes made through `model.set` will be
771
+ reflected in the generated MPC.
772
+ """
773
+ return write_m(self, outfile=outfile, overwrite=overwrite)
741
774
 
742
775
  def to_xlsx(self, outfile: str, overwrite: bool = None):
743
776
  """
744
777
  Export an AMS system to an Excel file.
778
+ Wrapper method for `ams.io.xlsx.write`.
745
779
 
746
780
  Parameters
747
781
  ----------
@@ -755,6 +789,7 @@ class System(adSystem):
755
789
  def to_json(self, outfile: str, overwrite: bool = None):
756
790
  """
757
791
  Export an AMS system to a JSON file.
792
+ Wrapper method for `ams.io.json.write`.
758
793
 
759
794
  Parameters
760
795
  ----------
@@ -768,6 +803,9 @@ class System(adSystem):
768
803
  def to_raw(self, outfile: str, overwrite: bool = None):
769
804
  """
770
805
  Export an AMS system to a v33 PSS/E RAW file.
806
+ Wrapper method for `ams.io.psse.write_raw`.
807
+
808
+ This method has not been fully benchmarked yet!
771
809
 
772
810
  Parameters
773
811
  ----------
ams/utils/paths.py CHANGED
@@ -255,3 +255,67 @@ def confirm_overwrite(outfile, overwrite=None):
255
255
  pass
256
256
 
257
257
  return True
258
+
259
+
260
+ def get_export_path(system, fname, path=None, fmt='csv'):
261
+ """
262
+ Get the absolute export path and the derived file name for
263
+ an AMS system export.
264
+
265
+ This function is not intended to be used directly by users.
266
+
267
+ Parameters
268
+ ----------
269
+ system : ams.system.System
270
+ The AMS system to export. (Mocked in example)
271
+ fname : str
272
+ The descriptive file name, e.g., 'PTDF', or 'DCOPF'.
273
+ path : str, optional
274
+ The desired output path.
275
+ - If it's a directory, the file name will be generated.
276
+ - If it's a full file path (with extension), the filename part will be used
277
+ as the base, and the `fmt` argument will determine the final extension.
278
+ - If None, the current working directory will be used, and the filename will
279
+ be generated.
280
+ fmt : str, optional
281
+ The file format to export, e.g., 'csv', 'json'. Default is 'csv'.
282
+
283
+ Returns
284
+ -------
285
+ tuple
286
+ (str, str): A tuple containing:
287
+ - The **absolute export path** (e.g., '/home/user/project/data_Routine.csv').
288
+ - The **export file name** (e.g., 'data_Routine.csv'), including the format extension.
289
+ """
290
+ # Determine the base name from system.files.fullname or default to "Untitled"
291
+ if system.files.fullname is None:
292
+ logger.info("Input file name not detected. Using `Untitled`.")
293
+ base_name_prefix = f'Untitled_{fname}'
294
+ else:
295
+ base_name_prefix = os.path.splitext(os.path.basename(system.files.fullname))[0]
296
+ base_name_prefix += f'_{fname}'
297
+
298
+ target_extension = fmt.lower() # Ensure consistent extension casing
299
+
300
+ if path:
301
+ abs_path = os.path.abspath(path) # Resolve to absolute path early
302
+
303
+ # Check if the provided path is likely intended as a directory
304
+ if not os.path.splitext(abs_path)[1]: # No extension implies it's a directory
305
+ dir_path = abs_path
306
+ final_file_name = f"{base_name_prefix}.{target_extension}"
307
+ full_export_path = os.path.join(dir_path, final_file_name)
308
+ else:
309
+ # Path includes a filename. Use its directory, and its base name.
310
+ dir_path = os.path.dirname(abs_path)
311
+ # Use the provided filename's base, but enforce the target_extension
312
+ provided_base_filename = os.path.splitext(os.path.basename(abs_path))[0]
313
+ final_file_name = f"{provided_base_filename}.{target_extension}"
314
+ full_export_path = os.path.join(dir_path, final_file_name)
315
+ else:
316
+ # No path provided, use current working directory
317
+ dir_path = os.getcwd()
318
+ final_file_name = f"{base_name_prefix}.{target_extension}"
319
+ full_export_path = os.path.join(dir_path, final_file_name)
320
+
321
+ return full_export_path, final_file_name
@@ -9,6 +9,13 @@ The APIs before v3.0.0 are in beta and may change without prior notice.
9
9
  v1.0
10
10
  ==========
11
11
 
12
+ v1.0.12 (2025-05-29)
13
+ ----------------------
14
+
15
+ - Add RParam pd and qd in ``DCPF1`` for easy access to load
16
+ - Bug fix in ``RoutineBase.export_csv`` when path is specified
17
+ - Fix bug in ``io.matpower.system2mpc`` with multiple PQ at one bus
18
+
12
19
  v1.0.11 (2025-05-23)
13
20
  ----------------------
14
21
 
@@ -18,8 +25,8 @@ v1.0.11 (2025-05-23)
18
25
  v1.0.10 (2025-05-23)
19
26
  ----------------------
20
27
 
21
- - Add bus type correction in ``system.System.setup()``
22
- - Revise ``ams.io.psse.read`` to complete model Zone when necessary
28
+ - Add bus type correction in ``system.System.setup``
29
+ - Revise ``io.psse.read`` to complete model Zone when necessary
23
30
  - Use numerical Area and Zone idx in MATPOWER and PSSE RAW file conversion
24
31
  - Support JSON format addfile when converting to ANDES case
25
32
  - Add PSS/E v33 RAW file writer
@@ -34,7 +41,7 @@ v1.0.10 (2025-05-23)
34
41
  - Revise ``andes.common.config.Config.update`` to ensure configuration parameters
35
42
  are consistently updated in both the object and its internal ``_dict``
36
43
  - Remove legacy revised PYPOWER module
37
- - Remove function ``ams.shared.ppc2df``
44
+ - Remove function ``shared.ppc2df``
38
45
 
39
46
  v1.0.9 (2025-04-23)
40
47
  --------------------
@@ -62,7 +69,7 @@ v1.0.7 (2025-04-14)
62
69
  - Extend common parameters in groups ``StaticGen`` and ``StaticLoad`` with ``area``
63
70
  - Set case ``pjm5bus_demo.xlsx`` as a all-inclusive case
64
71
  - Include module ``MatProcessor`` in the API documentation
65
- - Improve Line parameters correction in ``System.setup()``
72
+ - Improve Line parameters correction in ``System.setup``
66
73
  - Make func ``interface._to_andes_pflow`` public
67
74
  - Discard ``sync_adsys`` step in func ``to_andes_pflow`` to fix mistake in
68
75
  parameters conversion
@@ -91,7 +98,7 @@ v1.0.4 (2025-04-05)
91
98
  v1.0.3 (2025-03-17)
92
99
  --------------------
93
100
 
94
- - Bug fix in function ``ams.interface.parse_addfile``, released in v1.0.3a1
101
+ - Bug fix in function ``interface.parse_addfile``, released in v1.0.3a1
95
102
 
96
103
  v1.0.2 (2025-02-01)
97
104
  --------------------
@@ -136,10 +143,10 @@ v0.9.12 (2024-11-23)
136
143
 
137
144
  - Refactor ``OModel.initialized`` as a property method
138
145
  - Add a demo to show using ``Constraint.e`` for debugging
139
- - Fix ``ams.opt.omodel.Param.evaluate`` when its value is a number
140
- - Improve ``ams.opt.omodel.ExpressionCalc`` for better performance
141
- - Refactor module ``ams.opt``
142
- - Add class ``ams.opt.Expression``
146
+ - Fix ``opt.omodel.Param.evaluate`` when its value is a number
147
+ - Improve ``opt.omodel.ExpressionCalc`` for better performance
148
+ - Refactor module ``opt``
149
+ - Add class ``opt.Expression``
143
150
  - Switch from PYPOWER to ANDES in routine ``PFlow``
144
151
  - Switch from PYPOWER to regular formulation in routine ``DCPF``
145
152
  - Refactor routines ``DCPF`` and ``DCOPF``
@@ -382,7 +389,7 @@ v0.6.5 (2023-06-27)
382
389
  -------------------
383
390
 
384
391
  - Update documentation with auto-generated model and routine reference
385
- - Add interface with ANDES ``ams.interop.andes``
392
+ - Add interface with ANDES ``interop.andes``
386
393
  - Add routine RTED and example of RTED-TDS co-simulation
387
394
  - Draft development documentation
388
395
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ltbams
3
- Version: 1.0.11
3
+ Version: 1.0.12
4
4
  Summary: Python software for scheduling modeling and co-simulation with dynamics.
5
5
  Home-page: https://github.com/CURENT/ams
6
6
  Author: Jinning Wang
@@ -1,12 +1,12 @@
1
1
  ams/__init__.py,sha256=q-9-f0YCg6aaTW19VCkY6VosfvRA2ObI9mF1f_3016Y,313
2
2
  ams/__main__.py,sha256=EB4GfGiKgvnQ_psNr0QwPoziYvjmGvQ2yVsBwQtfrLw,170
3
- ams/_version.py,sha256=9w0v2vIzAou5TWcsacfNdhiCAr9S83BM7FAKRy_t8h0,498
3
+ ams/_version.py,sha256=jNLhv-DaZngBxRAi3mCJd0jmQgAAbgAoAn-N8TbLgbQ,498
4
4
  ams/cli.py,sha256=EyNFXn565gFCppTxpyTZviBdPgUuKtgAPZ4WE6xewRk,6164
5
5
  ams/interface.py,sha256=-UN7-TiSt7mOS0W7526LuOk4KCIGDZSguRaVmh_KMRM,45240
6
6
  ams/main.py,sha256=lIqC16TO0pye75Wv8l_6EemNtm15iyMdvu8YQFkdd_4,23599
7
7
  ams/report.py,sha256=ewcffopOzT5o45eJNmcV8pxeQqPIjKbarGN33-yHGA8,10961
8
8
  ams/shared.py,sha256=4sY2U0sghs_S2V6C7Z0OyQ6WZRfpCXk2_Cf_Mg24Vr4,3900
9
- ams/system.py,sha256=eAsaXO2O_7_PAIuPFup3edztuAhKMhODrR9l92CYx38,29765
9
+ ams/system.py,sha256=bGbZQrN5N2tx1tnxoPz6JSGE568EGH1Oi1zQIyLFLug,31948
10
10
  ams/cases/5bus/pjm5bus_demo.json,sha256=IpxO2vzB9-9Kg9xjOXREKeXEz9wjFE7cuQbcUu8VORA,23152
11
11
  ams/cases/5bus/pjm5bus_demo.xlsx,sha256=OWIUprkg8_aQ_bTCaEFMl7Bhfa1R20zxAHXRQtXBix0,32607
12
12
  ams/cases/5bus/pjm5bus_ev.xlsx,sha256=vR8dJv5jIxib1pgcMonhzvraoqZVJWhBSJdVXDL0qsI,19498
@@ -38,7 +38,7 @@ ams/cases/wecc/wecc_uced.xlsx,sha256=R3tZgxEqz_ctKcjA1wwFecxn-QZXutvf7NzgnCg_078
38
38
  ams/core/__init__.py,sha256=lIvrAvYf2yrHWqYi0rVuJgJt8KNA8nTmN2iuZ125z9Q,123
39
39
  ams/core/common.py,sha256=934J7xq5JvC14yKp2Z4hWKUFJFhxzAnxB_8_99CChY0,704
40
40
  ams/core/documenter.py,sha256=3-jUYrtN8zDZXd8tQZlmZouJluJPH9_xIDbK9ZEEnRU,25762
41
- ams/core/matprocessor.py,sha256=3eEij-ul8Rx25ZwNbv66YEOKyUXB-fYO-ZQs5BpqmIU,26610
41
+ ams/core/matprocessor.py,sha256=gOEqg5xYpzmj4aMgTdzYeOPaL-5Bp493TrxUVmMaiTw,26453
42
42
  ams/core/model.py,sha256=LNZtzyf2A7Tz3pn9IDs35JYaHSkQRqhqZicTpZGSqsc,10926
43
43
  ams/core/param.py,sha256=LPH48xUHyqWqODD6IsiamUtkJDDSgGCEMAo6vroFoHE,11130
44
44
  ams/core/service.py,sha256=Q4aeaYFWycIEH7I8DSO8Itah2CJxc3oAW46dtKCQEyA,28041
@@ -48,8 +48,8 @@ ams/extension/__init__.py,sha256=5IFTNirDL0uDaUsg05_oociVT9VCy2rhPx1ad4LGveM,65
48
48
  ams/extension/eva.py,sha256=4_q4ME0WrQIcd205SBTjv0-rMRZZIdQ07QJuEHwLIC8,16340
49
49
  ams/io/__init__.py,sha256=GIfF7X44olHaySL8EzOBU1eXUnunr1owzzdPfPvvHZU,3802
50
50
  ams/io/json.py,sha256=IurwcZDuKczSbRTpcbQZiIi0ARCrMK6kJ0E3wS8ENy8,2585
51
- ams/io/matpower.py,sha256=IK6NwVUAM0FAv2rFF1hBoSTowfXD8l6YXrKiPch9hW8,26166
52
- ams/io/psse.py,sha256=qvKP9CP-70UejUsffPu4ibtyF1JXO0Sz0-evT4fDPXg,13631
51
+ ams/io/matpower.py,sha256=R8ynl88bWJAzISYZKfMQi7AFRUINwqk_e-Zsh_pnVCE,27541
52
+ ams/io/psse.py,sha256=S2JqpEFaXEiazbVtU77i93BA4ZnoKP7jDZ1rQGg24sE,13684
53
53
  ams/io/pypower.py,sha256=Fe0sxzC5DZegtYksTixadXk_LcfJ1GNetWu9EuHCkG8,2790
54
54
  ams/io/xlsx.py,sha256=7ATW1seyxsGn7d5p5IuSRFHcoCHVVjMu3E7mP1Mc74U,2460
55
55
  ams/models/__init__.py,sha256=EGkViLkVX_We9FAGuEkgfleMNmPw_vGp2Nq1OQimL7U,691
@@ -90,14 +90,14 @@ ams/routines/dopf.py,sha256=8D36-FkPORYGaMnwGTqwz8HxAXk5ywo3mk8NlGq327g,6289
90
90
  ams/routines/ed.py,sha256=9Hf_ZqD6poIxCIBfsTMC0DGoPNEja1ZtVxqhb4ijhgE,11875
91
91
  ams/routines/grbopt.py,sha256=RjrMq6XPHeBEbf-Pt9sLk2D1vnXZYYwaic4BP8lbACg,5480
92
92
  ams/routines/pflow.py,sha256=5_9n10r_PfsVXIRkaBgKxVITumImZ8mvpHnwxX_ECdw,9432
93
- ams/routines/pypower.py,sha256=U1UJbcNG7JawGmK9sKtjZlRrGwODVWDjJFucisMcvJs,26545
94
- ams/routines/routine.py,sha256=v4jzsziQPvbVh5m0AH629NBEtmKlLvitFW5m-YfFtfY,35833
93
+ ams/routines/pypower.py,sha256=3nKm7VoVXWsiiBK0fEdHv0W2T2AsJ4a6Nqb9ts-1u1A,27214
94
+ ams/routines/routine.py,sha256=teafNQ7DmXntwNP-RbtJJ9Ixa5xOEioO-nXFhY1psmA,35574
95
95
  ams/routines/rted.py,sha256=GOHRxo0-HS5HhwQg8lv7-2VcGr_M_TdUvvomgJ31fDQ,22070
96
96
  ams/routines/type.py,sha256=lvTWSnCYIbRJXIm3HX6jA0Hv-WWYusTOUPfoW8DITlU,3877
97
97
  ams/routines/uc.py,sha256=VcuNy2TnBjsewKEGIqeo2EFTyuhpx5QsEvgpAtscDIQ,15648
98
98
  ams/utils/__init__.py,sha256=2hAQmWRgmnE-bCGT9cJoW9FkPDMGRiGkbBcUgj-bgjI,122
99
99
  ams/utils/misc.py,sha256=Y6tPKpUKJa7bL9alroJuG2UNW9vdQjnfAmKb2EbIls8,2027
100
- ams/utils/paths.py,sha256=o9Ku2ETmYxsoD7VbMlS4qv_wGtXtT-SWIgDs0F7jpCQ,6693
100
+ ams/utils/paths.py,sha256=6iTux5UaHzRi6mGHnpbYUp-MoD-IFny4IuRsV8FKDcQ,9481
101
101
  docs/Makefile,sha256=UKXBFAbKGPt1Xw9J84343v0Mh8ylAZ-tE0uCd74DrQU,725
102
102
  docs/make.bat,sha256=9UgKGb4SdP006622fJiFxeFT1BeycYAs6hDbV1xwPy8,804
103
103
  docs/source/api.rst,sha256=BRzdDFDzDwVL7Jr_Xj-O5Icgx0gt5hNNd1OjvPl7ap0,1490
@@ -105,7 +105,7 @@ docs/source/conf.py,sha256=UyoWogZTUNSJU7pQS_JaR28nKddW94zr01LYoIciZZw,6688
105
105
  docs/source/genmodelref.py,sha256=IhmF7bDw8BXPvLD8V3WjQNrfc-H07r5iS-_4DHbbp-8,1420
106
106
  docs/source/genroutineref.py,sha256=glHhbWUXfZzMDrkque9CBZu8QtdxlxPojInERzAAOwA,1063
107
107
  docs/source/index.rst,sha256=N5phQS5RIyYs-NZo_5yYB8LjvHzOKLeXzRA-M8i-g3Q,2688
108
- docs/source/release-notes.rst,sha256=7BtbNLQ2EUvSvB9KGNELicPAFqNT8uBzQQLqlT9MpMc,13830
108
+ docs/source/release-notes.rst,sha256=zBHZ9dALCLSKKz5lwwalCQJiNCD2l2d3iJjISWtUiPg,14031
109
109
  docs/source/_templates/autosummary/base.rst,sha256=zl3U4baR4a6YjsHyT-x9zCOrHwKZOVUdWn1NPX2u3bc,106
110
110
  docs/source/_templates/autosummary/class.rst,sha256=Hv_igCsLsUpM62_zN0nqj6FSfKnS5xLyu8ZldMbfOAk,668
111
111
  docs/source/_templates/autosummary/module.rst,sha256=YdbpCudOrEU-JbuSlzGvcOI2hn_KrCM6FW5HcGqkaEE,1113
@@ -143,18 +143,18 @@ tests/test_cli.py,sha256=TtCGBy2e7Ll_2gJTFo9juZtzhaakho_MqkcqhG2w2dk,870
143
143
  tests/test_export_csv.py,sha256=NTULXfTexgI1jf5koUMOYi3RLrSQouS7zxRjXo3ng50,2921
144
144
  tests/test_group.py,sha256=Tq0s9gtenqrv4ws5YNzWxbiF4WgyhtMEAXZfJtew6M4,2699
145
145
  tests/test_interface.py,sha256=8hOZ1caRfoyoEdy1lnh8Y4rIL97FLb2oUKfX_N1lEDo,8838
146
- tests/test_io.py,sha256=MIale4g0jMC9Vy4jjtFKpnh12aavp9lusczff1iIVC0,6493
146
+ tests/test_io.py,sha256=OXt1C6F_RAt0RoRj3xLKdbA3raKEAjvVyWlKD7Ju7BA,6947
147
147
  tests/test_jumper.py,sha256=bdOknplEGnO_tiJc7p3xQvgTe2b6Dz53bOgbFaXKMAI,537
148
148
  tests/test_known_good.py,sha256=NBrlAxnVMxIHXR2cWps-Kwjh36fiU4Y-eupspZkM0ks,9670
149
149
  tests/test_matp.py,sha256=LkjhqxSFr6oY_ENpduDQ77rhLLBl6RzIHZ2d0m_8i-8,17262
150
150
  tests/test_model.py,sha256=ZgghNYmEeALf8_1sPjq9lIJwuYDQ2SelivJNEYpHxJU,1621
151
151
  tests/test_omodel.py,sha256=niVdTZJEZNSVHz-ocA0nnVx84Dt3-8P5FUrwKkNiA0I,4305
152
- tests/test_paths.py,sha256=BU6Tjl4Q_WuHbid0ujkiibqx7yas42YJ7eV_avVMwuw,633
152
+ tests/test_paths.py,sha256=j_D9-Cjg73mwhTS3sUHDfQ8mCebD8enCQxjyRaR2ZH4,3020
153
153
  tests/test_report.py,sha256=RR23by4G-cyleaHTy9A7SEVet0eOVS-Tm0kk1GXY5dM,8134
154
154
  tests/test_repr.py,sha256=g7MRdxLcZRI1PlREFdUG_npp0LkcQiJZfOPw1aq0EFM,570
155
155
  tests/test_routine.py,sha256=DP5CwKtzliw2gNLW2W_3kp0Ihy4rCaxeBxMpgyN5AXA,6234
156
156
  tests/test_rtn_acopf.py,sha256=Kg-RHflrrwyX2uHr16xSAze9XAdYcK7eg1LHMyr1uyA,2354
157
- tests/test_rtn_dcopf.py,sha256=Pl6yGfvznnjP1oGiIWEhnqFIBW2futxPZS4o_DRkbrw,3660
157
+ tests/test_rtn_dcopf.py,sha256=qFY9mKTAIG-qDlU9-loHZddKDDszdGQwRjL-ZDnESu8,4204
158
158
  tests/test_rtn_dcopf2.py,sha256=57_62TnHW-cS6iK2zXT_eXChnh83YiYoPypJwub3VmQ,3747
159
159
  tests/test_rtn_ed.py,sha256=SeSuzqpTZZnBph41PV2Piplo9yxR3qpbhQUa6UCYnSw,10267
160
160
  tests/test_rtn_opf.py,sha256=MmMNwb9-G0KzHcrrP4uHTvs9DBU71_XPIJqNnkQQPHo,4846
@@ -163,8 +163,8 @@ tests/test_rtn_pypower.py,sha256=KO5VOZxETxVH2mY1mgNvzj1gz1Gdak1sAWxYleDU4E8,104
163
163
  tests/test_rtn_rted.py,sha256=QHDUymorCqQAJKFlDgTy40JyLTGDvNVNU3tjbjDl3-0,9850
164
164
  tests/test_rtn_uc.py,sha256=UbMeaam3dZwgq2LAJokGOl3LT5B3TWKMjCp4dRcLs40,8497
165
165
  tests/test_service.py,sha256=6IP6CAH2xHxGHZM4-R8LjZxVJ2L10LcGaPDyRIqKLmc,2438
166
- ltbams-1.0.11.dist-info/METADATA,sha256=HJopLlES434MQhl_hg4DDQNs90TfSJLovZKG-NChtNs,13808
167
- ltbams-1.0.11.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
168
- ltbams-1.0.11.dist-info/entry_points.txt,sha256=FA56FlhO_yVNeEf810SrorVQb7_Xsmo3_EW-W-ijUfA,37
169
- ltbams-1.0.11.dist-info/top_level.txt,sha256=pyKDqG2kj13F9-BYd_wkruRdBSqLXw8Nwc-cmljqrxg,15
170
- ltbams-1.0.11.dist-info/RECORD,,
166
+ ltbams-1.0.12.dist-info/METADATA,sha256=ZKZlrfSVEg8RkdZ-YjEWGZfY4p7kdEGppQfhHV6hgpg,13808
167
+ ltbams-1.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
168
+ ltbams-1.0.12.dist-info/entry_points.txt,sha256=FA56FlhO_yVNeEf810SrorVQb7_Xsmo3_EW-W-ijUfA,37
169
+ ltbams-1.0.12.dist-info/top_level.txt,sha256=pyKDqG2kj13F9-BYd_wkruRdBSqLXw8Nwc-cmljqrxg,15
170
+ ltbams-1.0.12.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/test_io.py CHANGED
@@ -85,6 +85,16 @@ class TestMATPOWER(unittest.TestCase):
85
85
  self.assertEqual(np.unique(mpc5['bus'][:, 10]).shape[0],
86
86
  np.unique(self.mpc5['bus'][:, 10]).shape[0])
87
87
 
88
+ def test_system2mpc_pq(self):
89
+ """Test AMS System to MPC conversion with multiple PQ at one bus."""
90
+ system = ams.load(ams.get_case('5bus/pjm5bus_demo.json'),
91
+ setup=False, no_output=True)
92
+ system.add('PQ', dict(bus=1, p0=0.2))
93
+
94
+ mpc = system.to_mpc()
95
+ np.testing.assert_almost_equal(mpc['bus'][:, 2].sum(),
96
+ system.config.mva * system.PQ.p0.v.sum())
97
+
88
98
  def test_gencost1(self):
89
99
  """Test when gencost is type 1."""
90
100
  mpcgc1 = self.mpc14.copy()
tests/test_paths.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import unittest
2
2
 
3
+ import os
4
+
3
5
  import ams
4
- from ams.utils.paths import list_cases
6
+ from ams.utils.paths import list_cases, get_export_path
5
7
 
6
8
 
7
9
  class TestPaths(unittest.TestCase):
@@ -20,3 +22,68 @@ class TestPaths(unittest.TestCase):
20
22
  no_output=True, default_config=True,
21
23
  )
22
24
  self.assertNotEqual(ss, None)
25
+
26
+
27
+ class TestExportPath(unittest.TestCase):
28
+
29
+ def setUp(self) -> None:
30
+ self.ss = ams.load(ams.get_case('5bus/pjm5bus_demo.json'),
31
+ setup=True, no_output=True)
32
+
33
+ def test_no_fullname(self):
34
+ """
35
+ Test export path with no full name specified.
36
+ """
37
+ self.ss.files.full_name = None
38
+
39
+ path, file_name = get_export_path(self.ss,
40
+ 'DCOPF',
41
+ path=None,
42
+ fmt='csv')
43
+
44
+ dir_path, only_file_name = os.path.split(path)
45
+ self.assertTrue(os.path.exists(dir_path))
46
+ self.assertIsNotNone(only_file_name)
47
+ self.assertEqual(only_file_name, file_name)
48
+
49
+ def test_no_path(self):
50
+ """
51
+ Test export path with no path specified.
52
+ """
53
+ path, file_name = get_export_path(self.ss,
54
+ 'DCOPF',
55
+ path=None,
56
+ fmt='csv')
57
+
58
+ dir_path, only_file_name = os.path.split(path)
59
+ self.assertTrue(os.path.exists(dir_path))
60
+ self.assertIsNotNone(only_file_name)
61
+ self.assertEqual(only_file_name, file_name)
62
+
63
+ def test_current_path(self):
64
+ """
65
+ Test export path with current path specified.
66
+ """
67
+ path, file_name = get_export_path(self.ss,
68
+ 'DCOPF',
69
+ path='.',
70
+ fmt='csv')
71
+
72
+ dir_path, only_file_name = os.path.split(path)
73
+ self.assertTrue(os.path.exists(dir_path))
74
+ self.assertIsNotNone(only_file_name)
75
+ self.assertEqual(only_file_name, file_name)
76
+
77
+ def test_path_with_file_name(self):
78
+ """
79
+ Test export path with path and file name specified.
80
+ """
81
+ path, file_name = get_export_path(self.ss,
82
+ 'DCOPF',
83
+ path='./test_export.csv',
84
+ fmt='csv',)
85
+
86
+ dir_path, only_file_name = os.path.split(path)
87
+ self.assertTrue(os.path.exists(dir_path))
88
+ self.assertEqual(only_file_name, 'test_export.csv')
89
+ self.assertEqual(only_file_name, file_name)
tests/test_rtn_dcopf.py CHANGED
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  import unittest
2
4
  import numpy as np
3
5
 
@@ -99,3 +101,21 @@ class TestDCOPF(unittest.TestCase):
99
101
  a_dcopf = self.ss.DCOPF.get(src='aBus', attr='v', idx=bus_idx)
100
102
  a_acopf = self.ss.ACOPF.get(src='aBus', attr='v', idx=bus_idx)
101
103
  np.testing.assert_almost_equal(a_dcopf, a_acopf, decimal=3)
104
+
105
+ def test_export_csv(self):
106
+ """
107
+ Test `Routine.export_csv()` method.
108
+ """
109
+ self.ss.DCOPF.run(solver='CLARABEL')
110
+
111
+ # test when path is none
112
+ out = self.ss.DCOPF.export_csv()
113
+ self.assertTrue(os.path.exists(out), "CSV export failed!")
114
+
115
+ # test when path is not none
116
+ out2 = self.ss.DCOPF.export_csv(path='test_dcopf.csv')
117
+ self.assertTrue(os.path.exists(out2), "CSV export with path failed!")
118
+
119
+ # Clean up
120
+ os.remove(out)
121
+ os.remove(out2)