NREL-reV 0.8.7__py3-none-any.whl → 0.8.9__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 (38) hide show
  1. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/METADATA +12 -10
  2. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
  3. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
  4. reV/SAM/SAM.py +182 -133
  5. reV/SAM/econ.py +18 -14
  6. reV/SAM/generation.py +608 -419
  7. reV/SAM/windbos.py +93 -79
  8. reV/bespoke/bespoke.py +690 -445
  9. reV/bespoke/place_turbines.py +6 -6
  10. reV/config/project_points.py +220 -140
  11. reV/econ/econ.py +165 -113
  12. reV/econ/economies_of_scale.py +57 -34
  13. reV/generation/base.py +310 -183
  14. reV/generation/generation.py +298 -190
  15. reV/handlers/exclusions.py +16 -15
  16. reV/handlers/multi_year.py +12 -9
  17. reV/handlers/outputs.py +6 -5
  18. reV/hybrids/hybrid_methods.py +28 -30
  19. reV/hybrids/hybrids.py +304 -188
  20. reV/nrwal/nrwal.py +262 -168
  21. reV/qa_qc/cli_qa_qc.py +14 -10
  22. reV/qa_qc/qa_qc.py +217 -119
  23. reV/qa_qc/summary.py +228 -146
  24. reV/rep_profiles/rep_profiles.py +349 -230
  25. reV/supply_curve/aggregation.py +349 -188
  26. reV/supply_curve/competitive_wind_farms.py +90 -48
  27. reV/supply_curve/exclusions.py +138 -85
  28. reV/supply_curve/extent.py +75 -50
  29. reV/supply_curve/points.py +536 -309
  30. reV/supply_curve/sc_aggregation.py +366 -225
  31. reV/supply_curve/supply_curve.py +505 -308
  32. reV/supply_curve/tech_mapping.py +144 -82
  33. reV/utilities/__init__.py +199 -16
  34. reV/utilities/pytest_utils.py +8 -4
  35. reV/version.py +1 -1
  36. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
  37. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
  38. {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.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,33 @@ 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 = cls._parse_sites(
627
+ df[SiteDataField.GID].values, res_file=res_file
628
+ )
597
629
 
598
- gids = df['gid'].values
630
+ gids = df[SiteDataField.GID].values
599
631
  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"')
632
+ msg = (
633
+ "WARNING: points are not in sequential order and will be "
634
+ "sorted! The original order is being preserved under "
635
+ 'column "points_order"'
636
+ )
603
637
  logger.warning(msg)
604
638
  warn(msg)
605
- df['points_order'] = df.index.values
606
- df = df.sort_values('gid').reset_index(drop=True)
639
+ df["points_order"] = df.index.values
640
+ df = df.sort_values(SiteDataField.GID).reset_index(drop=True)
607
641
 
608
642
  return df
609
643
 
@@ -628,16 +662,16 @@ class ProjectPoints:
628
662
  if isinstance(sam_config, SAMConfig):
629
663
  return sam_config
630
664
 
665
+ if isinstance(sam_config, dict):
666
+ config_dict = sam_config
667
+ elif isinstance(sam_config, str):
668
+ config_dict = {sam_config: sam_config}
631
669
  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)))
670
+ raise ValueError(
671
+ "Cannot parse SAM configs from {}".format(type(sam_config))
672
+ )
639
673
 
640
- return SAMConfig(config_dict)
674
+ return SAMConfig(config_dict)
641
675
 
642
676
  @staticmethod
643
677
  def _parse_curtailment(curtailment_input):
@@ -670,10 +704,12 @@ class ProjectPoints:
670
704
 
671
705
  else:
672
706
  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)
707
+ warn(
708
+ "Curtailment inputs not recognized. Received curtailment "
709
+ 'input of type: "{}". Expected None, dict, str, or '
710
+ "Curtailment object. Defaulting to no curtailment.",
711
+ ConfigWarning,
712
+ )
677
713
 
678
714
  return curtailment
679
715
 
@@ -691,13 +727,15 @@ class ProjectPoints:
691
727
  ind : int
692
728
  Row index of gid in the project points dataframe.
693
729
  """
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))
730
+ if gid not in self._df[SiteDataField.GID].values:
731
+ e = (
732
+ "Requested resource gid {} is not present in the project "
733
+ "points dataframe. Cannot return row index.".format(gid)
734
+ )
697
735
  logger.error(e)
698
736
  raise ConfigError(e)
699
737
 
700
- ind = np.where(self._df['gid'] == gid)[0][0]
738
+ ind = np.where(self._df[SiteDataField.GID] == gid)[0][0]
701
739
 
702
740
  return ind
703
741
 
@@ -707,21 +745,24 @@ class ProjectPoints:
707
745
  (sam_config_obj) are compatible. Update as necessary or break
708
746
  """
709
747
  # Extract unique config refences from project_points DataFrame
710
- df_configs = self.df['config'].unique()
748
+ df_configs = self.df[SiteDataField.CONFIG].unique()
711
749
  sam_configs = self.sam_inputs
712
750
 
713
751
  # Checks to make sure that the same number of SAM config files
714
752
  # as references in project_points DataFrame
715
753
  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)))
754
+ msg = (
755
+ "Points references {} configs while only "
756
+ "{} SAM configs were provided!".format(
757
+ len(df_configs), len(sam_configs)
758
+ )
759
+ )
719
760
  logger.error(msg)
720
761
  raise ConfigError(msg)
721
762
 
722
763
  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()
764
+ self._df[SiteDataField.CONFIG] = list(sam_configs)[0]
765
+ df_configs = self.df[SiteDataField.CONFIG].unique()
725
766
 
726
767
  # Check to see if config references in project_points DataFrame
727
768
  # are valid file paths, if compare with SAM configs
@@ -733,21 +774,25 @@ class ProjectPoints:
733
774
  elif config in sam_configs:
734
775
  configs[config] = sam_configs[config]
735
776
  else:
736
- msg = ('{} does not map to a valid configuration file'
737
- .format(config))
777
+ msg = "{} does not map to a valid configuration file".format(
778
+ config
779
+ )
738
780
  logger.error(msg)
739
781
  raise ConfigError(msg)
740
782
 
741
783
  # If configs has any keys that are not in sam_configs then
742
784
  # something really weird happened so raise an error.
743
785
  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)))
786
+ msg = (
787
+ "A wild config has appeared! Requested config keys for "
788
+ "ProjectPoints are {} and previous config keys are {}".format(
789
+ list(configs), list(sam_configs)
790
+ )
791
+ )
747
792
  logger.error(msg)
748
793
  raise ConfigError(msg)
749
794
 
750
- def join_df(self, df2, key='gid'):
795
+ def join_df(self, df2, key=SiteDataField.GID):
751
796
  """Join new df2 to the _df attribute using the _df's gid as pkey.
752
797
 
753
798
  This can be used to add site-specific data to the project_points,
@@ -767,8 +812,15 @@ class ProjectPoints:
767
812
  """
768
813
  # ensure df2 doesnt have any duplicate columns for suffix reasons.
769
814
  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')
815
+ self._df = pd.merge(
816
+ self._df,
817
+ df2[df2_cols],
818
+ how="left",
819
+ left_on=SiteDataField.GID,
820
+ right_on=key,
821
+ copy=False,
822
+ validate="1:1",
823
+ )
772
824
 
773
825
  def get_sites_from_config(self, config):
774
826
  """Get a site list that corresponds to a config key.
@@ -784,7 +836,9 @@ class ProjectPoints:
784
836
  List of sites associated with the requested configuration ID. If
785
837
  the configuration ID is not recognized, an empty list is returned.
786
838
  """
787
- sites = self.df.loc[(self.df['config'] == config), 'gid'].values
839
+ sites = self.df.loc[
840
+ (self.df[SiteDataField.CONFIG] == config), SiteDataField.GID
841
+ ].values
788
842
 
789
843
  return list(sites)
790
844
 
@@ -817,32 +871,39 @@ class ProjectPoints:
817
871
  # Extract DF subset with only index values between i0 and i1
818
872
  n = len(project_points)
819
873
  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))
874
+ raise ValueError(
875
+ "{} and {} must be within the range of "
876
+ "project_points (0 - {})".format(i0, i1, n - 1)
877
+ )
822
878
 
823
879
  points_df = project_points.df.iloc[i0:i1]
824
880
 
825
881
  # 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)
882
+ sub = cls(
883
+ points_df,
884
+ project_points.sam_config_obj,
885
+ project_points.tech,
886
+ curtailment=project_points.curtailment,
887
+ )
830
888
 
831
889
  return sub
832
890
 
833
891
  @staticmethod
834
892
  def _parse_lat_lons(lat_lons):
835
- msg = ('Expecting a pair or multiple pairs of latitude and '
836
- 'longitude coordinates!')
893
+ msg = (
894
+ "Expecting a pair or multiple pairs of latitude and "
895
+ "longitude coordinates!"
896
+ )
837
897
  if isinstance(lat_lons, str):
838
898
  lat_lons = parse_table(lat_lons)
839
- cols = [c for c in lat_lons
840
- if c.lower().startswith(('lat', 'lon'))]
899
+ cols = [
900
+ c for c in lat_lons if c.lower().startswith(("lat", "lon"))
901
+ ]
841
902
  lat_lons = lat_lons[sorted(cols)].values
842
903
  elif isinstance(lat_lons, (list, tuple)):
843
904
  lat_lons = np.array(lat_lons)
844
905
  elif isinstance(lat_lons, (int, float)):
845
- msg += ' Recieved a single coordinate value!'
906
+ msg += " Recieved a single coordinate value!"
846
907
  logger.error(msg)
847
908
  raise ValueError(msg)
848
909
 
@@ -850,15 +911,16 @@ class ProjectPoints:
850
911
  lat_lons = np.expand_dims(lat_lons, axis=0)
851
912
 
852
913
  if lat_lons.shape[1] != 2:
853
- msg += ' Received {} coordinate values!'.format(lat_lons.shape[1])
914
+ msg += " Received {} coordinate values!".format(lat_lons.shape[1])
854
915
  logger.error(msg)
855
916
  raise ValueError(msg)
856
917
 
857
918
  return lat_lons
858
919
 
859
920
  @classmethod
860
- def lat_lon_coords(cls, lat_lons, res_file, sam_configs, tech=None,
861
- curtailment=None):
921
+ def lat_lon_coords(
922
+ cls, lat_lons, res_file, sam_configs, tech=None, curtailment=None
923
+ ):
862
924
  """
863
925
  Generate ProjectPoints for gids nearest to given latitude longitudes
864
926
 
@@ -903,11 +965,13 @@ class ProjectPoints:
903
965
  res_kwargs = {}
904
966
  else:
905
967
  res_cls = ResourceX
906
- res_kwargs = {'hsds': hsds}
968
+ res_kwargs = {"hsds": hsds}
907
969
 
908
- logger.info('Converting latitude longitude coordinates into nearest '
909
- 'ProjectPoints')
910
- logger.debug('- (lat, lon) pairs:\n{}'.format(lat_lons))
970
+ logger.info(
971
+ "Converting latitude longitude coordinates into nearest "
972
+ "ProjectPoints"
973
+ )
974
+ logger.debug("- (lat, lon) pairs:\n{}".format(lat_lons))
911
975
  with res_cls(res_file, **res_kwargs) as f:
912
976
  gids = f.lat_lon_gid(lat_lons) # pylint: disable=no-member
913
977
 
@@ -915,37 +979,46 @@ class ProjectPoints:
915
979
  gids = [gids]
916
980
  else:
917
981
  if len(gids) != len(np.unique(gids)):
918
- uniques, pos, counts = np.unique(gids, return_counts=True,
919
- return_inverse=True)
982
+ uniques, pos, counts = np.unique(
983
+ gids, return_counts=True, return_inverse=True
984
+ )
920
985
  duplicates = {}
921
986
  for idx in np.where(counts > 1)[0]:
922
987
  duplicate_lat_lons = lat_lons[np.where(pos == idx)[0]]
923
988
  duplicates[uniques[idx]] = duplicate_lat_lons
924
989
 
925
- msg = ('reV Cannot currently handle duplicate Resource gids! '
926
- 'The given latitude and longitudes map to the same '
927
- 'gids:\n{}'.format(duplicates))
990
+ msg = (
991
+ "reV Cannot currently handle duplicate Resource gids! "
992
+ "The given latitude and longitudes map to the same "
993
+ "gids:\n{}".format(duplicates)
994
+ )
928
995
  logger.error(msg)
929
996
  raise RuntimeError(msg)
930
997
 
931
998
  gids = gids.tolist()
932
999
 
933
- logger.debug('- Resource gids:\n{}'.format(gids))
1000
+ logger.debug("- Resource gids:\n{}".format(gids))
934
1001
 
935
- pp = cls(gids, sam_configs, tech=tech, res_file=res_file,
936
- curtailment=curtailment)
1002
+ pp = cls(
1003
+ gids,
1004
+ sam_configs,
1005
+ tech=tech,
1006
+ res_file=res_file,
1007
+ curtailment=curtailment,
1008
+ )
937
1009
 
938
- if 'points_order' in pp.df:
939
- lat_lons = lat_lons[pp.df['points_order'].values]
1010
+ if "points_order" in pp.df:
1011
+ lat_lons = lat_lons[pp.df["points_order"].values]
940
1012
 
941
- pp._df['latitude'] = lat_lons[:, 0]
942
- pp._df['longitude'] = lat_lons[:, 1]
1013
+ pp._df["latitude"] = lat_lons[:, 0]
1014
+ pp._df["longitude"] = lat_lons[:, 1]
943
1015
 
944
1016
  return pp
945
1017
 
946
1018
  @classmethod
947
- def regions(cls, regions, res_file, sam_configs, tech=None,
948
- curtailment=None):
1019
+ def regions(
1020
+ cls, regions, res_file, sam_configs, tech=None, curtailment=None
1021
+ ):
949
1022
  """
950
1023
  Generate ProjectPoints for gids nearest to given latitude longitudes
951
1024
 
@@ -989,28 +1062,35 @@ class ProjectPoints:
989
1062
  else:
990
1063
  res_cls = ResourceX
991
1064
 
992
- logger.info('Extracting ProjectPoints for desired regions')
1065
+ logger.info("Extracting ProjectPoints for desired regions")
993
1066
  points = []
994
1067
  with res_cls(res_file, hsds=hsds) as f:
995
1068
  meta = f.meta
996
1069
  for region, region_col in regions.items():
997
- logger.debug('- {}: {}'.format(region_col, region))
1070
+ logger.debug("- {}: {}".format(region_col, region))
998
1071
  # pylint: disable=no-member
999
1072
  gids = f.region_gids(region, region_col=region_col)
1000
- logger.debug('- Resource gids:\n{}'.format(gids))
1073
+ logger.debug("- Resource gids:\n{}".format(gids))
1001
1074
  if points:
1002
1075
  duplicates = np.intersect1d(gids, points).tolist()
1003
1076
  if duplicates:
1004
- msg = ('reV Cannot currently handle duplicate '
1005
- 'Resource gids! The given regions containg the '
1006
- 'same gids:\n{}'.format(duplicates))
1077
+ msg = (
1078
+ "reV Cannot currently handle duplicate "
1079
+ "Resource gids! The given regions containg the "
1080
+ "same gids:\n{}".format(duplicates)
1081
+ )
1007
1082
  logger.error(msg)
1008
1083
  raise RuntimeError(msg)
1009
1084
 
1010
1085
  points.extend(gids.tolist())
1011
1086
 
1012
- pp = cls(points, sam_configs, tech=tech, res_file=res_file,
1013
- curtailment=curtailment)
1087
+ pp = cls(
1088
+ points,
1089
+ sam_configs,
1090
+ tech=tech,
1091
+ res_file=res_file,
1092
+ curtailment=curtailment,
1093
+ )
1014
1094
 
1015
1095
  meta = meta.loc[pp.sites]
1016
1096
  cols = list(set(regions.values()))