! ch13_ant_colony.f95 ! Companion code for Chapter 13: Optimization and Data Fitting. ! ! Ant colony optimization for the symmetric traveling-salesman problem. ! Find a short closed route visiting every city once. Edit coordinates and ! search settings in the driver. Distances use the coordinate units. ! Repeat with different seeds; global optimality is not guaranteed. ! Console output only. Uses double precision and Fortran 95 syntax. ! ! Compile: ! gfortran -std=f95 -Wall -Wextra -O2 ch13_ant_colony.f95 -o ch13_ant_colony ! Run: ./ch13_ant_colony (Windows: ch13_ant_colony.exe) module ant_colony_methods implicit none private integer, parameter :: dp=selected_real_kind(15,307) public :: dp, initialize_random, ant_colony contains subroutine initialize_random(seed) integer, intent(in) :: seed integer, allocatable :: seeds(:) integer :: n,i call random_seed(size=n) allocate(seeds(n)) do i=1,n seeds(i)=1+modulo(modulo(seed,1000000)+37*modulo(i,1000000),1000000) end do call random_seed(put=seeds) deallocate(seeds) ! Repeatable on the same compiler/runtime; sequences depend on compiler. end subroutine initialize_random subroutine ant_colony(D,M,K,alpha,beta,rho,Q,tau0,elite_weight,best_tour,best_length) real(dp), intent(in) :: D(:,:),alpha,beta,rho,Q,tau0,elite_weight integer, intent(in) :: M,K integer, intent(out) :: best_tour(:) real(dp), intent(out) :: best_length real(dp) :: tau(size(D,1),size(D,1)),lengths(M) real(dp) :: logw(size(D,1)),weights(size(D,1)),u,total,cumulative integer :: tours(M,size(D,1)),available(size(D,1)),rotated(size(D,1)) integer :: n,i,j,ant,iteration,step,current,nav,choice,idx(1),first logical :: visited(size(D,1)) n=size(D,1) if (n<3 .or. size(D,2)/=n .or. size(best_tour)/=n) stop 'Invalid distance-matrix dimensions.' if (.not. all(finite_value(D))) stop 'Distances must be finite.' do i=1,n if (D(i,i)/=0.0_dp) stop 'Distance matrix must have zero diagonal.' do j=i+1,n if (D(i,j)<=0.0_dp .or. D(i,j)/=D(j,i)) stop 'Require positive symmetric edge distances.' end do end do if (M<1 .or. K<1) stop 'Ant and iteration counts must be positive.' if (.not. finite_value(alpha) .or. alpha<0.0_dp) stop 'Invalid pheromone exponent.' if (.not. finite_value(beta) .or. beta<0.0_dp) stop 'Invalid distance exponent.' if (.not. finite_value(rho) .or. rho<=0.0_dp .or. rho>=1.0_dp) stop 'Invalid evaporation rate.' if (.not. finite_value(Q) .or. Q<=0.0_dp) stop 'Invalid deposit scale.' if (.not. finite_value(tau0) .or. tau0<=0.0_dp) stop 'Invalid initial pheromone.' if (.not. finite_value(elite_weight) .or. elite_weight<0.0_dp) stop 'Invalid elite weight.' tau=tau0 best_length=huge(1.0_dp) best_tour=0 do iteration=1,K do ant=1,M visited=.false. call random_number(u) tours(ant,1)=1+int(n*u) visited(tours(ant,1))=.true. do step=2,n current=tours(ant,step-1) nav=0 do j=1,n if (.not. visited(j)) then nav=nav+1 available(nav)=j logw(nav)=alpha*log(tau(current,j))-beta*log(D(current,j)) end if end do ! Relative weights proportional to pheromone^alpha / distance^beta. ! Subtracting the largest log weight avoids underflow of all weights. if (.not. all(finite_value(logw(1:nav)))) stop 'Rescale pheromone or distance settings.' weights(1:nav)=exp(logw(1:nav)-maxval(logw(1:nav))) total=sum(weights(1:nav)) call random_number(u) cumulative=0.0_dp choice=nav ! Rounding-safe fallback to the last available city. do j=1,nav cumulative=cumulative+weights(j)/total if (u