NREL-reV 0.8.7__py3-none-any.whl → 0.9.0__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.
Files changed (43) hide show
  1. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
  2. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
  3. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +217 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +611 -422
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +681 -377
  9. reV/bespoke/cli_bespoke.py +2 -0
  10. reV/bespoke/place_turbines.py +187 -43
  11. reV/config/output_request.py +2 -1
  12. reV/config/project_points.py +218 -140
  13. reV/econ/econ.py +166 -114
  14. reV/econ/economies_of_scale.py +91 -45
  15. reV/generation/base.py +331 -184
  16. reV/generation/generation.py +326 -200
  17. reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
  18. reV/handlers/__init__.py +0 -1
  19. reV/handlers/exclusions.py +16 -15
  20. reV/handlers/multi_year.py +57 -26
  21. reV/handlers/outputs.py +6 -5
  22. reV/handlers/transmission.py +44 -27
  23. reV/hybrids/hybrid_methods.py +30 -30
  24. reV/hybrids/hybrids.py +305 -189
  25. reV/nrwal/nrwal.py +262 -168
  26. reV/qa_qc/cli_qa_qc.py +14 -10
  27. reV/qa_qc/qa_qc.py +217 -119
  28. reV/qa_qc/summary.py +228 -146
  29. reV/rep_profiles/rep_profiles.py +349 -230
  30. reV/supply_curve/aggregation.py +349 -188
  31. reV/supply_curve/competitive_wind_farms.py +90 -48
  32. reV/supply_curve/exclusions.py +138 -85
  33. reV/supply_curve/extent.py +75 -50
  34. reV/supply_curve/points.py +735 -390
  35. reV/supply_curve/sc_aggregation.py +357 -248
  36. reV/supply_curve/supply_curve.py +604 -347
  37. reV/supply_curve/tech_mapping.py +144 -82
  38. reV/utilities/__init__.py +274 -16
  39. reV/utilities/pytest_utils.py +8 -4
  40. reV/version.py +1 -1
  41. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
  42. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
  43. {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
@@ -2,28 +2,33 @@
2
2
  """
3
3
  reV Project Points Configuration
4
4
  """
5
+
5
6
  import copy
6
7
  import logging
7
- import numpy as np
8
8
  import os
9
- import pandas as pd
10
9
  from warnings import warn
11
10
 
11
+ import numpy as np
12
+ import pandas as pd
13
+ from rex.multi_file_resource import MultiFileResource
14
+ from rex.resource import Resource
15
+ from rex.resource_extraction.resource_extraction import (
16
+ MultiFileResourceX,
17
+ ResourceX,
18
+ )
19
+ from rex.utilities import check_res_file, parse_table
20
+
12
21
  from reV.config.curtailment import Curtailment
13
22
  from reV.config.sam_config import SAMConfig
23
+ from reV.utilities import SiteDataField, SupplyCurveField
14
24
  from reV.utilities.exceptions import ConfigError, ConfigWarning
15
25
 
16
- from rex.resource import Resource
17
- from rex.multi_file_resource import MultiFileResource
18
- from rex.resource_extraction.resource_extraction import (ResourceX,
19
- MultiFileResourceX)
20
- from rex.utilities import check_res_file, parse_table
21
-
22
26
  logger = logging.getLogger(__name__)
23
27
 
24
28
 
25
29
  class PointsControl:
26
30
  """Class to manage and split ProjectPoints."""
31
+
27
32
  def __init__(self, project_points, sites_per_split=100):
28
33
  """
29
34
  Parameters
@@ -46,9 +51,12 @@ class PointsControl:
46
51
  last_site = 0
47
52
  ilim = len(self.project_points)
48
53
 
49
- logger.debug('PointsControl iterator initializing with sites '
50
- '{} through {}'.format(self.project_points.sites[0],
51
- self.project_points.sites[-1]))
54
+ logger.debug(
55
+ "PointsControl iterator initializing with sites "
56
+ "{} through {}".format(
57
+ self.project_points.sites[0], self.project_points.sites[-1]
58
+ )
59
+ )
52
60
 
53
61
  # pre-initialize all iter objects
54
62
  while True:
@@ -59,14 +67,19 @@ class PointsControl:
59
67
 
60
68
  last_site = i1
61
69
 
62
- new = self.split(i0, i1, self.project_points,
63
- sites_per_split=self.sites_per_split)
70
+ new = self.split(
71
+ i0,
72
+ i1,
73
+ self.project_points,
74
+ sites_per_split=self.sites_per_split,
75
+ )
64
76
  new._split_range = [i0, i1]
65
77
  self._iter_list.append(new)
66
78
 
67
- logger.debug('PointsControl stopped iteration at attempted '
68
- 'index of {}. Length of iterator is: {}'
69
- .format(i1, len(self)))
79
+ logger.debug(
80
+ "PointsControl stopped iteration at attempted "
81
+ "index of {}. Length of iterator is: {}".format(i1, len(self))
82
+ )
70
83
  return self
71
84
 
72
85
  def __next__(self):
@@ -85,17 +98,22 @@ class PointsControl:
85
98
  # No more points controllers left in initialized list
86
99
  raise StopIteration
87
100
 
88
- logger.debug('PointsControl passing site project points '
89
- 'with indices {} to {} on iteration #{} '
90
- .format(next_pc.split_range[0],
91
- next_pc.split_range[1], self._i))
101
+ logger.debug(
102
+ "PointsControl passing site project points "
103
+ "with indices {} to {} on iteration #{} ".format(
104
+ next_pc.split_range[0], next_pc.split_range[1], self._i
105
+ )
106
+ )
92
107
  self._i += 1
93
108
  return next_pc
94
109
 
95
110
  def __repr__(self):
96
- msg = ("{} with {} sites from gid {} through {}"
97
- .format(self.__class__.__name__, len(self.project_points),
98
- self.sites[0], self.sites[-1]))
111
+ msg = "{} with {} sites from gid {} through {}".format(
112
+ self.__class__.__name__,
113
+ len(self.project_points),
114
+ self.sites[0],
115
+ self.sites[-1],
116
+ )
99
117
  return msg
100
118
 
101
119
  def __len__(self):
@@ -210,8 +228,9 @@ class ProjectPoints:
210
228
  >>> h_list = pp.h
211
229
  """
212
230
 
213
- def __init__(self, points, sam_configs, tech=None, res_file=None,
214
- curtailment=None):
231
+ def __init__(
232
+ self, points, sam_configs, tech=None, res_file=None, curtailment=None
233
+ ):
215
234
  """
216
235
  Parameters
217
236
  ----------
@@ -269,22 +288,25 @@ class ProjectPoints:
269
288
  names (keys) and values.
270
289
  """
271
290
 
272
- site_bool = (self.df['gid'] == site)
291
+ site_bool = self.df[SiteDataField.GID] == site
273
292
  try:
274
- config_id = self.df.loc[site_bool, 'config'].values[0]
293
+ config_id = self.df.loc[site_bool, SiteDataField.CONFIG].values[0]
275
294
  except (KeyError, IndexError) as ex:
276
- msg = ('Site {} not found in this instance of '
277
- 'ProjectPoints. Available sites include: {}'
278
- .format(site, self.sites))
295
+ msg = (
296
+ "Site {} not found in this instance of "
297
+ "ProjectPoints. Available sites include: {}".format(
298
+ site, self.sites
299
+ )
300
+ )
279
301
  logger.exception(msg)
280
302
  raise KeyError(msg) from ex
281
303
 
282
304
  return config_id, copy.deepcopy(self.sam_inputs[config_id])
283
305
 
284
306
  def __repr__(self):
285
- msg = ("{} with {} sites from gid {} through {}"
286
- .format(self.__class__.__name__, len(self),
287
- self.sites[0], self.sites[-1]))
307
+ msg = "{} with {} sites from gid {} through {}".format(
308
+ self.__class__.__name__, len(self), self.sites[0], self.sites[-1]
309
+ )
288
310
  return msg
289
311
 
290
312
  def __len__(self):
@@ -299,7 +321,7 @@ class ProjectPoints:
299
321
  -------
300
322
  _df : pd.DataFrame
301
323
  Table of sites and corresponding SAM configuration IDs.
302
- Has columns 'gid' and 'config'.
324
+ Has columns "gid" and 'config'.
303
325
  """
304
326
  return self._df
305
327
 
@@ -384,7 +406,7 @@ class ProjectPoints:
384
406
  List of integer sites (resource file gids) belonging to this
385
407
  instance of ProjectPoints.
386
408
  """
387
- return self.df['gid'].values.tolist()
409
+ return self.df[SiteDataField.GID].values.tolist()
388
410
 
389
411
  @property
390
412
  def sites_as_slice(self):
@@ -425,7 +447,7 @@ class ProjectPoints:
425
447
  solarwaterheat, troughphysicalheat, lineardirectsteam)
426
448
  The string should be lower-cased with spaces and _ removed.
427
449
  """
428
- return 'windpower' if 'wind' in self._tech.lower() else self._tech
450
+ return "windpower" if "wind" in self._tech.lower() else self._tech
429
451
 
430
452
  @property
431
453
  def h(self):
@@ -437,9 +459,9 @@ class ProjectPoints:
437
459
  Hub heights corresponding to each site, taken from the sam config
438
460
  for each site. This is None if the technology is not wind.
439
461
  """
440
- h_var = 'wind_turbine_hub_ht'
462
+ h_var = "wind_turbine_hub_ht"
441
463
  if self._h is None:
442
- if 'wind' in self.tech:
464
+ if "wind" in self.tech:
443
465
  # wind technology, get a list of h values
444
466
  self._h = [self[site][1][h_var] for site in self.sites]
445
467
 
@@ -456,9 +478,9 @@ class ProjectPoints:
456
478
  the sam config for each site. This is None if the technology
457
479
  is not geothermal.
458
480
  """
459
- d_var = 'resource_depth'
481
+ d_var = "resource_depth"
460
482
  if self._d is None:
461
- if 'geothermal' in self.tech:
483
+ if "geothermal" in self.tech:
462
484
  if d_var in self.df:
463
485
  self._d = list(self.df[d_var])
464
486
  else:
@@ -485,8 +507,8 @@ class ProjectPoints:
485
507
  Parameters
486
508
  ----------
487
509
  fname : str
488
- Project points .csv file (with path). Must have 'gid' and 'config'
489
- column names.
510
+ Project points .csv file (with path). Must have 'gid' and
511
+ 'config' column names.
490
512
 
491
513
  Returns
492
514
  -------
@@ -494,12 +516,13 @@ class ProjectPoints:
494
516
  DataFrame mapping sites (gids) to SAM technology (config)
495
517
  """
496
518
  fname = fname.strip()
497
- if fname.endswith('.csv'):
519
+ if fname.endswith(".csv"):
498
520
  df = pd.read_csv(fname)
499
521
  else:
500
- raise ValueError('Config project points file must be '
501
- '.csv, but received: {}'
502
- .format(fname))
522
+ raise ValueError(
523
+ "Config project points file must be "
524
+ ".csv, but received: {}".format(fname)
525
+ )
503
526
 
504
527
  return df
505
528
 
@@ -522,7 +545,7 @@ class ProjectPoints:
522
545
  df : pd.DataFrame
523
546
  DataFrame mapping sites (gids) to SAM technology (config)
524
547
  """
525
- df = pd.DataFrame(columns=['gid', 'config'])
548
+ df = pd.DataFrame(columns=[SiteDataField.GID, SiteDataField.CONFIG])
526
549
  if isinstance(points, int):
527
550
  points = [points]
528
551
  if isinstance(points, (list, tuple, np.ndarray)):
@@ -532,14 +555,16 @@ class ProjectPoints:
532
555
  logger.error(msg)
533
556
  raise RuntimeError(msg)
534
557
 
535
- df['gid'] = points
558
+ df[SiteDataField.GID] = points
536
559
  elif isinstance(points, slice):
537
560
  stop = points.stop
538
561
  if stop is None:
539
562
  if res_file is None:
540
- raise ValueError('Must supply a resource file if '
541
- 'points is a slice of type '
542
- ' slice(*, None, *)')
563
+ raise ValueError(
564
+ "Must supply a resource file if "
565
+ "points is a slice of type "
566
+ " slice(*, None, *)"
567
+ )
543
568
 
544
569
  multi_h5_res, _ = check_res_file(res_file)
545
570
  if multi_h5_res:
@@ -547,13 +572,14 @@ class ProjectPoints:
547
572
  else:
548
573
  stop = Resource(res_file).shape[1]
549
574
 
550
- df['gid'] = list(range(*points.indices(stop)))
575
+ df[SiteDataField.GID] = list(range(*points.indices(stop)))
551
576
  else:
552
- raise TypeError('Project Points sites needs to be set as a list, '
553
- 'tuple, or slice, but was set as: {}'
554
- .format(type(points)))
577
+ raise TypeError(
578
+ "Project Points sites needs to be set as a list, "
579
+ "tuple, or slice, but was set as: {}".format(type(points))
580
+ )
555
581
 
556
- df['config'] = None
582
+ df[SiteDataField.CONFIG] = None
557
583
 
558
584
  return df
559
585
 
@@ -585,25 +611,31 @@ class ProjectPoints:
585
611
  elif isinstance(points, pd.DataFrame):
586
612
  df = points
587
613
  else:
588
- raise ValueError('Cannot parse Project points data from {}'
589
- .format(type(points)))
590
-
591
- if 'gid' not in df.columns:
592
- raise KeyError('Project points data must contain "gid" column.')
614
+ raise ValueError(
615
+ "Cannot parse Project points data from {}".format(type(points))
616
+ )
617
+ df = df.rename(SupplyCurveField.map_to(SiteDataField), axis=1)
618
+ if SiteDataField.GID not in df.columns:
619
+ raise KeyError(
620
+ "Project points data must contain "
621
+ f"{SiteDataField.GID} column."
622
+ )
593
623
 
594
624
  # pylint: disable=no-member
595
- if 'config' not in df.columns:
596
- df = cls._parse_sites(points["gid"].values, res_file=res_file)
625
+ if SiteDataField.CONFIG not in df.columns:
626
+ df[SiteDataField.CONFIG] = None
597
627
 
598
- gids = df['gid'].values
628
+ gids = df[SiteDataField.GID].values
599
629
  if not np.array_equal(np.sort(gids), gids):
600
- msg = ('WARNING: points are not in sequential order and will be '
601
- 'sorted! The original order is being preserved under '
602
- 'column "points_order"')
630
+ msg = (
631
+ "WARNING: points are not in sequential order and will be "
632
+ "sorted! The original order is being preserved under "
633
+ 'column "points_order"'
634
+ )
603
635
  logger.warning(msg)
604
636
  warn(msg)
605
- df['points_order'] = df.index.values
606
- df = df.sort_values('gid').reset_index(drop=True)
637
+ df["points_order"] = df.index.values
638
+ df = df.sort_values(SiteDataField.GID).reset_index(drop=True)
607
639
 
608
640
  return df
609
641
 
@@ -628,16 +660,16 @@ class ProjectPoints:
628
660
  if isinstance(sam_config, SAMConfig):
629
661
  return sam_config
630
662
 
663
+ if isinstance(sam_config, dict):
664
+ config_dict = sam_config
665
+ elif isinstance(sam_config, str):
666
+ config_dict = {sam_config: sam_config}
631
667
  else:
632
- if isinstance(sam_config, dict):
633
- config_dict = sam_config
634
- elif isinstance(sam_config, str):
635
- config_dict = {sam_config: sam_config}
636
- else:
637
- raise ValueError('Cannot parse SAM configs from {}'
638
- .format(type(sam_config)))
668
+ raise ValueError(
669
+ "Cannot parse SAM configs from {}".format(type(sam_config))
670
+ )
639
671
 
640
- return SAMConfig(config_dict)
672
+ return SAMConfig(config_dict)
641
673
 
642
674
  @staticmethod
643
675
  def _parse_curtailment(curtailment_input):
@@ -670,10 +702,12 @@ class ProjectPoints:
670
702
 
671
703
  else:
672
704
  curtailment = None
673
- warn('Curtailment inputs not recognized. Received curtailment '
674
- 'input of type: "{}". Expected None, dict, str, or '
675
- 'Curtailment object. Defaulting to no curtailment.',
676
- ConfigWarning)
705
+ warn(
706
+ "Curtailment inputs not recognized. Received curtailment "
707
+ 'input of type: "{}". Expected None, dict, str, or '
708
+ "Curtailment object. Defaulting to no curtailment.",
709
+ ConfigWarning,
710
+ )
677
711
 
678
712
  return curtailment
679
713
 
@@ -691,13 +725,15 @@ class ProjectPoints:
691
725
  ind : int
692
726
  Row index of gid in the project points dataframe.
693
727
  """
694
- if gid not in self._df['gid'].values:
695
- e = ('Requested resource gid {} is not present in the project '
696
- 'points dataframe. Cannot return row index.'.format(gid))
728
+ if gid not in self._df[SiteDataField.GID].values:
729
+ e = (
730
+ "Requested resource gid {} is not present in the project "
731
+ "points dataframe. Cannot return row index.".format(gid)
732
+ )
697
733
  logger.error(e)
698
734
  raise ConfigError(e)
699
735
 
700
- ind = np.where(self._df['gid'] == gid)[0][0]
736
+ ind = np.where(self._df[SiteDataField.GID] == gid)[0][0]
701
737
 
702
738
  return ind
703
739
 
@@ -707,21 +743,24 @@ class ProjectPoints:
707
743
  (sam_config_obj) are compatible. Update as necessary or break
708
744
  """
709
745
  # Extract unique config refences from project_points DataFrame
710
- df_configs = self.df['config'].unique()
746
+ df_configs = self.df[SiteDataField.CONFIG].unique()
711
747
  sam_configs = self.sam_inputs
712
748
 
713
749
  # Checks to make sure that the same number of SAM config files
714
750
  # as references in project_points DataFrame
715
751
  if len(df_configs) > len(sam_configs):
716
- msg = ('Points references {} configs while only '
717
- '{} SAM configs were provided!'
718
- .format(len(df_configs), len(sam_configs)))
752
+ msg = (
753
+ "Points references {} configs while only "
754
+ "{} SAM configs were provided!".format(
755
+ len(df_configs), len(sam_configs)
756
+ )
757
+ )
719
758
  logger.error(msg)
720
759
  raise ConfigError(msg)
721
760
 
722
761
  if len(df_configs) == 1 and df_configs[0] is None:
723
- self._df['config'] = list(sam_configs)[0]
724
- df_configs = self.df['config'].unique()
762
+ self._df[SiteDataField.CONFIG] = list(sam_configs)[0]
763
+ df_configs = self.df[SiteDataField.CONFIG].unique()
725
764
 
726
765
  # Check to see if config references in project_points DataFrame
727
766
  # are valid file paths, if compare with SAM configs
@@ -733,21 +772,25 @@ class ProjectPoints:
733
772
  elif config in sam_configs:
734
773
  configs[config] = sam_configs[config]
735
774
  else:
736
- msg = ('{} does not map to a valid configuration file'
737
- .format(config))
775
+ msg = "{} does not map to a valid configuration file".format(
776
+ config
777
+ )
738
778
  logger.error(msg)
739
779
  raise ConfigError(msg)
740
780
 
741
781
  # If configs has any keys that are not in sam_configs then
742
782
  # something really weird happened so raise an error.
743
783
  if any(set(configs) - set(sam_configs)):
744
- msg = ('A wild config has appeared! Requested config keys for '
745
- 'ProjectPoints are {} and previous config keys are {}'
746
- .format(list(configs), list(sam_configs)))
784
+ msg = (
785
+ "A wild config has appeared! Requested config keys for "
786
+ "ProjectPoints are {} and previous config keys are {}".format(
787
+ list(configs), list(sam_configs)
788
+ )
789
+ )
747
790
  logger.error(msg)
748
791
  raise ConfigError(msg)
749
792
 
750
- def join_df(self, df2, key='gid'):
793
+ def join_df(self, df2, key=SiteDataField.GID):
751
794
  """Join new df2 to the _df attribute using the _df's gid as pkey.
752
795
 
753
796
  This can be used to add site-specific data to the project_points,
@@ -767,8 +810,15 @@ class ProjectPoints:
767
810
  """
768
811
  # ensure df2 doesnt have any duplicate columns for suffix reasons.
769
812
  df2_cols = [c for c in df2.columns if c not in self._df or c == key]
770
- self._df = pd.merge(self._df, df2[df2_cols], how='left', left_on='gid',
771
- right_on=key, copy=False, validate='1:1')
813
+ self._df = pd.merge(
814
+ self._df,
815
+ df2[df2_cols],
816
+ how="left",
817
+ left_on=SiteDataField.GID,
818
+ right_on=key,
819
+ copy=False,
820
+ validate="1:1",
821
+ )
772
822
 
773
823
  def get_sites_from_config(self, config):
774
824
  """Get a site list that corresponds to a config key.
@@ -784,7 +834,9 @@ class ProjectPoints:
784
834
  List of sites associated with the requested configuration ID. If
785
835
  the configuration ID is not recognized, an empty list is returned.
786
836
  """
787
- sites = self.df.loc[(self.df['config'] == config), 'gid'].values
837
+ sites = self.df.loc[
838
+ (self.df[SiteDataField.CONFIG] == config), SiteDataField.GID
839
+ ].values
788
840
 
789
841
  return list(sites)
790
842
 
@@ -817,32 +869,39 @@ class ProjectPoints:
817
869
  # Extract DF subset with only index values between i0 and i1
818
870
  n = len(project_points)
819
871
  if i0 > n or i1 > n:
820
- raise ValueError('{} and {} must be within the range of '
821
- 'project_points (0 - {})'.format(i0, i1, n - 1))
872
+ raise ValueError(
873
+ "{} and {} must be within the range of "
874
+ "project_points (0 - {})".format(i0, i1, n - 1)
875
+ )
822
876
 
823
877
  points_df = project_points.df.iloc[i0:i1]
824
878
 
825
879
  # make a new instance of ProjectPoints with subset DF
826
- sub = cls(points_df,
827
- project_points.sam_config_obj,
828
- project_points.tech,
829
- curtailment=project_points.curtailment)
880
+ sub = cls(
881
+ points_df,
882
+ project_points.sam_config_obj,
883
+ project_points.tech,
884
+ curtailment=project_points.curtailment,
885
+ )
830
886
 
831
887
  return sub
832
888
 
833
889
  @staticmethod
834
890
  def _parse_lat_lons(lat_lons):
835
- msg = ('Expecting a pair or multiple pairs of latitude and '
836
- 'longitude coordinates!')
891
+ msg = (
892
+ "Expecting a pair or multiple pairs of latitude and "
893
+ "longitude coordinates!"
894
+ )
837
895
  if isinstance(lat_lons, str):
838
896
  lat_lons = parse_table(lat_lons)
839
- cols = [c for c in lat_lons
840
- if c.lower().startswith(('lat', 'lon'))]
897
+ cols = [
898
+ c for c in lat_lons if c.lower().startswith(("lat", "lon"))
899
+ ]
841
900
  lat_lons = lat_lons[sorted(cols)].values
842
901
  elif isinstance(lat_lons, (list, tuple)):
843
902
  lat_lons = np.array(lat_lons)
844
903
  elif isinstance(lat_lons, (int, float)):
845
- msg += ' Recieved a single coordinate value!'
904
+ msg += " Recieved a single coordinate value!"
846
905
  logger.error(msg)
847
906
  raise ValueError(msg)
848
907
 
@@ -850,15 +909,16 @@ class ProjectPoints:
850
909
  lat_lons = np.expand_dims(lat_lons, axis=0)
851
910
 
852
911
  if lat_lons.shape[1] != 2:
853
- msg += ' Received {} coordinate values!'.format(lat_lons.shape[1])
912
+ msg += " Received {} coordinate values!".format(lat_lons.shape[1])
854
913
  logger.error(msg)
855
914
  raise ValueError(msg)
856
915
 
857
916
  return lat_lons
858
917
 
859
918
  @classmethod
860
- def lat_lon_coords(cls, lat_lons, res_file, sam_configs, tech=None,
861
- curtailment=None):
919
+ def lat_lon_coords(
920
+ cls, lat_lons, res_file, sam_configs, tech=None, curtailment=None
921
+ ):
862
922
  """
863
923
  Generate ProjectPoints for gids nearest to given latitude longitudes
864
924
 
@@ -903,11 +963,13 @@ class ProjectPoints:
903
963
  res_kwargs = {}
904
964
  else:
905
965
  res_cls = ResourceX
906
- res_kwargs = {'hsds': hsds}
966
+ res_kwargs = {"hsds": hsds}
907
967
 
908
- logger.info('Converting latitude longitude coordinates into nearest '
909
- 'ProjectPoints')
910
- logger.debug('- (lat, lon) pairs:\n{}'.format(lat_lons))
968
+ logger.info(
969
+ "Converting latitude longitude coordinates into nearest "
970
+ "ProjectPoints"
971
+ )
972
+ logger.debug("- (lat, lon) pairs:\n{}".format(lat_lons))
911
973
  with res_cls(res_file, **res_kwargs) as f:
912
974
  gids = f.lat_lon_gid(lat_lons) # pylint: disable=no-member
913
975
 
@@ -915,37 +977,46 @@ class ProjectPoints:
915
977
  gids = [gids]
916
978
  else:
917
979
  if len(gids) != len(np.unique(gids)):
918
- uniques, pos, counts = np.unique(gids, return_counts=True,
919
- return_inverse=True)
980
+ uniques, pos, counts = np.unique(
981
+ gids, return_counts=True, return_inverse=True
982
+ )
920
983
  duplicates = {}
921
984
  for idx in np.where(counts > 1)[0]:
922
985
  duplicate_lat_lons = lat_lons[np.where(pos == idx)[0]]
923
986
  duplicates[uniques[idx]] = duplicate_lat_lons
924
987
 
925
- msg = ('reV Cannot currently handle duplicate Resource gids! '
926
- 'The given latitude and longitudes map to the same '
927
- 'gids:\n{}'.format(duplicates))
988
+ msg = (
989
+ "reV Cannot currently handle duplicate Resource gids! "
990
+ "The given latitude and longitudes map to the same "
991
+ "gids:\n{}".format(duplicates)
992
+ )
928
993
  logger.error(msg)
929
994
  raise RuntimeError(msg)
930
995
 
931
996
  gids = gids.tolist()
932
997
 
933
- logger.debug('- Resource gids:\n{}'.format(gids))
998
+ logger.debug("- Resource gids:\n{}".format(gids))
934
999
 
935
- pp = cls(gids, sam_configs, tech=tech, res_file=res_file,
936
- curtailment=curtailment)
1000
+ pp = cls(
1001
+ gids,
1002
+ sam_configs,
1003
+ tech=tech,
1004
+ res_file=res_file,
1005
+ curtailment=curtailment,
1006
+ )
937
1007
 
938
- if 'points_order' in pp.df:
939
- lat_lons = lat_lons[pp.df['points_order'].values]
1008
+ if "points_order" in pp.df:
1009
+ lat_lons = lat_lons[pp.df["points_order"].values]
940
1010
 
941
- pp._df['latitude'] = lat_lons[:, 0]
942
- pp._df['longitude'] = lat_lons[:, 1]
1011
+ pp._df["latitude"] = lat_lons[:, 0]
1012
+ pp._df["longitude"] = lat_lons[:, 1]
943
1013
 
944
1014
  return pp
945
1015
 
946
1016
  @classmethod
947
- def regions(cls, regions, res_file, sam_configs, tech=None,
948
- curtailment=None):
1017
+ def regions(
1018
+ cls, regions, res_file, sam_configs, tech=None, curtailment=None
1019
+ ):
949
1020
  """
950
1021
  Generate ProjectPoints for gids nearest to given latitude longitudes
951
1022
 
@@ -989,28 +1060,35 @@ class ProjectPoints:
989
1060
  else:
990
1061
  res_cls = ResourceX
991
1062
 
992
- logger.info('Extracting ProjectPoints for desired regions')
1063
+ logger.info("Extracting ProjectPoints for desired regions")
993
1064
  points = []
994
1065
  with res_cls(res_file, hsds=hsds) as f:
995
1066
  meta = f.meta
996
1067
  for region, region_col in regions.items():
997
- logger.debug('- {}: {}'.format(region_col, region))
1068
+ logger.debug("- {}: {}".format(region_col, region))
998
1069
  # pylint: disable=no-member
999
1070
  gids = f.region_gids(region, region_col=region_col)
1000
- logger.debug('- Resource gids:\n{}'.format(gids))
1071
+ logger.debug("- Resource gids:\n{}".format(gids))
1001
1072
  if points:
1002
1073
  duplicates = np.intersect1d(gids, points).tolist()
1003
1074
  if duplicates:
1004
- msg = ('reV Cannot currently handle duplicate '
1005
- 'Resource gids! The given regions containg the '
1006
- 'same gids:\n{}'.format(duplicates))
1075
+ msg = (
1076
+ "reV Cannot currently handle duplicate "
1077
+ "Resource gids! The given regions containg the "
1078
+ "same gids:\n{}".format(duplicates)
1079
+ )
1007
1080
  logger.error(msg)
1008
1081
  raise RuntimeError(msg)
1009
1082
 
1010
1083
  points.extend(gids.tolist())
1011
1084
 
1012
- pp = cls(points, sam_configs, tech=tech, res_file=res_file,
1013
- curtailment=curtailment)
1085
+ pp = cls(
1086
+ points,
1087
+ sam_configs,
1088
+ tech=tech,
1089
+ res_file=res_file,
1090
+ curtailment=curtailment,
1091
+ )
1014
1092
 
1015
1093
  meta = meta.loc[pp.sites]
1016
1094
  cols = list(set(regions.values()))