From e8c63e7b4bd3e21391225f3e735aaf7a6b5baa89 Mon Sep 17 00:00:00 2001 From: David Sagan Date: Mon, 23 Oct 2023 16:15:26 -0400 Subject: [PATCH] Start devel of ASCII::4 beam storage format. --- bmad/code/lat_sanity_check.f90 | 9 ++ bmad/doc/beam-init.tex | 12 +- bmad/modules/bmad_routine_interface.f90 | 3 +- bmad/modules/bmad_struct.f90 | 59 ++++++--- bmad/multiparticle/beam_file_io.f90 | 124 ++++++++++++++---- bmad/multiparticle/reallocate_bunch.f90 | 28 ++-- .../long_term_tracking_mpi.f90 | 22 ++-- bsim/modules/lt_tracking_mod.f90 | 4 +- bsim/tune_scan/tune_scan_mpi.f90 | 2 +- regression_tests/coord_test/coord_test.bmad | 4 +- 10 files changed, 196 insertions(+), 71 deletions(-) diff --git a/bmad/code/lat_sanity_check.f90 b/bmad/code/lat_sanity_check.f90 index 9494d0b218..a7ba4809be 100644 --- a/bmad/code/lat_sanity_check.f90 +++ b/bmad/code/lat_sanity_check.f90 @@ -267,6 +267,15 @@ subroutine lat_sanity_check (lat, err_flag) endif endif + if (has_attribute(ele, 'X_PITCH_TOT')) then + if (abs(ele%value(x_pitch_tot$)) + abs(ele%value(y_pitch_tot$)) > 0.5*pi) then + call out_io (s_fatal$, r_name, & + 'HAVING |X_PITCH_TOT| + |Y_PITCH_TOT| > PI/2 FOR AN ELEMENT (' // trim(ele%name) // ') DOES NOT MAKE SENSE ', & + 'SINCE PARTICLES WILL NOT BE MOVING IN THE RIGHT DIRECTION WITHIN THE ELEMENT.') + err_flag = .true. + endif + endif + ! Do not check the extra elements temporarily inserted by bmad_parser2. select case (ele%key) diff --git a/bmad/doc/beam-init.tex b/bmad/doc/beam-init.tex index 3012c2eb09..79a0324dfa 100644 --- a/bmad/doc/beam-init.tex +++ b/bmad/doc/beam-init.tex @@ -322,7 +322,7 @@ \section{File Based Beam Initialization} [bunch loop: ib = 1 to n_bunch] BEGIN_BUNCH ! Marker to mark the beginning of a bunch specification block. ! Species of particle - ! Charge of bunch. 0 => Use . + ! Total charge of bunch (alive + dead). 0 => Use . ! z position at center of bunch. ! t position at center of bunch. [particle loop: Stop when END_BUNCH marker found] @@ -338,7 +338,7 @@ \section{File Based Beam Initialization} 25000 ! n_particle BEGIN_BUNCH POSITRON - 3.2E-9 ! bunch_charge + 3.2E-9 ! charge_tot 0.0 ! z_center 0.0 ! t_center -6.5E-3 9.6E-3 -1.9E-2 8.8E-3 2.2E-2 -2.4E-2 1.2E-13 1 1.0 0.0 0.0 @@ -366,7 +366,7 @@ \section{File Based Beam Initialization} After this, there are \vn{} blocks of data, one for each bunch. Each one of these blocks starts with a \vn{BEGIN_BUNCH} line to mark the beginning of the block and ends with a \vn{END_BUNCH} marker line. In between, the first four lines give the \vn{} name, -\vn{}, \vn{}, and \vn{} values. The \vn{} name may +\vn{}, \vn{}, and \vn{} values. The \vn{} name may be one of: \begin{example} positron ! default @@ -381,7 +381,7 @@ \section{File Based Beam Initialization} The lines following the \vn{} line specify particle coordinates. One line for each particle. Only the first six numbers, which are the phase space coordinates, need to be specified for each particle. If the \vn{} column is not present, or is zero, it defaults to -\vn{/}. +\vn{/}. The \vn{} parameter indicates whether a particle is alive or dead. Values are \begin{example} @@ -394,9 +394,9 @@ \section{File Based Beam Initialization} The particle spin is specified by $x$, $y$ and $z$ components. -Each particle has an associated \vn{}. If \vn{} is set to a non-zero +Each particle has an associated \vn{}. If \vn{} is set to a non-zero value, the charge of all the particles will be scaled by a factor to make the total macro charge -equal to \vn{}. The macro charge is ignored in tracking. The charge of the particle +equal to \vn{}. The macro charge is ignored in tracking. The charge of the particle used in tracking is the charge as calculated for the particle species. On the other hand, the macro charge is used to calculate such things as the total charge in a particular region or the field produced by a particle. That is, the macro charge acts as a weighting factor for a particle when the diff --git a/bmad/modules/bmad_routine_interface.f90 b/bmad/modules/bmad_routine_interface.f90 index 237782e031..8e03e22c63 100644 --- a/bmad/modules/bmad_routine_interface.f90 +++ b/bmad/modules/bmad_routine_interface.f90 @@ -1875,11 +1875,12 @@ subroutine reallocate_beam (beam, n_bunch, n_particle, save) logical, optional :: save end subroutine -subroutine reallocate_bunch (bunch, n_particle) +subroutine reallocate_bunch (bunch, n_particle, save) import implicit none type (bunch_struct) bunch integer n_particle + logical, optional :: save end subroutine function relative_mode_flip (ele1, ele2) diff --git a/bmad/modules/bmad_struct.f90 b/bmad/modules/bmad_struct.f90 index db6e99b873..c22450c39d 100644 --- a/bmad/modules/bmad_struct.f90 +++ b/bmad/modules/bmad_struct.f90 @@ -495,8 +495,13 @@ module bmad_struct integer, parameter :: lost$ = 2 integer, parameter :: lost_neg_x_aperture$ = 3, lost_pos_x_aperture$ = 4 integer, parameter :: lost_neg_y_aperture$ = 5, lost_pos_y_aperture$ = 6 -integer, parameter :: lost_pz_aperture$ = 7 ! Particle "turned around" when not tracking with time_runge_kutta. -integer, parameter :: lost_z_aperture$ = 9 +integer, parameter :: lost_z_aperture$ = 7 +integer, parameter :: lost_pz_aperture$ = 8 ! Particle "turned around" when not tracking with time_runge_kutta. + +! State_name is not the full list of coord%state possible settings! Missing is not_set$ +character(12), parameter ::state_name(0:8) = [character(12):: 'Pre_Born', 'Alive', 'Lost', 'Hit_Neg_X', & + 'Hit_Pos_X', 'Hit_Neg_Y', 'Hit_Pos_Y', 'Hit_Pz_Aper', 'Hit_Z_Aper'] + real(rp), parameter :: vec0$(6) = 0 @@ -2414,39 +2419,59 @@ end function next_in_branch !------------------------------------------------------------------------------------------------------- !------------------------------------------------------------------------------------------------------- !+ -! Function coord_state_name (coord_state) result (state_str) +! Function coord_state_name (coord_state, one_word) result (state_str) ! ! Routine to return the string representation of a coord%state state. ! ! Input: ! coord_state -- integer: coord%state value +! one_word -- logical, optional. Default False. If True then output string will be one word (no blanks). +! ! ! Output: ! state_str -- character(16): String representation. !- -function coord_state_name (coord_state) result (state_str) +function coord_state_name (coord_state, one_word) result (state_str) implicit none integer coord_state character(16) state_str +logical, optional :: one_word ! -select case (coord_state) -case (pre_born$); state_str = 'Pre_Born' -case (alive$); state_str = 'Alive' -case (lost$); state_str = 'Lost' -case (not_set$); state_str = 'Not_Set' -case (lost_neg_x_aperture$); state_str = 'Hit -X Side' -case (lost_pos_x_aperture$); state_str = 'Hit +X Side' -case (lost_neg_y_aperture$); state_str = 'Hit -Y Side' -case (lost_pos_y_aperture$); state_str = 'Hit +Y Side' -case (lost_pz_aperture$); state_str = 'Hit Energy Aper' -case (lost_z_aperture$); state_str = 'Hit Z Side' -case default; state_str = 'UNKNOWN!' -end select +if (logic_option(.false., one_word)) then + select case (coord_state) + case (not_set$); state_str = 'Not_Set' + case (pre_born$); state_str = 'Pre_Born' + case (alive$); state_str = 'Alive' + case (lost$); state_str = 'Lost' + case (lost_neg_x_aperture$); state_str = 'Hit_Neg_X' + case (lost_pos_x_aperture$); state_str = 'Hit_Pos_X' + case (lost_neg_y_aperture$); state_str = 'Hit_Neg_Y' + case (lost_pos_y_aperture$); state_str = 'Hit_Pos_Y' + case (lost_pz_aperture$); state_str = 'Hit_Pz_Aper' + case (lost_z_aperture$); state_str = 'Hit_Z_Aper' + case default; state_str = 'UNKNOWN!' + end select + +else + select case (coord_state) + case (not_set$); state_str = 'Not_Set' + case (pre_born$); state_str = 'Pre_Born' + case (alive$); state_str = 'Alive' + case (lost$); state_str = 'Lost' + case (lost_neg_x_aperture$); state_str = 'Hit -X Side' + case (lost_pos_x_aperture$); state_str = 'Hit +X Side' + case (lost_neg_y_aperture$); state_str = 'Hit -Y Side' + case (lost_pos_y_aperture$); state_str = 'Hit +Y Side' + case (lost_z_aperture$); state_str = 'Hit Z Side' + case (lost_pz_aperture$); state_str = 'Hit Energy Aper' + case default; state_str = 'UNKNOWN!' + end select +endif end function coord_state_name diff --git a/bmad/multiparticle/beam_file_io.f90 b/bmad/multiparticle/beam_file_io.f90 index 24215505a7..955170de79 100644 --- a/bmad/multiparticle/beam_file_io.f90 +++ b/bmad/multiparticle/beam_file_io.f90 @@ -72,7 +72,7 @@ subroutine write_beam_file (file_name, beam, new_file, file_format, lat) bunch => beam%bunch(ib) write (iu, *) 'BEGIN_BUNCH' write (iu, *) ' ', trim(species_name(bunch%particle(1)%species)) - write (iu, *) bunch%charge_tot, ' ! bunch_charge_tot' + write (iu, *) bunch%charge_tot, ' ! charge_tot' write (iu, *) bunch%z_center, ' ! z_center' write (iu, *) bunch%t_center, ' ! t_center' do ip = 1, size(bunch%particle) @@ -97,7 +97,7 @@ end subroutine write_beam_file ! If non_zero, the following components of beam_init are used to rescale the beam: ! %n_bunch ! %n_particle -! %bunch_charge +! %charge_tot ! ! If the beam file has '.h5' or '.hdf5' suffix then the file is taken to be an HDF5 file. ! Otherwise the file is assumed to be ASCII. @@ -128,7 +128,7 @@ subroutine read_beam_file (file_name, beam, beam_init, err_flag, ele, print_mom_ integer i, j, k, n, np, ix, iu, ix_word, ios, ix_ele, species integer n_bunch, n_particle, n_particle_lines, ix_lost -real(rp) vec(6), sum_charge, bunch_charge +real(rp) vec(6), sum_charge, charge_tot complex(rp) spinor(2) character(*) file_name @@ -554,7 +554,7 @@ end subroutine read_beam_file ! If non_zero, the following components of beam_init are used to rescale the beam: ! %n_bunch ! %n_particle -! %bunch_charge +! %charge_tot ! ! If the beam file has '.h5' or '.hdf5' suffix then the file is taken to be an HDF5 file. ! Otherwise the file is assumed to be ASCII. @@ -579,59 +579,133 @@ subroutine read_beam_ascii4 (iu, file_name, beam, beam_init, err_flag, ele, prin type (ele_struct), optional :: ele type (bunch_struct), pointer :: bunch type (coord_struct), pointer :: p +type (coord_struct) p0 -real(rp) bunch_charge, z_center, t_center +real(rp) charge_tot, z_center, t_center integer iu, ip, ix, ios, n_particle, n_bunch character(*) file_name character(*), parameter :: r_name = 'read_beam_ascii4' -character(200) cols, line +character(200) cols, line, str -logical err_flag +logical err_flag, err logical, optional :: print_mom_shift_warning, conserve_momentum ! - -cols = '' n_bunch = 0 +err_flag = .true. +err = .false. + +! bunch loop do + n_bunch = n_bunch + 1 + call reallocate_beam(beam, n_bunch, save = .true.) + call reallocate_bunch(beam%bunch(n_bunch), 1000) + bunch => beam%bunch(n_bunch) + + cols = '' + p0 = coord_struct() + + ! Read bunch header do read (iu, '(a)', iostat = ios) line if (line(1:1) /= '#') exit call string_trim(line(2:), line, ix) select case (line(:ix)) - case ('n_bunch'); n_bunch = nint(read_param(line)) - case ('n_particle'); n_particle = nint(read_param(line)) - case ('bunch_charge'); bunch_charge = read_param(line) - case ('z_center'); z_center = read_param(line) - case ('t_center'); t_center = read_param(line) - case ('columns'); cols = read_string(line) + case ('charge_tot'); bunch%charge_tot = read_param(line) + case ('z_center'); bunch%z_center = read_param(line) + case ('t_center'); bunch%t_center = read_param(line) + + case ('columns'); cols = read_string(line) + + case ('species') + str = read_string(line) + p0%species = species_id(str, positron$) + + case ('location') + str = read_string(line) + call match_word(str, location_name(1:), p0%location) + if (p0%location <= 0) then + call out_io (s_error$, r_name, 'LOCATION NAME NOT RECOGNIZED: ' // str) + return + endif + + case ('state') + str = read_string(line) + call match_word(str, state_name, p0%state) + if (p0%state <= 0) then + call out_io (s_error$, r_name, 'PARTICLE STATE NAME NOT RECOGNIZED: ' // str) + return + endif + p0%state = p0%state - 1 ! Since state_name is zero based. + + case ('s_position'); p0%s = read_param(line) + case ('time'); p0%t = read_param(line) + case ('p0c'); p0%p0c = read_param(line) + case ('charge'); p0%charge = read_param(line) + case ('time_dir'); p0%time_dir = nint(read_param(line)) + case ('direction'); p0%direction = nint(read_param(line)) + case ('ix_ele'); p0%ix_ele = nint(read_param(line)) + case ('ix_branch'); p0%ix_branch = nint(read_param(line)) end select enddo - call reallocate_beam(beam, n_bunch, n_particle) + ! Read particle info + backspace(unit = iu) - do ip = 1, n_particle - p => beam%bunch(ip)%particle(ip) - read (iu, *, iostat = ios, err = 9000) p%vec, p%p0c - enddo + ip = 0 + do + read (iu, '(a)', iostat = ios, end = 8000) line + if (line == '') cycle + if (line(1:1) == '#') exit + if (ios /= 0) then + call out_io (s_error$, r_name, 'CANNOT READ BEAM FILE: ' // file_name) + return + endif - n_bunch = n_bunch + 1 - !!call reallocate_bunch( + ip = ip + 1 + if (ip > size(bunch%particle)) call reallocate_bunch (bunch, 2*ip, .true.) + p => bunch%particle(ip) + p = p0 -enddo + do + call string_trim(cols, cols, ix) + call read_particle_params(p, cols(1:ix), err); if (err) return + cols = cols(ix+1:) + if (cols == '') exit + enddo + enddo +enddo -9000 continue -stop +8000 continue +call reallocate_bunch(bunch, ip, .true.) +err_flag = .false. !--------------------------------------------------------------------------------------------------- contains +subroutine read_particle_params(p, col, err) +type (coord_struct) p +character(*) col +logical err + +! + +select case (col) + +case default +end select + +end subroutine read_particle_params + +!--------------------------------------------------------------------------------------------------- +! contains + function read_param(line) result (param) character(*) line real(rp) param diff --git a/bmad/multiparticle/reallocate_bunch.f90 b/bmad/multiparticle/reallocate_bunch.f90 index 21349f7f9e..c97ccb6bd2 100644 --- a/bmad/multiparticle/reallocate_bunch.f90 +++ b/bmad/multiparticle/reallocate_bunch.f90 @@ -1,34 +1,46 @@ !+ -! Subroutine reallocate_bunch (bunch, n_particle) +! Subroutine reallocate_bunch (bunch, n_particle, save) ! ! Subroutine to reallocate particles within a bunch_struct. ! ! Input: ! n_particle -- Integer: Number of particles. Must be non-negative. +! save -- logical, optional: If present and True then save the old bunch info. ! ! Output: -! bunch -- bunch_struct: Allocated bunch_struct structure. +! bunch -- bunch_struct: Allocated bunch_struct structure. !- -subroutine reallocate_bunch (bunch, n_particle) +subroutine reallocate_bunch (bunch, n_particle, save) use equal_mod, except_dummy => reallocate_bunch implicit none type (bunch_struct) bunch +type (coord_struct), allocatable :: particle(:) -integer i, n_particle +integer i, n_particle, n +logical, optional :: save ! Deallocate if needed if (allocated(bunch%particle)) then - if (size(bunch%particle) /= n_particle) deallocate (bunch%particle, bunch%ix_z) + if (size(bunch%particle) == n_particle) return + call move_alloc(bunch%particle, particle) endif -if (.not. allocated(bunch%particle)) then - allocate (bunch%particle(n_particle), bunch%ix_z(n_particle)) - bunch%ix_z = 0 +if (allocated(bunch%ix_z)) deallocate(bunch%ix_z) +allocate (bunch%particle(n_particle), bunch%ix_z(n_particle)) +bunch%ix_z = 0 + +if (logic_option(.false., save) .and. allocated(particle)) then + n = min(n_particle, size(particle)) + bunch%particle(1:n) = particle(1:n) +endif + +if (allocated(particle)) then + deallocate(particle) endif end subroutine reallocate_bunch diff --git a/bsim/long_term_tracking/long_term_tracking_mpi.f90 b/bsim/long_term_tracking/long_term_tracking_mpi.f90 index c9e5d9a761..52e86a7baa 100644 --- a/bsim/long_term_tracking/long_term_tracking_mpi.f90 +++ b/bsim/long_term_tracking/long_term_tracking_mpi.f90 @@ -11,12 +11,13 @@ program long_term_tracking type (beam_struct), target :: beam, beam2 type (bunch_struct), pointer :: bunch type (coord_struct) orb +type (coord_struct), pointer :: particle real(rp) particles_per_thread, now_time integer num_slaves, slave_rank, stat(MPI_STATUS_SIZE) integer i, n, nn, nb, ib, ix, ierr, rc, leng, bd_size, storage_size, dat_size, ix_stage -integer ip0, ip1, nt0, nt1, mpi_n_proc, iu, ios, n_particle, i_turn, n_part_tot, seed +integer ip, ip0, ip1, nt0, nt1, mpi_n_proc, iu, ios, n_particle, i_turn, n_part_tot, seed integer, allocatable :: ix_stop_turn(:), ixp_slave(:) integer, parameter :: base_tag$ = 1000 @@ -129,7 +130,7 @@ program long_term_tracking case ('INDIVIDUAL') if (.not. ltt_com%using_mpi) then print '(a, i0)', 'Number of threads is one! (Need to use mpirun or mpiexec if on a single machine.)' - call ltt_run_individual_mode(lttp, ltt_com, lttp%ix_turn_start, lttp%ix_turn_stop, beam) + call ltt_run_individual_mode(lttp, ltt_com) stop endif @@ -164,22 +165,27 @@ program long_term_tracking if (all(slave_working)) then slave_rank = MPI_ANY_SOURCE call ltt_print_mpi_info (lttp, ltt_com, 'Master: Waiting for data.') - call mpi_recv (orbit, dat_size, MPI_BYTE, slave_rank, base_tag$+3, MPI_COMM_WORLD, stat, ierr) + call mpi_recv (orb, dat_size, MPI_BYTE, slave_rank, base_tag$+3, MPI_COMM_WORLD, stat, ierr) if (ierr /= MPI_SUCCESS) call ltt_print_mpi_info (lttp, ltt_com, 'MPI ERROR!', .true.) slave_rank = stat(MPI_SOURCE) ! Slave rank nn = ixp_slave(slave_rank) - beam%bunch(ib)%particle(nn) = orbit + beam%bunch(ib)%particle(nn) = orb slave_working(slave_rank) = .false. call ltt_print_mpi_info (lttp, ltt_com, 'Master: Got data from slave: ' // int_str(slave_rank)) endif - ix = findloc(slave_working, .false.) - call ltt_print_mpi_info (lttp, ltt_com, 'Master: Tell slave ' // int_str(ix)) // ' to be ready to track using Slave') + do ix = 1, num_slaves + if (.not. slave_working(ix)) exit + enddo + + call ltt_print_mpi_info (lttp, ltt_com, 'Master: Tell slave ' // int_str(ix) // ' to be ready to track using Slave') call mpi_send (1, 1, MPI_INTEGER, ix, base_tag$+1, MPI_COMM_WORLD, ierr) if (ierr /= MPI_SUCCESS) call ltt_print_mpi_info (lttp, ltt_com, 'MPI ERROR #2!', .true.) ! Tell slave more tracking needed. + call ltt_print_mpi_info (lttp, ltt_com, 'Master: Starting particle ' // int_str(ip) // ' using Slave: ' // int_str(ix)) call mpi_send (particle, dat_size, MPI_BYTE, ix, base_tag$+2, MPI_COMM_WORLD, ierr) if (ierr /= MPI_SUCCESS) call ltt_print_mpi_info (lttp, ltt_com, 'MPI ERROR #3!', .true.) + ixp_slave(ix) = ip slave_working(ix) = .true. enddo @@ -190,11 +196,11 @@ program long_term_tracking call ltt_print_mpi_info (lttp, ltt_com, 'Master: Finished broadcasting particle runs. Now collecting final data.') do if (all(.not. slave_working)) exit - call mpi_recv (orbit, dat_size, MPI_BYTE, slave_rank, base_tag$+3, MPI_COMM_WORLD, stat, ierr) + call mpi_recv (orb, dat_size, MPI_BYTE, slave_rank, base_tag$+3, MPI_COMM_WORLD, stat, ierr) if (ierr /= MPI_SUCCESS) call ltt_print_mpi_info (lttp, ltt_com, 'MPI ERROR!', .true.) slave_rank = stat(MPI_SOURCE) ! Slave rank nn = ixp_slave(slave_rank) - beam%bunch(ib)%particle(nn) = orbit + beam%bunch(ib)%particle(nn) = orb slave_working(slave_rank) = .false. enddo diff --git a/bsim/modules/lt_tracking_mod.f90 b/bsim/modules/lt_tracking_mod.f90 index 9db9019c92..e5c59b0342 100644 --- a/bsim/modules/lt_tracking_mod.f90 +++ b/bsim/modules/lt_tracking_mod.f90 @@ -982,9 +982,7 @@ subroutine ltt_run_individual_mode (lttp, ltt_com) enddo enddo -if (.not. ltt_com%using_mpi) then - call ltt_write_particle_data (lttp, ltt_com, 0, beam) -endif +call ltt_write_particle_data (lttp, ltt_com, 0, beam) end subroutine diff --git a/bsim/tune_scan/tune_scan_mpi.f90 b/bsim/tune_scan/tune_scan_mpi.f90 index b551961a2a..c3baec012d 100644 --- a/bsim/tune_scan/tune_scan_mpi.f90 +++ b/bsim/tune_scan/tune_scan_mpi.f90 @@ -166,7 +166,7 @@ program tune_scan_mpi ix = ix - ja * num_b * num_z jb = ix / num_z jz = ix - jb * num_z - call ts_track_particle (ts, ts_com, ja, jb, jz, dat_arr(i)) + call ts_track_particle (ts, ts_com, [ja, jb, jz], dat_arr(i)) call ts_print_mpi_info (ts, ts_com, 'Slave: Tracked particle at index: ' // & int_str(ja) // ', ' // int_str(jb) // ', ' // int_str(jz)) enddo diff --git a/regression_tests/coord_test/coord_test.bmad b/regression_tests/coord_test/coord_test.bmad index 067b335eb8..ab64a98d6e 100644 --- a/regression_tests/coord_test/coord_test.bmad +++ b/regression_tests/coord_test/coord_test.bmad @@ -12,9 +12,9 @@ particle_start[spin_y] = 2 particle_start[spin_z] = 3 q: quadrupole, l = 0 -s: sample, x_offset = 1, y_offset = 2, x_pitch = 4, y_pitch = 5, tilt = 6, z_offset = 7, l = 2 +s: sample, x_offset = 1, y_offset = 2, x_pitch = 0.4, y_pitch = 0.5, tilt = 6, z_offset = 7, l = 2 m: mirror, graze_angle = pi/2, & - x_offset = 2, y_offset = 2, x_pitch = 4, y_pitch = 5, tilt = 6, z_offset = 7, x_limit = 0.1, y_limit = 0.1 + x_offset = 2, y_offset = 2, x_pitch = 0.4, y_pitch = 0.5, tilt = 6, z_offset = 7, x_limit = 0.1, y_limit = 0.1 !!m2: mirror, graze_angle = pi/4, tilt = pi/2 m2: mirror, graze_angle = pi/4!, tilt = pi/2