@@ -328,6 +328,9 @@ def __init__(
328
328
# Delete stored x and y to avoid confusion
329
329
del self .x , self .y
330
330
331
+ # Set up to run in normal mode
332
+ self .debug = False
333
+
331
334
def describe (self ):
332
335
print ("Random Layout Optimization" )
333
336
print (f"Number of turbines to optimize = { self .N_turbines } " )
@@ -491,74 +494,78 @@ def _get_initial_and_final_locs(self):
491
494
y_opt = self .y_opt
492
495
return x_initial , y_initial , x_opt , y_opt
493
496
494
-
495
- # Public methods
496
-
497
- def optimize (self ):
497
+ def _initialize_optimization (self ):
498
498
"""
499
- Perform the optimization
499
+ Set up logs etc
500
500
"""
501
501
print (f'Optimizing using { self .n_individuals } individuals.' )
502
- opt_start_time = timerpc ()
503
- opt_stop_time = opt_start_time + self .total_optimization_seconds
504
- sim_time = 0
502
+ self ._opt_start_time = timerpc ()
503
+ self ._opt_stop_time = self ._opt_start_time + self .total_optimization_seconds
505
504
506
505
self .objective_candidate_log = [self .objective_candidate .copy ()]
507
506
self .num_objective_calls_log = []
508
507
self ._num_objective_calls = [0 ]* self .n_individuals
509
508
510
- while timerpc () < opt_stop_time :
511
-
512
- # Set random seed for the main loop
513
- if self .random_seed is None :
514
- multi_random_seeds = [None ]* self .n_individuals
515
- else :
516
- multi_random_seeds = [55 + self .iteration_step + i
517
- for i in range (self .n_individuals )]
518
- # 55 is just an arbitrary choice to ensure different random seeds
519
- # to the initialization code
520
-
521
- # Update the optimization time
522
- sim_time = timerpc () - opt_start_time
523
- print (f'Optimization time: { sim_time :.1f} s / { self .total_optimization_seconds :.1f} s' )
524
-
525
-
526
- # Generate the multiargs for parallel execution of single individual optimization
527
- multiargs = [
528
- (self .seconds_per_iteration ,
529
- self .objective_candidate [i ],
530
- self .x_candidate [i , :],
531
- self .y_candidate [i , :],
532
- self .fmodel_dict ,
533
- self .fmodel .wind_data ,
534
- self .min_dist ,
535
- self ._boundary_polygon ,
536
- self .distance_pmf ,
537
- self .enable_geometric_yaw ,
538
- multi_random_seeds [i ],
539
- self .use_value
540
- )
541
- for i in range (self .n_individuals )
542
- ]
509
+ def _run_optimization_generation (self ):
510
+ """
511
+ Run a generation of the outer genetic algorithm
512
+ """
513
+ # Set random seed for the main loop
514
+ if self .random_seed is None :
515
+ multi_random_seeds = [None ]* self .n_individuals
516
+ else :
517
+ multi_random_seeds = [55 + self .iteration_step + i
518
+ for i in range (self .n_individuals )]
519
+ # 55 is just an arbitrary choice to ensure different random seeds
520
+ # to the initialization code
543
521
544
- # Run the single individual optimization in parallel
545
- if self ._PoolExecutor : # Parallelized
546
- with self ._PoolExecutor (self .max_workers ) as p :
547
- out = p .starmap (_single_individual_opt , multiargs )
548
- else : # Parallelization not activated
549
- out = [_single_individual_opt (* multiargs [0 ])]
522
+ # Update the optimization time
523
+ sim_time = timerpc () - self ._opt_start_time
524
+ print (f'Optimization time: { sim_time :.1f} s / { self .total_optimization_seconds :.1f} s' )
550
525
551
- # Unpack the results
552
- for i in range (self .n_individuals ):
553
- self .objective_candidate [i ] = out [i ][0 ]
554
- self .x_candidate [i , :] = out [i ][1 ]
555
- self .y_candidate [i , :] = out [i ][2 ]
556
- self ._num_objective_calls [i ] = out [i ][3 ]
557
- self .objective_candidate_log .append (self .objective_candidate )
558
- self .num_objective_calls_log .append (self ._num_objective_calls )
559
526
560
- # Evaluate the individuals for this step
561
- self ._evaluate_opt_step ()
527
+ # Generate the multiargs for parallel execution of single individual optimization
528
+ multiargs = [
529
+ (self .seconds_per_iteration ,
530
+ self .objective_candidate [i ],
531
+ self .x_candidate [i , :],
532
+ self .y_candidate [i , :],
533
+ self .fmodel_dict ,
534
+ self .fmodel .wind_data ,
535
+ self .min_dist ,
536
+ self ._boundary_polygon ,
537
+ self .distance_pmf ,
538
+ self .enable_geometric_yaw ,
539
+ multi_random_seeds [i ],
540
+ self .use_value ,
541
+ self .debug
542
+ )
543
+ for i in range (self .n_individuals )
544
+ ]
545
+
546
+ # Run the single individual optimization in parallel
547
+ if self ._PoolExecutor : # Parallelized
548
+ with self ._PoolExecutor (self .max_workers ) as p :
549
+ out = p .starmap (_single_individual_opt , multiargs )
550
+ else : # Parallelization not activated
551
+ out = [_single_individual_opt (* multiargs [0 ])]
552
+
553
+ # Unpack the results
554
+ for i in range (self .n_individuals ):
555
+ self .objective_candidate [i ] = out [i ][0 ]
556
+ self .x_candidate [i , :] = out [i ][1 ]
557
+ self .y_candidate [i , :] = out [i ][2 ]
558
+ self ._num_objective_calls [i ] = out [i ][3 ]
559
+ self .objective_candidate_log .append (self .objective_candidate )
560
+ self .num_objective_calls_log .append (self ._num_objective_calls )
561
+
562
+ # Evaluate the individuals for this step
563
+ self ._evaluate_opt_step ()
564
+
565
+ def _finalize_optimization (self ):
566
+ """
567
+ Package and print final results.
568
+ """
562
569
563
570
# Finalize the result
564
571
self .objective_final = self .objective_candidate [0 ]
@@ -572,8 +579,42 @@ def optimize(self):
572
579
f" { self ._obj_unit } ({ increase :+.2f} %)"
573
580
)
574
581
582
+ def _test_optimize (self ):
583
+ """
584
+ Perform a fixed number of iterations with a single worker for
585
+ debugging and testing purposes.
586
+ """
587
+ # Set up a minimal problem to run on a single worker
588
+ print ("Running test optimization on a single worker." )
589
+ self ._PoolExecutor = None
590
+ self .max_workers = None
591
+ self .n_individuals = 1
592
+ self .debug = True
593
+
594
+ self ._initialize_optimization ()
595
+
596
+ # Run 2 generations
597
+ for _ in range (2 ):
598
+ self ._run_optimization_generation ()
599
+
600
+ self ._finalize_optimization ()
601
+
575
602
return self .objective_final , self .x_opt , self .y_opt
576
603
604
+ # Public methods
605
+ def optimize (self ):
606
+ """
607
+ Perform the optimization
608
+ """
609
+ self ._initialize_optimization ()
610
+
611
+ # Run generations until the overall stop time
612
+ while timerpc () < self ._opt_stop_time :
613
+ self ._run_optimization_generation ()
614
+
615
+ self ._finalize_optimization ()
616
+
617
+ return self .objective_final , self .x_opt , self .y_opt
577
618
578
619
# Helpful visualizations
579
620
def plot_distance_pmf (self , ax = None ):
@@ -605,7 +646,8 @@ def _single_individual_opt(
605
646
dist_pmf ,
606
647
enable_geometric_yaw ,
607
648
s ,
608
- use_value
649
+ use_value ,
650
+ debug
609
651
):
610
652
# Set random seed
611
653
np .random .seed (s )
@@ -639,69 +681,80 @@ def _single_individual_opt(
639
681
# disabled.
640
682
use_momentum = False
641
683
684
+ # Special handling for debug mode
685
+ if debug :
686
+ debug_iterations = 100
687
+ stop_time = np .inf
688
+ dd = 0
689
+
642
690
# Loop as long as we've not hit the stop time
643
691
while timerpc () < stop_time :
644
692
645
- if not use_momentum :
646
- get_new_point = True
647
-
648
- if get_new_point : #If the last test wasn't successful
649
-
650
- # Randomly select a turbine to nudge
651
- tr = np .random .randint (0 ,num_turbines )
652
-
653
- # Randomly select a direction to nudge in (uniform direction)
654
- rand_dir = np .random .uniform (low = 0.0 , high = 2 * np .pi )
655
-
656
- # Randomly select a distance to travel according to pmf
657
- rand_dist = np .random .choice (dist_pmf ["d" ], p = dist_pmf ["p" ])
658
-
659
- # Get a new test point
660
- test_x = layout_x [tr ] + np .cos (rand_dir ) * rand_dist
661
- test_y = layout_y [tr ] + np .sin (rand_dir ) * rand_dist
662
-
663
- # In bounds?
664
- if not test_point_in_bounds (test_x , test_y , poly_outer ):
665
- get_new_point = True
666
- continue
667
-
668
- # Make a new layout
669
- original_x = layout_x [tr ]
670
- original_y = layout_y [tr ]
671
- layout_x [tr ] = test_x
672
- layout_y [tr ] = test_y
673
-
674
- # Acceptable distances?
675
- if not test_min_dist (layout_x , layout_y ,min_dist ):
676
- # Revert and continue
677
- layout_x [tr ] = original_x
678
- layout_y [tr ] = original_y
679
- get_new_point = True
680
- continue
681
-
682
- # Does it improve the objective?
683
- if enable_geometric_yaw : # Select appropriate yaw angles
684
- yaw_opt .fmodel_subset .set (layout_x = layout_x , layout_y = layout_y )
685
- df_opt = yaw_opt .optimize ()
686
- yaw_angles = np .vstack (df_opt ['yaw_angles_opt' ])
687
-
688
- num_objective_calls += 1
689
- test_objective = _get_objective (layout_x , layout_y , fmodel_ , yaw_angles , use_value )
690
-
691
- if test_objective > current_objective :
692
- # Accept the change
693
- current_objective = test_objective
694
-
695
- # If not a random point this cycle and it did improve things
696
- # try not getting a new point
697
- # Feature is currently disabled by use_momentum flag
698
- get_new_point = False
699
-
700
- else :
701
- # Revert the change
702
- layout_x [tr ] = original_x
703
- layout_y [tr ] = original_y
704
- get_new_point = True
693
+ if debug and dd >= debug_iterations :
694
+ break
695
+ elif debug :
696
+ dd += 1
697
+
698
+ if not use_momentum :
699
+ get_new_point = True
700
+
701
+ if get_new_point : #If the last test wasn't successful
702
+
703
+ # Randomly select a turbine to nudge
704
+ tr = np .random .randint (0 ,num_turbines )
705
+
706
+ # Randomly select a direction to nudge in (uniform direction)
707
+ rand_dir = np .random .uniform (low = 0.0 , high = 2 * np .pi )
708
+
709
+ # Randomly select a distance to travel according to pmf
710
+ rand_dist = np .random .choice (dist_pmf ["d" ], p = dist_pmf ["p" ])
711
+
712
+ # Get a new test point
713
+ test_x = layout_x [tr ] + np .cos (rand_dir ) * rand_dist
714
+ test_y = layout_y [tr ] + np .sin (rand_dir ) * rand_dist
715
+
716
+ # In bounds?
717
+ if not test_point_in_bounds (test_x , test_y , poly_outer ):
718
+ get_new_point = True
719
+ continue
720
+
721
+ # Make a new layout
722
+ original_x = layout_x [tr ]
723
+ original_y = layout_y [tr ]
724
+ layout_x [tr ] = test_x
725
+ layout_y [tr ] = test_y
726
+
727
+ # Acceptable distances?
728
+ if not test_min_dist (layout_x , layout_y ,min_dist ):
729
+ # Revert and continue
730
+ layout_x [tr ] = original_x
731
+ layout_y [tr ] = original_y
732
+ get_new_point = True
733
+ continue
734
+
735
+ # Does it improve the objective?
736
+ if enable_geometric_yaw : # Select appropriate yaw angles
737
+ yaw_opt .fmodel_subset .set (layout_x = layout_x , layout_y = layout_y )
738
+ df_opt = yaw_opt .optimize ()
739
+ yaw_angles = np .vstack (df_opt ['yaw_angles_opt' ])
740
+
741
+ num_objective_calls += 1
742
+ test_objective = _get_objective (layout_x , layout_y , fmodel_ , yaw_angles , use_value )
743
+
744
+ if test_objective > current_objective :
745
+ # Accept the change
746
+ current_objective = test_objective
747
+
748
+ # If not a random point this cycle and it did improve things
749
+ # try not getting a new point
750
+ # Feature is currently disabled by use_momentum flag
751
+ get_new_point = False
752
+
753
+ else :
754
+ # Revert the change
755
+ layout_x [tr ] = original_x
756
+ layout_y [tr ] = original_y
757
+ get_new_point = True
705
758
706
759
# Return the best result from this individual
707
760
return current_objective , layout_x , layout_y , num_objective_calls
0 commit comments